PSF-BotServer/src/main/scala/net/psforever/objects/SpawnPoint.scala
Fate-JH ced228509c
Intercontinental Gaslighting (#998)
* diversified building management by injecting behavior; allocated entries for the intercontinental lattice and have begun connecting warp gate entities along the intercontinental lattice; beginnings of warp gate broadcast operations; disabled free merit commendations

* allow transit across a predetermined warp gate path, i.e., proper zone-to-zone gating

* game variables for modifying warp gate behaviors; moved choice of building game logic into overloaded constructor; only handle the capitol fore dome in more realistic conditions; warp gate state restored primarily b y internal game logic; changes to which and how gates are declared inactive or broadcast at startup

* initial work on WarpgateLinkOverrideMessage, even if the packet doesn't seem to do anything; added basic service for rotating cavern zone locks via the galaxy messaging service; moved error checking for lattice connectedness

* cavern closing warning messages queued

* starting to set up ChatActor for /zlock command, and added /setbaseresources; conditions for correcting broadcast conditions of a locking warp gate pair; system for rotating through locking and unlocking cavern zones only uses two timers now and has an advance command that speeds to the next closing warning or cavern opening

* expedited cavern rotations available via '/zonerotate' and '!zonerotate [-list]'; '/zonelock' should work for caverns, though distorting the rotation order to accommodate the cavern being unlocked; configuration arguments exist for the setup of cavern rotations and for the rotation itself

* populated cavern lattice connections for a specific rotation order; warp gates will properly activate and deactivate and modify their neighborhood information based on which stage of the rotation; fed up with the blockmap going wrong; added a sanity test for the cavern lattice; Spiker damage calculation changes

* adjusted local variable requirements of BuildingActor to integrate retained actors more closely with the Behavior; on the other hand, another value is passed around the logic

* bug fixes observed from issues found in logs since 20220520; halved the spawn height when gating to a cavern warpgate

* cavern benefits are now represented by enumeration classes rather than additive binary numbers; when facilities change state, benefits are evaluated; when caverns rotate, benefits are evaluated; cavern facility logic added; attempted handling for inventory disarray conditions (untested)

* broke down tabs for easier navigation; added test to stop spawning of cavern equipment when not otherwise permitted

* code comments, everywhere; correcting issues with cavern rotation timing reports

* but is it flying?
2022-06-14 02:21:24 -04:00

182 lines
6.1 KiB
Scala

// Copyright (c) 2019 PSForever
package net.psforever.objects
import net.psforever.objects.definition.{ObjectDefinition, VehicleDefinition}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.types.{PlanetSideGUID, Vector3}
import scala.collection.mutable
trait SpawnPoint {
psso: PlanetSideServerObject =>
/**
* An element of the contract of `PlanetSideServerObject`;
* but, this makes it visible to a `SpawnPoint` object without casting.
* @see `Identifiable.GUID`
*/
def GUID: PlanetSideGUID
/**
* An element of the contract of `PlanetSideServerObject`;
* but, this makes it visible to a `SpawnPoint` object without casting.
* @see `WorldEntity.GUID`
* @see `SpecificPoint`
*/
def Position: Vector3
/**
* An element of the contract of `PlanetSideServerObject`;
* but, this makes it visible to a `SpawnPoint` object without casting.
* @see `WorldEntity.GUID`
* @see `SpecificPoint`
*/
def Orientation: Vector3
/**
* An element of an unspoken contract with `Amenity`.
* While not all `SpawnPoint` objects will be `Amenity` objects, a subclass of the `PlanetSideServerObject` class,
* they will all promote having an object owner, or "parent."
* This should generally be themselves.
* @see `Amenity.Owner`
*/
def Owner: PlanetSideServerObject
/**
* An element of the contract of `PlanetSideServerObject`;
* but, this makes it visible to a `SpawnPoint` object without casting.
* @see `PlanetSideGameObject.Definition`
* @see `SpecificPoint`
*/
def Definition: ObjectDefinition with SpawnPointDefinition
def isOffline: Boolean = psso.Destroyed
/**
* Determine a specific position and orientation in which to spawn the target.
* @return a `Tuple` of `Vector3` objects;
* the first represents the game world position of spawning;
* the second represents the game world direction of spawning
*/
def SpecificPoint(target: PlanetSideGameObject): (Vector3, Vector3) = {
psso.Definition match {
case d: SpawnPointDefinition =>
d.SpecificPoint(this, target)
case _ =>
SpawnPoint.Default(this, target)
}
}
}
object SpawnPoint {
def Default(obj: SpawnPoint, target: PlanetSideGameObject): (Vector3, Vector3) = (obj.Position, obj.Orientation)
def Tube(obj: SpawnPoint, target: PlanetSideGameObject): (Vector3, Vector3) =
(
obj.Position + Vector3.z(1.5f),
obj.Orientation.xy + Vector3.z(obj.Orientation.z + 90 % 360)
)
def AMS(obj: SpawnPoint, target: PlanetSideGameObject): (Vector3, Vector3) = {
//position the player alongside either of the AMS's terminals, facing away from it
val ori = obj.Orientation
val side = if (System.currentTimeMillis() % 2 == 0) 1 else -1 //right | left
val x = ori.x
val xsin = 3 * side * math.abs(math.sin(math.toRadians(x))).toFloat + 0.5f //sin because 0-degrees is up
val z = ori.z
val zrot = (z + 90) % 360
val zrad = math.toRadians(zrot)
val shift = Vector3(
math.sin(zrad).toFloat,
math.cos(zrad).toFloat,
0
) * (3 * side).toFloat //x=sin, y=cos because compass-0 is East, not North
(
obj.Position + shift + (if (x >= 330) { //ams leaning to the left
Vector3.z(xsin)
} else { //ams leaning to the right
Vector3.z(-xsin)
}),
if (side == 1) {
Vector3.z(zrot)
} else {
Vector3.z((z - 90) % 360)
}
)
}
def Gate(obj: SpawnPoint, target: PlanetSideGameObject): (Vector3, Vector3) = {
obj.Definition match {
case d: SpawnPointDefinition =>
val ori = target.Orientation
val zrad = math.toRadians(ori.z)
val radius =
scala.math.random().toFloat * d.UseRadius / 2 + 20f //20 is definitely outside of the gating energy field
val shift = Vector3(math.sin(zrad).toFloat, math.cos(zrad).toFloat, 0) * radius
val altitudeShift = target.Definition match {
case vdef: VehicleDefinition if GlobalDefinitions.isFlightVehicle(vdef) =>
Vector3.z(scala.math.random().toFloat * d.UseRadius / 4 + 20f)
case _ =>
Vector3.Zero
}
(obj.Position + shift + altitudeShift, ori)
case _ =>
Default(obj, target)
}
}
def HalfHighGate(obj: SpawnPoint, target: PlanetSideGameObject): (Vector3, Vector3) = {
val (a, b) = Gate(obj, target)
target match {
case v: Vehicle if GlobalDefinitions.isFlightVehicle(v.Definition) =>
(a.xy + Vector3.z((target.Position.z + a.z) * 0.5f), b)
case _ =>
(a, b)
}
}
}
trait SpawnPointDefinition {
private var radius: Float = 0f //m
private var delay: Long = 0 //s
private var noWarp: Option[mutable.Set[VehicleDefinition]] = None
private var spawningFunc: (SpawnPoint, PlanetSideGameObject) => (Vector3, Vector3) = SpawnPoint.Default
def UseRadius: Float = radius
def UseRadius_=(rad: Float): Float = {
radius = rad
UseRadius
}
def Delay: Long = delay
def Delay_=(toDelay: Long): Long = {
delay = toDelay
Delay
}
def VehicleAllowance: Boolean = noWarp.isDefined
def VehicleAllowance_=(allow: Boolean): Boolean = {
if (allow && noWarp.isEmpty) {
noWarp = Some(mutable.Set.empty[VehicleDefinition])
} else if (!allow && noWarp.isDefined) {
noWarp = None
}
VehicleAllowance
}
def NoWarp: mutable.Set[VehicleDefinition] = {
noWarp.getOrElse(mutable.Set.empty[VehicleDefinition])
}
def SpecificPointFunc: (SpawnPoint, PlanetSideGameObject) => (Vector3, Vector3) = spawningFunc
def SpecificPointFunc_=(func: (SpawnPoint, PlanetSideGameObject) => (Vector3, Vector3)): Unit = {
spawningFunc = func
}
def SpecificPoint(obj: SpawnPoint, target: PlanetSideGameObject): (Vector3, Vector3) = spawningFunc(obj, target)
}