Better Respawn (#810)

* biolab spawn benefit should now be active; spawn point integrity at point of player avatar creation, or else try again

* streamlined the process of determining whether a facility possesses individual benefits; warp gates do not have benefits

* fix for mounted turret persistance
This commit is contained in:
Fate-JH 2021-05-09 07:17:49 -04:00 committed by GitHub
parent 97b4ad8f67
commit 010b1c5845
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 196 additions and 95 deletions

View file

@ -200,6 +200,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
var lastTerminalOrderFulfillment: Boolean = true
var shiftPosition: Option[Vector3] = None
var shiftOrientation: Option[Vector3] = None
var nextSpawnPoint: Option[SpawnPoint] = None
var setupAvatarFunc: () => Unit = AvatarCreate
var setCurrentAvatarFunc: Player => Unit = SetCurrentAvatarNormally
var persist: () => Unit = NoPersistence
@ -509,14 +510,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case Some(0) =>
deadState = DeadState.Release // cancel movement updates
vehicle.Position = position
LoadZonePhysicalSpawnPoint(zoneId, position, Vector3.Zero, 0 seconds)
LoadZonePhysicalSpawnPoint(zoneId, position, Vector3.Zero, 0 seconds, None)
case _ => // not seated as the driver, in which case we can't move
}
case None =>
deadState = DeadState.Release // cancel movement updates
player.Position = position
// continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, player.GUID))
LoadZonePhysicalSpawnPoint(zoneId, position, Vector3.Zero, 0 seconds)
LoadZonePhysicalSpawnPoint(zoneId, position, Vector3.Zero, 0 seconds, None)
case _ => // seated in something that is not a vehicle or the vehicle is cargo, in which case we can't move
}
}
@ -530,7 +531,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case Some(0) =>
deadState = DeadState.Release // cancel movement updates
vehicle.Position = position
LoadZonePhysicalSpawnPoint(continent.id, position, Vector3.z(vehicle.Orientation.z), 0 seconds)
LoadZonePhysicalSpawnPoint(continent.id, position, Vector3.z(vehicle.Orientation.z), 0 seconds, None)
case _ => // not seated as the driver, in which case we can't move
}
case None =>
@ -610,7 +611,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
deadState = DeadState.Release
sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, true))
interstellarFerry = Some(v) //on the other continent and registered to that continent's GUID system
LoadZonePhysicalSpawnPoint(v.Continent, v.Position, v.Orientation, 1 seconds)
LoadZonePhysicalSpawnPoint(v.Continent, v.Position, v.Orientation, 1 seconds, None)
case _ =>
interstellarFerry match {
case None =>
@ -1003,16 +1004,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
response match {
case Some((zone, spawnPoint)) =>
val obj = continent.GUID(player.VehicleSeated) match {
case Some(obj: Vehicle) if !obj.Destroyed =>
obj
case _ =>
player
case Some(obj: Vehicle) if !obj.Destroyed => obj
case _ => player
}
val (pos, ori) = spawnPoint.SpecificPoint(obj)
if (previousZoningType == Zoning.Method.InstantAction)
LoadZonePhysicalSpawnPoint(zone.id, pos, ori, respawnTime = 0 seconds)
LoadZonePhysicalSpawnPoint(zone.id, pos, ori, respawnTime = 0 seconds, Some(spawnPoint))
else
LoadZonePhysicalSpawnPoint(zone.id, pos, ori, CountSpawnDelay(zone.id, spawnPoint, continent.id))
LoadZonePhysicalSpawnPoint(zone.id, pos, ori, CountSpawnDelay(zone.id, spawnPoint, continent.id), Some(spawnPoint))
case None =>
log.warn(
s"SpawnPointResponse: ${player.Name} received no spawn point response when asking InterstellarClusterService; sending home"
@ -1300,37 +1299,58 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
session.zone.map.checksum
)
)
//important! the LoadMapMessage must be processed by the client before the avatar is created
setupAvatarFunc()
//interimUngunnedVehicle should have been setup by setupAvatarFunc, if it is applicable
turnCounterFunc = interimUngunnedVehicle match {
case Some(_) =>
TurnCounterDuringInterimWhileInPassengerSeat
case None =>
TurnCounterDuringInterim
if (isAcceptableNextSpawnPoint()) {
//important! the LoadMapMessage must be processed by the client before the avatar is created
setupAvatarFunc()
//interimUngunnedVehicle should have been setup by setupAvatarFunc, if it is applicable
turnCounterFunc = interimUngunnedVehicle match {
case Some(_) =>
TurnCounterDuringInterimWhileInPassengerSeat
case None =>
TurnCounterDuringInterim
}
keepAliveFunc = NormalKeepAlive
upstreamMessageCount = 0
setAvatar = false
persist()
} else {
//look for different spawn point in same zone
cluster ! ICS.GetNearbySpawnPoint(
session.zone.Number,
tplayer,
Seq(SpawnGroup.Facility, SpawnGroup.Tower, SpawnGroup.AMS),
context.self
)
}
keepAliveFunc = NormalKeepAlive
upstreamMessageCount = 0
setAvatar = false
persist()
case PlayerLoaded(tplayer) =>
//same zone
log.info(s"${tplayer.Name} will respawn")
tplayer.avatar = avatar
session = session.copy(player = tplayer)
setupAvatarFunc()
//interimUngunnedVehicle should have been setup by setupAvatarFunc, if it is applicable
turnCounterFunc = interimUngunnedVehicle match {
case Some(_) =>
TurnCounterDuringInterimWhileInPassengerSeat
case None =>
TurnCounterDuringInterim
if (isAcceptableNextSpawnPoint()) {
//try this spawn point
setupAvatarFunc()
//interimUngunnedVehicle should have been setup by setupAvatarFunc, if it is applicable
turnCounterFunc = interimUngunnedVehicle match {
case Some(_) =>
TurnCounterDuringInterimWhileInPassengerSeat
case None =>
TurnCounterDuringInterim
}
keepAliveFunc = NormalKeepAlive
upstreamMessageCount = 0
setAvatar = false
persist()
} else {
//look for different spawn point in same zone
cluster ! ICS.GetNearbySpawnPoint(
continent.Number,
tplayer,
Seq(SpawnGroup.Facility, SpawnGroup.Tower, SpawnGroup.AMS),
context.self
)
}
keepAliveFunc = NormalKeepAlive
upstreamMessageCount = 0
setAvatar = false
persist()
case PlayerFailedToLoad(tplayer) =>
player.Continent match {
@ -1386,7 +1406,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
continent.Population ! Zone.Population.Leave(avatar) //does not matter if it doesn't work
zoneLoaded = None
zoneReload = true
LoadZonePhysicalSpawnPoint(toZoneId, pos, orient, 0 seconds)
LoadZonePhysicalSpawnPoint(toZoneId, pos, orient, respawnTime = 0 seconds, None)
}
} else if (tplayer.isAlive) {
if (
@ -1399,7 +1419,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
if (!setAvatar || waitingOnUpstream) {
setCurrentAvatarFunc(tplayer)
respawnTimer = context.system.scheduler.scheduleOnce(
delay = (if (attempt <= max_attempts / 2) 10 else 5) seconds,
delay = (if (attempt <= max_attempts / 2) 10
else 5) seconds,
self,
SetCurrentAvatar(tplayer, max_attempts, attempt + max_attempts / 3)
)
@ -1408,8 +1429,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case (Some(v: Vehicle), Some(seatNumber))
if seatNumber > 0 && v.WeaponControlledFromSeat(seatNumber).isEmpty =>
KeepAlivePersistence
case _ => NormalKeepAlive
case _ =>
NormalKeepAlive
}
nextSpawnPoint = None
}
//if not the condition above, player has started playing normally
} else {
@ -1693,7 +1716,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
interstellarFerry = Some(droppod) //leverage vehicle gating
player.Position = droppod.Position
player.VehicleSeated = PlanetSideGUID(0)
LoadZonePhysicalSpawnPoint(zone.id, droppod.Position, Vector3.Zero, 0 seconds)
LoadZonePhysicalSpawnPoint(zone.id, droppod.Position, Vector3.Zero, 0 seconds, None)
}
/**
@ -2571,7 +2594,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, obj.Health))
UpdateWeaponAtSeatPosition(obj, seat_number)
MountingAction(tplayer, obj, seat_number)
keepAliveFunc = KeepAlivePersistence
case Mountable.CanMount(obj: Mountable, _, _) =>
log.warn(s"MountVehicleMsg: $obj is some mountable object and nothing will happen for ${player.Name}")
@ -3948,11 +3970,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
FindContainedWeapon match {
case (Some(o), Some(tool)) =>
(o match {
case mount: Mountable => mount.PassengerInSeat(player)
case _ => None
case mount: Mountable => (o, mount.PassengerInSeat(player))
case _ => (None, None)
}) match {
case None | Some(0) => ;
case Some(_) =>
case (None, None) | (_, None) | (_: Vehicle, Some(0)) => ;
case _ =>
persist()
turnCounterFunc(player.GUID)
}
@ -8593,7 +8615,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* @param respawnTime the character downtime spent respawning, as clocked on the redeployment screen;
* does not factor in any time required for loading zone or game objects
*/
def LoadZonePhysicalSpawnPoint(zoneId: String, pos: Vector3, ori: Vector3, respawnTime: FiniteDuration): Unit = {
def LoadZonePhysicalSpawnPoint(
zoneId: String,
pos: Vector3,
ori: Vector3,
respawnTime: FiniteDuration,
physSpawnPoint: Option[SpawnPoint]
): Unit = {
log.info(s"${player.Name} will load in zone $zoneId at position $pos in $respawnTime")
respawnTimer.cancel()
reviveTimer.cancel()
@ -8608,6 +8636,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
unk5 = true
)
)
nextSpawnPoint = physSpawnPoint
shiftPosition = Some(pos)
shiftOrientation = Some(ori)
@ -9128,6 +9157,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
/**
* Given an origin and a destination, determine how long the process of traveling should take in reconstruction time.
* For most destinations, the unit of receiving ("spawn point") determines the reconstruction time.
* Possession of a lattice-linked friendly Bio Laboratory halves the time of spawning at facilities.
* In a special consideration, travel to any sanctuary or sanctuary-special zone should be as immediate as zone loading.
*
* @param toZoneId the zone where the target is headed
@ -9137,12 +9167,18 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
*/
def CountSpawnDelay(toZoneId: String, toSpawnPoint: SpawnPoint, fromZoneId: String): FiniteDuration = {
val sanctuaryZoneId = Zones.sanctuaryZoneId(player.Faction)
if (fromZoneId.equals("Nowhere") || sanctuaryZoneId.equals(toZoneId)) { //to sanctuary
if (fromZoneId.equals("Nowhere") || sanctuaryZoneId.equals(toZoneId) || !isAcceptableNextSpawnPoint()) {
//first login, to sanctuary, resolution of invalid spawn point
0 seconds
} else if (!player.isAlive) {
toSpawnPoint.Definition.Delay seconds //TODO +cumulative death penalty
} else {
toSpawnPoint.Definition.Delay seconds
//for other zones ...
//biolabs have/grant benefits
val cryoBenefit: Float = toSpawnPoint.Owner match {
case b: Building if b.hasLatticeBenefit(GlobalDefinitions.cryo_facility) => 0.5f
case _ => 1f
}
//TODO cumulative death penalty
toSpawnPoint.Definition.Delay.toFloat * cryoBenefit seconds
}
}
@ -9714,6 +9750,24 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
}
def isAcceptableNextSpawnPoint(): Boolean = isAcceptableSpawnPoint(nextSpawnPoint)
def isAcceptableSpawnPoint(spawnPoint: SpawnPoint): Boolean = isAcceptableSpawnPoint(Some(spawnPoint))
def isAcceptableSpawnPoint(spawnPoint: Option[SpawnPoint]): Boolean = {
spawnPoint match {
case Some(aSpawnPoint) =>
!aSpawnPoint.isOffline &&
(aSpawnPoint.Owner match {
case w: WarpGate => w.Active
case b: Building => b.Faction == player.Faction
case v: Vehicle => v.Faction == player.Faction && !v.Destroyed && v.DeploymentState == DriveState.Deployed
case _ => true
})
case None => true
}
}
def failWithError(error: String) = {
log.error(error)
middlewareActor ! MiddlewareActor.Teardown()

View file

@ -182,53 +182,6 @@ class Building(
(o.nonEmpty, false) //TODO poll pain field strength
}
val latticeBenefit: Int = {
if (Faction == PlanetSideEmpire.NEUTRAL) 0
else {
def FindLatticeBenefit(
wantedBenefit: ObjectDefinition,
subGraph: Graph[Building, GraphEdge.UnDiEdge]
): Boolean = {
var found = false
subGraph find this match {
case Some(self) =>
if (this.Definition == wantedBenefit) found = true
else {
self pathUntil (_.Definition == wantedBenefit) match {
case Some(_) => found = true
case None => ;
}
}
case None => ;
}
found
}
// Check this Building is on the lattice first
zone.Lattice find this match {
case Some(_) =>
val subGraph = Zone.Lattice filter ((b: Building) =>
b.Faction == this.Faction
&& !b.CaptureTerminalIsHacked
&& b.NtuLevel > 0
&& (b.Generator.isEmpty || b.Generator.get.Condition != PlanetSideGeneratorState.Destroyed)
)
var stackedBenefit = 0
if (FindLatticeBenefit(GlobalDefinitions.amp_station, subGraph)) stackedBenefit |= 1
if (FindLatticeBenefit(GlobalDefinitions.comm_station_dsp, subGraph)) stackedBenefit |= 2
if (FindLatticeBenefit(GlobalDefinitions.cryo_facility, subGraph)) stackedBenefit |= 4
if (FindLatticeBenefit(GlobalDefinitions.comm_station, subGraph)) stackedBenefit |= 8
if (FindLatticeBenefit(GlobalDefinitions.tech_plant, subGraph)) stackedBenefit |= 16
stackedBenefit
case None => 0;
}
}
}
BuildingInfoUpdateMessage(
Zone.Number,
MapId,
@ -242,7 +195,7 @@ class Building(
generatorState,
spawnTubesNormal,
forceDomeActive,
if (generatorState != PlanetSideGeneratorState.Destroyed) latticeBenefit else 0,
if (generatorState != PlanetSideGeneratorState.Destroyed) latticeBenefitsValue() else 0,
if (generatorState != PlanetSideGeneratorState.Destroyed) 48 else 0, // cavern benefit
Nil, // unk4,
0, // unk5
@ -254,6 +207,95 @@ class Building(
)
}
def hasLatticeBenefit(wantedBenefit: ObjectDefinition): Boolean = {
if (Faction == PlanetSideEmpire.NEUTRAL) {
false
} else {
// Check this Building is on the lattice first
zone.Lattice find this match {
case Some(_) =>
val subGraph = Zone.Lattice filter (
(b : Building) =>
b.Faction == this.Faction &&
!b.CaptureTerminalIsHacked &&
b.NtuLevel > 0 &&
(b.Generator.isEmpty || b.Generator.get.Condition != PlanetSideGeneratorState.Destroyed)
)
findLatticeBenefit(wantedBenefit, subGraph)
case None =>
false
}
}
}
private def findLatticeBenefit(
wantedBenefit: ObjectDefinition,
subGraph: Graph[Building, GraphEdge.UnDiEdge]
): Boolean = {
var found = false
subGraph find this match {
case Some(self) =>
if (this.Definition == wantedBenefit) {
found = true
} else {
self pathUntil (_.Definition == wantedBenefit) match {
case Some(_) => found = true
case None => ;
}
}
case None => ;
}
found
}
def latticeConnectedFacilityBenefits(): Set[ObjectDefinition] = {
if (Faction == PlanetSideEmpire.NEUTRAL) {
Set.empty
} else {
// Check this Building is on the lattice first
zone.Lattice find this match {
case Some(_) =>
val subGraph = Zone.Lattice filter ((b: Building) =>
b.Faction == this.Faction
&& !b.CaptureTerminalIsHacked
&& b.NtuLevel > 0
&& (b.Generator.isEmpty || b.Generator.get.Condition != PlanetSideGeneratorState.Destroyed)
)
import scala.collection.mutable
var connectedBases: mutable.Set[ObjectDefinition] = mutable.Set()
if (findLatticeBenefit(GlobalDefinitions.amp_station, subGraph)) {
connectedBases.add(GlobalDefinitions.amp_station)
}
if (findLatticeBenefit(GlobalDefinitions.comm_station_dsp, subGraph)) {
connectedBases.add(GlobalDefinitions.comm_station_dsp)
}
if (findLatticeBenefit(GlobalDefinitions.cryo_facility, subGraph)) {
connectedBases.add(GlobalDefinitions.cryo_facility)
}
if (findLatticeBenefit(GlobalDefinitions.comm_station, subGraph)) {
connectedBases.add(GlobalDefinitions.comm_station)
}
if (findLatticeBenefit(GlobalDefinitions.tech_plant, subGraph)) {
connectedBases.add(GlobalDefinitions.tech_plant)
}
connectedBases.toSet
case None =>
Set.empty
}
}
}
def latticeBenefitsValue(): Int = {
latticeConnectedFacilityBenefits().collect {
case GlobalDefinitions.amp_station => 1
case GlobalDefinitions.comm_station_dsp => 2
case GlobalDefinitions.cryo_facility => 4
case GlobalDefinitions.comm_station => 8
case GlobalDefinitions.tech_plant => 16
}.sum
}
def BuildingType: StructureType = buildingType
override def Zone_=(zone: Zone): Zone = Zone //building never leaves zone after being set in constructor

View file

@ -9,6 +9,7 @@ import net.psforever.packet.game.BuildingInfoUpdateMessage
import net.psforever.types.{PlanetSideEmpire, PlanetSideGeneratorState, Vector3}
import akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.definition.ObjectDefinition
import scala.collection.mutable
@ -154,6 +155,10 @@ class WarpGate(name: String, building_guid: Int, map_id: Int, zone: Zone, buildi
def NtuCapacitor_=(value: Float): Float = NtuCapacitor
override def hasLatticeBenefit(wantedBenefit: ObjectDefinition): Boolean = false
override def latticeConnectedFacilityBenefits(): Set[ObjectDefinition] = Set.empty
override def Definition: WarpGateDefinition = buildingDefinition
}