mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-19 18:14:44 +00:00
commit
fcf0240342
|
|
@ -1,7 +1,7 @@
|
|||
add_property ace allowed false
|
||||
add_property ace allowed true
|
||||
add_property ace equiptime 500
|
||||
add_property ace holstertime 500
|
||||
add_property ace_deployable allowed false
|
||||
add_property ace_deployable allowed true
|
||||
add_property ace_deployable equiptime 500
|
||||
add_property ace_deployable holstertime 500
|
||||
add_property advanced_ace equiptime 750
|
||||
|
|
|
|||
|
|
@ -1433,7 +1433,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
deadState = DeadState.RespawnTime
|
||||
|
||||
session = session.copy(player = new Player(avatar))
|
||||
//xy-coordinates indicate sanctuary spawn bias:
|
||||
//ay-coordinates indicate sanctuary spawn bias:
|
||||
player.Position = math.abs(scala.util.Random.nextInt() % avatar.name.hashCode % 4) match {
|
||||
case 0 => Vector3(8192, 8192, 0) //NE
|
||||
case 1 => Vector3(8192, 0, 0) //SE
|
||||
|
|
@ -2184,6 +2184,17 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
if (player.HasGUID) player.GUID
|
||||
else PlanetSideGUID(0)
|
||||
reply match {
|
||||
case LocalResponse.AlertDestroyDeployable(obj: BoomerDeployable) =>
|
||||
//the (former) owner (obj.OwnerName) should process this message
|
||||
obj.Trigger match {
|
||||
case Some(item: BoomerTrigger) =>
|
||||
FindEquipmentToDelete(item.GUID, item)
|
||||
item.Companion = None
|
||||
case _ => ;
|
||||
}
|
||||
avatar.deployables.Remove(obj)
|
||||
UpdateDeployableUIElements(avatar.deployables.UpdateUIElement(obj.Definition.Item))
|
||||
|
||||
case LocalResponse.AlertDestroyDeployable(obj) =>
|
||||
//the (former) owner (obj.OwnerName) should process this message
|
||||
avatar.deployables.Remove(obj)
|
||||
|
|
@ -2194,17 +2205,17 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
sendResponse(DeployableObjectsInfoMessage(behavior, deployInfo))
|
||||
}
|
||||
|
||||
case LocalResponse.Detonate(guid, obj: BoomerDeployable) =>
|
||||
sendResponse(TriggerEffectMessage(guid, "detonate_boomer"))
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 29, 1))
|
||||
sendResponse(ObjectDeleteMessage(guid, 0))
|
||||
case LocalResponse.Detonate(dguid, obj: BoomerDeployable) =>
|
||||
sendResponse(TriggerEffectMessage(dguid, "detonate_boomer"))
|
||||
sendResponse(PlanetsideAttributeMessage(dguid, 29, 1))
|
||||
sendResponse(ObjectDeleteMessage(dguid, 0))
|
||||
|
||||
case LocalResponse.Detonate(guid, obj: ExplosiveDeployable) =>
|
||||
sendResponse(GenericObjectActionMessage(guid, 19))
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 29, 1))
|
||||
sendResponse(ObjectDeleteMessage(guid, 0))
|
||||
case LocalResponse.Detonate(dguid, obj: ExplosiveDeployable) =>
|
||||
sendResponse(GenericObjectActionMessage(dguid, 19))
|
||||
sendResponse(PlanetsideAttributeMessage(dguid, 29, 1))
|
||||
sendResponse(ObjectDeleteMessage(dguid, 0))
|
||||
|
||||
case LocalResponse.Detonate(guid, obj) =>
|
||||
case LocalResponse.Detonate(_, obj) =>
|
||||
log.warn(s"LocalResponse.Detonate: ${obj.Definition.Name} not configured to explode correctly")
|
||||
|
||||
case LocalResponse.DoorOpens(door_guid) =>
|
||||
|
|
@ -4039,13 +4050,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
)
|
||||
continent.GUID(trigger.Companion) match {
|
||||
case Some(boomer: BoomerDeployable) =>
|
||||
boomer.Destroyed = true
|
||||
continent.LocalEvents ! LocalServiceMessage(continent.id, LocalAction.Detonate(boomer.GUID, boomer))
|
||||
Deployables.AnnounceDestroyDeployable(boomer, Some(500 milliseconds))
|
||||
boomer.Actor ! CommonMessages.Use(player, Some(trigger))
|
||||
case Some(_) | None => ;
|
||||
}
|
||||
FindEquipmentToDelete(item_guid, trigger)
|
||||
trigger.Companion = None
|
||||
case _ => ;
|
||||
}
|
||||
progressBarUpdate.cancel()
|
||||
|
|
@ -5245,8 +5252,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
|
||||
CheckForHitPositionDiscrepancy(projectile_guid, target.Position, target)
|
||||
ResolveProjectileInteraction(projectile, resolution1, target, target.Position) match {
|
||||
case Some(projectile) =>
|
||||
HandleDealingDamage(target, projectile)
|
||||
case Some(_projectile) =>
|
||||
HandleDealingDamage(target, _projectile)
|
||||
case None => ;
|
||||
}
|
||||
case _ => ;
|
||||
|
|
@ -5257,13 +5264,25 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
|
||||
CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target)
|
||||
ResolveProjectileInteraction(projectile, resolution2, target, explosion_pos) match {
|
||||
case Some(projectile) =>
|
||||
HandleDealingDamage(target, projectile)
|
||||
case Some(_projectile) =>
|
||||
HandleDealingDamage(target, _projectile)
|
||||
case None => ;
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
})
|
||||
if (
|
||||
projectile.profile.HasJammedEffectDuration ||
|
||||
projectile.profile.JammerProjectile ||
|
||||
projectile.profile.SympatheticExplosion
|
||||
) {
|
||||
Zone.causeSpecialEmp(
|
||||
continent,
|
||||
player,
|
||||
explosion_pos,
|
||||
GlobalDefinitions.special_emp.innateDamage.get
|
||||
)
|
||||
}
|
||||
if (profile.ExistsOnRemoteClients && projectile.HasGUID) {
|
||||
//cleanup
|
||||
val localIndex = projectile_guid.guid - Projectile.baseUID
|
||||
|
|
|
|||
|
|
@ -2,16 +2,20 @@
|
|||
package net.psforever.objects
|
||||
|
||||
import akka.actor.{Actor, ActorContext, Props}
|
||||
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.ce._
|
||||
import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition}
|
||||
import net.psforever.objects.definition.converter.SmallDeployableConverter
|
||||
import net.psforever.objects.equipment.JammableUnit
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.geometry.Geometry3D
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||
import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity}
|
||||
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||
import net.psforever.objects.vital.resolution.ResolutionCalculations.Output
|
||||
import net.psforever.objects.vital.SimpleResolutions
|
||||
import net.psforever.objects.vital.interaction.DamageResult
|
||||
import net.psforever.objects.vital.{SimpleResolutions, Vitality}
|
||||
import net.psforever.objects.vital.etc.TriggerUsedReason
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.types.Vector3
|
||||
|
|
@ -21,7 +25,9 @@ import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
|||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class ExplosiveDeployable(cdef: ExplosiveDeployableDefinition) extends ComplexDeployable(cdef) with JammableUnit {
|
||||
class ExplosiveDeployable(cdef: ExplosiveDeployableDefinition)
|
||||
extends ComplexDeployable(cdef)
|
||||
with JammableUnit {
|
||||
|
||||
override def Definition: ExplosiveDeployableDefinition = cdef
|
||||
}
|
||||
|
|
@ -63,6 +69,24 @@ class ExplosiveDeployableControl(mine: ExplosiveDeployable) extends Actor with D
|
|||
def receive: Receive =
|
||||
takesDamage
|
||||
.orElse {
|
||||
case CommonMessages.Use(player, Some(trigger: BoomerTrigger)) if {
|
||||
mine match {
|
||||
case boomer: BoomerDeployable => boomer.Trigger.contains(trigger) && mine.Definition.Damageable
|
||||
case _ => false
|
||||
}
|
||||
} =>
|
||||
// the trigger damages the mine, which sets it off, which causes an explosion
|
||||
// think of this as an initiator to the proper explosion
|
||||
mine.Destroyed = true
|
||||
ExplosiveDeployableControl.DamageResolution(
|
||||
mine,
|
||||
DamageInteraction(
|
||||
SourceEntry(mine),
|
||||
TriggerUsedReason(PlayerSource(player), trigger.GUID),
|
||||
mine.Position
|
||||
).calculate()(mine),
|
||||
damage = 0
|
||||
)
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
|
|
@ -74,19 +98,48 @@ class ExplosiveDeployableControl(mine: ExplosiveDeployable) extends Actor with D
|
|||
val originalHealth = mine.Health
|
||||
val cause = applyDamageTo(mine)
|
||||
val damage = originalHealth - mine.Health
|
||||
if (Damageable.CanDamageOrJammer(mine, damage, cause.interaction)) {
|
||||
if (CanDetonate(mine, damage, cause.interaction)) {
|
||||
ExplosiveDeployableControl.DamageResolution(mine, cause, damage)
|
||||
} else {
|
||||
mine.Health = originalHealth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A supplement for checking target susceptibility
|
||||
* to account for sympathetic explosives even if there is no damage.
|
||||
* This does not supercede other underlying checks or undo prior damage checks.
|
||||
* @see `Damageable.CanDamageOrJammer`
|
||||
* @see `DamageProperties.SympatheticExplosives`
|
||||
* @param obj the entity being damaged
|
||||
* @param damage the amount of damage
|
||||
* @param data historical information about the damage
|
||||
* @return `true`, if the target can be affected;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def CanDetonate(obj: Vitality with FactionAffinity, damage: Int, data: DamageInteraction): Boolean = {
|
||||
!mine.Destroyed && (if (damage == 0 && data.cause.source.SympatheticExplosion) {
|
||||
Damageable.CanDamageOrJammer(mine, damage = 1, data)
|
||||
} else {
|
||||
Damageable.CanDamageOrJammer(mine, damage, data)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
object ExplosiveDeployableControl {
|
||||
/**
|
||||
* na
|
||||
* @param target na
|
||||
* @param cause na
|
||||
* @param damage na
|
||||
*/
|
||||
def DamageResolution(target: ExplosiveDeployable, cause: DamageResult, damage: Int): Unit = {
|
||||
target.History(cause)
|
||||
if (target.Health == 0) {
|
||||
if (cause.interaction.cause.source.SympatheticExplosion) {
|
||||
explodes(target, cause)
|
||||
DestructionAwareness(target, cause)
|
||||
} else if (target.Health == 0) {
|
||||
DestructionAwareness(target, cause)
|
||||
} else if (!target.Jammed && Damageable.CanJammer(target, cause.interaction)) {
|
||||
if ( {
|
||||
|
|
@ -99,17 +152,27 @@ object ExplosiveDeployableControl {
|
|||
}
|
||||
}
|
||||
) {
|
||||
if (cause.interaction.cause.source.SympatheticExplosion || target.Definition.DetonateOnJamming) {
|
||||
val zone = target.Zone
|
||||
zone.Activity ! Zone.HotSpot.Activity(cause)
|
||||
zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.Detonate(target.GUID, target))
|
||||
Zone.causeExplosion(zone, target, Some(cause))
|
||||
if (target.Definition.DetonateOnJamming) {
|
||||
explodes(target, cause)
|
||||
}
|
||||
DestructionAwareness(target, cause)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param target na
|
||||
* @param cause na
|
||||
*/
|
||||
def explodes(target: Damageable.Target, cause: DamageResult): Unit = {
|
||||
target.Health = 1 // short-circuit logic in DestructionAwareness
|
||||
val zone = target.Zone
|
||||
zone.Activity ! Zone.HotSpot.Activity(cause)
|
||||
zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.Detonate(target.GUID, target))
|
||||
Zone.causeExplosion(zone, target, Some(cause), ExplosiveDeployableControl.detectionForExplosiveSource(target))
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param target na
|
||||
|
|
@ -118,8 +181,11 @@ object ExplosiveDeployableControl {
|
|||
def DestructionAwareness(target: ExplosiveDeployable, cause: DamageResult): Unit = {
|
||||
val zone = target.Zone
|
||||
val attribution = DamageableEntity.attributionTo(cause, target.Zone)
|
||||
Deployables.AnnounceDestroyDeployable(
|
||||
target,
|
||||
Some(if (target.Jammed || target.Destroyed) 0 seconds else 500 milliseconds)
|
||||
)
|
||||
target.Destroyed = true
|
||||
Deployables.AnnounceDestroyDeployable(target, Some(if (target.Jammed) 0 seconds else 500 milliseconds))
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zone.id,
|
||||
AvatarAction.Destroy(target.GUID, attribution, Service.defaultPlayerGUID, target.Position)
|
||||
|
|
@ -131,4 +197,52 @@ object ExplosiveDeployableControl {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Two game entities are considered "near" each other if they are within a certain distance of one another.
|
||||
* For explosives, the source of the explosion is always typically constant.
|
||||
* @see `detectsTarget`
|
||||
* @see `ObjectDefinition.Geometry`
|
||||
* @see `Vector3.relativeUp`
|
||||
* @param obj a game entity that explodes
|
||||
* @return a function that resolves a potential target as detected
|
||||
*/
|
||||
def detectionForExplosiveSource(obj: PlanetSideGameObject): (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = {
|
||||
val up = Vector3.relativeUp(obj.Orientation) //check relativeUp; rotate as little as necessary!
|
||||
val g1 = obj.Definition.Geometry(obj)
|
||||
detectTarget(g1, up)
|
||||
}
|
||||
|
||||
/**
|
||||
* Two game entities are considered "near" each other if they are within a certain distance of one another.
|
||||
* For explosives, targets in the damage radius in the direction of the blast (above the explosive) are valid targets.
|
||||
* Targets that are ~0.5916f units in the opposite direction of the blast (below the explosive) are also selected.
|
||||
* @see `ObjectDefinition.Geometry`
|
||||
* @see `PrimitiveGeometry.pointOnOutside`
|
||||
* @see `Vector3.DistanceSquared`
|
||||
* @see `Vector3.neg`
|
||||
* @see `Vector3.relativeUp`
|
||||
* @see `Vector3.ScalarProjection`
|
||||
* @see `Vector3.Unit`
|
||||
* @param g1 a cached geometric representation that should belong to `obj1`
|
||||
* @param up a cached vector in the direction of "above `obj1`'s geometric representation"
|
||||
* @param obj1 a game entity that explodes
|
||||
* @param obj2 a game entity that suffers the explosion
|
||||
* @param maxDistance the square of the maximum distance permissible between game entities
|
||||
* before they are no longer considered "near"
|
||||
* @return `true`, if the target entities are near enough to each other;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def detectTarget(g1: Geometry3D, up: Vector3)(obj1: PlanetSideGameObject, obj2: PlanetSideGameObject, maxDistance: Float) : Boolean = {
|
||||
val g2 = obj2.Definition.Geometry(obj2)
|
||||
val dir = g2.center.asVector3 - g1.center.asVector3
|
||||
val scalar = Vector3.ScalarProjection(dir, up)
|
||||
val point1 = g1.pointOnOutside(dir).asVector3
|
||||
val point2 = g2.pointOnOutside(Vector3.neg(dir)).asVector3
|
||||
(scalar >= 0 || Vector3.MagnitudeSquared(up * scalar) < 0.35f) &&
|
||||
math.min(
|
||||
Vector3.DistanceSquared(g1.center.asVector3, g2.center.asVector3),
|
||||
Vector3.DistanceSquared(point1, point2)
|
||||
) <= maxDistance
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import net.psforever.objects.ce.{DeployableCategory, DeployedItem}
|
|||
import net.psforever.objects.definition._
|
||||
import net.psforever.objects.definition.converter._
|
||||
import net.psforever.objects.equipment._
|
||||
import net.psforever.objects.geometry.GeometryForm
|
||||
import net.psforever.objects.inventory.InventoryTile
|
||||
import net.psforever.objects.serverobject.aura.Aura
|
||||
import net.psforever.objects.serverobject.doors.DoorDefinition
|
||||
|
|
@ -24,6 +25,7 @@ import net.psforever.objects.serverobject.turret.{FacilityTurretDefinition, Turr
|
|||
import net.psforever.objects.vehicles.{DestroyedVehicle, InternalTelepadDefinition, SeatArmorRestriction, UtilityType}
|
||||
import net.psforever.objects.vital.base.DamageType
|
||||
import net.psforever.objects.vital.damage._
|
||||
import net.psforever.objects.vital.etc.ExplodingRadialDegrade
|
||||
import net.psforever.objects.vital.projectile._
|
||||
import net.psforever.objects.vital.prop.DamageWithPosition
|
||||
import net.psforever.objects.vital.{ComplexDeployableResolutions, MaxResolutions, SimpleResolutions}
|
||||
|
|
@ -970,6 +972,8 @@ object GlobalDefinitions {
|
|||
|
||||
val router_telepad_deployable = SimpleDeployableDefinition(DeployedItem.router_telepad_deployable)
|
||||
|
||||
val special_emp = ExplosiveDeployableDefinition(DeployedItem.jammer_mine)
|
||||
|
||||
//this is only treated like a deployable
|
||||
val internal_router_telepad_deployable = InternalTelepadDefinition() //objectId: 744
|
||||
init_deployables()
|
||||
|
|
@ -5601,6 +5605,11 @@ object GlobalDefinitions {
|
|||
* Initialize `VehicleDefinition` globals.
|
||||
*/
|
||||
private def init_vehicles(): Unit = {
|
||||
val atvForm = GeometryForm.representByCylinder(radius = 1.1797f, height = 1.1875f) _
|
||||
val delivererForm = GeometryForm.representByCylinder(radius = 2.46095f, height = 2.40626f) _ //TODO hexahedron
|
||||
val apcForm = GeometryForm.representByCylinder(radius = 4.6211f, height = 3.90626f) _ //TODO hexahedron
|
||||
val liberatorForm = GeometryForm.representByCylinder(radius = 3.74615f, height = 2.51563f) _
|
||||
|
||||
fury.Name = "fury"
|
||||
fury.MaxHealth = 650
|
||||
fury.Damageable = true
|
||||
|
|
@ -5626,11 +5635,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 225
|
||||
DamageRadius = 5
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
fury.DrownAtMaxDepth = true
|
||||
fury.MaxDepth = 1.3f
|
||||
fury.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
fury.Geometry = atvForm
|
||||
|
||||
quadassault.Name = "quadassault" // Basilisk
|
||||
quadassault.MaxHealth = 650
|
||||
|
|
@ -5657,11 +5667,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 225
|
||||
DamageRadius = 5
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
quadassault.DrownAtMaxDepth = true
|
||||
quadassault.MaxDepth = 1.3f
|
||||
quadassault.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
quadassault.Geometry = atvForm
|
||||
|
||||
quadstealth.Name = "quadstealth" // Wraith
|
||||
quadstealth.MaxHealth = 650
|
||||
|
|
@ -5688,11 +5699,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 225
|
||||
DamageRadius = 5
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
quadstealth.DrownAtMaxDepth = true
|
||||
quadstealth.MaxDepth = 1.25f
|
||||
quadstealth.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
quadstealth.Geometry = atvForm
|
||||
|
||||
two_man_assault_buggy.Name = "two_man_assault_buggy" // Harasser
|
||||
two_man_assault_buggy.MaxHealth = 1250
|
||||
|
|
@ -5721,11 +5733,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 8
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
two_man_assault_buggy.DrownAtMaxDepth = true
|
||||
two_man_assault_buggy.MaxDepth = 1.5f
|
||||
two_man_assault_buggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
two_man_assault_buggy.Geometry = GeometryForm.representByCylinder(radius = 2.10545f, height = 1.59376f)
|
||||
|
||||
skyguard.Name = "skyguard"
|
||||
skyguard.MaxHealth = 1000
|
||||
|
|
@ -5748,7 +5761,6 @@ 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
|
||||
|
|
@ -5756,11 +5768,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 8
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
skyguard.DrownAtMaxDepth = true
|
||||
skyguard.MaxDepth = 1.5f
|
||||
skyguard.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
skyguard.Geometry = GeometryForm.representByCylinder(radius = 1.8867f, height = 1.4375f)
|
||||
|
||||
threemanheavybuggy.Name = "threemanheavybuggy" // Marauder
|
||||
threemanheavybuggy.MaxHealth = 1700
|
||||
|
|
@ -5795,11 +5808,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 10
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
threemanheavybuggy.DrownAtMaxDepth = true
|
||||
threemanheavybuggy.MaxDepth = 1.83f
|
||||
threemanheavybuggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
threemanheavybuggy.Geometry = GeometryForm.representByCylinder(radius = 2.1953f, height = 2.03125f)
|
||||
|
||||
twomanheavybuggy.Name = "twomanheavybuggy" // Enforcer
|
||||
twomanheavybuggy.MaxHealth = 1800
|
||||
|
|
@ -5829,11 +5843,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 8
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
twomanheavybuggy.DrownAtMaxDepth = true
|
||||
twomanheavybuggy.MaxDepth = 1.95f
|
||||
twomanheavybuggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
twomanheavybuggy.Geometry = GeometryForm.representByCylinder(radius = 2.60935f, height = 1.79688f)
|
||||
|
||||
twomanhoverbuggy.Name = "twomanhoverbuggy" // Thresher
|
||||
twomanhoverbuggy.MaxHealth = 1600
|
||||
|
|
@ -5863,10 +5878,11 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 10
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
twomanhoverbuggy.DrownAtMaxDepth = true
|
||||
twomanhoverbuggy.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the thresher hovers over water, so ...?
|
||||
twomanhoverbuggy.Geometry = GeometryForm.representByCylinder(radius = 2.1875f, height = 2.01563f)
|
||||
|
||||
mediumtransport.Name = "mediumtransport" // Deliverer
|
||||
mediumtransport.MaxHealth = 2500
|
||||
|
|
@ -5903,11 +5919,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 12
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
mediumtransport.DrownAtMaxDepth = false
|
||||
mediumtransport.MaxDepth = 1.2f
|
||||
mediumtransport.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||
mediumtransport.Geometry = delivererForm
|
||||
|
||||
battlewagon.Name = "battlewagon" // Raider
|
||||
battlewagon.MaxHealth = 2500
|
||||
|
|
@ -5947,11 +5964,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 12
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
battlewagon.DrownAtMaxDepth = true
|
||||
battlewagon.MaxDepth = 1.2f
|
||||
battlewagon.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||
battlewagon.Geometry = delivererForm
|
||||
|
||||
thunderer.Name = "thunderer"
|
||||
thunderer.MaxHealth = 2500
|
||||
|
|
@ -5988,11 +6006,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 12
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
thunderer.DrownAtMaxDepth = true
|
||||
thunderer.MaxDepth = 1.2f
|
||||
thunderer.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||
thunderer.Geometry = delivererForm
|
||||
|
||||
aurora.Name = "aurora"
|
||||
aurora.MaxHealth = 2500
|
||||
|
|
@ -6029,11 +6048,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 12
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
aurora.DrownAtMaxDepth = true
|
||||
aurora.MaxDepth = 1.2f
|
||||
aurora.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||
aurora.Geometry = delivererForm
|
||||
|
||||
apc_tr.Name = "apc_tr" // Juggernaut
|
||||
apc_tr.MaxHealth = 6000
|
||||
|
|
@ -6092,11 +6112,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 450
|
||||
DamageRadius = 15
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
apc_tr.DrownAtMaxDepth = true
|
||||
apc_tr.MaxDepth = 3
|
||||
apc_tr.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
|
||||
apc_tr.Geometry = apcForm
|
||||
|
||||
apc_nc.Name = "apc_nc" // Vindicator
|
||||
apc_nc.MaxHealth = 6000
|
||||
|
|
@ -6155,11 +6176,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 450
|
||||
DamageRadius = 15
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
apc_nc.DrownAtMaxDepth = true
|
||||
apc_nc.MaxDepth = 3
|
||||
apc_nc.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
|
||||
apc_nc.Geometry = apcForm
|
||||
|
||||
apc_vs.Name = "apc_vs" // Leviathan
|
||||
apc_vs.MaxHealth = 6000
|
||||
|
|
@ -6218,11 +6240,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 450
|
||||
DamageRadius = 15
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
apc_vs.DrownAtMaxDepth = true
|
||||
apc_vs.MaxDepth = 3
|
||||
apc_vs.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
|
||||
apc_vs.Geometry = apcForm
|
||||
|
||||
lightning.Name = "lightning"
|
||||
lightning.MaxHealth = 2000
|
||||
|
|
@ -6250,11 +6273,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 375
|
||||
DamageRadius = 10
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
lightning.DrownAtMaxDepth = true
|
||||
lightning.MaxDepth = 1.38f
|
||||
lightning.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||
lightning.Geometry = GeometryForm.representByCylinder(radius = 2.5078f, height = 1.79688f)
|
||||
|
||||
prowler.Name = "prowler"
|
||||
prowler.MaxHealth = 4800
|
||||
|
|
@ -6287,11 +6311,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 375
|
||||
DamageRadius = 12
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
prowler.DrownAtMaxDepth = true
|
||||
prowler.MaxDepth = 3
|
||||
prowler.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||
prowler.Geometry = GeometryForm.representByCylinder(radius = 3.461f, height = 3.48438f)
|
||||
|
||||
vanguard.Name = "vanguard"
|
||||
vanguard.MaxHealth = 5400
|
||||
|
|
@ -6320,11 +6345,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 375
|
||||
DamageRadius = 12
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
vanguard.DrownAtMaxDepth = true
|
||||
vanguard.MaxDepth = 2.7f
|
||||
vanguard.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||
vanguard.Geometry = GeometryForm.representByCylinder(radius = 3.8554f, height = 2.60938f)
|
||||
|
||||
magrider.Name = "magrider"
|
||||
magrider.MaxHealth = 4200
|
||||
|
|
@ -6355,11 +6381,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 375
|
||||
DamageRadius = 12
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
magrider.DrownAtMaxDepth = true
|
||||
magrider.MaxDepth = 2
|
||||
magrider.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the magrider hovers over water, so ...?
|
||||
magrider.Geometry = GeometryForm.representByCylinder(radius = 3.3008f, height = 3.26562f)
|
||||
|
||||
val utilityConverter = new UtilityVehicleConverter
|
||||
ant.Name = "ant"
|
||||
|
|
@ -6388,11 +6415,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 450
|
||||
DamageRadius = 12
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
ant.DrownAtMaxDepth = true
|
||||
ant.MaxDepth = 2
|
||||
ant.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||
ant.Geometry = GeometryForm.representByCylinder(radius = 2.16795f, height = 2.09376f) //TODO hexahedron
|
||||
|
||||
ams.Name = "ams"
|
||||
ams.MaxHealth = 5000 // Temporary - original value is 3000
|
||||
|
|
@ -6424,11 +6452,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 450
|
||||
DamageRadius = 15
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
ams.DrownAtMaxDepth = true
|
||||
ams.MaxDepth = 3
|
||||
ams.UnderwaterLifespan(suffocation = 5000L, recovery = 5000L)
|
||||
ams.Geometry = GeometryForm.representByCylinder(radius = 3.0117f, height = 3.39062f) //TODO hexahedron
|
||||
|
||||
val variantConverter = new VariantVehicleConverter
|
||||
router.Name = "router"
|
||||
|
|
@ -6460,11 +6489,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 10
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
router.DrownAtMaxDepth = true
|
||||
router.MaxDepth = 2
|
||||
router.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the router hovers over water, so ...?
|
||||
router.Geometry = GeometryForm.representByCylinder(radius = 3.64845f, height = 3.51563f) //TODO hexahedron
|
||||
|
||||
switchblade.Name = "switchblade"
|
||||
switchblade.MaxHealth = 1750
|
||||
|
|
@ -6496,11 +6526,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 10
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
switchblade.DrownAtMaxDepth = true
|
||||
switchblade.MaxDepth = 2
|
||||
switchblade.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the switchblade hovers over water, so ...?
|
||||
switchblade.Geometry = GeometryForm.representByCylinder(radius = 2.4335f, height = 2.73438f)
|
||||
|
||||
flail.Name = "flail"
|
||||
flail.MaxHealth = 2400
|
||||
|
|
@ -6530,11 +6561,12 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 10
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
flail.DrownAtMaxDepth = true
|
||||
flail.MaxDepth = 2
|
||||
flail.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the flail hovers over water, so ...?
|
||||
flail.Geometry = GeometryForm.representByCylinder(radius = 2.1875f, height = 2.21875f)
|
||||
|
||||
mosquito.Name = "mosquito"
|
||||
mosquito.MaxHealth = 665
|
||||
|
|
@ -6564,10 +6596,11 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 10
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
mosquito.DrownAtMaxDepth = true
|
||||
mosquito.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
mosquito.Geometry = GeometryForm.representByCylinder(radius = 2.72108f, height = 2.5f)
|
||||
|
||||
lightgunship.Name = "lightgunship" // Reaver
|
||||
lightgunship.MaxHealth = 1000
|
||||
|
|
@ -6598,10 +6631,11 @@ object GlobalDefinitions {
|
|||
Damage1 = 375
|
||||
DamageRadius = 12
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
lightgunship.DrownAtMaxDepth = true
|
||||
lightgunship.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
lightgunship.Geometry = GeometryForm.representByCylinder(radius = 2.375f, height = 1.98438f)
|
||||
|
||||
wasp.Name = "wasp"
|
||||
wasp.MaxHealth = 515
|
||||
|
|
@ -6631,10 +6665,11 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 10
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
wasp.DrownAtMaxDepth = true
|
||||
wasp.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
wasp.Geometry = GeometryForm.representByCylinder(radius = 2.88675f, height = 2.5f)
|
||||
|
||||
liberator.Name = "liberator"
|
||||
liberator.MaxHealth = 2500
|
||||
|
|
@ -6674,10 +6709,11 @@ object GlobalDefinitions {
|
|||
Damage1 = 375
|
||||
DamageRadius = 12
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
liberator.DrownAtMaxDepth = true
|
||||
liberator.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
liberator.Geometry = liberatorForm
|
||||
|
||||
vulture.Name = "vulture"
|
||||
vulture.MaxHealth = 2500
|
||||
|
|
@ -6718,10 +6754,11 @@ object GlobalDefinitions {
|
|||
Damage1 = 375
|
||||
DamageRadius = 12
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
vulture.DrownAtMaxDepth = true
|
||||
vulture.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
vulture.Geometry = liberatorForm
|
||||
|
||||
dropship.Name = "dropship" // Galaxy
|
||||
dropship.MaxHealth = 5000
|
||||
|
|
@ -6792,10 +6829,11 @@ object GlobalDefinitions {
|
|||
Damage1 = 450
|
||||
DamageRadius = 30
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
dropship.DrownAtMaxDepth = true
|
||||
dropship.MaxDepth = 2
|
||||
dropship.Geometry = GeometryForm.representByCylinder(radius = 10.52202f, height = 6.23438f)
|
||||
|
||||
galaxy_gunship.Name = "galaxy_gunship"
|
||||
galaxy_gunship.MaxHealth = 6000
|
||||
|
|
@ -6850,10 +6888,11 @@ object GlobalDefinitions {
|
|||
Damage1 = 450
|
||||
DamageRadius = 30
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
galaxy_gunship.DrownAtMaxDepth = true
|
||||
galaxy_gunship.MaxDepth = 2
|
||||
galaxy_gunship.Geometry = GeometryForm.representByCylinder(radius = 9.2382f, height = 5.01562f)
|
||||
|
||||
lodestar.Name = "lodestar"
|
||||
lodestar.MaxHealth = 5000
|
||||
|
|
@ -6891,10 +6930,11 @@ object GlobalDefinitions {
|
|||
Damage1 = 450
|
||||
DamageRadius = 30
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
lodestar.DrownAtMaxDepth = true
|
||||
lodestar.MaxDepth = 2
|
||||
lodestar.Geometry = GeometryForm.representByCylinder(radius = 7.8671f, height = 6.79688f) //TODO hexahedron
|
||||
|
||||
phantasm.Name = "phantasm"
|
||||
phantasm.MaxHealth = 2500
|
||||
|
|
@ -6933,10 +6973,11 @@ object GlobalDefinitions {
|
|||
Damage1 = 150
|
||||
DamageRadius = 12
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
phantasm.DrownAtMaxDepth = true
|
||||
phantasm.MaxDepth = 2
|
||||
phantasm.Geometry = GeometryForm.representByCylinder(radius = 5.2618f, height = 3f)
|
||||
|
||||
droppod.Name = "droppod"
|
||||
droppod.MaxHealth = 20000
|
||||
|
|
@ -6950,12 +6991,18 @@ object GlobalDefinitions {
|
|||
droppod.DestroyedModel = None //the adb calls out a droppod; the cyclic nature of this confounds me
|
||||
droppod.DamageUsing = DamageCalculations.AgainstAircraft
|
||||
droppod.DrownAtMaxDepth = false
|
||||
//TODO geometry?
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize `Deployable` globals.
|
||||
*/
|
||||
private def init_deployables(): Unit = {
|
||||
val mine = GeometryForm.representByCylinder(radius = 0.1914f, height = 0.0957f) _
|
||||
val smallTurret = GeometryForm.representByCylinder(radius = 0.48435f, height = 1.23438f) _
|
||||
val sensor = GeometryForm.representByCylinder(radius = 0.1914f, height = 1.21875f) _
|
||||
val largeTurret = GeometryForm.representByCylinder(radius = 0.8437f, height = 2.29687f) _
|
||||
|
||||
boomer.Name = "boomer"
|
||||
boomer.Descriptor = "Boomers"
|
||||
boomer.MaxHealth = 100
|
||||
|
|
@ -6975,8 +7022,9 @@ object GlobalDefinitions {
|
|||
Damage4 = 1850
|
||||
DamageRadius = 5.1f
|
||||
DamageAtEdge = 0.1f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
boomer.Geometry = mine
|
||||
|
||||
he_mine.Name = "he_mine"
|
||||
he_mine.Descriptor = "Mines"
|
||||
|
|
@ -6996,8 +7044,9 @@ object GlobalDefinitions {
|
|||
Damage4 = 1600
|
||||
DamageRadius = 6.6f
|
||||
DamageAtEdge = 0.25f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
he_mine.Geometry = mine
|
||||
|
||||
jammer_mine.Name = "jammer_mine"
|
||||
jammer_mine.Descriptor = "JammerMines"
|
||||
|
|
@ -7007,6 +7056,7 @@ object GlobalDefinitions {
|
|||
jammer_mine.Repairable = false
|
||||
jammer_mine.DeployTime = Duration.create(1000, "ms")
|
||||
jammer_mine.DetonateOnJamming = false
|
||||
jammer_mine.Geometry = mine
|
||||
|
||||
spitfire_turret.Name = "spitfire_turret"
|
||||
spitfire_turret.Descriptor = "Spitfires"
|
||||
|
|
@ -7028,8 +7078,9 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 8
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
spitfire_turret.Geometry = smallTurret
|
||||
|
||||
spitfire_cloaked.Name = "spitfire_cloaked"
|
||||
spitfire_cloaked.Descriptor = "CloakingSpitfires"
|
||||
|
|
@ -7050,8 +7101,9 @@ object GlobalDefinitions {
|
|||
Damage1 = 75
|
||||
DamageRadius = 8
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
spitfire_cloaked.Geometry = smallTurret
|
||||
|
||||
spitfire_aa.Name = "spitfire_aa"
|
||||
spitfire_aa.Descriptor = "FlakSpitfires"
|
||||
|
|
@ -7072,8 +7124,9 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 8
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
spitfire_aa.Geometry = smallTurret
|
||||
|
||||
motionalarmsensor.Name = "motionalarmsensor"
|
||||
motionalarmsensor.Descriptor = "MotionSensors"
|
||||
|
|
@ -7082,6 +7135,7 @@ object GlobalDefinitions {
|
|||
motionalarmsensor.Repairable = true
|
||||
motionalarmsensor.RepairIfDestroyed = false
|
||||
motionalarmsensor.DeployTime = Duration.create(1000, "ms")
|
||||
motionalarmsensor.Geometry = sensor
|
||||
|
||||
sensor_shield.Name = "sensor_shield"
|
||||
sensor_shield.Descriptor = "SensorShields"
|
||||
|
|
@ -7090,6 +7144,7 @@ object GlobalDefinitions {
|
|||
sensor_shield.Repairable = true
|
||||
sensor_shield.RepairIfDestroyed = false
|
||||
sensor_shield.DeployTime = Duration.create(5000, "ms")
|
||||
sensor_shield.Geometry = sensor
|
||||
|
||||
tank_traps.Name = "tank_traps"
|
||||
tank_traps.Descriptor = "TankTraps"
|
||||
|
|
@ -7106,8 +7161,9 @@ object GlobalDefinitions {
|
|||
Damage1 = 10
|
||||
DamageRadius = 8
|
||||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
tank_traps.Geometry = GeometryForm.representByCylinder(radius = 2.89680997f, height = 3.57812f)
|
||||
|
||||
val fieldTurretConverter = new FieldTurretConverter
|
||||
portable_manned_turret.Name = "portable_manned_turret"
|
||||
|
|
@ -7133,8 +7189,9 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 8
|
||||
DamageAtEdge = 0.1f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
portable_manned_turret.Geometry = largeTurret
|
||||
|
||||
portable_manned_turret_nc.Name = "portable_manned_turret_nc"
|
||||
portable_manned_turret_nc.Descriptor = "FieldTurrets"
|
||||
|
|
@ -7159,8 +7216,9 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 8
|
||||
DamageAtEdge = 0.1f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
portable_manned_turret_nc.Geometry = largeTurret
|
||||
|
||||
portable_manned_turret_tr.Name = "portable_manned_turret_tr"
|
||||
portable_manned_turret_tr.Descriptor = "FieldTurrets"
|
||||
|
|
@ -7185,8 +7243,9 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 8
|
||||
DamageAtEdge = 0.1f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
portable_manned_turret_tr.Geometry = largeTurret
|
||||
|
||||
portable_manned_turret_vs.Name = "portable_manned_turret_vs"
|
||||
portable_manned_turret_vs.Descriptor = "FieldTurrets"
|
||||
|
|
@ -7211,8 +7270,9 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 8
|
||||
DamageAtEdge = 0.1f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
portable_manned_turret_vs.Geometry = largeTurret
|
||||
|
||||
deployable_shield_generator.Name = "deployable_shield_generator"
|
||||
deployable_shield_generator.Descriptor = "ShieldGenerators"
|
||||
|
|
@ -7222,6 +7282,7 @@ object GlobalDefinitions {
|
|||
deployable_shield_generator.RepairIfDestroyed = false
|
||||
deployable_shield_generator.DeployTime = Duration.create(6000, "ms")
|
||||
deployable_shield_generator.Model = ComplexDeployableResolutions.calculate
|
||||
deployable_shield_generator.Geometry = GeometryForm.representByCylinder(radius = 0.6562f, height = 2.17188f)
|
||||
|
||||
router_telepad_deployable.Name = "router_telepad_deployable"
|
||||
router_telepad_deployable.MaxHealth = 100
|
||||
|
|
@ -7231,6 +7292,7 @@ object GlobalDefinitions {
|
|||
router_telepad_deployable.DeployCategory = DeployableCategory.Telepads
|
||||
router_telepad_deployable.Packet = new TelepadDeployableConverter
|
||||
router_telepad_deployable.Model = SimpleResolutions.calculate
|
||||
router_telepad_deployable.Geometry = GeometryForm.representByRaisedSphere(radius = 1.2344f)
|
||||
|
||||
internal_router_telepad_deployable.Name = "router_telepad_deployable"
|
||||
internal_router_telepad_deployable.MaxHealth = 1
|
||||
|
|
@ -7239,12 +7301,30 @@ object GlobalDefinitions {
|
|||
internal_router_telepad_deployable.DeployTime = Duration.create(1, "ms")
|
||||
internal_router_telepad_deployable.DeployCategory = DeployableCategory.Telepads
|
||||
internal_router_telepad_deployable.Packet = new InternalTelepadDeployableConverter
|
||||
|
||||
special_emp.Name = "emp"
|
||||
special_emp.MaxHealth = 1
|
||||
special_emp.Damageable = false
|
||||
special_emp.Repairable = false
|
||||
special_emp.DeployCategory = DeployableCategory.Mines
|
||||
special_emp.explodes = true
|
||||
special_emp.innateDamage = new DamageWithPosition {
|
||||
CausesDamageType = DamageType.Splash
|
||||
SympatheticExplosion = true
|
||||
Damage0 = 0
|
||||
DamageAtEdge = 1.0f
|
||||
DamageRadius = 5f
|
||||
AdditionalEffect = true
|
||||
Modifiers = MaxDistanceCutoff
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize `Miscellaneous` globals.
|
||||
*/
|
||||
private def initMiscellaneous(): Unit = {
|
||||
val vterm = GeometryForm.representByCylinder(radius = 1.03515f, height = 1.09374f) _
|
||||
|
||||
ams_respawn_tube.Name = "ams_respawn_tube"
|
||||
ams_respawn_tube.Delay = 10
|
||||
ams_respawn_tube.SpecificPointFunc = SpawnPoint.AMS
|
||||
|
|
@ -7285,9 +7365,10 @@ object GlobalDefinitions {
|
|||
order_terminal.MaxHealth = 500
|
||||
order_terminal.Damageable = true
|
||||
order_terminal.Repairable = true
|
||||
order_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
order_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
order_terminal.RepairIfDestroyed = true
|
||||
order_terminal.Subtract.Damage1 = 8
|
||||
order_terminal.Geometry = GeometryForm.representByCylinder(radius = 0.8438f, height = 1.3f)
|
||||
|
||||
order_terminala.Name = "order_terminala"
|
||||
order_terminala.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(
|
||||
|
|
@ -7349,16 +7430,18 @@ object GlobalDefinitions {
|
|||
cert_terminal.MaxHealth = 500
|
||||
cert_terminal.Damageable = true
|
||||
cert_terminal.Repairable = true
|
||||
cert_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
cert_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
cert_terminal.RepairIfDestroyed = true
|
||||
cert_terminal.Subtract.Damage1 = 8
|
||||
cert_terminal.Geometry = GeometryForm.representByCylinder(radius = 0.66405f, height = 1.09374f)
|
||||
|
||||
implant_terminal_mech.Name = "implant_terminal_mech"
|
||||
implant_terminal_mech.MaxHealth = 1500 //TODO 1000; right now, 1000 (mech) + 500 (interface)
|
||||
implant_terminal_mech.Damageable = true
|
||||
implant_terminal_mech.Repairable = true
|
||||
implant_terminal_mech.autoRepair = AutoRepairStats(1.6f, 5000, 2400, 0.5f) //ori. 1, 5000, 2400, 0.5f
|
||||
implant_terminal_mech.autoRepair = AutoRepairStats(1.6f, 5000, 2400, 0.5f)
|
||||
implant_terminal_mech.RepairIfDestroyed = true
|
||||
implant_terminal_mech.Geometry = GeometryForm.representByCylinder(radius = 2.7813f, height = 6.4375f)
|
||||
|
||||
implant_terminal_interface.Name = "implant_terminal_interface"
|
||||
implant_terminal_interface.Tab += 0 -> OrderTerminalDefinition.ImplantPage(ImplantTerminalDefinition.implants)
|
||||
|
|
@ -7367,6 +7450,7 @@ object GlobalDefinitions {
|
|||
implant_terminal_interface.Repairable = true
|
||||
implant_terminal_interface.autoRepair = AutoRepairStats(1, 5000, 200, 1) //TODO amount and drain are default? undefined?
|
||||
implant_terminal_interface.RepairIfDestroyed = true
|
||||
//TODO will need geometry when Damageable = true
|
||||
|
||||
ground_vehicle_terminal.Name = "ground_vehicle_terminal"
|
||||
ground_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(
|
||||
|
|
@ -7377,9 +7461,10 @@ object GlobalDefinitions {
|
|||
ground_vehicle_terminal.MaxHealth = 500
|
||||
ground_vehicle_terminal.Damageable = true
|
||||
ground_vehicle_terminal.Repairable = true
|
||||
ground_vehicle_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
ground_vehicle_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
ground_vehicle_terminal.RepairIfDestroyed = true
|
||||
ground_vehicle_terminal.Subtract.Damage1 = 8
|
||||
ground_vehicle_terminal.Geometry = vterm
|
||||
|
||||
air_vehicle_terminal.Name = "air_vehicle_terminal"
|
||||
air_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(
|
||||
|
|
@ -7390,9 +7475,10 @@ object GlobalDefinitions {
|
|||
air_vehicle_terminal.MaxHealth = 500
|
||||
air_vehicle_terminal.Damageable = true
|
||||
air_vehicle_terminal.Repairable = true
|
||||
air_vehicle_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
air_vehicle_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
air_vehicle_terminal.RepairIfDestroyed = true
|
||||
air_vehicle_terminal.Subtract.Damage1 = 8
|
||||
air_vehicle_terminal.Geometry = vterm
|
||||
|
||||
dropship_vehicle_terminal.Name = "dropship_vehicle_terminal"
|
||||
dropship_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(
|
||||
|
|
@ -7403,9 +7489,10 @@ object GlobalDefinitions {
|
|||
dropship_vehicle_terminal.MaxHealth = 500
|
||||
dropship_vehicle_terminal.Damageable = true
|
||||
dropship_vehicle_terminal.Repairable = true
|
||||
dropship_vehicle_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
dropship_vehicle_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
dropship_vehicle_terminal.RepairIfDestroyed = true
|
||||
dropship_vehicle_terminal.Subtract.Damage1 = 8
|
||||
dropship_vehicle_terminal.Geometry = vterm
|
||||
|
||||
vehicle_terminal_combined.Name = "vehicle_terminal_combined"
|
||||
vehicle_terminal_combined.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(
|
||||
|
|
@ -7416,9 +7503,10 @@ object GlobalDefinitions {
|
|||
vehicle_terminal_combined.MaxHealth = 500
|
||||
vehicle_terminal_combined.Damageable = true
|
||||
vehicle_terminal_combined.Repairable = true
|
||||
vehicle_terminal_combined.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
vehicle_terminal_combined.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
vehicle_terminal_combined.RepairIfDestroyed = true
|
||||
vehicle_terminal_combined.Subtract.Damage1 = 8
|
||||
vehicle_terminal_combined.Geometry = vterm
|
||||
|
||||
vanu_air_vehicle_term.Name = "vanu_air_vehicle_term"
|
||||
vanu_air_vehicle_term.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(
|
||||
|
|
@ -7429,7 +7517,7 @@ object GlobalDefinitions {
|
|||
vanu_air_vehicle_term.MaxHealth = 500
|
||||
vanu_air_vehicle_term.Damageable = true
|
||||
vanu_air_vehicle_term.Repairable = true
|
||||
vanu_air_vehicle_term.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
vanu_air_vehicle_term.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
vanu_air_vehicle_term.RepairIfDestroyed = true
|
||||
vanu_air_vehicle_term.Subtract.Damage1 = 8
|
||||
|
||||
|
|
@ -7442,7 +7530,7 @@ object GlobalDefinitions {
|
|||
vanu_vehicle_term.MaxHealth = 500
|
||||
vanu_vehicle_term.Damageable = true
|
||||
vanu_vehicle_term.Repairable = true
|
||||
vanu_vehicle_term.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
vanu_vehicle_term.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
vanu_vehicle_term.RepairIfDestroyed = true
|
||||
vanu_vehicle_term.Subtract.Damage1 = 8
|
||||
|
||||
|
|
@ -7455,9 +7543,10 @@ object GlobalDefinitions {
|
|||
bfr_terminal.MaxHealth = 500
|
||||
bfr_terminal.Damageable = true
|
||||
bfr_terminal.Repairable = true
|
||||
bfr_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
bfr_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
bfr_terminal.RepairIfDestroyed = true
|
||||
bfr_terminal.Subtract.Damage1 = 8
|
||||
bfr_terminal.Geometry = GeometryForm.representByCylinder(radius = 0.92185f, height = 2.64693f)
|
||||
|
||||
respawn_tube.Name = "respawn_tube"
|
||||
respawn_tube.Delay = 10
|
||||
|
|
@ -7466,9 +7555,10 @@ object GlobalDefinitions {
|
|||
respawn_tube.Damageable = true
|
||||
respawn_tube.DamageableByFriendlyFire = false
|
||||
respawn_tube.Repairable = true
|
||||
respawn_tube.autoRepair = AutoRepairStats(1.6f, 10000, 2400, 1) //orig. 1, 10000, 2400, 1
|
||||
respawn_tube.autoRepair = AutoRepairStats(1.6f, 10000, 2400, 1)
|
||||
respawn_tube.RepairIfDestroyed = true
|
||||
respawn_tube.Subtract.Damage1 = 8
|
||||
respawn_tube.Geometry = GeometryForm.representByCylinder(radius = 0.9336f, height = 2.84375f)
|
||||
|
||||
respawn_tube_sanctuary.Name = "respawn_tube"
|
||||
respawn_tube_sanctuary.Delay = 10
|
||||
|
|
@ -7477,7 +7567,8 @@ object GlobalDefinitions {
|
|||
respawn_tube_sanctuary.Damageable = false //true?
|
||||
respawn_tube_sanctuary.DamageableByFriendlyFire = false
|
||||
respawn_tube_sanctuary.Repairable = true
|
||||
respawn_tube_sanctuary.autoRepair = AutoRepairStats(1.6f, 10000, 2400, 1) //orig. 1, 10000, 2400, 1
|
||||
respawn_tube_sanctuary.autoRepair = AutoRepairStats(1.6f, 10000, 2400, 1)
|
||||
//TODO will need geometry when Damageable = true
|
||||
|
||||
respawn_tube_tower.Name = "respawn_tube_tower"
|
||||
respawn_tube_tower.Delay = 10
|
||||
|
|
@ -7486,9 +7577,10 @@ object GlobalDefinitions {
|
|||
respawn_tube_tower.Damageable = true
|
||||
respawn_tube_tower.DamageableByFriendlyFire = false
|
||||
respawn_tube_tower.Repairable = true
|
||||
respawn_tube_tower.autoRepair = AutoRepairStats(1.6f, 10000, 2400, 1) //orig. 1, 10000, 2400, 1
|
||||
respawn_tube_tower.autoRepair = AutoRepairStats(1.6f, 10000, 2400, 1)
|
||||
respawn_tube_tower.RepairIfDestroyed = true
|
||||
respawn_tube_tower.Subtract.Damage1 = 8
|
||||
respawn_tube_tower.Geometry = GeometryForm.representByCylinder(radius = 0.9336f, height = 2.84375f)
|
||||
|
||||
teleportpad_terminal.Name = "teleportpad_terminal"
|
||||
teleportpad_terminal.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.routerTerminal)
|
||||
|
|
@ -7504,8 +7596,9 @@ object GlobalDefinitions {
|
|||
medical_terminal.MaxHealth = 500
|
||||
medical_terminal.Damageable = true
|
||||
medical_terminal.Repairable = true
|
||||
medical_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
medical_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
medical_terminal.RepairIfDestroyed = true
|
||||
medical_terminal.Geometry = GeometryForm.representByCylinder(radius = 0.711f, height = 1.75f)
|
||||
|
||||
adv_med_terminal.Name = "adv_med_terminal"
|
||||
adv_med_terminal.Interval = 500
|
||||
|
|
@ -7516,8 +7609,9 @@ object GlobalDefinitions {
|
|||
adv_med_terminal.MaxHealth = 750
|
||||
adv_med_terminal.Damageable = true
|
||||
adv_med_terminal.Repairable = true
|
||||
adv_med_terminal.autoRepair = AutoRepairStats(1.57894f, 5000, 2400, 0.5f) //orig. 1, 5000, 2400, 0.5f
|
||||
adv_med_terminal.autoRepair = AutoRepairStats(1.57894f, 5000, 2400, 0.5f)
|
||||
adv_med_terminal.RepairIfDestroyed = true
|
||||
adv_med_terminal.Geometry = GeometryForm.representByCylinder(radius = 0.8662125f, height = 3.47f)
|
||||
|
||||
crystals_health_a.Name = "crystals_health_a"
|
||||
crystals_health_a.Interval = 500
|
||||
|
|
@ -7544,7 +7638,7 @@ object GlobalDefinitions {
|
|||
portable_med_terminal.MaxHealth = 500
|
||||
portable_med_terminal.Damageable = false //TODO actually true
|
||||
portable_med_terminal.Repairable = false
|
||||
portable_med_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
|
||||
portable_med_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f)
|
||||
|
||||
pad_landing_frame.Name = "pad_landing_frame"
|
||||
pad_landing_frame.Interval = 1000
|
||||
|
|
@ -7658,7 +7752,7 @@ object GlobalDefinitions {
|
|||
manned_turret.Damageable = true
|
||||
manned_turret.DamageDisablesAt = 0
|
||||
manned_turret.Repairable = true
|
||||
manned_turret.autoRepair = AutoRepairStats(1.0909f, 10000, 1600, 0.5f) //orig. 1, 10000, 1600, 0.5f
|
||||
manned_turret.autoRepair = AutoRepairStats(1.0909f, 10000, 1600, 0.5f)
|
||||
manned_turret.RepairIfDestroyed = true
|
||||
manned_turret.Weapons += 1 -> new mutable.HashMap()
|
||||
manned_turret.Weapons(1) += TurretUpgrade.None -> phalanx_sgl_hevgatcan
|
||||
|
|
@ -7674,15 +7768,16 @@ object GlobalDefinitions {
|
|||
Damage1 = 300
|
||||
DamageRadius = 5
|
||||
DamageAtEdge = 0.1f
|
||||
Modifiers = RadialDegrade
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
manned_turret.Geometry = GeometryForm.representByCylinder(radius = 1.2695f, height = 2.6875f)
|
||||
|
||||
vanu_sentry_turret.Name = "vanu_sentry_turret"
|
||||
vanu_sentry_turret.MaxHealth = 1500
|
||||
vanu_sentry_turret.Damageable = true
|
||||
vanu_sentry_turret.DamageDisablesAt = 0
|
||||
vanu_sentry_turret.Repairable = true
|
||||
vanu_sentry_turret.autoRepair = AutoRepairStats(3.27272f, 10000, 1000, 0.5f) //orig. 3, 10000, 1000, 0.5f
|
||||
vanu_sentry_turret.autoRepair = AutoRepairStats(3.27272f, 10000, 1000, 0.5f)
|
||||
vanu_sentry_turret.RepairIfDestroyed = true
|
||||
vanu_sentry_turret.Weapons += 1 -> new mutable.HashMap()
|
||||
vanu_sentry_turret.Weapons(1) += TurretUpgrade.None -> vanu_sentry_turret_weapon
|
||||
|
|
@ -7690,6 +7785,7 @@ object GlobalDefinitions {
|
|||
vanu_sentry_turret.MountPoints += 2 -> 0
|
||||
vanu_sentry_turret.FactionLocked = false
|
||||
vanu_sentry_turret.ReserveAmmunition = false
|
||||
vanu_sentry_turret.Geometry = GeometryForm.representByCylinder(radius = 1.76311f, height = 3.984375f)
|
||||
|
||||
painbox.Name = "painbox"
|
||||
painbox.alwaysOn = false
|
||||
|
|
@ -7764,7 +7860,7 @@ object GlobalDefinitions {
|
|||
generator.Damageable = true
|
||||
generator.DamageableByFriendlyFire = false
|
||||
generator.Repairable = true
|
||||
generator.autoRepair = AutoRepairStats(0.77775f, 5000, 875, 1) //orig. 1, 5000, 875, 1
|
||||
generator.autoRepair = AutoRepairStats(0.77775f, 5000, 875, 1)
|
||||
generator.RepairDistance = 13.5f
|
||||
generator.RepairIfDestroyed = true
|
||||
generator.Subtract.Damage1 = 9
|
||||
|
|
@ -7772,12 +7868,12 @@ object GlobalDefinitions {
|
|||
generator.innateDamage = new DamageWithPosition {
|
||||
CausesDamageType = DamageType.One
|
||||
Damage0 = 99999
|
||||
//DamageRadius should be 14, but 14 is insufficient for hitting the whole chamber; hence, ...
|
||||
DamageRadius = 15.75f
|
||||
DamageRadiusMin = 14
|
||||
DamageRadius = 14.5f
|
||||
DamageAtEdge = 0.00002f
|
||||
Modifiers = RadialDegrade
|
||||
//damage is 99999 at 14m, dropping rapidly to ~1 at 15.75m
|
||||
Modifiers = ExplodingRadialDegrade
|
||||
//damage is 99999 at 14m, dropping rapidly to ~1 at 14.5m
|
||||
}
|
||||
generator.Geometry = GeometryForm.representByCylinder(radius = 1.2617f, height = 9.14063f)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ final case class PlayerSource(
|
|||
position: Vector3,
|
||||
orientation: Vector3,
|
||||
velocity: Option[Vector3],
|
||||
crouching: Boolean,
|
||||
jumping: Boolean,
|
||||
modifiers: ResistanceProfile
|
||||
) extends SourceEntry {
|
||||
override def Name = name
|
||||
|
|
@ -48,6 +50,8 @@ object PlayerSource {
|
|||
tplayer.Position,
|
||||
tplayer.Orientation,
|
||||
tplayer.Velocity,
|
||||
tplayer.Crouching,
|
||||
tplayer.Jumping,
|
||||
ExoSuitDefinition.Select(tplayer.ExoSuit, tplayer.Faction)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,17 @@ package net.psforever.objects.definition
|
|||
|
||||
import net.psforever.objects.avatar.Avatars
|
||||
import net.psforever.objects.definition.converter.AvatarConverter
|
||||
import net.psforever.objects.geometry.GeometryForm
|
||||
import net.psforever.objects.vital.VitalityDefinition
|
||||
|
||||
/**
|
||||
* The definition for game objects that look like other people, and also for players.
|
||||
* @param objectId the object's identifier number
|
||||
* The definition for game objects that look like players.
|
||||
* @param objectId the object type number
|
||||
*/
|
||||
class AvatarDefinition(objectId: Int) extends ObjectDefinition(objectId) with VitalityDefinition {
|
||||
Avatars(objectId) //let throw NoSuchElementException
|
||||
Packet = AvatarDefinition.converter
|
||||
Geometry = GeometryForm.representPlayerByCylinder(radius = 1.6f)
|
||||
}
|
||||
|
||||
object AvatarDefinition {
|
||||
|
|
|
|||
|
|
@ -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.objects.geometry.{Geometry3D, GeometryForm}
|
||||
import net.psforever.types.OxygenState
|
||||
|
||||
/**
|
||||
|
|
@ -76,5 +77,27 @@ abstract class ObjectDefinition(private val objectId: Int) extends BasicDefiniti
|
|||
UnderwaterLifespan()
|
||||
}
|
||||
|
||||
private var serverSplashTargetsCentroid: Boolean = false
|
||||
|
||||
def ServerSplashTargetsCentroid: Boolean = serverSplashTargetsCentroid
|
||||
|
||||
def ServerSplashTargetsCentroid_=(splash: Boolean): Boolean = {
|
||||
serverSplashTargetsCentroid = splash
|
||||
ServerSplashTargetsCentroid
|
||||
}
|
||||
|
||||
private var serverGeometry: Any => Geometry3D = GeometryForm.representByPoint()
|
||||
|
||||
def Geometry: Any => Geometry3D = if (ServerSplashTargetsCentroid) {
|
||||
serverGeometry
|
||||
} else {
|
||||
GeometryForm.representByPoint()
|
||||
}
|
||||
|
||||
def Geometry_=(func: Any => Geometry3D): Any => Geometry3D = {
|
||||
serverGeometry = func
|
||||
Geometry
|
||||
}
|
||||
|
||||
def ObjectId: Int = objectId
|
||||
}
|
||||
|
|
|
|||
30
src/main/scala/net/psforever/objects/geometry/Geometry.scala
Normal file
30
src/main/scala/net/psforever/objects/geometry/Geometry.scala
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package net.psforever.objects.geometry
|
||||
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
object Geometry {
|
||||
def equalFloats(value1: Float, value2: Float, off: Float = 0.001f): Boolean = {
|
||||
val diff = value1 - value2
|
||||
if (diff >= 0) diff <= off else diff > -off
|
||||
}
|
||||
|
||||
def equalVectors(value1: Vector3, value2: Vector3, off: Float = 0.001f): Boolean = {
|
||||
equalFloats(value1.x, value2.x, off) &&
|
||||
equalFloats(value1.y, value2.y, off) &&
|
||||
equalFloats(value1.z, value2.z, off)
|
||||
}
|
||||
|
||||
def closeToInsignificance(d: Float, epsilon: Float = 10f): Float = {
|
||||
val ulp = math.ulp(epsilon)
|
||||
math.signum(d) match {
|
||||
case -1f =>
|
||||
val n = math.abs(d)
|
||||
val p = math.abs(n - n.toInt)
|
||||
if (p < ulp || d > ulp) d + p else d
|
||||
case _ =>
|
||||
val p = math.abs(d - d.toInt)
|
||||
if (p < ulp || d < ulp) d - p else d
|
||||
}
|
||||
}
|
||||
}
|
||||
126
src/main/scala/net/psforever/objects/geometry/GeometryForm.scala
Normal file
126
src/main/scala/net/psforever/objects/geometry/GeometryForm.scala
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package net.psforever.objects.geometry
|
||||
|
||||
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player}
|
||||
import net.psforever.types.{ExoSuitType, Vector3}
|
||||
|
||||
object GeometryForm {
|
||||
/** this point can not be used for purposes of geometric representation */
|
||||
lazy val invalidPoint: Point3D = Point3D(Float.MinValue, Float.MinValue, Float.MinValue)
|
||||
/** this cylinder can not be used for purposes of geometric representation */
|
||||
lazy val invalidCylinder: Cylinder = Cylinder(invalidPoint.asVector3, Vector3.Zero, Float.MinValue, 0)
|
||||
|
||||
/**
|
||||
* The geometric representation is the entity's centroid.
|
||||
* @param o the entity
|
||||
* @return the representation
|
||||
*/
|
||||
def representByPoint()(o: Any): Geometry3D = {
|
||||
o match {
|
||||
case p: PlanetSideGameObject => Point3D(p.Position)
|
||||
case s: SourceEntry => Point3D(s.Position)
|
||||
case _ => invalidPoint
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The geometric representation is a sphere around the entity's centroid
|
||||
* positioned following the axis of rotation (the entity's base).
|
||||
* @param radius how wide a hemisphere is
|
||||
* @param o the entity
|
||||
* @return the representation
|
||||
*/
|
||||
def representBySphere(radius: Float)(o: Any): Geometry3D = {
|
||||
o match {
|
||||
case p: PlanetSideGameObject =>
|
||||
Sphere(p.Position, radius)
|
||||
case s: SourceEntry =>
|
||||
Sphere(s.Position, radius)
|
||||
case _ =>
|
||||
Sphere(invalidPoint, radius)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The geometric representation is a sphere around the entity's centroid
|
||||
* positioned following the axis of rotation (the entity's base).
|
||||
* @param radius how wide a hemisphere is
|
||||
* @param o the entity
|
||||
* @return the representation
|
||||
*/
|
||||
def representByRaisedSphere(radius: Float)(o: Any): Geometry3D = {
|
||||
o match {
|
||||
case p: PlanetSideGameObject =>
|
||||
Sphere(p.Position + Vector3.relativeUp(p.Orientation) * radius, radius)
|
||||
case s: SourceEntry =>
|
||||
Sphere(s.Position + Vector3.relativeUp(s.Orientation) * radius, radius)
|
||||
case _ =>
|
||||
Sphere(invalidPoint, radius)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The geometric representation is a cylinder around the entity's base.
|
||||
* @param radius half the distance across
|
||||
* @param height how tall the cylinder is (the distance of the top to the base)
|
||||
* @param o the entity
|
||||
* @return the representation
|
||||
*/
|
||||
def representByCylinder(radius: Float, height: Float)(o: Any): Geometry3D = {
|
||||
o match {
|
||||
case p: PlanetSideGameObject => Cylinder(p.Position, Vector3.relativeUp(p.Orientation), radius, height)
|
||||
case s: SourceEntry => Cylinder(s.Position, Vector3.relativeUp(s.Orientation), radius, height)
|
||||
case _ => invalidCylinder
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The geometric representation is a cylinder around the entity's base
|
||||
* if the target represents a player entity.
|
||||
* @param radius a measure of the player's bulk
|
||||
* @param o the entity
|
||||
* @return the representation
|
||||
*/
|
||||
def representPlayerByCylinder(radius: Float)(o: Any): Geometry3D = {
|
||||
o match {
|
||||
case p: Player =>
|
||||
val radialOffset = if(p.ExoSuit == ExoSuitType.MAX) 0.25f else 0f
|
||||
Cylinder(
|
||||
p.Position,
|
||||
radius + radialOffset,
|
||||
GlobalDefinitions.MaxDepth(p)
|
||||
)
|
||||
case p: PlayerSource =>
|
||||
val radialOffset = if(p.ExoSuit == ExoSuitType.MAX) 0.125f else 0f
|
||||
val heightOffset = if(p.crouching) 1.093750f else GlobalDefinitions.avatar.MaxDepth
|
||||
Cylinder(
|
||||
p.Position,
|
||||
radius + radialOffset,
|
||||
heightOffset
|
||||
)
|
||||
case _ =>
|
||||
invalidCylinder
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The geometric representation is a cylinder around the entity's base
|
||||
* as if the target is displaced from the ground at an expected (fixed?) distance.
|
||||
* @param radius half the distance across
|
||||
* @param height how tall the cylinder is (the distance of the top to the base)
|
||||
* @param hoversAt how far off the base coordinates the actual cylinder begins
|
||||
* @param o the entity
|
||||
* @return the representation
|
||||
*/
|
||||
def representHoveringEntityByCylinder(radius: Float, height: Float, hoversAt: Float)(o: Any): Geometry3D = {
|
||||
o match {
|
||||
case p: PlanetSideGameObject =>
|
||||
Cylinder(p.Position, Vector3.relativeUp(p.Orientation), radius, height)
|
||||
case s: SourceEntry =>
|
||||
Cylinder(s.Position, Vector3.relativeUp(s.Orientation), radius, height)
|
||||
case _ =>
|
||||
invalidCylinder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,433 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package net.psforever.objects.geometry
|
||||
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
/**
|
||||
* Basic interface for all geometry.
|
||||
*/
|
||||
trait PrimitiveGeometry {
|
||||
/**
|
||||
* The centroid of the geometry.
|
||||
* @return a point
|
||||
*/
|
||||
def center: Point
|
||||
|
||||
/**
|
||||
* Find a point on the exterior of the geometry if a line was drawn outwards from the centroid.
|
||||
* What counts as "the exterior" is limited to the complexity of the geometry.
|
||||
* @param v the vector in the direction of the point on the exterior
|
||||
* @return a point
|
||||
*/
|
||||
def pointOnOutside(v: Vector3) : Point
|
||||
}
|
||||
|
||||
//trait Geometry2D extends PrimitiveGeometry {
|
||||
// def center: Point2D
|
||||
//
|
||||
// def pointOnOutside(v: Vector3): Point2D = center
|
||||
//}
|
||||
|
||||
/**
|
||||
* Basic interface of all three-dimensional geometry.
|
||||
* For the only real requirement for a hree-dimensional geometric figure is that it has three components of position
|
||||
* and an equal number of components demonstrating equal that said dimensionality.
|
||||
*/
|
||||
trait Geometry3D extends PrimitiveGeometry {
|
||||
def center: Point3D
|
||||
|
||||
def pointOnOutside(v: Vector3): Point3D = center
|
||||
}
|
||||
|
||||
/**
|
||||
* Characteristics of a geometric figure with only three coordinates to define a position.
|
||||
*/
|
||||
trait Point {
|
||||
/**
|
||||
* Transform the point into the common interchangeable format for coordinates.
|
||||
* They're very similar, anyway.
|
||||
* @return a `Vector3` entity of the same denomination
|
||||
*/
|
||||
def asVector3: Vector3
|
||||
}
|
||||
|
||||
/**
|
||||
* Characteristics of a geometric figure defining a direction or a progressive change in coordinates.
|
||||
*/
|
||||
trait Slope {
|
||||
/**
|
||||
* The slope itself.
|
||||
* @return a `Vector3` entity
|
||||
*/
|
||||
def d: Vector3
|
||||
|
||||
/**
|
||||
* How long the slope goes on for.
|
||||
* @return The length of the slope
|
||||
*/
|
||||
def length: Float
|
||||
}
|
||||
|
||||
object Slope {
|
||||
/**
|
||||
* On occasions, the defined slope should have a length of one unit.
|
||||
* It is a unit vector.
|
||||
* @param v the input slope as a `Vector3` entity
|
||||
* @throws `AssertionError` if the length is more or less than 1.
|
||||
*/
|
||||
def assertUnitVector(v: Vector3): Unit = {
|
||||
assert({
|
||||
val mag = Vector3.Magnitude(v)
|
||||
mag - 0.05f < 1f && mag + 0.05f > 1f
|
||||
}, "not a unit vector")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Characteristics of a geometric figure indicating an infinite slope - a mathematical line.
|
||||
* The slope is always a unit vector.
|
||||
* The point that assists to define the line is a constraint that the line must pass through.
|
||||
*/
|
||||
trait Line extends Slope {
|
||||
Slope.assertUnitVector(d)
|
||||
|
||||
def p: Point
|
||||
|
||||
/**
|
||||
* The length of a mathematical line is infinite.
|
||||
* @return The length of the slope
|
||||
*/
|
||||
def length: Float = Float.PositiveInfinity
|
||||
}
|
||||
|
||||
/**
|
||||
* Characteristics of a geometric figure that have two endpoints, defining a fixed-length slope.
|
||||
*/
|
||||
trait Segment extends Slope {
|
||||
/** The first point, considered the "start". */
|
||||
def p1: Point
|
||||
/** The second point, considered the "end". */
|
||||
def p2: Point
|
||||
|
||||
def length: Float = Vector3.Magnitude(d)
|
||||
|
||||
/**
|
||||
* Transform the segment into a matheatical line of the same slope.
|
||||
* @return
|
||||
*/
|
||||
def asLine: PrimitiveGeometry
|
||||
}
|
||||
|
||||
/**
|
||||
* The instance of a geometric coordinate position.
|
||||
* @see `Vector3`
|
||||
* @param x the 'x' coordinate of the position
|
||||
* @param y the 'y' coordinate of the position
|
||||
* @param z the 'z' coordinate of the position
|
||||
*/
|
||||
final case class Point3D(x: Float, y: Float, z: Float) extends Geometry3D with Point {
|
||||
def center: Point3D = this
|
||||
|
||||
def asVector3: Vector3 = Vector3(x, y, z)
|
||||
}
|
||||
|
||||
object Point3D {
|
||||
/**
|
||||
* An overloaded constructor that assigns world origin coordinates.
|
||||
* @return a `Point3D` entity
|
||||
*/
|
||||
def apply(): Point3D = Point3D(0,0,0)
|
||||
|
||||
/**
|
||||
* An overloaded constructor that uses the same coordinates from a `Vector3` entity.
|
||||
* @param v the entity with the corresponding points
|
||||
* @return a `Point3D` entity
|
||||
*/
|
||||
def apply(v: Vector3): Point3D = Point3D(v.x, v.y, v.z)
|
||||
}
|
||||
|
||||
/**
|
||||
* The instance of a geometric coordinate position and a specific direction from that position.
|
||||
* Rays are like mathematical lines in that they have infinite length;
|
||||
* but, that infinite length is only expressed in a single direction,
|
||||
* rather than proceeding in both a direction and its opposite direction from a target point.
|
||||
* Infinity just be like that.
|
||||
* Additionally, the point is not merely any point on the ray used to assist defining it
|
||||
* and is instead considered the clearly-defined origin of the ray.
|
||||
* @param p the point of origin
|
||||
* @param d the direction
|
||||
*/
|
||||
final case class Ray3D(p: Point3D, d: Vector3) extends Geometry3D with Line {
|
||||
def center: Point3D = p
|
||||
}
|
||||
|
||||
object Ray3D {
|
||||
/**
|
||||
* An overloaded constructor that uses individual coordinates.
|
||||
* @param x the 'x' coordinate of the position
|
||||
* @param y the 'y' coordinate of the position
|
||||
* @param z the 'z' coordinate of the position
|
||||
* @param d the direction
|
||||
* @return a `Ray3D` entity
|
||||
*/
|
||||
def apply(x: Float, y: Float, z: Float, d: Vector3): Ray3D = Ray3D(Point3D(x,y,z), d)
|
||||
|
||||
/**
|
||||
* An overloaded constructor that uses a `Vector3` entity to express coordinates.
|
||||
* @param v the coordinates of the position
|
||||
* @param d the direction
|
||||
* @return a `Ray3D` entity
|
||||
*/
|
||||
def apply(v: Vector3, d: Vector3): Ray3D = Ray3D(Point3D(v.x, v.y, v.z), d)
|
||||
}
|
||||
|
||||
/**
|
||||
* The instance of a geometric coordinate position and a specific direction from that position.
|
||||
* Mathematical lines have infinite length and their slope is represented as a unit vector.
|
||||
* The point is merely a point used to assist in defining the line.
|
||||
* @param p the point of origin
|
||||
* @param d the direction
|
||||
*/
|
||||
final case class Line3D(p: Point3D, d: Vector3) extends Geometry3D with Line {
|
||||
def center: Point3D = p
|
||||
}
|
||||
|
||||
object Line3D {
|
||||
/**
|
||||
* An overloaded constructor that uses individual coordinates.
|
||||
* @param x the 'x' coordinate of the position
|
||||
* @param y the 'y' coordinate of the position
|
||||
* @param z the 'z' coordinate of the position
|
||||
* @param d the direction
|
||||
* @return a `Line3D` entity
|
||||
*/
|
||||
def apply(x: Float, y: Float, z: Float, d: Vector3): Line3D = {
|
||||
Line3D(Point3D(x,y,z), d)
|
||||
}
|
||||
|
||||
/**
|
||||
* An overloaded constructor that uses a pair of individual coordinates
|
||||
* and uses their difference to produce a unit vector to define a direction.
|
||||
* @param ax the 'x' coordinate of the position
|
||||
* @param ay the 'y' coordinate of the position
|
||||
* @param az the 'z' coordinate of the position
|
||||
* @param bx the 'x' coordinate of a destination position
|
||||
* @param by the 'y' coordinate of a destination position
|
||||
* @param bz the 'z' coordinate of a destination position
|
||||
* @return a `Line3D` entity
|
||||
*/
|
||||
def apply(ax: Float, ay: Float, az: Float, bx: Float, by: Float, bz: Float): Line3D = {
|
||||
Line3D(Point3D(ax, ay, az), Vector3.Unit(Vector3(bx-ax, by-ay, bz-az)))
|
||||
}
|
||||
|
||||
/**
|
||||
* An overloaded constructor that uses a pair of points
|
||||
* and uses their difference to produce a unit vector to define a direction.
|
||||
* @param p1 the coordinates of the position
|
||||
* @param p2 the coordinates of a destination position
|
||||
* @return a `Line3D` entity
|
||||
*/
|
||||
def apply(p1: Point3D, p2: Point3D): Line3D = {
|
||||
Line3D(p1, Vector3.Unit(Vector3(p2.x-p1.x, p2.y-p1.y, p2.z-p1.z)))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The instance of a limited span between two geometric coordinate positions, called "endpoints".
|
||||
* Unlike mathematical lines, slope is treated the same as the vector leading from one point to the other
|
||||
* and is the length of the segment.
|
||||
* @param p1 a point
|
||||
* @param p2 another point
|
||||
*/
|
||||
final case class Segment3D(p1: Point3D, p2: Point3D) extends Geometry3D with Segment {
|
||||
/**
|
||||
* The center point of a segment is a position that is equally in between both endpoints.
|
||||
* @return a point
|
||||
*/
|
||||
def center: Point3D = Point3D((p2.asVector3 + p1.asVector3) * 0.5f)
|
||||
|
||||
def d: Vector3 = p2.asVector3 - p1.asVector3
|
||||
|
||||
def asLine: Line3D = Line3D(p1, Vector3.Unit(d))
|
||||
}
|
||||
|
||||
object Segment3D {
|
||||
/**
|
||||
* An overloaded constructor that uses a pair of individual coordinates
|
||||
* and uses their difference to define a direction.
|
||||
* @param ax the 'x' coordinate of the position
|
||||
* @param ay the 'y' coordinate of the position
|
||||
* @param az the 'z' coordinate of the position
|
||||
* @param bx the 'x' coordinate of a destination position
|
||||
* @param by the 'y' coordinate of a destination position
|
||||
* @param bz the 'z' coordinate of a destination position
|
||||
* @return a `Segment3D` entity
|
||||
*/
|
||||
def apply(ax: Float, ay: Float, az: Float, bx: Float, by: Float, bz: Float): Segment3D = {
|
||||
Segment3D(Point3D(ax, ay, az), Point3D(bx, by, bz))
|
||||
}
|
||||
|
||||
/**
|
||||
* An overloaded constructor.
|
||||
* @param p the point of origin
|
||||
* @param d the direction and distance (of the second point)
|
||||
*/
|
||||
def apply(p: Point3D, d: Vector3): Segment3D = {
|
||||
Segment3D(p, Point3D(p.x + d.x, p.y + d.y, p.z + d.z))
|
||||
}
|
||||
|
||||
/**
|
||||
* An overloaded constructor that uses individual coordinates.
|
||||
* @param x the 'x' coordinate of the position
|
||||
* @param y the 'y' coordinate of the position
|
||||
* @param z the 'z' coordinate of the position
|
||||
* @param d the direction
|
||||
* @return a `Segment3D` entity
|
||||
*/
|
||||
def apply(x: Float, y: Float, z: Float, d: Vector3): Segment3D = {
|
||||
Segment3D(Point3D(x, y, z), Point3D(x + d.x, y + d.y, z + d.z))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The instance of a volumetric region that encapsulates all points within a certain distance of a central point.
|
||||
* (That's what a sphere is.)
|
||||
* A sphere has no real "top", "base", or "side" as all directions are described the same.
|
||||
* @param p the point
|
||||
* @param radius a distance that spans all points in any direction from the central point
|
||||
*/
|
||||
final case class Sphere(p: Point3D, radius: Float) extends Geometry3D {
|
||||
def center: Point3D = p
|
||||
|
||||
/**
|
||||
* Find a point on the exterior of the geometry if a line was drawn outwards from the centroid.
|
||||
* All points that exist on the exterior of a sphere are on the surface of that sphere
|
||||
* and are equally distant from the central point.
|
||||
* @param v the vector in the direction of the point on the exterior
|
||||
* @return a point
|
||||
*/
|
||||
override def pointOnOutside(v: Vector3): Point3D = {
|
||||
val slope = Vector3.Unit(v)
|
||||
val mult = radius / Vector3.Magnitude(slope)
|
||||
Point3D(center.asVector3 + slope * mult)
|
||||
}
|
||||
}
|
||||
|
||||
object Sphere {
|
||||
/**
|
||||
* An overloaded constructor that only defines the radius of the sphere
|
||||
* and places it at the world origin.
|
||||
* @param radius a distance around the world origin coordinates
|
||||
* @return a `Sphere` entity
|
||||
*/
|
||||
def apply(radius: Float): Sphere = Sphere(Point3D(), radius)
|
||||
|
||||
/**
|
||||
* An overloaded constructor that uses individual coordinates to define the central point.
|
||||
* * @param x the 'x' coordinate of the position
|
||||
* * @param y the 'y' coordinate of the position
|
||||
* * @param z the 'z' coordinate of the position
|
||||
* @param radius a distance around the world origin coordinates
|
||||
* @return a `Sphere` entity
|
||||
*/
|
||||
def apply(x: Float, y: Float, z: Float, radius: Float): Sphere = Sphere(Point3D(x,y,z), radius)
|
||||
|
||||
/**
|
||||
* An overloaded constructor that uses vector coordinates to define the central point.
|
||||
* @param v the coordinates of the position
|
||||
* @param radius a distance around the world origin coordinates
|
||||
* @return a `Sphere` entity
|
||||
*/
|
||||
def apply(v: Vector3, radius: Float): Sphere = Sphere(Point3D(v), radius)
|
||||
}
|
||||
|
||||
/**
|
||||
* The instance of a volumetric region that encapsulates all points within a certain distance of a central point.
|
||||
* The region is characterized by a regular circular cross-section when observed from above or below
|
||||
* and a flat top and a flat base when viewed from the side.
|
||||
* The "base" is where the origin point is defined (at the center of a circular cross-section)
|
||||
* and the "top" is discovered a `height` from the base along what the cylinder considers its `relativeUp` direction.
|
||||
* @param p the point
|
||||
* @param relativeUp what the cylinder considers its "up" direction
|
||||
* @param radius a distance expressed in all circular cross-sections along the `relativeUp` direction
|
||||
* @param height the distance between the "base" and the "top"
|
||||
*/
|
||||
final case class Cylinder(p: Point3D, relativeUp: Vector3, radius: Float, height: Float) extends Geometry3D {
|
||||
Slope.assertUnitVector(relativeUp)
|
||||
|
||||
/**
|
||||
* The center point of a cylinder is halfway between the "top" and the "base" along the direction of `relativeUp`.
|
||||
* @return a point
|
||||
*/
|
||||
def center: Point3D = Point3D(p.asVector3 + relativeUp * height * 0.5f)
|
||||
|
||||
/**
|
||||
* Find a point on the exterior of the geometry if a line was drawn outwards from the centroid.
|
||||
* A cylinder is composed of three clearly-defined regions on its exterior -
|
||||
* two flat but circular surfaces that are the "top" and the "base"
|
||||
* and a wrapped "sides" surface that defines all points connecting the "base" to the "top"
|
||||
* along the `relativeUp` direction.
|
||||
* The requested point may exist on any of these surfaces.
|
||||
* @param v the vector in the direction of the point on the exterior
|
||||
* @return a point
|
||||
*/
|
||||
override def pointOnOutside(v: Vector3): Point3D = {
|
||||
val centerVector = center.asVector3
|
||||
val slope = Vector3.Unit(v)
|
||||
val dotProdOfSlopeAndUp = Vector3.DotProduct(slope, relativeUp)
|
||||
if (Geometry.equalFloats(dotProdOfSlopeAndUp, value2 = 1) || Geometry.equalFloats(dotProdOfSlopeAndUp, value2 = -1)) {
|
||||
// very rare condition: 'slope' and 'relativeUp' are parallel or antiparallel
|
||||
Point3D(centerVector + slope * height * 0.5f)
|
||||
} else {
|
||||
val acrossTopAndBase = slope - relativeUp * dotProdOfSlopeAndUp
|
||||
val pointOnSide = centerVector + slope * (radius / Vector3.Magnitude(acrossTopAndBase))
|
||||
val pointOnBase = p.asVector3 + acrossTopAndBase * radius
|
||||
val pointOnTop = pointOnBase + relativeUp * height
|
||||
val fromPointOnTopToSide = Vector3.Unit(pointOnTop - pointOnSide)
|
||||
val fromPointOnSideToBase = Vector3.Unit(pointOnSide - pointOnBase)
|
||||
val target = if(Geometry.equalVectors(fromPointOnTopToSide, Vector3.Zero) ||
|
||||
Geometry.equalVectors(fromPointOnSideToBase, Vector3.Zero) ||
|
||||
Geometry.equalVectors(fromPointOnTopToSide, fromPointOnSideToBase)) {
|
||||
//on side, including top rim or base rim
|
||||
pointOnSide
|
||||
} else {
|
||||
//on top or base
|
||||
// the full equation would be 'centerVector + slope * (height * 0.5f / Vector3.Magnitude(relativeUp))'
|
||||
// 'relativeUp` is already a unit vector (magnitude of 1)
|
||||
centerVector + slope * height * 0.5f
|
||||
}
|
||||
Point3D(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Cylinder {
|
||||
/**
|
||||
* An overloaded constructor where the 'relativeUp' of the cylinder is perpendicular to the xy-plane.
|
||||
* @param p the point
|
||||
* @param radius a distance expressed in all circular cross-sections along the `relativeUp` direction
|
||||
* @param height the distance between the "base" and the "top"
|
||||
* @return
|
||||
*/
|
||||
def apply(p: Point3D, radius: Float, height: Float): Cylinder = Cylinder(p, Vector3(0,0,1), radius, height)
|
||||
|
||||
/**
|
||||
* An overloaded constructor where the origin point is expressed as a vector
|
||||
* and the 'relativeUp' of the cylinder is perpendicular to the xy-plane.
|
||||
* @param p the point
|
||||
* @param radius a distance expressed in all circular cross-sections along the `relativeUp` direction
|
||||
* @param height the distance between the "base" and the "top"
|
||||
* @return
|
||||
*/
|
||||
def apply(p: Vector3, radius: Float, height: Float): Cylinder = Cylinder(Point3D(p), Vector3(0,0,1), radius, height)
|
||||
|
||||
/**
|
||||
* An overloaded constructor the origin point is expressed as a vector.
|
||||
* @param p the point
|
||||
* @param v what the cylinder considers its "up" direction
|
||||
* @param radius a distance expressed in all circular cross-sections along the `relativeUp` direction
|
||||
* @param height the distance between the "base" and the "top"
|
||||
* @return
|
||||
*/
|
||||
def apply(p: Vector3, v: Vector3, radius: Float, height: Float): Cylinder = Cylinder(Point3D(p), v, radius, height)
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package net.psforever.objects.vital.etc
|
||||
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.ballistics.SourceEntry
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
|
||||
import net.psforever.objects.vital.prop.DamageWithPosition
|
||||
import net.psforever.objects.vital.resolution.DamageAndResistance
|
||||
|
||||
/**
|
||||
* A wrapper for a "damage source" in damage calculations
|
||||
* that parameterizes information necessary to explain a server-driven electromagnetic pulse occurring.
|
||||
* @see `VitalityDefinition.explodes`
|
||||
* @see `VitalityDefinition.innateDamage`
|
||||
* @see `Zone.causesSpecialEmp`
|
||||
* @param entity the source of the explosive yield
|
||||
* @param damageModel the model to be utilized in these calculations;
|
||||
* typically, but not always, defined by the target
|
||||
*/
|
||||
final case class EmpReason(
|
||||
entity: SourceEntry,
|
||||
source: DamageWithPosition,
|
||||
damageModel: DamageAndResistance,
|
||||
override val attribution: Int
|
||||
) extends DamageReason {
|
||||
def resolution: DamageResolution.Value = DamageResolution.Splash
|
||||
|
||||
def same(test: DamageReason): Boolean = test match {
|
||||
case eer: ExplodingEntityReason => eer.entity eq entity
|
||||
case _ => false
|
||||
}
|
||||
|
||||
/** lay the blame on that which caused this emp to occur */
|
||||
def adversary: Option[SourceEntry] = Some(entity)
|
||||
}
|
||||
|
||||
object EmpReason {
|
||||
def apply(
|
||||
owner: PlanetSideGameObject with FactionAffinity,
|
||||
source: DamageWithPosition,
|
||||
target: PlanetSideServerObject with Vitality
|
||||
): EmpReason = {
|
||||
EmpReason(SourceEntry(owner), source, target.DamageModel, owner.Definition.ObjectId)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,11 @@ import net.psforever.objects.PlanetSideGameObject
|
|||
import net.psforever.objects.ballistics.SourceEntry
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.vital.{Vitality, VitalityDefinition}
|
||||
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
|
||||
import net.psforever.objects.vital.interaction.DamageResult
|
||||
import net.psforever.objects.vital.base.{DamageModifiers, DamageReason, DamageResolution}
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
import net.psforever.objects.vital.prop.DamageWithPosition
|
||||
import net.psforever.objects.vital.resolution.DamageAndResistance
|
||||
import net.psforever.objects.zones.Zone
|
||||
|
||||
/**
|
||||
* A wrapper for a "damage source" in damage calculations
|
||||
|
|
@ -48,3 +49,48 @@ final case class ExplodingEntityReason(
|
|||
/** the entity that exploded is the source of the damage */
|
||||
override def attribution: Int = definition.ObjectId
|
||||
}
|
||||
|
||||
object ExplodingDamageModifiers {
|
||||
trait Mod extends DamageModifiers.Mod {
|
||||
def calculate(damage : Int, data : DamageInteraction, cause : DamageReason) : Int = {
|
||||
cause match {
|
||||
case o: ExplodingEntityReason => calculate(damage, data, o)
|
||||
case _ => damage
|
||||
}
|
||||
}
|
||||
|
||||
def calculate(damage : Int, data : DamageInteraction, cause : ExplodingEntityReason) : Int
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A variation of the normal radial damage degradation
|
||||
* that uses the geometric representations of the exploding entity and of the affected target
|
||||
* in its calculations that determine the distance between them.
|
||||
* @see `DamageModifierFunctions.RadialDegrade`
|
||||
*/
|
||||
case object ExplodingRadialDegrade extends ExplodingDamageModifiers.Mod {
|
||||
def calculate(damage: Int, data: DamageInteraction, cause: ExplodingEntityReason): Int = {
|
||||
cause.source match {
|
||||
case withPosition: DamageWithPosition =>
|
||||
val radius = withPosition.DamageRadius
|
||||
val radiusMin = withPosition.DamageRadiusMin
|
||||
val distance = math.sqrt(Zone.distanceCheck(
|
||||
cause.entity.Definition.asInstanceOf[ObjectDefinition].Geometry(cause.entity),
|
||||
data.target.Definition.Geometry(data.target)
|
||||
))
|
||||
if (distance <= radiusMin) {
|
||||
damage
|
||||
} else if (distance <= radius) {
|
||||
//damage - (damage * profile.DamageAtEdge * (distance - radiusMin) / (radius - radiusMin)).toInt
|
||||
val base = withPosition.DamageAtEdge
|
||||
val radi = radius - radiusMin
|
||||
(damage * ((1 - base) * ((radi - (distance - radiusMin)) / radi) + base)).toInt
|
||||
} else {
|
||||
0
|
||||
}
|
||||
case _ =>
|
||||
damage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.vital.etc
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions
|
||||
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions}
|
||||
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
|
||||
import net.psforever.objects.vital.damage.DamageCalculations.AgainstExoSuit
|
||||
import net.psforever.objects.vital.prop.DamageProperties
|
||||
import net.psforever.objects.vital.resolution.{DamageAndResistance, DamageResistanceModel}
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
|
||||
/**
|
||||
* A wrapper for a "damage source" in damage calculations
|
||||
* that parameterizes information necessary to explain a `BoomerDeployable` being detonated
|
||||
* using its complementary trigger.
|
||||
* Should be applied as the reason applied to the Boomer
|
||||
* in `DamageInteractions` that lead up to the Boomer exploding
|
||||
* which will carry the trigger as the reason and the user as the culprit.
|
||||
* Due to faction affiliation complicity between the user and the Boomer, however,
|
||||
* normal `Damageable` functionality would have to interject in a way where the trigger works anyway.
|
||||
* @see `BoomerDeployable`
|
||||
* @see `BoomerTrigger`
|
||||
* @see `DamageCalculations`
|
||||
* @see `VitalityDefinition.DamageableByFriendlyFire`
|
||||
* @param user the player who is holding the trigger
|
||||
* @param item_guid the trigger
|
||||
*/
|
||||
final case class TriggerUsedReason(user: PlayerSource, item_guid: PlanetSideGUID)
|
||||
extends DamageReason {
|
||||
def source: DamageProperties = TriggerUsedReason.triggered
|
||||
|
||||
def resolution: DamageResolution.Value = DamageResolution.Resolved
|
||||
|
||||
def same(test: DamageReason): Boolean = test match {
|
||||
case tur: TriggerUsedReason => tur.item_guid == item_guid && tur.user.Name.equals(user.Name)
|
||||
case _ => false
|
||||
}
|
||||
|
||||
/** lay the blame on the player who caused this explosion to occur */
|
||||
def adversary: Option[SourceEntry] = Some(user)
|
||||
|
||||
override def damageModel : DamageAndResistance = TriggerUsedReason.drm
|
||||
|
||||
/** while weird, the trigger was accredited as the method of death on Gemini Live;
|
||||
* even though its icon looks like an misshapen AMS */
|
||||
override def attribution: Int = GlobalDefinitions.boomer_trigger.ObjectId
|
||||
}
|
||||
|
||||
object TriggerUsedReason {
|
||||
private val triggered = new DamageProperties {
|
||||
Damage0 = 1 //token damage
|
||||
SympatheticExplosion = true //sets off a boomer
|
||||
}
|
||||
|
||||
/** basic damage, no resisting, quick and simple */
|
||||
private val drm = new DamageResistanceModel {
|
||||
DamageUsing = AgainstExoSuit
|
||||
ResistUsing = NoResistanceSelection
|
||||
Model = SimpleResolutions.calculate
|
||||
}
|
||||
}
|
||||
|
|
@ -38,12 +38,14 @@ import akka.actor.typed
|
|||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.actors.zone.ZoneActor
|
||||
import net.psforever.objects.avatar.Avatar
|
||||
import net.psforever.objects.geometry.Geometry3D
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||
import net.psforever.objects.vehicles.UtilityType
|
||||
import net.psforever.objects.vital.etc.ExplodingEntityReason
|
||||
import net.psforever.objects.vital.etc.{EmpReason, ExplodingEntityReason}
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
import net.psforever.objects.vital.prop.DamageWithPosition
|
||||
|
||||
/**
|
||||
* A server object representing the one-landmass planets as well as the individual subterranean caverns.<br>
|
||||
|
|
@ -1134,7 +1136,7 @@ object Zone {
|
|||
.flatMap { _.Amenities }
|
||||
.filter { _.Definition.Damageable }
|
||||
}
|
||||
//restrict to targets in the damage radius
|
||||
//restrict to targets according to the detection plan
|
||||
val allAffectedTargets = (playerTargets ++ vehicleTargets ++ complexDeployableTargets ++ soiTargets)
|
||||
.filter { target =>
|
||||
(target ne obj) && detectionTest(obj, target, radius)
|
||||
|
|
@ -1171,19 +1173,106 @@ object Zone {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates `Damageable` targets within the radius of a server-prepared electromagnetic pulse
|
||||
* and informs those entities that they have affected by the aforementioned pulse.
|
||||
* Targets within the effect radius within other rooms are affected, unlike with normal damage.
|
||||
* The only affected target is Boomer deployables.
|
||||
* @see `Amenity.Owner`
|
||||
* @see `BoomerDeployable`
|
||||
* @see `DamageInteraction`
|
||||
* @see `DamageResult`
|
||||
* @see `DamageWithPosition`
|
||||
* @see `EmpReason`
|
||||
* @see `Zone.DeployableList`
|
||||
* @param zone the zone in which the emp should occur
|
||||
* @param obj the entity that triggered the emp (information)
|
||||
* @param sourcePosition where the emp physically originates
|
||||
* @param effect characteristics of the emp produced
|
||||
* @param detectionTest a custom test to determine if any given target is affected;
|
||||
* defaults to an internal test for simple radial proximity
|
||||
* @return a list of affected entities
|
||||
*/
|
||||
def causeSpecialEmp(
|
||||
zone: Zone,
|
||||
obj: PlanetSideServerObject with Vitality,
|
||||
sourcePosition: Vector3,
|
||||
effect: DamageWithPosition,
|
||||
detectionTest: (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = distanceCheck
|
||||
): List[PlanetSideServerObject] = {
|
||||
val proxy: ExplosiveDeployable = {
|
||||
//construct a proxy unit to represent the pulse
|
||||
val o = new ExplosiveDeployable(GlobalDefinitions.special_emp)
|
||||
o.Owner = Some(obj.GUID)
|
||||
o.OwnerName = obj match {
|
||||
case p: Player => p.Name
|
||||
case o: OwnableByPlayer => o.OwnerName.getOrElse("")
|
||||
case _ => ""
|
||||
}
|
||||
o.Position = sourcePosition
|
||||
o.Faction = obj.Faction
|
||||
o
|
||||
}
|
||||
val radius = effect.DamageRadius * effect.DamageRadius
|
||||
//only boomers can be affected (that's why it's special)
|
||||
val allAffectedTargets = zone.DeployableList
|
||||
.collect { case o: BoomerDeployable if !o.Destroyed && (o ne obj) && detectionTest(proxy, o, radius) => o }
|
||||
//inform targets that they have suffered the effects of the emp
|
||||
allAffectedTargets
|
||||
.foreach { target =>
|
||||
target.Actor ! Vitality.Damage(
|
||||
DamageInteraction(
|
||||
SourceEntry(target),
|
||||
EmpReason(obj, effect, target),
|
||||
sourcePosition
|
||||
).calculate()
|
||||
)
|
||||
}
|
||||
allAffectedTargets
|
||||
}
|
||||
|
||||
/**
|
||||
* Two game entities are considered "near" each other if they are within a certain distance of one another.
|
||||
* A default function literal mainly used for `causesExplosion`.
|
||||
* @see `causeExplosion`
|
||||
* @see `Vector3.DistanceSquare`
|
||||
* @param obj1 a game entity
|
||||
* @param obj2 a game entity
|
||||
* @see `ObjectDefinition.Geometry`
|
||||
* @param obj1 a game entity, should be the source of the explosion
|
||||
* @param obj2 a game entity, should be the target of the explosion
|
||||
* @param maxDistance the square of the maximum distance permissible between game entities
|
||||
* before they are no longer considered "near"
|
||||
* @return `true`, if the target entities are near to each other;
|
||||
* @return `true`, if the target entities are near enough to each other;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
private def distanceCheck(obj1: PlanetSideGameObject, obj2: PlanetSideGameObject, maxDistance: Float): Boolean = {
|
||||
Vector3.DistanceSquared(obj1.Position, obj2.Position) <= maxDistance
|
||||
def distanceCheck(obj1: PlanetSideGameObject, obj2: PlanetSideGameObject, maxDistance: Float): Boolean = {
|
||||
distanceCheck(obj1.Definition.Geometry(obj1), obj2.Definition.Geometry(obj2), maxDistance)
|
||||
}
|
||||
/**
|
||||
* Two game entities are considered "near" each other if they are within a certain distance of one another.
|
||||
* @param g1 the geometric representation of a game entity
|
||||
* @param g2 the geometric representation of a game entity
|
||||
* @param maxDistance the square of the maximum distance permissible between game entities
|
||||
* before they are no longer considered "near"
|
||||
* @return `true`, if the target entities are near enough to each other;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def distanceCheck(g1: Geometry3D, g2: Geometry3D, maxDistance: Float): Boolean = {
|
||||
Vector3.DistanceSquared(g1.center.asVector3, g2.center.asVector3) <= maxDistance ||
|
||||
distanceCheck(g1, g2) <= maxDistance
|
||||
}
|
||||
/**
|
||||
* Two game entities are considered "near" each other if they are within a certain distance of one another.
|
||||
* @see `PrimitiveGeometry.pointOnOutside`
|
||||
* @see `Vector3.DistanceSquared`
|
||||
* @see `Vector3.neg`
|
||||
* @see `Vector3.Unit`
|
||||
* @param g1 the geometric representation of a game entity
|
||||
* @param g2 the geometric representation of a game entity
|
||||
* @return the crude distance between the two geometric representations
|
||||
*/
|
||||
def distanceCheck(g1: Geometry3D, g2: Geometry3D): Float = {
|
||||
val dir = Vector3.Unit(g2.center.asVector3 - g1.center.asVector3)
|
||||
val point1 = g1.pointOnOutside(dir).asVector3
|
||||
val point2 = g2.pointOnOutside(Vector3.neg(dir)).asVector3
|
||||
Vector3.DistanceSquared(point1, point2)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,14 @@ object Vector3 {
|
|||
*/
|
||||
def z(value: Float): Vector3 = Vector3(0, 0, value)
|
||||
|
||||
/**
|
||||
* Calculate the negation of this vector,
|
||||
* the same vector in the antiparallel direction.
|
||||
* @param v the original vector
|
||||
* @return the negation of the original vector
|
||||
*/
|
||||
def neg(v: Vector3): Vector3 = Vector3(-v.x, -v.y, -v.z)
|
||||
|
||||
/**
|
||||
* Calculate the actual distance between two points.
|
||||
* @param pos1 the first point
|
||||
|
|
@ -379,8 +387,8 @@ object Vector3 {
|
|||
|
||||
/**
|
||||
* Given a `Vector3` element composed of Euler angles
|
||||
* and a `Vector3` element in the direction of "up",
|
||||
* find a standard unit vector that points in the direction of "up" after rotating by the Euler angles.
|
||||
* and a `Vector3` element in the vector direction of "up",
|
||||
* find a standard unit vector that points in the direction of the entity's "up" after rotating by the Euler angles.
|
||||
* Compass direction rules apply (North is 0 degrees, East is 90 degrees, etc.).
|
||||
* @see `Vector3.Rx(Float)`
|
||||
* @see `Vector3.Ry(Float)`
|
||||
|
|
@ -390,7 +398,12 @@ object Vector3 {
|
|||
* @return a mathematical vector representing a relative "up" direction
|
||||
*/
|
||||
def relativeUp(orient: Vector3, up: Vector3): Vector3 = {
|
||||
//TODO is the missing calculation before Rz(Rx(Ry(v, x), y), z) or after Rz(Ry(Rx(v, y), x), z)?
|
||||
Rz(Rx(up, orient.y), (orient.z + 180) % 360f)
|
||||
/*
|
||||
rotate in Ry using the x-component and rotate in Rx using the y-component
|
||||
only Rz is rotated using its corresponding component, and you add 180 clamping to 0-360 degrees
|
||||
I'm sure mathematicians know what's going on here, but I don't
|
||||
the purpose of this comment is to make certain that the future me knows that all this is not a mistake
|
||||
*/
|
||||
Rz(Rx(Ry(Unit(up), orient.x), orient.y), (orient.z + 180) % 360f)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -373,7 +373,7 @@ object PointOfInterest {
|
|||
"anguta" -> Vector3(3999, 4170, 266),
|
||||
"igaluk" -> Vector3(3241, 5658, 235),
|
||||
"keelut" -> Vector3(3630, 1904, 265),
|
||||
"nerrivik" -> Vector3(3522, 3703, 322),
|
||||
"nerrivik" -> Vector3(3522, 3703, 222),
|
||||
"pinga" -> Vector3(5938, 3545, 96),
|
||||
"sedna" -> Vector3(3932, 5160, 232),
|
||||
"tarqaq" -> Vector3(2980, 2155, 237),
|
||||
|
|
|
|||
|
|
@ -257,18 +257,62 @@ class Vector3Test extends Specification {
|
|||
|
||||
"find a relative up (y-rot)" in {
|
||||
Vector3.relativeUp(Vector3(0, 0, 0)) mustEqual Vector3(0,0,1) //up
|
||||
Vector3.relativeUp(Vector3(0, 90, 0)) mustEqual Vector3(0,-1,0) //north
|
||||
Vector3.relativeUp(Vector3(0, 90, 0)) mustEqual Vector3(0,-1,0) //south
|
||||
Vector3.relativeUp(Vector3(0, 180, 0)) mustEqual Vector3(0,0,-1) //down
|
||||
Vector3.relativeUp(Vector3(0, 270, 0)) mustEqual Vector3(0,1,0) //south
|
||||
Vector3.relativeUp(Vector3(0, 270, 0)) mustEqual Vector3(0,1,0) //north
|
||||
Vector3.relativeUp(Vector3(0, 360, 0)) mustEqual Vector3(0,0,1) //up
|
||||
}
|
||||
|
||||
"find a relative up (x-rot)" in {
|
||||
Vector3.relativeUp(Vector3(0, 0, 0)) mustEqual Vector3(0,0,1) //up
|
||||
Vector3.relativeUp(Vector3(90, 0, 0)) mustEqual Vector3(-1,0,0) //west
|
||||
Vector3.relativeUp(Vector3(180, 0, 0)) mustEqual Vector3(0,0,-1) //down
|
||||
Vector3.relativeUp(Vector3(270, 0, 0)) mustEqual Vector3(1,0,0) //east
|
||||
Vector3.relativeUp(Vector3(360, 0, 0)) mustEqual Vector3(0,0,1) //up
|
||||
}
|
||||
|
||||
"find a relative up (combined y,z)" in {
|
||||
Vector3.relativeUp(Vector3(0, 0, 90)) mustEqual Vector3(0,0,1) //up
|
||||
Vector3.relativeUp(Vector3(0, 90, 90)) mustEqual Vector3(-1,0,0) //west
|
||||
Vector3.relativeUp(Vector3(0, 180, 90)) mustEqual Vector3(0,0,-1) //down
|
||||
Vector3.relativeUp(Vector3(0, 270, 90)) mustEqual Vector3(1,0,0) //east
|
||||
Vector3.relativeUp(Vector3(0, 360, 90)) mustEqual Vector3(0,0,1) //up
|
||||
|
||||
Vector3.relativeUp(Vector3(0, 90, 180)) mustEqual Vector3(0,1,0) //north
|
||||
Vector3.relativeUp(Vector3(0, 180, 180)) mustEqual Vector3(0,0,-1) //down
|
||||
Vector3.relativeUp(Vector3(0, 270, 180)) mustEqual Vector3(0,-1,0) //south
|
||||
Vector3.relativeUp(Vector3(0, 360, 180)) mustEqual Vector3(0,0,1) //up
|
||||
|
||||
Vector3.relativeUp(Vector3(0, 90, 270)) mustEqual Vector3(1,0,0) //east
|
||||
Vector3.relativeUp(Vector3(0, 180, 270)) mustEqual Vector3(0,0,-1) //down
|
||||
Vector3.relativeUp(Vector3(0, 270, 270)) mustEqual Vector3(-1,0,0) //west
|
||||
Vector3.relativeUp(Vector3(0, 360, 270)) mustEqual Vector3(0,0,1) //up
|
||||
}
|
||||
|
||||
"find a relative up (combined x,z)" in {
|
||||
Vector3.relativeUp(Vector3(0, 0, 90)) mustEqual Vector3(0,0,1) //up
|
||||
Vector3.relativeUp(Vector3(90, 0, 90)) mustEqual Vector3(0,-1,0) //south
|
||||
Vector3.relativeUp(Vector3(180, 0, 90)) mustEqual Vector3(0,0,-1) //down
|
||||
Vector3.relativeUp(Vector3(270, 0, 90)) mustEqual Vector3(0,1,0) //north
|
||||
Vector3.relativeUp(Vector3(360, 0, 90)) mustEqual Vector3(0,0,1) //up
|
||||
|
||||
Vector3.relativeUp(Vector3(90, 0, 180)) mustEqual Vector3(1,0,0) //east
|
||||
Vector3.relativeUp(Vector3(180, 0, 180)) mustEqual Vector3(0,0,-1) //down
|
||||
Vector3.relativeUp(Vector3(270, 0, 180)) mustEqual Vector3(-1,0,0) //west
|
||||
Vector3.relativeUp(Vector3(360, 0, 180)) mustEqual Vector3(0,0,1) //up
|
||||
|
||||
Vector3.relativeUp(Vector3(90, 0, 270)) mustEqual Vector3(0,1,0) //north
|
||||
Vector3.relativeUp(Vector3(180, 0, 270)) mustEqual Vector3(0,0,-1) //down
|
||||
Vector3.relativeUp(Vector3(270, 0, 270)) mustEqual Vector3(0,-1,0) //south
|
||||
Vector3.relativeUp(Vector3(360, 0, 270)) mustEqual Vector3(0,0,1) //up
|
||||
}
|
||||
|
||||
"find a relative up (combined x,y)" in {
|
||||
val south = Vector3(0,-1,0)
|
||||
Vector3.relativeUp(Vector3(0, 90, 0)) mustEqual Vector3(0,-1,0) //south
|
||||
Vector3.relativeUp(Vector3(90, 90, 0)) mustEqual Vector3(-1,0,0) //west
|
||||
Vector3.relativeUp(Vector3(180, 90, 0)) mustEqual Vector3(0,1,0) //north
|
||||
Vector3.relativeUp(Vector3(270, 90, 0)) mustEqual Vector3(1,0,0) //east
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
244
src/test/scala/objects/GeometryTest.scala
Normal file
244
src/test/scala/objects/GeometryTest.scala
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package objects
|
||||
|
||||
import net.psforever.objects.geometry._
|
||||
import net.psforever.types.Vector3
|
||||
import org.specs2.mutable.Specification
|
||||
|
||||
class GeometryTest extends Specification {
|
||||
"Point3D" should {
|
||||
"construct (1)" in {
|
||||
Point3D(1,2,3.5f)
|
||||
ok
|
||||
}
|
||||
|
||||
"construct (2)" in {
|
||||
Point3D() mustEqual Point3D(0,0,0)
|
||||
}
|
||||
|
||||
"construct (3)" in {
|
||||
Point3D(Vector3(1,2,3)) mustEqual Point3D(1,2,3)
|
||||
}
|
||||
|
||||
"be its own center point" in {
|
||||
val obj = Point3D(1,2,3.5f)
|
||||
obj.center mustEqual obj
|
||||
}
|
||||
|
||||
"define its own exterior" in {
|
||||
val obj = Point3D(1,2,3.5f)
|
||||
obj.pointOnOutside(Vector3(1,0,0)) mustEqual obj
|
||||
obj.pointOnOutside(Vector3(0,1,0)) mustEqual obj
|
||||
obj.pointOnOutside(Vector3(0,0,1)) mustEqual obj
|
||||
}
|
||||
|
||||
"convert to Vector3" in {
|
||||
val obj = Point3D(1,2,3.5f)
|
||||
obj.asVector3 mustEqual Vector3(1,2,3.5f)
|
||||
}
|
||||
}
|
||||
|
||||
"Ray3D" should {
|
||||
"construct (1)" in {
|
||||
Ray3D(Point3D(1,2,3.5f), Vector3(1,0,0))
|
||||
ok
|
||||
}
|
||||
|
||||
"construct (2)" in {
|
||||
Ray3D(1,2,3.5f, Vector3(1,0,0)) mustEqual Ray3D(Point3D(1,2,3.5f), Vector3(1,0,0))
|
||||
}
|
||||
|
||||
"construct (3)" in {
|
||||
Ray3D(Vector3(1,2,3.5f), Vector3(1,0,0)) mustEqual Ray3D(Point3D(1,2,3.5f), Vector3(1,0,0))
|
||||
}
|
||||
|
||||
"have a unit vector as its direction vector" in {
|
||||
Ray3D(1,2,3.5f, Vector3(1,1,1)) must throwA[AssertionError]
|
||||
}
|
||||
|
||||
"have its target point as the center point" in {
|
||||
val obj = Ray3D(1,2,3.5f, Vector3(1,0,0))
|
||||
obj.center mustEqual Point3D(1,2,3.5f)
|
||||
}
|
||||
|
||||
"define its own exterior" in {
|
||||
val obj1 = Ray3D(1,2,3.5f, Vector3(1,0,0))
|
||||
val obj2 = Point3D(1,2,3.5f)
|
||||
obj1.pointOnOutside(Vector3(1,0,0)) mustEqual obj2
|
||||
obj1.pointOnOutside(Vector3(0,1,0)) mustEqual obj2
|
||||
obj1.pointOnOutside(Vector3(0,0,1)) mustEqual obj2
|
||||
}
|
||||
}
|
||||
|
||||
"Line3D" should {
|
||||
"construct (1)" in {
|
||||
Line3D(Point3D(1,2,3.5f), Vector3(1,0,0))
|
||||
ok
|
||||
}
|
||||
|
||||
"construct (2)" in {
|
||||
Line3D(1,2,3.5f, Vector3(1,0,0))
|
||||
ok
|
||||
}
|
||||
|
||||
"construct (3)" in {
|
||||
Line3D(1,2,3.5f, 2,2,3.5f) mustEqual Line3D(1,2,3.5f, Vector3(1,0,0))
|
||||
}
|
||||
|
||||
"have a unit vector as its direction vector" in {
|
||||
Line3D(1,2,3.5f, Vector3(1,1,1)) must throwA[AssertionError]
|
||||
}
|
||||
|
||||
"have its target point as the center point" in {
|
||||
val obj = Line3D(1,2,3.5f, Vector3(1,0,0))
|
||||
obj.center mustEqual Point3D(1,2,3.5f)
|
||||
}
|
||||
|
||||
"define its own exterior" in {
|
||||
val obj1 = Line3D(1,2,3.5f, Vector3(1,0,0))
|
||||
val obj2 = Point3D(1,2,3.5f)
|
||||
obj1.pointOnOutside(Vector3(1,0,0)) mustEqual obj2
|
||||
obj1.pointOnOutside(Vector3(0,1,0)) mustEqual obj2
|
||||
obj1.pointOnOutside(Vector3(0,0,1)) mustEqual obj2
|
||||
}
|
||||
}
|
||||
|
||||
"Segment3D" should {
|
||||
"construct (1)" in {
|
||||
Segment3D(Point3D(1,2,3), Point3D(3,2,3))
|
||||
ok
|
||||
}
|
||||
|
||||
"construct (2)" in {
|
||||
Segment3D(1,2,3, 3,2,3) mustEqual Segment3D(Point3D(1,2,3), Point3D(3,2,3))
|
||||
ok
|
||||
}
|
||||
|
||||
"construct (3)" in {
|
||||
Segment3D(Point3D(1,2,3), Vector3(1,0,0)) mustEqual Segment3D(Point3D(1,2,3), Point3D(2,2,3))
|
||||
}
|
||||
|
||||
"construct (4)" in {
|
||||
Segment3D(1,2,3, Vector3(1,0,0)) mustEqual Segment3D(Point3D(1,2,3), Point3D(2,2,3))
|
||||
}
|
||||
|
||||
"does not need to have unit vector as its direction vector" in {
|
||||
val obj1 = Segment3D(1,2,3, Vector3(5,1,1))
|
||||
val obj2 = Segment3D(Point3D(1,2,3), Point3D(6,3,4))
|
||||
obj1 mustEqual obj2
|
||||
obj1.d mustEqual obj2.d
|
||||
}
|
||||
|
||||
"have a midway point between its two endpoints" in {
|
||||
Segment3D(Point3D(1,2,3), Point3D(3,4,5)).center mustEqual Point3D(2,3,4)
|
||||
}
|
||||
|
||||
"report the point on the outside as its center point" in {
|
||||
val obj1 = Segment3D(Point3D(1,2,3), Point3D(3,4,5))
|
||||
val obj2 = obj1.center
|
||||
obj1.pointOnOutside(Vector3(1,0,0)) mustEqual obj2
|
||||
obj1.pointOnOutside(Vector3(0,1,0)) mustEqual obj2
|
||||
obj1.pointOnOutside(Vector3(0,0,1)) mustEqual obj2
|
||||
}
|
||||
}
|
||||
|
||||
"Sphere3D" should {
|
||||
"construct (1)" in {
|
||||
Sphere(Point3D(1,2,3), 3)
|
||||
ok
|
||||
}
|
||||
|
||||
"construct (2)" in {
|
||||
Sphere(3) mustEqual Sphere(Point3D(0,0,0), 3)
|
||||
ok
|
||||
}
|
||||
|
||||
"construct (3)" in {
|
||||
Sphere(1,2,3, 3) mustEqual Sphere(Point3D(1,2,3), 3)
|
||||
}
|
||||
|
||||
"construct (4)" in {
|
||||
Sphere(Vector3(1,2,3), 3) mustEqual Sphere(Point3D(1,2,3), 3)
|
||||
}
|
||||
|
||||
"the center point is self-evident" in {
|
||||
Sphere(Point3D(1,2,3), 3).center mustEqual Point3D(1,2,3)
|
||||
}
|
||||
|
||||
"report the point on the outside depending on the requested direction" in {
|
||||
val obj1 = Sphere(1,2,3, 3)
|
||||
obj1.pointOnOutside(Vector3( 1, 0, 0)) mustEqual Point3D( 4, 2,3) //east
|
||||
obj1.pointOnOutside(Vector3( 0, 1, 0)) mustEqual Point3D( 1, 5,3) //north
|
||||
obj1.pointOnOutside(Vector3( 0, 0, 1)) mustEqual Point3D( 1, 2,6) //up
|
||||
obj1.pointOnOutside(Vector3(-1, 0, 0)) mustEqual Point3D(-2, 2,3) //west
|
||||
obj1.pointOnOutside(Vector3( 0,-1, 0)) mustEqual Point3D( 1,-1,3) //south
|
||||
obj1.pointOnOutside(Vector3( 0, 0,-1)) mustEqual Point3D( 1, 2,0) //down
|
||||
}
|
||||
}
|
||||
|
||||
"Cylinder (normal)" should {
|
||||
"construct (1)" in {
|
||||
Cylinder(Point3D(1,2,3), Vector3(0,0,1), 2, 3)
|
||||
ok
|
||||
}
|
||||
|
||||
"construct (2)" in {
|
||||
Cylinder(Point3D(1,2,3), 2, 3) mustEqual Cylinder(Point3D(1,2,3), Vector3(0,0,1), 2, 3)
|
||||
}
|
||||
|
||||
"construct (3)" in {
|
||||
Cylinder(Vector3(1,2,3), 2, 3) mustEqual Cylinder(Point3D(1,2,3), Vector3(0,0,1), 2, 3)
|
||||
}
|
||||
|
||||
"construct (4)" in {
|
||||
Cylinder(Vector3(1,2,3), Vector3(0,0,1), 2, 3) mustEqual Cylinder(Point3D(1,2,3), Vector3(0,0,1), 2, 3)
|
||||
}
|
||||
|
||||
"report the center point as the center of the cylinder" in {
|
||||
Cylinder(Point3D(1,2,3), 2, 3).center mustEqual Point3D(1,2,4.5f)
|
||||
}
|
||||
|
||||
"the point on the outside is different depending on the requested direction" in {
|
||||
val obj1 = Cylinder(Point3D(1,2,3), 2, 3)
|
||||
obj1.pointOnOutside(Vector3( 1, 0, 0)) mustEqual Point3D( 3, 2, 4.5f) //east
|
||||
obj1.pointOnOutside(Vector3( 0, 1, 0)) mustEqual Point3D( 1, 4, 4.5f) //north
|
||||
obj1.pointOnOutside(Vector3( 0, 0, 1)) mustEqual Point3D( 1, 2, 6f) //up
|
||||
obj1.pointOnOutside(Vector3(-1, 0, 0)) mustEqual Point3D(-1, 2, 4.5f) //west
|
||||
obj1.pointOnOutside(Vector3( 0,-1, 0)) mustEqual Point3D( 1, 0, 4.5f) //south
|
||||
obj1.pointOnOutside(Vector3( 0, 0,-1)) mustEqual Point3D( 1, 2, 3f) //down
|
||||
}
|
||||
}
|
||||
|
||||
"Cylinder (side tilt)" should {
|
||||
"not require a specific direction to be relative up" in {
|
||||
Cylinder(Point3D(1,2,3), Vector3(1,0,0), 2, 3)
|
||||
ok
|
||||
}
|
||||
|
||||
"require its specific relative up direction to be expressed as a unit vector" in {
|
||||
Cylinder(Point3D(1,2,3), Vector3(4,0,0), 2, 3) must throwA[AssertionError]
|
||||
}
|
||||
|
||||
"report the center point as the center of the cylinder, as if rotated about its base" in {
|
||||
Cylinder(Point3D(1,2,3), Vector3(1,0,0), 2, 3).center mustEqual Point3D(2.5f, 2, 3)
|
||||
}
|
||||
|
||||
"report the point on the outside as different depending on the requested direction and the relative up direction" in {
|
||||
val obj1 = Cylinder(Point3D(1,2,3), Vector3(1,0,0), 2, 3)
|
||||
obj1.pointOnOutside(Vector3( 1, 0, 0)) mustEqual Point3D(4, 2, 3) //east
|
||||
obj1.pointOnOutside(Vector3( 0, 1, 0)) mustEqual Point3D(2.5f, 4, 3) //north
|
||||
obj1.pointOnOutside(Vector3( 0, 0, 1)) mustEqual Point3D(2.5f, 2, 5) //up
|
||||
obj1.pointOnOutside(Vector3(-1, 0, 0)) mustEqual Point3D(1, 2, 3) //west
|
||||
obj1.pointOnOutside(Vector3( 0,-1, 0)) mustEqual Point3D(2.5f, 0, 3) //south
|
||||
obj1.pointOnOutside(Vector3( 0, 0,-1)) mustEqual Point3D(2.5f, 2, 1) //down
|
||||
|
||||
val obj2 = Cylinder(Point3D(1,2,3), Vector3(0,0,1), 2, 3)
|
||||
obj1.pointOnOutside(Vector3( 1, 0, 0)) mustNotEqual obj2.pointOnOutside(Vector3( 1, 0, 0))
|
||||
obj1.pointOnOutside(Vector3( 0, 1, 0)) mustNotEqual obj2.pointOnOutside(Vector3( 1, 1, 0))
|
||||
obj1.pointOnOutside(Vector3( 0, 0, 1)) mustNotEqual obj2.pointOnOutside(Vector3( 1, 0, 1))
|
||||
obj1.pointOnOutside(Vector3(-1, 0, 0)) mustNotEqual obj2.pointOnOutside(Vector3(-1, 0, 0))
|
||||
obj1.pointOnOutside(Vector3( 0,-1, 0)) mustNotEqual obj2.pointOnOutside(Vector3( 0,-1, 0))
|
||||
obj1.pointOnOutside(Vector3( 0, 0,-1)) mustNotEqual obj2.pointOnOutside(Vector3( 0, 0,-1))
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue