mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +00:00
Merge branch 'master' into rad-poisoning
This commit is contained in:
commit
0ae5a46934
|
|
@ -60,10 +60,6 @@ add_property maelstrom equiptime 1000
|
||||||
add_property maelstrom holstertime 1000
|
add_property maelstrom holstertime 1000
|
||||||
add_property magcutter equiptime 250
|
add_property magcutter equiptime 250
|
||||||
add_property magcutter holstertime 250
|
add_property magcutter holstertime 250
|
||||||
add_property medicalapplicator equiptime 500
|
|
||||||
add_property medicalapplicator holstertime 500
|
|
||||||
add_property medicalapplicator firemode0_refiretime 500
|
|
||||||
add_property medicalapplicator firemode1_refiretime 500
|
|
||||||
add_property mini_chaingun equiptime 750
|
add_property mini_chaingun equiptime 750
|
||||||
add_property mini_chaingun holstertime 750
|
add_property mini_chaingun holstertime 750
|
||||||
add_property nano_dispenser equiptime 750
|
add_property nano_dispenser equiptime 750
|
||||||
|
|
@ -95,7 +91,7 @@ add_property super_staminakit nodrop false
|
||||||
add_property super_staminakit requirement_award0 false
|
add_property super_staminakit requirement_award0 false
|
||||||
add_property suppressor equiptime 600
|
add_property suppressor equiptime 600
|
||||||
add_property suppressor holstertime 600
|
add_property suppressor holstertime 600
|
||||||
add_property trek allowed false
|
add_property trek allowed true
|
||||||
add_property trek equiptime 500
|
add_property trek equiptime 500
|
||||||
add_property trek holstertime 500
|
add_property trek holstertime 500
|
||||||
add_property vulture requirement_award0 false
|
add_property vulture requirement_award0 false
|
||||||
|
|
|
||||||
|
|
@ -21625,7 +21625,7 @@
|
||||||
"Owner": 1303,
|
"Owner": 1303,
|
||||||
"AbsX": 3577.178,
|
"AbsX": 3577.178,
|
||||||
"AbsY": 2712.24927,
|
"AbsY": 2712.24927,
|
||||||
"AbsZ": 32.8808441,
|
"AbsZ": 34.289,
|
||||||
"Yaw": 318.0,
|
"Yaw": 318.0,
|
||||||
"GUID": 1843,
|
"GUID": 1843,
|
||||||
"MapID": null,
|
"MapID": null,
|
||||||
|
|
@ -21664,7 +21664,7 @@
|
||||||
"Owner": 1303,
|
"Owner": 1303,
|
||||||
"AbsX": 3588.1582,
|
"AbsX": 3588.1582,
|
||||||
"AbsY": 2700.0542,
|
"AbsY": 2700.0542,
|
||||||
"AbsZ": 32.8808441,
|
"AbsZ": 34.289,
|
||||||
"Yaw": 318.0,
|
"Yaw": 318.0,
|
||||||
"GUID": 1846,
|
"GUID": 1846,
|
||||||
"MapID": null,
|
"MapID": null,
|
||||||
|
|
|
||||||
|
|
@ -8289,7 +8289,7 @@
|
||||||
"AbsY": 1070.60535,
|
"AbsY": 1070.60535,
|
||||||
"AbsZ": 173.239014,
|
"AbsZ": 173.239014,
|
||||||
"Yaw": 338.0,
|
"Yaw": 338.0,
|
||||||
"GUID": 648,
|
"GUID": 649,
|
||||||
"MapID": null,
|
"MapID": null,
|
||||||
"IsChildObject": true
|
"IsChildObject": true
|
||||||
},
|
},
|
||||||
|
|
@ -8302,7 +8302,7 @@
|
||||||
"AbsY": 1132.36707,
|
"AbsY": 1132.36707,
|
||||||
"AbsZ": 138.999,
|
"AbsZ": 138.999,
|
||||||
"Yaw": 124.0,
|
"Yaw": 124.0,
|
||||||
"GUID": 649,
|
"GUID": 648,
|
||||||
"MapID": null,
|
"MapID": null,
|
||||||
"IsChildObject": true
|
"IsChildObject": true
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,11 @@ object MiddlewareActor {
|
||||||
packet.isInstanceOf[ChatMsg]
|
packet.isInstanceOf[ChatMsg]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** `PropertyOverrideMessage` ptsd from other large packets causing issues when bundled */
|
||||||
|
private def propertyOverrideMessageGuard(packet: PlanetSidePacket): Boolean = {
|
||||||
|
packet.isInstanceOf[PropertyOverrideMessage]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function for blanking tasks related to inbound packet resolution.
|
* A function for blanking tasks related to inbound packet resolution.
|
||||||
* Do nothing.
|
* Do nothing.
|
||||||
|
|
@ -259,7 +264,8 @@ class MiddlewareActor(
|
||||||
MiddlewareActor.keepAliveMessageGuard,
|
MiddlewareActor.keepAliveMessageGuard,
|
||||||
MiddlewareActor.characterInfoMessageGuard,
|
MiddlewareActor.characterInfoMessageGuard,
|
||||||
MiddlewareActor.squadDetailDefinitionMessageGuard,
|
MiddlewareActor.squadDetailDefinitionMessageGuard,
|
||||||
MiddlewareActor.chatMsgGuard
|
MiddlewareActor.chatMsgGuard,
|
||||||
|
MiddlewareActor.propertyOverrideMessageGuard
|
||||||
)
|
)
|
||||||
|
|
||||||
private val smpHistoryLength: Int = 100
|
private val smpHistoryLength: Int = 100
|
||||||
|
|
|
||||||
|
|
@ -2218,12 +2218,12 @@ class AvatarActor(
|
||||||
.Holsters()
|
.Holsters()
|
||||||
.foreach(holster =>
|
.foreach(holster =>
|
||||||
holster.Equipment match {
|
holster.Equipment match {
|
||||||
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator =>
|
/*case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator =>
|
||||||
//todo fix so player may hold medapp when loading the zone (client crash)
|
//todo fix so player may hold medapp when loading the zone (client crash)
|
||||||
val item = SimpleItem(GlobalDefinitions.flail_targeting_laser)
|
val item = SimpleItem(GlobalDefinitions.flail_targeting_laser)
|
||||||
holster.Equipment = None
|
holster.Equipment = None
|
||||||
holster.Equipment = item
|
holster.Equipment = item
|
||||||
item.GUID = PlanetSideGUID(gen.getAndIncrement)
|
item.GUID = PlanetSideGUID(gen.getAndIncrement)*/
|
||||||
case Some(tool: Tool) =>
|
case Some(tool: Tool) =>
|
||||||
tool.AmmoSlots.foreach(slot => {
|
tool.AmmoSlots.foreach(slot => {
|
||||||
slot.Box.GUID = PlanetSideGUID(gen.getAndIncrement)
|
slot.Box.GUID = PlanetSideGUID(gen.getAndIncrement)
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,13 @@ class GalaxyHandlerLogic(val ops: SessionGalaxyHandlers, implicit val context: A
|
||||||
|
|
||||||
case GalaxyResponse.MapUpdate(msg) =>
|
case GalaxyResponse.MapUpdate(msg) =>
|
||||||
sendResponse(msg)
|
sendResponse(msg)
|
||||||
|
import net.psforever.actors.zone.ZoneActor
|
||||||
|
import net.psforever.zones.Zones
|
||||||
|
Zones.zones.find(_.Number == msg.continent_id) match {
|
||||||
|
case Some(zone) =>
|
||||||
|
zone.actor ! ZoneActor.BuildingInfoState(msg)
|
||||||
|
case None =>
|
||||||
|
}
|
||||||
|
|
||||||
case GalaxyResponse.UpdateBroadcastPrivileges(zoneId, gateMapId, fromFactions, toFactions) =>
|
case GalaxyResponse.UpdateBroadcastPrivileges(zoneId, gateMapId, fromFactions, toFactions) =>
|
||||||
val faction = player.Faction
|
val faction = player.Faction
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
package net.psforever.actors.session.normal
|
package net.psforever.actors.session.normal
|
||||||
|
|
||||||
import akka.actor.ActorContext
|
import akka.actor.ActorContext
|
||||||
import net.psforever.actors.session.support.{LocalHandlerFunctions, SessionData, SessionLocalHandlers}
|
import net.psforever.actors.session.support.SpawnOperations.ActivityQueuedTask
|
||||||
|
import net.psforever.actors.session.support.{LocalHandlerFunctions, SessionData, SessionLocalHandlers, SpawnOperations}
|
||||||
import net.psforever.objects.ce.Deployable
|
import net.psforever.objects.ce.Deployable
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
import net.psforever.objects.serverobject.doors.Door
|
||||||
import net.psforever.objects.vehicles.MountableWeapons
|
import net.psforever.objects.vehicles.MountableWeapons
|
||||||
|
|
@ -191,7 +192,17 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act
|
||||||
sessionLogic.general.useRouterTelepadEffect(passengerGuid, srcGuid, destGuid)
|
sessionLogic.general.useRouterTelepadEffect(passengerGuid, srcGuid, destGuid)
|
||||||
|
|
||||||
case LocalResponse.SendResponse(msg) =>
|
case LocalResponse.SendResponse(msg) =>
|
||||||
sendResponse(msg)
|
msg match {
|
||||||
|
case m: GenericObjectActionMessage =>
|
||||||
|
// delay building virus alert if player is dead/respawning
|
||||||
|
if ((m.code == 58 || m.code == 60 || m.code == 61) && !sessionLogic.zoning.spawn.startEnqueueSquadMessages) {
|
||||||
|
sessionLogic.zoning.spawn.enqueueNewActivity(ActivityQueuedTask(
|
||||||
|
SpawnOperations.delaySendGenericObjectActionMessage(msg), 1))
|
||||||
|
}
|
||||||
|
else sendResponse(msg)
|
||||||
|
case _ =>
|
||||||
|
sendResponse(msg)
|
||||||
|
}
|
||||||
|
|
||||||
case LocalResponse.SetEmpire(objectGuid, empire) =>
|
case LocalResponse.SetEmpire(objectGuid, empire) =>
|
||||||
sendResponse(SetEmpireMessage(objectGuid, empire))
|
sendResponse(SetEmpireMessage(objectGuid, empire))
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import net.psforever.actors.session.{AvatarActor, SessionActor}
|
||||||
import net.psforever.actors.zone.ZoneActor
|
import net.psforever.actors.zone.ZoneActor
|
||||||
import net.psforever.objects.LivePlayerList
|
import net.psforever.objects.LivePlayerList
|
||||||
import net.psforever.objects.sourcing.PlayerSource
|
import net.psforever.objects.sourcing.PlayerSource
|
||||||
import net.psforever.objects.zones.ZoneInfo
|
import net.psforever.objects.zones.{Zone, ZoneInfo}
|
||||||
import net.psforever.packet.game.SetChatFilterMessage
|
import net.psforever.packet.game.SetChatFilterMessage
|
||||||
import net.psforever.services.chat.{DefaultChannel, OutfitChannel, SquadChannel}
|
import net.psforever.services.chat.{DefaultChannel, OutfitChannel, SquadChannel}
|
||||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||||
|
|
@ -20,7 +20,7 @@ import net.psforever.services.teamwork.{SquadResponse, SquadService, SquadServic
|
||||||
import net.psforever.types.ChatMessageType.CMT_QUIT
|
import net.psforever.types.ChatMessageType.CMT_QUIT
|
||||||
import org.log4s.Logger
|
import org.log4s.Logger
|
||||||
|
|
||||||
import java.util.concurrent.{Executors, TimeUnit}
|
import java.util.concurrent.{Executors, ScheduledFuture, TimeUnit}
|
||||||
import scala.annotation.unused
|
import scala.annotation.unused
|
||||||
import scala.collection.{Seq, mutable}
|
import scala.collection.{Seq, mutable}
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
|
@ -368,7 +368,10 @@ class ChatOperations(
|
||||||
case (Some(buildings), Some(faction), Some(_)) =>
|
case (Some(buildings), Some(faction), Some(_)) =>
|
||||||
//TODO implement timer
|
//TODO implement timer
|
||||||
//schedule processing of buildings with a delay
|
//schedule processing of buildings with a delay
|
||||||
processBuildingsWithDelay(buildings, faction, 1000) //delay of 1000ms between each building operation
|
processBuildingsWithDelay(buildings, faction, 100) { zone =>
|
||||||
|
zone.actor ! ZoneActor.ZoneMapUpdate()
|
||||||
|
zone.actor ! ZoneActor.AssignLockedBy(zone, notifyPlayers=true)
|
||||||
|
}
|
||||||
true
|
true
|
||||||
case _ =>
|
case _ =>
|
||||||
false
|
false
|
||||||
|
|
@ -379,30 +382,36 @@ class ChatOperations(
|
||||||
buildings: Seq[Building],
|
buildings: Seq[Building],
|
||||||
faction: PlanetSideEmpire.Value,
|
faction: PlanetSideEmpire.Value,
|
||||||
delayMillis: Long
|
delayMillis: Long
|
||||||
): Unit = {
|
)(onComplete: Zone => Unit): Unit = {
|
||||||
val buildingIterator = buildings.iterator
|
import net.psforever.objects.serverobject.structures.StructureType
|
||||||
scheduler.scheduleAtFixedRate(
|
val buildingsToProcess = buildings.filter(b => b.CaptureTerminal.isDefined && b.Faction != faction)
|
||||||
|
val iterator = buildingsToProcess.iterator
|
||||||
|
val zone = buildings.head.Zone
|
||||||
|
var handle: ScheduledFuture[_] = null
|
||||||
|
handle = scheduler.scheduleAtFixedRate(
|
||||||
() => {
|
() => {
|
||||||
if (buildingIterator.hasNext) {
|
if (iterator.hasNext) {
|
||||||
val building = buildingIterator.next()
|
val building = iterator.next()
|
||||||
val terminal = building.CaptureTerminal.get
|
val terminal = building.CaptureTerminal.get
|
||||||
val zone = building.Zone
|
if (building.BuildingType == StructureType.Tower) {
|
||||||
val zoneActor = zone.actor
|
building.Actor ! BuildingActor.SetFaction(faction)
|
||||||
val buildingActor = building.Actor
|
building.Actor ! BuildingActor.AmenityStateChange(terminal, Some(false))
|
||||||
//clear any previous hack
|
building.Actor ! BuildingActor.MapUpdate()
|
||||||
if (building.CaptureTerminalIsHacked) {
|
|
||||||
zone.LocalEvents ! LocalServiceMessage(
|
|
||||||
zone.id,
|
|
||||||
LocalAction.ResecureCaptureTerminal(terminal, PlayerSource.Nobody)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
//push any updates this might cause
|
else {
|
||||||
zoneActor ! ZoneActor.ZoneMapUpdate()
|
if (building.CaptureTerminalIsHacked) {
|
||||||
//convert faction affiliation
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
buildingActor ! BuildingActor.SetFaction(faction)
|
zone.id,
|
||||||
buildingActor ! BuildingActor.AmenityStateChange(terminal, Some(false))
|
LocalAction.ResecureCaptureTerminal(terminal, PlayerSource.Nobody)
|
||||||
//push for map updates again
|
)
|
||||||
zoneActor ! ZoneActor.ZoneMapUpdate()
|
}
|
||||||
|
building.Actor ! BuildingActor.SetFaction(faction)
|
||||||
|
building.Actor ! BuildingActor.AmenityStateChange(terminal, Some(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
handle.cancel(false)
|
||||||
|
onComplete(zone)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
0,
|
0,
|
||||||
|
|
|
||||||
|
|
@ -1258,7 +1258,12 @@ class GeneralOperations(
|
||||||
def handleUseTerminal(terminal: Terminal, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
|
def handleUseTerminal(terminal: Terminal, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
|
||||||
equipment match {
|
equipment match {
|
||||||
case Some(item) =>
|
case Some(item) =>
|
||||||
sendUseGeneralEntityMessage(terminal, item)
|
if (terminal.Definition == GlobalDefinitions.main_terminal) {
|
||||||
|
sendUseMainTerminalMessage(terminal, item, msg.unk2)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sendUseGeneralEntityMessage(terminal, item)
|
||||||
|
}
|
||||||
case None
|
case None
|
||||||
if terminal.Owner == Building.NoBuilding || terminal.Faction == player.Faction ||
|
if terminal.Owner == Building.NoBuilding || terminal.Faction == player.Faction ||
|
||||||
terminal.HackedBy.nonEmpty || terminal.Faction == PlanetSideEmpire.NEUTRAL =>
|
terminal.HackedBy.nonEmpty || terminal.Faction == PlanetSideEmpire.NEUTRAL =>
|
||||||
|
|
@ -1484,6 +1489,14 @@ class GeneralOperations(
|
||||||
obj.Actor ! CommonMessages.Use(player, Some(equipment))
|
obj.Actor ! CommonMessages.Use(player, Some(equipment))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def sendUseMainTerminalMessage(obj: PlanetSideServerObject, equipment: Equipment, virus: Long): Unit = {
|
||||||
|
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||||
|
if (player.Faction == obj.Faction)
|
||||||
|
obj.Actor ! CommonMessages.RemoveVirus(player, Some(equipment))
|
||||||
|
else
|
||||||
|
obj.Actor ! CommonMessages.UploadVirus(player, Some(equipment), virus)
|
||||||
|
}
|
||||||
|
|
||||||
def handleUseDefaultEntity(obj: PlanetSideGameObject, equipment: Option[Equipment]): Unit = {
|
def handleUseDefaultEntity(obj: PlanetSideGameObject, equipment: Option[Equipment]): Unit = {
|
||||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||||
equipment match {
|
equipment match {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ import net.psforever.objects.serverobject.interior.Sidedness.OutsideOf
|
||||||
import net.psforever.objects.{PlanetSideGameObject, Tool, Vehicle}
|
import net.psforever.objects.{PlanetSideGameObject, Tool, Vehicle}
|
||||||
import net.psforever.objects.vehicles.{CargoBehavior, MountableWeapons}
|
import net.psforever.objects.vehicles.{CargoBehavior, MountableWeapons}
|
||||||
import net.psforever.objects.vital.InGameHistory
|
import net.psforever.objects.vital.InGameHistory
|
||||||
import net.psforever.packet.game.{DismountVehicleCargoMsg, InventoryStateMessage, MountVehicleCargoMsg, MountVehicleMsg, ObjectAttachMessage}
|
import net.psforever.packet.game.{DismountVehicleCargoMsg, GenericObjectActionMessage, InventoryStateMessage, MountVehicleCargoMsg, MountVehicleMsg, ObjectAttachMessage, ObjectDetachMessage, PlanetsideAttributeMessage}
|
||||||
|
import net.psforever.services.Service
|
||||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||||
import net.psforever.types.{BailType, PlanetSideGUID, Vector3}
|
import net.psforever.types.{BailType, PlanetSideGUID, Vector3}
|
||||||
//
|
//
|
||||||
|
|
@ -197,8 +198,23 @@ class SessionMountHandlers(
|
||||||
* @param seatNum the mount out of which which the player is disembarking
|
* @param seatNum the mount out of which which the player is disembarking
|
||||||
*/
|
*/
|
||||||
def DismountVehicleAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
|
def DismountVehicleAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
|
||||||
DismountAction(tplayer, obj, seatNum)
|
|
||||||
tplayer.WhichSide = OutsideOf
|
tplayer.WhichSide = OutsideOf
|
||||||
|
if (tplayer.BailProtection) {
|
||||||
|
tplayer.ContributionFrom(obj)
|
||||||
|
sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive
|
||||||
|
continent.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
continent.id,
|
||||||
|
VehicleAction.SendResponse(Service.defaultPlayerGUID, PlanetsideAttributeMessage(obj.GUID, 81, 1))
|
||||||
|
)
|
||||||
|
continent.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
continent.id,
|
||||||
|
VehicleAction.SendResponse(Service.defaultPlayerGUID, ObjectDetachMessage(obj.GUID, tplayer.GUID, tplayer.Position, obj.Orientation))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sendResponse(GenericObjectActionMessage(obj.GUID, 24))
|
||||||
|
DismountAction(tplayer, obj, seatNum)
|
||||||
|
}
|
||||||
//until vehicles maintain synchronized momentum without a driver
|
//until vehicles maintain synchronized momentum without a driver
|
||||||
obj match {
|
obj match {
|
||||||
case v: Vehicle
|
case v: Vehicle
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ import net.psforever.objects.serverobject.turret.FacilityTurret
|
||||||
import net.psforever.objects.vehicles._
|
import net.psforever.objects.vehicles._
|
||||||
import net.psforever.objects.zones.{Zone, ZoneHotSpotProjector, Zoning}
|
import net.psforever.objects.zones.{Zone, ZoneHotSpotProjector, Zoning}
|
||||||
import net.psforever.objects._
|
import net.psforever.objects._
|
||||||
import net.psforever.packet.game.{AvatarAwardMessage, AvatarSearchCriteriaMessage, AvatarStatisticsMessage, AwardCompletion, BindPlayerMessage, BindStatus, CargoMountPointStatusMessage, ChangeShortcutBankMessage, ChatChannel, CreateShortcutMessage, DroppodFreefallingMessage, LoadMapMessage, ObjectCreateDetailedMessage, ObjectDeleteMessage, PlanetsideStringAttributeMessage, PlayerStateShiftMessage, SetChatFilterMessage, SetCurrentAvatarMessage, ShiftState}
|
import net.psforever.packet.game.{AvatarAwardMessage, AvatarSearchCriteriaMessage, AvatarStatisticsMessage, AwardCompletion, BindPlayerMessage, BindStatus, CargoMountPointStatusMessage, ChangeShortcutBankMessage, ChatChannel, CreateShortcutMessage, DroppodFreefallingMessage, LoadMapMessage, ObjectCreateDetailedMessage, ObjectDeleteMessage, PlayerStateShiftMessage, SetChatFilterMessage, SetCurrentAvatarMessage, ShiftState}
|
||||||
import net.psforever.packet.game.{AvatarDeadStateMessage, BroadcastWarpgateUpdateMessage, ChatMsg, ContinentalLockUpdateMessage, DeadState, DensityLevelUpdateMessage, DeployRequestMessage, DeployableInfo, DeployableObjectsInfoMessage, DeploymentAction, DisconnectMessage, DroppodError, DroppodLaunchResponseMessage, FriendsResponse, GenericObjectActionMessage, GenericObjectStateMsg, HotSpotUpdateMessage, ObjectAttachMessage, ObjectCreateMessage, PlanetsideAttributeEnum, PlanetsideAttributeMessage, PropertyOverrideMessage, ReplicationStreamMessage, SetEmpireMessage, TimeOfDayMessage, TriggerEffectMessage, ZoneForcedCavernConnectionsMessage, ZoneInfoMessage, ZoneLockInfoMessage, ZonePopulationUpdateMessage, HotSpotInfo => PacketHotSpotInfo}
|
import net.psforever.packet.game.{AvatarDeadStateMessage, BroadcastWarpgateUpdateMessage, ChatMsg, ContinentalLockUpdateMessage, DeadState, DensityLevelUpdateMessage, DeployRequestMessage, DeployableInfo, DeployableObjectsInfoMessage, DeploymentAction, DisconnectMessage, DroppodError, DroppodLaunchResponseMessage, FriendsResponse, GenericObjectActionMessage, GenericObjectStateMsg, HotSpotUpdateMessage, ObjectAttachMessage, ObjectCreateMessage, PlanetsideAttributeEnum, PlanetsideAttributeMessage, PropertyOverrideMessage, ReplicationStreamMessage, SetEmpireMessage, TimeOfDayMessage, TriggerEffectMessage, ZoneForcedCavernConnectionsMessage, ZoneInfoMessage, ZoneLockInfoMessage, ZonePopulationUpdateMessage, HotSpotInfo => PacketHotSpotInfo}
|
||||||
import net.psforever.packet.game.{BeginZoningMessage, DroppodLaunchRequestMessage, ReleaseAvatarRequestMessage, SpawnRequestMessage, WarpgateRequest}
|
import net.psforever.packet.game.{BeginZoningMessage, DroppodLaunchRequestMessage, ReleaseAvatarRequestMessage, SpawnRequestMessage, WarpgateRequest}
|
||||||
import net.psforever.packet.game.DeathStatistic
|
import net.psforever.packet.game.DeathStatistic
|
||||||
|
|
@ -193,6 +193,10 @@ object SpawnOperations {
|
||||||
def sendEventMessage(msg: ChatMsg)(sessionLogic: SessionData): Unit = {
|
def sendEventMessage(msg: ChatMsg)(sessionLogic: SessionData): Unit = {
|
||||||
sessionLogic.sendResponse(msg)
|
sessionLogic.sendResponse(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def delaySendGenericObjectActionMessage(msg: PlanetSideGamePacket)(sessionLogic: SessionData): Unit = {
|
||||||
|
sessionLogic.sendResponse(msg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ZoningOperations(
|
class ZoningOperations(
|
||||||
|
|
@ -338,6 +342,13 @@ class ZoningOperations(
|
||||||
sendResponse(PlanetsideAttributeMessage(targetPlayer.GUID, 19, 1))
|
sendResponse(PlanetsideAttributeMessage(targetPlayer.GUID, 19, 1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//adjust for health module benefit so overhead health bar accounts for added health
|
||||||
|
live.filter { tplayer =>
|
||||||
|
tplayer.MaxHealth == 120
|
||||||
|
}
|
||||||
|
.foreach { targetPlayer =>
|
||||||
|
sendResponse(PlanetsideAttributeMessage(targetPlayer.GUID, 1, 120))
|
||||||
|
}
|
||||||
//load corpses in zone
|
//load corpses in zone
|
||||||
continent.Corpses.foreach {
|
continent.Corpses.foreach {
|
||||||
spawn.DepictPlayerAsCorpse
|
spawn.DepictPlayerAsCorpse
|
||||||
|
|
@ -559,27 +570,19 @@ class ZoningOperations(
|
||||||
val popTR = zone.Players.count(_.faction == PlanetSideEmpire.TR)
|
val popTR = zone.Players.count(_.faction == PlanetSideEmpire.TR)
|
||||||
val popNC = zone.Players.count(_.faction == PlanetSideEmpire.NC)
|
val popNC = zone.Players.count(_.faction == PlanetSideEmpire.NC)
|
||||||
val popVS = zone.Players.count(_.faction == PlanetSideEmpire.VS)
|
val popVS = zone.Players.count(_.faction == PlanetSideEmpire.VS)
|
||||||
|
|
||||||
zone.Buildings.foreach({ case (_, building) => initBuilding(continentNumber, building.MapId, building) })
|
zone.Buildings.foreach({ case (_, building) => initBuilding(continentNumber, building.MapId, building) })
|
||||||
sendResponse(ZonePopulationUpdateMessage(continentNumber, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO))
|
sendResponse(ZonePopulationUpdateMessage(continentNumber, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO))
|
||||||
//TODO should actually not claim that the sanctuary or VR zones are locked by their respective empire
|
if (continentNumber == 11 || continentNumber == 12 || continentNumber == 13)
|
||||||
if (continentNumber == 11)
|
|
||||||
sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NC))
|
|
||||||
else if (continentNumber == 12)
|
|
||||||
sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.TR))
|
|
||||||
else if (continentNumber == 13)
|
|
||||||
sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.VS))
|
|
||||||
else
|
|
||||||
sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NEUTRAL))
|
sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NEUTRAL))
|
||||||
|
else
|
||||||
|
sendResponse(ContinentalLockUpdateMessage(continentNumber, zone.lockedBy))
|
||||||
//CaptureFlagUpdateMessage()
|
//CaptureFlagUpdateMessage()
|
||||||
//VanuModuleUpdateMessage()
|
//VanuModuleUpdateMessage()
|
||||||
//ModuleLimitsMessage()
|
//ModuleLimitsMessage()
|
||||||
val isCavern = continent.map.cavern
|
val isCavern = zone.map.cavern
|
||||||
sendResponse(ZoneInfoMessage(continentNumber, empire_status=true, if (isCavern) {
|
if (!isCavern) {
|
||||||
Int.MaxValue.toLong
|
sendResponse(ZoneInfoMessage(continentNumber, empire_status = true, 0L))
|
||||||
} else {
|
}
|
||||||
0L
|
|
||||||
}))
|
|
||||||
sendResponse(ZoneLockInfoMessage(continentNumber, lock_status=false, unk=true))
|
sendResponse(ZoneLockInfoMessage(continentNumber, lock_status=false, unk=true))
|
||||||
sendResponse(ZoneForcedCavernConnectionsMessage(continentNumber, 0))
|
sendResponse(ZoneForcedCavernConnectionsMessage(continentNumber, 0))
|
||||||
sendResponse(
|
sendResponse(
|
||||||
|
|
@ -701,6 +704,7 @@ class ZoningOperations(
|
||||||
log.warn(
|
log.warn(
|
||||||
s"SpawnPointResponse: ${player.Name}'s zoning was not in order at the time a response was received; attempting to guess what ${player.Sex.pronounSubject} wants to do"
|
s"SpawnPointResponse: ${player.Name}'s zoning was not in order at the time a response was received; attempting to guess what ${player.Sex.pronounSubject} wants to do"
|
||||||
)
|
)
|
||||||
|
player.protectedWhileZoning = true
|
||||||
}
|
}
|
||||||
val previousZoningType = ztype
|
val previousZoningType = ztype
|
||||||
CancelZoningProcess()
|
CancelZoningProcess()
|
||||||
|
|
@ -1117,6 +1121,27 @@ class ZoningOperations(
|
||||||
PlanetsideAttributeEnum.ControlConsoleHackUpdate,
|
PlanetsideAttributeEnum.ControlConsoleHackUpdate,
|
||||||
HackCaptureActor.GetHackUpdateAttributeValue(amenity.asInstanceOf[CaptureTerminal], isResecured = false)
|
HackCaptureActor.GetHackUpdateAttributeValue(amenity.asInstanceOf[CaptureTerminal], isResecured = false)
|
||||||
)
|
)
|
||||||
|
case GlobalDefinitions.vanu_control_console =>
|
||||||
|
sessionLogic.general.sendPlanetsideAttributeMessage(
|
||||||
|
amenity.GUID,
|
||||||
|
PlanetsideAttributeEnum.ControlConsoleHackUpdate,
|
||||||
|
HackCaptureActor.GetHackUpdateAttributeValue(amenity.asInstanceOf[CaptureTerminal], isResecured = false)
|
||||||
|
)
|
||||||
|
case GlobalDefinitions.main_terminal =>
|
||||||
|
val virus = amenity.asInstanceOf[Terminal].Owner.asInstanceOf[Building].virusId
|
||||||
|
val hackStateMap: Map[Long, HackState7] = Map(
|
||||||
|
0L -> HackState7.UnlockDoors,
|
||||||
|
1L -> HackState7.DisableLatticeBenefits,
|
||||||
|
2L -> HackState7.NTUDrain,
|
||||||
|
3L -> HackState7.DisableRadar,
|
||||||
|
4L -> HackState7.AccessEquipmentTerms
|
||||||
|
)
|
||||||
|
val hackState = hackStateMap.getOrElse(virus, HackState7.Unk8)
|
||||||
|
sessionLogic.general.hackObject(amenity.GUID, unk1 = 1114636288L, hackState)
|
||||||
|
if (virus != 8 && !sessionLogic.zoning.spawn.startEnqueueSquadMessages) {
|
||||||
|
sessionLogic.zoning.spawn.enqueueNewActivity(ActivityQueuedTask(
|
||||||
|
SpawnOperations.delaySendGenericObjectActionMessage(GenericObjectActionMessage(amenityId, 58)), 1))
|
||||||
|
}
|
||||||
case _ =>
|
case _ =>
|
||||||
sessionLogic.general.hackObject(amenity.GUID, unk1 = 1114636288L, HackState7.Unk8) //generic hackable object
|
sessionLogic.general.hackObject(amenity.GUID, unk1 = 1114636288L, HackState7.Unk8) //generic hackable object
|
||||||
}
|
}
|
||||||
|
|
@ -1403,6 +1428,7 @@ class ZoningOperations(
|
||||||
s"LoadZoneTransferPassengerMessages: ${player.Name} expected a manifest for zone transfer; got nothing"
|
s"LoadZoneTransferPassengerMessages: ${player.Name} expected a manifest for zone transfer; got nothing"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
vehicle.protectedWhileZoning = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Before changing zones, perform the following task (which can be a nesting of subtasks). */
|
/** Before changing zones, perform the following task (which can be a nesting of subtasks). */
|
||||||
|
|
@ -1967,6 +1993,7 @@ class ZoningOperations(
|
||||||
deadState = DeadState.RespawnTime
|
deadState = DeadState.RespawnTime
|
||||||
val tplayer = new Player(avatar)
|
val tplayer = new Player(avatar)
|
||||||
session = session.copy(player = tplayer)
|
session = session.copy(player = tplayer)
|
||||||
|
tplayer.protectedWhileZoning = true
|
||||||
//actual zone is undefined; going to our sanctuary
|
//actual zone is undefined; going to our sanctuary
|
||||||
RandomSanctuarySpawnPosition(tplayer)
|
RandomSanctuarySpawnPosition(tplayer)
|
||||||
DefinitionUtil.applyDefaultLoadout(tplayer)
|
DefinitionUtil.applyDefaultLoadout(tplayer)
|
||||||
|
|
@ -1979,6 +2006,7 @@ class ZoningOperations(
|
||||||
deadState = DeadState.RespawnTime
|
deadState = DeadState.RespawnTime
|
||||||
session = session.copy(player = new Player(avatar))
|
session = session.copy(player = new Player(avatar))
|
||||||
player.Zone = inZone
|
player.Zone = inZone
|
||||||
|
player.protectedWhileZoning = true
|
||||||
optionalSavedData match {
|
optionalSavedData match {
|
||||||
case Some(results) =>
|
case Some(results) =>
|
||||||
val health = results.health
|
val health = results.health
|
||||||
|
|
@ -2092,9 +2120,10 @@ class ZoningOperations(
|
||||||
log.info(s"RestoreInfo: player $name is alive")
|
log.info(s"RestoreInfo: player $name is alive")
|
||||||
deadState = DeadState.Alive
|
deadState = DeadState.Alive
|
||||||
session = session.copy(player = p, avatar = a)
|
session = session.copy(player = p, avatar = a)
|
||||||
|
p.protectedWhileZoning = true
|
||||||
sessionLogic.persist()
|
sessionLogic.persist()
|
||||||
setupAvatarFunc = AvatarRejoin
|
setupAvatarFunc = AvatarRejoin
|
||||||
dropMedicalApplicators(p)
|
//dropMedicalApplicators(p)
|
||||||
avatarActor ! AvatarActor.ReplaceAvatar(a)
|
avatarActor ! AvatarActor.ReplaceAvatar(a)
|
||||||
avatarLoginResponse(a)
|
avatarLoginResponse(a)
|
||||||
|
|
||||||
|
|
@ -2104,7 +2133,7 @@ class ZoningOperations(
|
||||||
deadState = DeadState.Dead
|
deadState = DeadState.Dead
|
||||||
session = session.copy(player = p, avatar = a)
|
session = session.copy(player = p, avatar = a)
|
||||||
sessionLogic.persist()
|
sessionLogic.persist()
|
||||||
dropMedicalApplicators(p)
|
//dropMedicalApplicators(p)
|
||||||
HandleReleaseAvatar(p, inZone)
|
HandleReleaseAvatar(p, inZone)
|
||||||
avatarActor ! AvatarActor.ReplaceAvatar(a)
|
avatarActor ! AvatarActor.ReplaceAvatar(a)
|
||||||
avatarLoginResponse(a)
|
avatarLoginResponse(a)
|
||||||
|
|
@ -2548,9 +2577,6 @@ class ZoningOperations(
|
||||||
sessionLogic.general.toggleTeleportSystem(obj, TelepadLike.AppraiseTeleportationSystem(obj, continent))
|
sessionLogic.general.toggleTeleportSystem(obj, TelepadLike.AppraiseTeleportationSystem(obj, continent))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (player.outfit_id == 0) {
|
|
||||||
SessionOutfitHandlers.HandleLoginOutfitCheck(player, sessionLogic)
|
|
||||||
}
|
|
||||||
/*make weather happen
|
/*make weather happen
|
||||||
sendResponse(WeatherMessage(List(),List(
|
sendResponse(WeatherMessage(List(),List(
|
||||||
StormInfo(Vector3(0.1f, 0.15f, 0.0f), 240, 217),
|
StormInfo(Vector3(0.1f, 0.15f, 0.0f), 240, 217),
|
||||||
|
|
@ -2644,6 +2670,7 @@ class ZoningOperations(
|
||||||
def AvatarRejoin(): Unit = {
|
def AvatarRejoin(): Unit = {
|
||||||
sessionLogic.vehicles.GetKnownVehicleAndSeat() match {
|
sessionLogic.vehicles.GetKnownVehicleAndSeat() match {
|
||||||
case (Some(vehicle: Vehicle), Some(seat: Int)) =>
|
case (Some(vehicle: Vehicle), Some(seat: Int)) =>
|
||||||
|
vehicle.protectedWhileZoning = true
|
||||||
//vehicle and driver/passenger
|
//vehicle and driver/passenger
|
||||||
val vguid = vehicle.GUID
|
val vguid = vehicle.GUID
|
||||||
sendResponse(OCM.apply(vehicle))
|
sendResponse(OCM.apply(vehicle))
|
||||||
|
|
@ -2674,7 +2701,6 @@ class ZoningOperations(
|
||||||
log.debug(s"AvatarRejoin: ${player.Name} - $guid -> $data")
|
log.debug(s"AvatarRejoin: ${player.Name} - $guid -> $data")
|
||||||
}
|
}
|
||||||
setupAvatarFunc = AvatarCreate
|
setupAvatarFunc = AvatarCreate
|
||||||
SessionOutfitHandlers.HandleLoginOutfitCheck(player, sessionLogic)
|
|
||||||
/*make weather happen
|
/*make weather happen
|
||||||
sendResponse(WeatherMessage(List(),List(
|
sendResponse(WeatherMessage(List(),List(
|
||||||
StormInfo(Vector3(0.1f, 0.15f, 0.0f), 240, 217),
|
StormInfo(Vector3(0.1f, 0.15f, 0.0f), 240, 217),
|
||||||
|
|
@ -2688,6 +2714,8 @@ class ZoningOperations(
|
||||||
StormInfo(Vector3(0.9f, 0.9f, 0.0f), 243, 215),
|
StormInfo(Vector3(0.9f, 0.9f, 0.0f), 243, 215),
|
||||||
StormInfo(Vector3(0.1f, 0.2f, 0.0f), 241, 215),
|
StormInfo(Vector3(0.1f, 0.2f, 0.0f), 241, 215),
|
||||||
StormInfo(Vector3(0.95f, 0.2f, 0.0f), 241, 215))))*/
|
StormInfo(Vector3(0.95f, 0.2f, 0.0f), 241, 215))))*/
|
||||||
|
player.Zone.ApplyHomeLockBenefitsOnLogin(player)
|
||||||
|
SessionOutfitHandlers.HandleLoginOutfitCheck(player, sessionLogic)
|
||||||
//begin looking for conditions to set the avatar
|
//begin looking for conditions to set the avatar
|
||||||
context.system.scheduler.scheduleOnce(delay = 750 millisecond, context.self, SessionActor.SetCurrentAvatar(player, 200))
|
context.system.scheduler.scheduleOnce(delay = 750 millisecond, context.self, SessionActor.SetCurrentAvatar(player, 200))
|
||||||
}
|
}
|
||||||
|
|
@ -2922,13 +2950,40 @@ class ZoningOperations(
|
||||||
0 seconds
|
0 seconds
|
||||||
} else {
|
} else {
|
||||||
//for other zones ...
|
//for other zones ...
|
||||||
//biolabs have/grant benefits
|
val spawnTimeBenefit: Float = toSpawnPoint.Owner match {
|
||||||
val cryoBenefit: Float = toSpawnPoint.Owner match {
|
case b: Building => FasterRespawnBenefits(b)
|
||||||
case b: Building if b.hasLatticeBenefit(LatticeBenefit.BioLaboratory) => 0.5f
|
case _ => 1f
|
||||||
case _ => 1f
|
|
||||||
}
|
}
|
||||||
//TODO cumulative death penalty
|
//TODO cumulative death penalty
|
||||||
(toSpawnPoint.Definition.Delay.toFloat * cryoBenefit).seconds
|
(toSpawnPoint.Definition.Delay.toFloat * spawnTimeBenefit).seconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiple benefits can be given to an empire based on global ownership of certain zones or facility types that
|
||||||
|
* are linked to the facility being spawned at.
|
||||||
|
* @return float to potentially lower the respawn time if benefits are available
|
||||||
|
*/
|
||||||
|
def FasterRespawnBenefits(building: Building): Float = {
|
||||||
|
//Searhus lock benefit also gives biolab faster respawn
|
||||||
|
val searhusBenefit = Zones.zones.find(_.Number == 9).exists(_.benefitRecipient == player.Faction)
|
||||||
|
building match {
|
||||||
|
case b: Building
|
||||||
|
if (b.hasLatticeBenefit(LatticeBenefit.BioLaboratory) && b.virusId != 1 &&
|
||||||
|
b.hasCavernLockBenefit) ||
|
||||||
|
(b.BuildingType == StructureType.Facility && !b.CaptureTerminalIsHacked &&
|
||||||
|
searhusBenefit && b.hasCavernLockBenefit) =>
|
||||||
|
0.3f
|
||||||
|
case b: Building
|
||||||
|
if !b.CaptureTerminalIsHacked && b.hasCavernLockBenefit && b.virusId != 1 =>
|
||||||
|
0.5f
|
||||||
|
case b: Building
|
||||||
|
if (b.hasLatticeBenefit(LatticeBenefit.BioLaboratory) && b.virusId != 1) ||
|
||||||
|
(b.BuildingType == StructureType.Facility && !b.CaptureTerminalIsHacked &&
|
||||||
|
searhusBenefit) =>
|
||||||
|
0.5f
|
||||||
|
case _ =>
|
||||||
|
1f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3204,7 +3259,11 @@ class ZoningOperations(
|
||||||
buildingType == StructureType.Bunker
|
buildingType == StructureType.Bunker
|
||||||
}
|
}
|
||||||
.foreach { case (_, building) =>
|
.foreach { case (_, building) =>
|
||||||
sendResponse(PlanetsideAttributeMessage(building.GUID, 67, 0 /*building.BuildingType == StructureType.Facility*/))
|
if (building.hasCavernLockBenefit) {
|
||||||
|
sendResponse(PlanetsideAttributeMessage(building.GUID, 67, 1))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
sendResponse(PlanetsideAttributeMessage(building.GUID, 67, 0))
|
||||||
}
|
}
|
||||||
statisticsPacketFunc()
|
statisticsPacketFunc()
|
||||||
if (tplayer.ExoSuit == ExoSuitType.MAX) {
|
if (tplayer.ExoSuit == ExoSuitType.MAX) {
|
||||||
|
|
@ -3329,6 +3388,20 @@ class ZoningOperations(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
nextSpawnPoint.map(_.Owner) match {
|
||||||
|
case Some(b: Building) if b.hasCavernLockBenefit =>
|
||||||
|
tplayer.MaxHealth = 120
|
||||||
|
tplayer.Health = 120
|
||||||
|
tplayer.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
|
tplayer.Zone.id,
|
||||||
|
AvatarAction.PlanetsideAttributeToAll(tplayer.GUID, 0, 120)
|
||||||
|
)
|
||||||
|
tplayer.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
|
tplayer.Zone.id,
|
||||||
|
AvatarAction.PlanetsideAttributeToAll(tplayer.GUID, 1, 120)
|
||||||
|
)
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
doorsThatShouldBeOpenInRange(pos, range = 100f)
|
doorsThatShouldBeOpenInRange(pos, range = 100f)
|
||||||
setAvatar = true
|
setAvatar = true
|
||||||
player.allowInteraction = true
|
player.allowInteraction = true
|
||||||
|
|
@ -3484,6 +3557,7 @@ class ZoningOperations(
|
||||||
deadState = DeadState.Release //we may be alive or dead, may or may not be a corpse
|
deadState = DeadState.Release //we may be alive or dead, may or may not be a corpse
|
||||||
sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, unk5=true))
|
sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, unk5=true))
|
||||||
DrawCurrentAmsSpawnPoint()
|
DrawCurrentAmsSpawnPoint()
|
||||||
|
player.protectedWhileZoning = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -3663,6 +3737,9 @@ class ZoningOperations(
|
||||||
//originally the client sent a death statistic update in between each change of statistic categories, about 30 times
|
//originally the client sent a death statistic update in between each change of statistic categories, about 30 times
|
||||||
sendResponse(AvatarStatisticsMessage(DeathStatistic(ScoreCard.deathCount(avatar.scorecard))))
|
sendResponse(AvatarStatisticsMessage(DeathStatistic(ScoreCard.deathCount(avatar.scorecard))))
|
||||||
statisticsPacketFunc = respawnAvatarStatisticsFields
|
statisticsPacketFunc = respawnAvatarStatisticsFields
|
||||||
|
player.protectedWhileZoning = false
|
||||||
|
player.Zone.ApplyHomeLockBenefitsOnLogin(player)
|
||||||
|
SessionOutfitHandlers.HandleLoginOutfitCheck(player, sessionLogic)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -3693,6 +3770,7 @@ class ZoningOperations(
|
||||||
}
|
}
|
||||||
//originally the client sent a death statistic update in between each change of statistic categories, about 30 times
|
//originally the client sent a death statistic update in between each change of statistic categories, about 30 times
|
||||||
sendResponse(AvatarStatisticsMessage(DeathStatistic(ScoreCard.deathCount(avatar.scorecard))))
|
sendResponse(AvatarStatisticsMessage(DeathStatistic(ScoreCard.deathCount(avatar.scorecard))))
|
||||||
|
player.protectedWhileZoning = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import net.psforever.actors.commands.NtuCommand
|
||||||
import net.psforever.actors.zone.building._
|
import net.psforever.actors.zone.building._
|
||||||
import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate}
|
import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate}
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
|
import net.psforever.packet.PlanetSideGamePacket
|
||||||
|
import net.psforever.packet.game.ContinentalLockUpdateMessage
|
||||||
import net.psforever.persistence
|
import net.psforever.persistence
|
||||||
import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage}
|
import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage}
|
||||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||||
|
|
@ -76,6 +78,9 @@ object BuildingActor {
|
||||||
|
|
||||||
final case class DensityLevelUpdate(building: Building) extends Command
|
final case class DensityLevelUpdate(building: Building) extends Command
|
||||||
|
|
||||||
|
final case class ContinentalLock(zone: Zone) extends Command
|
||||||
|
|
||||||
|
final case class HomeLockBenefits(msg: PlanetSideGamePacket) extends Command
|
||||||
/**
|
/**
|
||||||
* Set a facility affiliated to one faction to be affiliated to a different faction.
|
* Set a facility affiliated to one faction to be affiliated to a different faction.
|
||||||
* @param details building and event system references
|
* @param details building and event system references
|
||||||
|
|
@ -162,7 +167,6 @@ object BuildingActor {
|
||||||
val building = details.building
|
val building = details.building
|
||||||
val zone = building.Zone
|
val zone = building.Zone
|
||||||
building.Faction = faction
|
building.Faction = faction
|
||||||
zone.actor ! ZoneActor.ZoneMapUpdate() // Update entire lattice to show lattice benefits
|
|
||||||
zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SetEmpire(building.GUID, faction))
|
zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SetEmpire(building.GUID, faction))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -252,6 +256,14 @@ class BuildingActor(
|
||||||
case DensityLevelUpdate(building) =>
|
case DensityLevelUpdate(building) =>
|
||||||
details.galaxyService ! GalaxyServiceMessage(GalaxyAction.SendResponse(details.building.densityLevelUpdateMessage(building)))
|
details.galaxyService ! GalaxyServiceMessage(GalaxyAction.SendResponse(details.building.densityLevelUpdateMessage(building)))
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
|
case ContinentalLock(zone) =>
|
||||||
|
details.galaxyService ! GalaxyServiceMessage(GalaxyAction.SendResponse(ContinentalLockUpdateMessage(zone.Number, zone.lockedBy)))
|
||||||
|
Behaviors.same
|
||||||
|
|
||||||
|
case HomeLockBenefits(msg) =>
|
||||||
|
details.galaxyService ! GalaxyServiceMessage(GalaxyAction.SendResponse(msg))
|
||||||
|
Behaviors.same
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import net.psforever.objects.serverobject.structures.{StructureType, WarpGate}
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
import net.psforever.objects.zones.blockmap.{BlockMapEntity, SectorGroup}
|
import net.psforever.objects.zones.blockmap.{BlockMapEntity, SectorGroup}
|
||||||
import net.psforever.objects.{ConstructionItem, PlanetSideGameObject, Player, Vehicle}
|
import net.psforever.objects.{ConstructionItem, PlanetSideGameObject, Player, Vehicle}
|
||||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
|
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, PlanetSideGeneratorState, Vector3}
|
||||||
import akka.actor.typed.scaladsl.adapter._
|
import akka.actor.typed.scaladsl.adapter._
|
||||||
import net.psforever.actors.zone.building.MajorFacilityLogic
|
import net.psforever.actors.zone.building.MajorFacilityLogic
|
||||||
import net.psforever.objects.avatar.scoring.Kill
|
import net.psforever.objects.avatar.scoring.Kill
|
||||||
|
|
@ -18,8 +18,10 @@ import net.psforever.objects.serverobject.turret.FacilityTurret
|
||||||
import net.psforever.objects.sourcing.SourceEntry
|
import net.psforever.objects.sourcing.SourceEntry
|
||||||
import net.psforever.objects.vital.{InGameActivity, InGameHistory}
|
import net.psforever.objects.vital.{InGameActivity, InGameHistory}
|
||||||
import net.psforever.objects.zones.exp.{ExperienceCalculator, SupportExperienceCalculator}
|
import net.psforever.objects.zones.exp.{ExperienceCalculator, SupportExperienceCalculator}
|
||||||
|
import net.psforever.packet.game.{BuildingInfoUpdateMessage, PlanetsideAttributeMessage}
|
||||||
import net.psforever.util.Database._
|
import net.psforever.util.Database._
|
||||||
import net.psforever.persistence
|
import net.psforever.persistence
|
||||||
|
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
import scala.util.{Failure, Success}
|
import scala.util.{Failure, Success}
|
||||||
|
|
@ -78,6 +80,10 @@ object ZoneActor {
|
||||||
final case class RewardThisDeath(entity: PlanetSideGameObject with FactionAffinity with InGameHistory) extends Command
|
final case class RewardThisDeath(entity: PlanetSideGameObject with FactionAffinity with InGameHistory) extends Command
|
||||||
|
|
||||||
final case class RewardOurSupporters(target: SourceEntry, history: Iterable[InGameActivity], kill: Kill, bep: Long) extends Command
|
final case class RewardOurSupporters(target: SourceEntry, history: Iterable[InGameActivity], kill: Kill, bep: Long) extends Command
|
||||||
|
|
||||||
|
final case class AssignLockedBy(zone: Zone, notifyPlayers: Boolean) extends Command
|
||||||
|
|
||||||
|
final case class BuildingInfoState(msg: BuildingInfoUpdateMessage) extends Command
|
||||||
}
|
}
|
||||||
|
|
||||||
class ZoneActor(
|
class ZoneActor(
|
||||||
|
|
@ -115,6 +121,7 @@ class ZoneActor(
|
||||||
// TODO this happens during testing, need a way to not always persist during tests
|
// TODO this happens during testing, need a way to not always persist during tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AssignLockedBy(zone, notifyPlayers=false)
|
||||||
case Failure(e) => log.error(e.getMessage)
|
case Failure(e) => log.error(e.getMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,14 +190,75 @@ class ZoneActor(
|
||||||
case ZoneMapUpdate() =>
|
case ZoneMapUpdate() =>
|
||||||
zone.Buildings
|
zone.Buildings
|
||||||
.filter(building =>
|
.filter(building =>
|
||||||
building._2.BuildingType == StructureType.Facility || building._2.BuildingType == StructureType.Tower)
|
building._2.BuildingType == StructureType.Facility)
|
||||||
.values
|
.values
|
||||||
.foreach(_.Actor ! BuildingActor.MapUpdate())
|
.foreach(_.Actor ! BuildingActor.MapUpdate())
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
|
case AssignLockedBy(zone, notifyPlayers) =>
|
||||||
|
AssignLockedBy(zone, notifyPlayers)
|
||||||
|
Behaviors.same
|
||||||
|
|
||||||
|
case BuildingInfoState(msg) =>
|
||||||
|
UpdateBuildingState(msg)
|
||||||
|
Behaviors.same
|
||||||
}
|
}
|
||||||
.receiveSignal {
|
.receiveSignal {
|
||||||
case (_, PostStop) =>
|
case (_, PostStop) =>
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def AssignLockedBy(zone: Zone, notifyPlayers: Boolean): Unit = {
|
||||||
|
val buildings = zone.Buildings.values
|
||||||
|
val facilities = if (zone.id.startsWith("c")) {
|
||||||
|
buildings.filter(b =>
|
||||||
|
b.Name.startsWith("N") || b.Name.startsWith("S")).toSeq
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buildings.filter(_.BuildingType == StructureType.Facility).toSeq
|
||||||
|
}
|
||||||
|
val factions = facilities.map(_.Faction).toSet
|
||||||
|
zone.lockedBy =
|
||||||
|
if (factions.size == 1) factions.head
|
||||||
|
else PlanetSideEmpire.NEUTRAL
|
||||||
|
zone.benefitRecipient =
|
||||||
|
if (facilities.nonEmpty && facilities.forall(_.Faction == facilities.head.Faction))
|
||||||
|
facilities.head.Faction
|
||||||
|
else
|
||||||
|
zone.benefitRecipient
|
||||||
|
if (facilities.nonEmpty && notifyPlayers) { zone.NotifyContinentalLockBenefits(zone, facilities.head) }
|
||||||
|
}
|
||||||
|
|
||||||
|
def UpdateBuildingState(msg: BuildingInfoUpdateMessage): Unit = {
|
||||||
|
val buildingOpt = zone.Buildings.collectFirst {
|
||||||
|
case (_, b) if b.MapId == msg.building_map_id => b
|
||||||
|
}
|
||||||
|
buildingOpt.foreach { building =>
|
||||||
|
if (msg.generator_state == PlanetSideGeneratorState.Normal && building.hasCavernLockBenefit) {
|
||||||
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
LocalAction.SendResponse(PlanetsideAttributeMessage(building.GUID, 67, 1))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
msg.is_hacked match {
|
||||||
|
case true if building.BuildingType == StructureType.Facility && !zone.map.cavern =>
|
||||||
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
LocalAction.SendResponse(PlanetsideAttributeMessage(building.GUID, 67, 0))
|
||||||
|
)
|
||||||
|
case false if building.hasCavernLockBenefit =>
|
||||||
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
LocalAction.SendResponse(PlanetsideAttributeMessage(building.GUID, 67, 1))
|
||||||
|
)
|
||||||
|
case false if building.BuildingType == StructureType.Facility && !zone.map.cavern && !building.hasCavernLockBenefit =>
|
||||||
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
LocalAction.SendResponse(PlanetsideAttributeMessage(building.GUID, 67, 0))
|
||||||
|
)
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ package net.psforever.actors.zone.building
|
||||||
import akka.actor.typed.Behavior
|
import akka.actor.typed.Behavior
|
||||||
import akka.actor.typed.scaladsl.{ActorContext, Behaviors}
|
import akka.actor.typed.scaladsl.{ActorContext, Behaviors}
|
||||||
import net.psforever.actors.commands.NtuCommand
|
import net.psforever.actors.commands.NtuCommand
|
||||||
import net.psforever.actors.zone.{BuildingActor, BuildingControlDetails}
|
import net.psforever.actors.zone.{BuildingActor, BuildingControlDetails, ZoneActor}
|
||||||
import net.psforever.objects.serverobject.structures.{Amenity, Building}
|
import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType}
|
||||||
import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalAware, CaptureTerminalAwareBehavior}
|
import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalAware, CaptureTerminalAwareBehavior}
|
||||||
import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage}
|
import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage}
|
||||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||||
|
|
@ -84,7 +84,16 @@ case object CavernFacilityLogic
|
||||||
): Behavior[Command] = {
|
): Behavior[Command] = {
|
||||||
BuildingActor.setFactionTo(details, faction, log)
|
BuildingActor.setFactionTo(details, faction, log)
|
||||||
val building = details.building
|
val building = details.building
|
||||||
building.Neighbours.getOrElse(Nil).foreach { _.Actor ! BuildingActor.AlertToFactionChange(building) }
|
val gates: Iterable[Building] = building.Zone.Buildings.values.filter(_.BuildingType == StructureType.WarpGate)
|
||||||
|
gates.foreach { g =>
|
||||||
|
val neighbors = g.Neighbours.getOrElse(Nil)
|
||||||
|
neighbors.collect {
|
||||||
|
case otherWg: Building => otherWg
|
||||||
|
}
|
||||||
|
.filter(_.Zone != g.Zone)
|
||||||
|
.foreach { otherGate => otherGate.Zone.actor ! ZoneActor.ZoneMapUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import net.psforever.objects.serverobject.generator.{Generator, GeneratorControl
|
||||||
import net.psforever.objects.serverobject.structures.{Amenity, Building}
|
import net.psforever.objects.serverobject.structures.{Amenity, Building}
|
||||||
import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalAware, CaptureTerminalAwareBehavior}
|
import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalAware, CaptureTerminalAwareBehavior}
|
||||||
import net.psforever.objects.sourcing.PlayerSource
|
import net.psforever.objects.sourcing.PlayerSource
|
||||||
|
import net.psforever.packet.game.PlanetsideAttributeMessage
|
||||||
import net.psforever.services.{InterstellarClusterService, Service}
|
import net.psforever.services.{InterstellarClusterService, Service}
|
||||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage}
|
import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage}
|
||||||
|
|
@ -158,6 +159,7 @@ case object MajorFacilityLogic
|
||||||
* @return the next behavior for this control agency messaging system
|
* @return the next behavior for this control agency messaging system
|
||||||
*/
|
*/
|
||||||
def amenityStateChange(details: BuildingWrapper, entity: Amenity, data: Option[Any]): Behavior[Command] = {
|
def amenityStateChange(details: BuildingWrapper, entity: Amenity, data: Option[Any]): Behavior[Command] = {
|
||||||
|
import net.psforever.objects.GlobalDefinitions
|
||||||
entity match {
|
entity match {
|
||||||
case gen: Generator =>
|
case gen: Generator =>
|
||||||
if (generatorStateChange(details, gen, data)) {
|
if (generatorStateChange(details, gen, data)) {
|
||||||
|
|
@ -176,12 +178,24 @@ case object MajorFacilityLogic
|
||||||
case _ =>
|
case _ =>
|
||||||
log(details).warn("CaptureTerminal AmenityStateChange was received with no attached data.")
|
log(details).warn("CaptureTerminal AmenityStateChange was received with no attached data.")
|
||||||
}
|
}
|
||||||
// When a CC is hacked (or resecured) all currently hacked amenities for the base should return to their default unhacked state
|
// When a CC is hacked (or resecured) clear hacks on amenities based on currently installed virus
|
||||||
building.HackableAmenities.foreach(amenity => {
|
val hackedAmenities = building.HackableAmenities.filter(_.HackedBy.isDefined)
|
||||||
if (amenity.HackedBy.isDefined) {
|
val amenitiesToClear = building.virusId match {
|
||||||
building.Zone.LocalEvents ! LocalServiceMessage(amenity.Zone.id,LocalAction.ClearTemporaryHack(PlanetSideGUID(0), amenity))
|
case 0 =>
|
||||||
}
|
hackedAmenities.filterNot(a => a.Definition == GlobalDefinitions.lock_external || a.Definition == GlobalDefinitions.main_terminal)
|
||||||
})
|
case 4 =>
|
||||||
|
hackedAmenities.filterNot(a => a.Definition == GlobalDefinitions.order_terminal || a.Definition == GlobalDefinitions.main_terminal)
|
||||||
|
case 8 =>
|
||||||
|
hackedAmenities
|
||||||
|
case _ =>
|
||||||
|
hackedAmenities
|
||||||
|
}
|
||||||
|
amenitiesToClear.foreach { amenity =>
|
||||||
|
building.Zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
amenity.Zone.id,
|
||||||
|
LocalAction.ClearTemporaryHack(PlanetSideGUID(0), amenity)
|
||||||
|
)
|
||||||
|
}
|
||||||
// No map update needed - will be sent by `HackCaptureActor` when required
|
// No map update needed - will be sent by `HackCaptureActor` when required
|
||||||
case _ =>
|
case _ =>
|
||||||
details.galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(details.building.infoUpdateMessage()))
|
details.galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(details.building.infoUpdateMessage()))
|
||||||
|
|
@ -231,6 +245,8 @@ case object MajorFacilityLogic
|
||||||
}
|
}
|
||||||
setFactionTo(details, PlanetSideEmpire.NEUTRAL)
|
setFactionTo(details, PlanetSideEmpire.NEUTRAL)
|
||||||
details.asInstanceOf[MajorFacilityWrapper].hasNtuSupply = false
|
details.asInstanceOf[MajorFacilityWrapper].hasNtuSupply = false
|
||||||
|
details.building.Zone.lockedBy = PlanetSideEmpire.NEUTRAL
|
||||||
|
details.building.Zone.NotifyContinentalLockBenefits(details.building.Zone, details.building)
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,6 +303,12 @@ case object MajorFacilityLogic
|
||||||
building.PlayersInSOI.foreach { player =>
|
building.PlayersInSOI.foreach { player =>
|
||||||
events ! AvatarServiceMessage(player.Name, msg)
|
events ! AvatarServiceMessage(player.Name, msg)
|
||||||
}
|
}
|
||||||
|
if (building.hasCavernLockBenefit) {
|
||||||
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
LocalAction.SendResponse(PlanetsideAttributeMessage(building.GUID, 67, 0))
|
||||||
|
)
|
||||||
|
}
|
||||||
false
|
false
|
||||||
case Some(GeneratorControl.Event.Destroyed) =>
|
case Some(GeneratorControl.Event.Destroyed) =>
|
||||||
true
|
true
|
||||||
|
|
|
||||||
|
|
@ -122,9 +122,6 @@ case object WarpGateLogic
|
||||||
}
|
}
|
||||||
updateBroadcastCapabilitiesOfWarpGate(details, wg, setBroadcastTo)
|
updateBroadcastCapabilitiesOfWarpGate(details, wg, setBroadcastTo)
|
||||||
updateBroadcastCapabilitiesOfWarpGate(details, otherWg, setBroadcastTo)
|
updateBroadcastCapabilitiesOfWarpGate(details, otherWg, setBroadcastTo)
|
||||||
if (wg.Zone.map.cavern && !otherWg.Zone.map.cavern) {
|
|
||||||
otherWg.Zone.actor ! ZoneActor.ZoneMapUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
case (Some(_), Some(wg : WarpGate), Some(otherWg : WarpGate), None) =>
|
case (Some(_), Some(wg : WarpGate), Some(otherWg : WarpGate), None) =>
|
||||||
handleWarpGateDeadendPair(details, otherWg, wg)
|
handleWarpGateDeadendPair(details, otherWg, wg)
|
||||||
|
|
|
||||||
|
|
@ -1125,6 +1125,8 @@ object GlobalDefinitions {
|
||||||
|
|
||||||
val medical_terminal = new MedicalTerminalDefinition(529)
|
val medical_terminal = new MedicalTerminalDefinition(529)
|
||||||
|
|
||||||
|
val medical_terminal_healing_module = new MedicalTerminalDefinition(530)
|
||||||
|
|
||||||
val portable_med_terminal = new MedicalTerminalDefinition(689)
|
val portable_med_terminal = new MedicalTerminalDefinition(689)
|
||||||
|
|
||||||
val pad_landing_frame = new MedicalTerminalDefinition(618)
|
val pad_landing_frame = new MedicalTerminalDefinition(618)
|
||||||
|
|
@ -1239,6 +1241,8 @@ object GlobalDefinitions {
|
||||||
|
|
||||||
val vanu_control_console = new CaptureTerminalDefinition(930) // Cavern CC
|
val vanu_control_console = new CaptureTerminalDefinition(930) // Cavern CC
|
||||||
|
|
||||||
|
val main_terminal = new MainTerminalDefinition(473)
|
||||||
|
|
||||||
val llm_socket = new CaptureFlagSocketDefinition()
|
val llm_socket = new CaptureFlagSocketDefinition()
|
||||||
|
|
||||||
val capture_flag = new CaptureFlagDefinition()
|
val capture_flag = new CaptureFlagDefinition()
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ class Player(var avatar: Avatar)
|
||||||
var outfit_window_open: Boolean = false
|
var outfit_window_open: Boolean = false
|
||||||
var outfit_list_open: Boolean = false
|
var outfit_list_open: Boolean = false
|
||||||
var maxAutoRunEnabled: Boolean = false
|
var maxAutoRunEnabled: Boolean = false
|
||||||
|
var protectedWhileZoning: Boolean = false
|
||||||
|
|
||||||
/** From PlanetsideAttributeMessage */
|
/** From PlanetsideAttributeMessage */
|
||||||
var PlanetsideAttribute: Array[Long] = Array.ofDim(120)
|
var PlanetsideAttribute: Array[Long] = Array.ofDim(120)
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,7 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
private var cloaked: Boolean = false
|
private var cloaked: Boolean = false
|
||||||
private var flying: Option[Int] = None
|
private var flying: Option[Int] = None
|
||||||
private var capacitor: Int = 0
|
private var capacitor: Int = 0
|
||||||
|
var protectedWhileZoning: Boolean = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Permissions control who gets to access different parts of the vehicle;
|
* Permissions control who gets to access different parts of the vehicle;
|
||||||
|
|
|
||||||
|
|
@ -72,9 +72,9 @@ object FirstTimeEvents {
|
||||||
)
|
)
|
||||||
|
|
||||||
val Other: Set[String] = Set(
|
val Other: Set[String] = Set(
|
||||||
"used_nchev_scattercannon",
|
"used_nc_hev_scattercannon",
|
||||||
"used_nchev_falcon",
|
"used_nc_hev_falcon",
|
||||||
"used_nchev_sparrow",
|
"used_nc_hev_sparrow",
|
||||||
"used_energy_gun_nc",
|
"used_energy_gun_nc",
|
||||||
"visited_portable_manned_turret_nc"
|
"visited_portable_manned_turret_nc"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -407,10 +407,28 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
||||||
terminalUsedAction
|
terminalUsedAction
|
||||||
|
|
||||||
case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) =>
|
case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) =>
|
||||||
|
import net.psforever.objects.serverobject.structures.Building
|
||||||
log.info(s"${player.Name} wants to change equipment loadout to their option #${msg.unk1 + 1}")
|
log.info(s"${player.Name} wants to change equipment loadout to their option #${msg.unk1 + 1}")
|
||||||
val originalSuit = player.ExoSuit
|
val originalSuit = player.ExoSuit
|
||||||
val originalSubtype = Loadout.DetermineSubtype(player)
|
val originalSubtype = Loadout.DetermineSubtype(player)
|
||||||
val dropPred = ContainableBehavior.DropPredicate(player)
|
val terminalOpt: Option[Terminal] =
|
||||||
|
player.Zone.GUID(msg.terminal_guid).collect {
|
||||||
|
case t: Terminal => t
|
||||||
|
}
|
||||||
|
val hasCavernEquipmentBenefit: Boolean =
|
||||||
|
terminalOpt.exists { terminal =>
|
||||||
|
terminal.Owner match {
|
||||||
|
case fac: Building =>
|
||||||
|
fac.hasCavernLockBenefit && player.Faction == fac.Faction
|
||||||
|
case _ =>
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val dropPred =
|
||||||
|
if (hasCavernEquipmentBenefit)
|
||||||
|
ContainableBehavior.DropPredicateEquipmentBenefit(player)
|
||||||
|
else
|
||||||
|
ContainableBehavior.DropPredicate(player)
|
||||||
//determine player's next exo-suit
|
//determine player's next exo-suit
|
||||||
val (nextSuit, nextSubtype) = {
|
val (nextSuit, nextSubtype) = {
|
||||||
lazy val fallbackSuit = if (Players.CertificationToUseExoSuit(player, originalSuit, originalSubtype)) {
|
lazy val fallbackSuit = if (Players.CertificationToUseExoSuit(player, originalSuit, originalSubtype)) {
|
||||||
|
|
@ -790,7 +808,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
||||||
target: Target,
|
target: Target,
|
||||||
applyDamageTo: Output
|
applyDamageTo: Output
|
||||||
): Unit = {
|
): Unit = {
|
||||||
if (player.isAlive && !player.spectator) {
|
if (player.isAlive && !player.spectator && !player.protectedWhileZoning) {
|
||||||
val originalHealth = player.Health
|
val originalHealth = player.Health
|
||||||
val originalArmor = player.Armor
|
val originalArmor = player.Armor
|
||||||
val originalStamina = player.avatar.stamina
|
val originalStamina = player.avatar.stamina
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
|
||||||
import net.psforever.objects.{PlanetSideGameObject, Vehicle}
|
import net.psforever.objects.{PlanetSideGameObject, Vehicle}
|
||||||
import net.psforever.packet.game.objectcreate._
|
import net.psforever.packet.game.objectcreate._
|
||||||
import net.psforever.types.{DriveState, PlanetSideGUID, VehicleFormat}
|
import net.psforever.types.{DriveState, PlanetSideGUID, VehicleFormat}
|
||||||
|
import net.psforever.zones.Zones
|
||||||
|
|
||||||
import scala.util.{Failure, Success, Try}
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
||||||
|
|
@ -14,6 +15,8 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
|
||||||
|
|
||||||
override def ConstructorData(obj: Vehicle): Try[VehicleData] = {
|
override def ConstructorData(obj: Vehicle): Try[VehicleData] = {
|
||||||
val health = StatConverter.Health(obj.Health, obj.MaxHealth)
|
val health = StatConverter.Health(obj.Health, obj.MaxHealth)
|
||||||
|
val boosted = if (Zones.zones.find(_.Number == 3).exists(_.benefitRecipient == obj.Faction)) true
|
||||||
|
else false
|
||||||
if (health > 0) { //active
|
if (health > 0) { //active
|
||||||
Success(
|
Success(
|
||||||
VehicleData(
|
VehicleData(
|
||||||
|
|
@ -32,7 +35,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
|
||||||
case None => PlanetSideGUID(0)
|
case None => PlanetSideGUID(0)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
unk3 = false,
|
boostMaxHealth = boosted,
|
||||||
health,
|
health,
|
||||||
unk4 = false,
|
unk4 = false,
|
||||||
no_mount_points = false,
|
no_mount_points = false,
|
||||||
|
|
@ -59,7 +62,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
|
||||||
v5 = None,
|
v5 = None,
|
||||||
guid = PlanetSideGUID(0)
|
guid = PlanetSideGUID(0)
|
||||||
),
|
),
|
||||||
unk3 = false,
|
boostMaxHealth = boosted,
|
||||||
health = 0,
|
health = 0,
|
||||||
unk4 = false,
|
unk4 = false,
|
||||||
no_mount_points = true,
|
no_mount_points = true,
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,23 @@ object EffectTarget {
|
||||||
//noinspection ScalaUnusedSymbol
|
//noinspection ScalaUnusedSymbol
|
||||||
def Valid(target: PlanetSideGameObject): Boolean = true
|
def Valid(target: PlanetSideGameObject): Boolean = true
|
||||||
|
|
||||||
def Medical(target: PlanetSideGameObject): Boolean =
|
def Medical(target: PlanetSideGameObject): Boolean = {
|
||||||
target match {
|
target match {
|
||||||
case p: Player =>
|
case p: Player =>
|
||||||
p.Health > 0 && (p.Health < p.MaxHealth || p.Armor < p.MaxArmor)
|
p.Health > 0 && (p.Health < p.MaxHealth || p.Armor < p.MaxArmor)
|
||||||
case _ =>
|
case _ =>
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def HealthModule(target: PlanetSideGameObject): Boolean = {
|
||||||
|
target match {
|
||||||
|
case p: Player =>
|
||||||
|
p.Health > 0 && p.Health < 120
|
||||||
|
case _ =>
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def HealthCrystal(target: PlanetSideGameObject): Boolean =
|
def HealthCrystal(target: PlanetSideGameObject): Boolean =
|
||||||
target match {
|
target match {
|
||||||
|
|
|
||||||
|
|
@ -370,6 +370,15 @@ object GlobalDefinitionsMiscellaneous {
|
||||||
medical_terminal.RepairIfDestroyed = true
|
medical_terminal.RepairIfDestroyed = true
|
||||||
medical_terminal.Geometry = GeometryForm.representByCylinder(radius = 0.711f, height = 1.75f)
|
medical_terminal.Geometry = GeometryForm.representByCylinder(radius = 0.711f, height = 1.75f)
|
||||||
|
|
||||||
|
medical_terminal_healing_module.Name = "medical_terminal_healing_module"
|
||||||
|
medical_terminal_healing_module.Interval = 2000
|
||||||
|
medical_terminal_healing_module.HealAmount = 1
|
||||||
|
medical_terminal_healing_module.ArmorAmount = 0
|
||||||
|
medical_terminal_healing_module.UseRadius = 300
|
||||||
|
medical_terminal_healing_module.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.HealthModule
|
||||||
|
medical_terminal_healing_module.Damageable = false
|
||||||
|
medical_terminal_healing_module.Repairable = false
|
||||||
|
|
||||||
adv_med_terminal.Name = "adv_med_terminal"
|
adv_med_terminal.Name = "adv_med_terminal"
|
||||||
adv_med_terminal.Interval = 500
|
adv_med_terminal.Interval = 500
|
||||||
adv_med_terminal.HealAmount = 8
|
adv_med_terminal.HealAmount = 8
|
||||||
|
|
@ -720,6 +729,10 @@ object GlobalDefinitionsMiscellaneous {
|
||||||
vanu_control_console.Repairable = false
|
vanu_control_console.Repairable = false
|
||||||
vanu_control_console.FacilityHackTime = 10.minutes
|
vanu_control_console.FacilityHackTime = 10.minutes
|
||||||
|
|
||||||
|
main_terminal.Name = "main_terminal"
|
||||||
|
main_terminal.Damageable = false
|
||||||
|
main_terminal.Repairable = false
|
||||||
|
|
||||||
lodestar_repair_terminal.Name = "lodestar_repair_terminal"
|
lodestar_repair_terminal.Name = "lodestar_repair_terminal"
|
||||||
lodestar_repair_terminal.Interval = 1000
|
lodestar_repair_terminal.Interval = 1000
|
||||||
lodestar_repair_terminal.HealAmount = 60
|
lodestar_repair_terminal.HealAmount = 60
|
||||||
|
|
|
||||||
|
|
@ -1782,6 +1782,8 @@ object GlobalDefinitionsProjectile {
|
||||||
spitfire_aa_ammo_projectile.ProjectileDamageTypeSecondary = DamageType.Splash
|
spitfire_aa_ammo_projectile.ProjectileDamageTypeSecondary = DamageType.Splash
|
||||||
spitfire_aa_ammo_projectile.InitialVelocity = 100
|
spitfire_aa_ammo_projectile.InitialVelocity = 100
|
||||||
spitfire_aa_ammo_projectile.Lifespan = 5f
|
spitfire_aa_ammo_projectile.Lifespan = 5f
|
||||||
|
spitfire_aa_ammo_projectile.DamageToArmorFirst = true
|
||||||
|
spitfire_aa_ammo_projectile.DamageToBattleframeOnly = true
|
||||||
ProjectileDefinition.CalculateDerivedFields(spitfire_aa_ammo_projectile)
|
ProjectileDefinition.CalculateDerivedFields(spitfire_aa_ammo_projectile)
|
||||||
spitfire_aa_ammo_projectile.Modifiers = List(
|
spitfire_aa_ammo_projectile.Modifiers = List(
|
||||||
CerberusTurretWrongTarget,
|
CerberusTurretWrongTarget,
|
||||||
|
|
@ -1797,6 +1799,7 @@ object GlobalDefinitionsProjectile {
|
||||||
spitfire_ammo_projectile.DegradeMultiplier = 0.5f
|
spitfire_ammo_projectile.DegradeMultiplier = 0.5f
|
||||||
spitfire_ammo_projectile.InitialVelocity = 100
|
spitfire_ammo_projectile.InitialVelocity = 100
|
||||||
spitfire_ammo_projectile.Lifespan = .5f
|
spitfire_ammo_projectile.Lifespan = .5f
|
||||||
|
spitfire_ammo_projectile.DamageToArmorFirst = true
|
||||||
spitfire_ammo_projectile.DamageToBattleframeOnly = true
|
spitfire_ammo_projectile.DamageToBattleframeOnly = true
|
||||||
ProjectileDefinition.CalculateDerivedFields(spitfire_ammo_projectile)
|
ProjectileDefinition.CalculateDerivedFields(spitfire_ammo_projectile)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,9 @@ object CommonMessages {
|
||||||
final case class Hack(player: Player, obj: PlanetSideServerObject with Hackable, data: Option[Any] = None)
|
final case class Hack(player: Player, obj: PlanetSideServerObject with Hackable, data: Option[Any] = None)
|
||||||
final case class ClearHack()
|
final case class ClearHack()
|
||||||
final case class EntityHackState(obj: PlanetSideGameObject with Hackable, hackState: Boolean)
|
final case class EntityHackState(obj: PlanetSideGameObject with Hackable, hackState: Boolean)
|
||||||
|
final case class UploadVirus(player: Player, data: Option[Any] = None, virus: Long)
|
||||||
|
final case class RemoveVirus(player: Player, data: Option[Any])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The message that progresses some form of user-driven activity with a certain eventual outcome
|
* The message that progresses some form of user-driven activity with a certain eventual outcome
|
||||||
* and potential feedback per cycle.
|
* and potential feedback per cycle.
|
||||||
|
|
|
||||||
|
|
@ -681,6 +681,19 @@ object ContainableBehavior {
|
||||||
entry.obj.isInstanceOf[BoomerTrigger] ||
|
entry.obj.isInstanceOf[BoomerTrigger] ||
|
||||||
(faction != tplayer.Faction && faction != PlanetSideEmpire.NEUTRAL)
|
(faction != tplayer.Faction && faction != PlanetSideEmpire.NEUTRAL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as above except the terminal used is from a facility that has cavern equipment benefit
|
||||||
|
* so allow cavern equipment to be kept
|
||||||
|
*/
|
||||||
|
def DropPredicateEquipmentBenefit(tplayer: Player): InventoryItem => Boolean =
|
||||||
|
entry => {
|
||||||
|
val objDef = entry.obj.Definition
|
||||||
|
val faction = GlobalDefinitions.isFactionEquipment(objDef)
|
||||||
|
objDef == GlobalDefinitions.router_telepad ||
|
||||||
|
entry.obj.isInstanceOf[BoomerTrigger] ||
|
||||||
|
(faction != tplayer.Faction && faction != PlanetSideEmpire.NEUTRAL)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object Containable {
|
object Containable {
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ trait DamageableVehicle
|
||||||
val shields = obj.Shields
|
val shields = obj.Shields
|
||||||
val damageToHealth = originalHealth - health
|
val damageToHealth = originalHealth - health
|
||||||
val damageToShields = originalShields - shields
|
val damageToShields = originalShields - shields
|
||||||
if (WillAffectTarget(target, damageToHealth + damageToShields, cause)) {
|
if (WillAffectTarget(target, damageToHealth + damageToShields, cause) && !obj.protectedWhileZoning) {
|
||||||
target.LogActivity(cause)
|
target.LogActivity(cause)
|
||||||
DamageLog(
|
DamageLog(
|
||||||
target,
|
target,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) 2020-2024 PSForever
|
// Copyright (c) 2020-2024 PSForever
|
||||||
package net.psforever.objects.serverobject.environment
|
package net.psforever.objects.serverobject.environment
|
||||||
|
|
||||||
|
import net.psforever.objects.serverobject.interior.Sidedness
|
||||||
import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle}
|
import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle}
|
||||||
import net.psforever.objects.vital.Vitality
|
import net.psforever.objects.vital.Vitality
|
||||||
import net.psforever.types.Vector3
|
import net.psforever.types.Vector3
|
||||||
|
|
@ -22,8 +23,8 @@ object EnvironmentAttribute {
|
||||||
(obj.Definition.DrownAtMaxDepth || obj.Definition.DisableAtMaxDepth) &&
|
(obj.Definition.DrownAtMaxDepth || obj.Definition.DisableAtMaxDepth) &&
|
||||||
canInteractWithPlayersAndVehicles(obj) &&
|
canInteractWithPlayersAndVehicles(obj) &&
|
||||||
(obj match {
|
(obj match {
|
||||||
case p: Player => p.VehicleSeated.isEmpty
|
case p: Player => p.VehicleSeated.isEmpty && p.WhichSide == Sidedness.OutsideOf
|
||||||
case v: Vehicle => v.MountedIn.isEmpty
|
case v: Vehicle => v.MountedIn.isEmpty && v.WhichSide == Sidedness.OutsideOf
|
||||||
case _ => false
|
case _ => false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ object Watery {
|
||||||
val oldTimeRemaining: Long = math.max(0, completionTime - System.currentTimeMillis())
|
val oldTimeRemaining: Long = math.max(0, completionTime - System.currentTimeMillis())
|
||||||
val oldTimeRatio: Float = oldTimeRemaining / oldDuration.toFloat
|
val oldTimeRatio: Float = oldTimeRemaining / oldDuration.toFloat
|
||||||
val percentage: Float = oldTimeRatio * 100
|
val percentage: Float = oldTimeRatio * 100
|
||||||
val recoveryTime: Long = newDuration * (1f - oldTimeRatio).toLong
|
val recoveryTime: Long = (newDuration * (1f - oldTimeRatio)).toLong
|
||||||
(true, recoveryTime, percentage)
|
(true, recoveryTime, percentage)
|
||||||
case Some(OxygenState.Recovery) =>
|
case Some(OxygenState.Recovery) =>
|
||||||
//interrupted while recovering, calculate the progress and keep recovering
|
//interrupted while recovering, calculate the progress and keep recovering
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
// Copyright (c) 2020 PSForever
|
// Copyright (c) 2020 PSForever
|
||||||
package net.psforever.objects.serverobject.hackable
|
package net.psforever.objects.serverobject.hackable
|
||||||
|
|
||||||
|
import net.psforever.actors.zone.BuildingActor
|
||||||
import net.psforever.objects.serverobject.structures.{Building, StructureType, WarpGate}
|
import net.psforever.objects.serverobject.structures.{Building, StructureType, WarpGate}
|
||||||
|
import net.psforever.objects.serverobject.terminals.Terminal
|
||||||
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
|
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
|
||||||
import net.psforever.objects.{Player, Vehicle}
|
import net.psforever.objects.{GlobalDefinitions, Player, Vehicle}
|
||||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||||
import net.psforever.packet.game.{HackMessage, HackState, HackState1, HackState7}
|
import net.psforever.packet.game.{GenericObjectActionMessage, HackMessage, HackState, HackState1, HackState7, TriggeredSound}
|
||||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID}
|
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID}
|
||||||
import net.psforever.services.Service
|
import net.psforever.services.Service
|
||||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
|
|
@ -140,6 +142,141 @@ object GenericHackables {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def FinishVirusAction(target: PlanetSideServerObject with Hackable, user: Player, hackValue: Int, hackClearValue: Int, virus: Long)(): Unit = {
|
||||||
|
import akka.pattern.ask
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
val tplayer = user
|
||||||
|
ask(target.Actor, CommonMessages.Hack(tplayer, target))(timeout = 2 second)
|
||||||
|
.mapTo[CommonMessages.EntityHackState]
|
||||||
|
.onComplete {
|
||||||
|
case Success(_) =>
|
||||||
|
val building = target.asInstanceOf[Terminal].Owner.asInstanceOf[Building]
|
||||||
|
val zone = target.Zone
|
||||||
|
val zoneId = zone.id
|
||||||
|
val pguid = tplayer.GUID
|
||||||
|
if (tplayer.Faction == target.Faction) {
|
||||||
|
//clear virus
|
||||||
|
val currVirus = building.virusId
|
||||||
|
building.virusId = 8
|
||||||
|
building.virusInstalledBy = None
|
||||||
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
zoneId,
|
||||||
|
LocalAction
|
||||||
|
.ClearTemporaryHack(pguid, target)
|
||||||
|
)
|
||||||
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
LocalAction.SendResponse(GenericObjectActionMessage(target.GUID, 60))
|
||||||
|
)
|
||||||
|
currVirus match {
|
||||||
|
case 0L =>
|
||||||
|
building.HackableAmenities.filter(d => d.Definition == GlobalDefinitions.lock_external).foreach { iff =>
|
||||||
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
zoneId,
|
||||||
|
LocalAction.ClearTemporaryHack(PlanetSideGUID(0), iff)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case 4L =>
|
||||||
|
building.HackableAmenities.filter(d => d.Definition == GlobalDefinitions.order_terminal).foreach { term =>
|
||||||
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
zoneId,
|
||||||
|
LocalAction.ClearTemporaryHack(PlanetSideGUID(0), term)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
|
building.Actor ! BuildingActor.MapUpdate()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//install virus
|
||||||
|
val currVirus = building.virusId
|
||||||
|
//clear previous virus unlocks to prevent virus stacking
|
||||||
|
currVirus match {
|
||||||
|
case 0L =>
|
||||||
|
if (virus != 0) {
|
||||||
|
building.HackableAmenities.filter(d => d.Definition == GlobalDefinitions.lock_external).foreach { iff =>
|
||||||
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
zoneId,
|
||||||
|
LocalAction.ClearTemporaryHack(PlanetSideGUID(0), iff)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 4L =>
|
||||||
|
if (virus != 4) {
|
||||||
|
building.HackableAmenities.filter(d => d.Definition == GlobalDefinitions.order_terminal).foreach { term =>
|
||||||
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
zoneId,
|
||||||
|
LocalAction.ClearTemporaryHack(PlanetSideGUID(0), term)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
|
val virusLength: Map[Long, Int] = Map(
|
||||||
|
0L -> 3600,
|
||||||
|
1L -> 900,
|
||||||
|
2L -> 3600,
|
||||||
|
3L -> 900,
|
||||||
|
4L -> 120
|
||||||
|
)
|
||||||
|
val installedVirusDuration = virusLength(virus)
|
||||||
|
val hackStateMap: Map[Long, HackState7] = Map(
|
||||||
|
0L -> HackState7.UnlockDoors,
|
||||||
|
1L -> HackState7.DisableLatticeBenefits,
|
||||||
|
2L -> HackState7.NTUDrain,
|
||||||
|
3L -> HackState7.DisableRadar,
|
||||||
|
4L -> HackState7.AccessEquipmentTerms
|
||||||
|
)
|
||||||
|
val hackState = hackStateMap.getOrElse(virus, HackState7.Unk8)
|
||||||
|
building.virusId = virus
|
||||||
|
building.virusInstalledBy = Some(tplayer.Faction.id)
|
||||||
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
zoneId,
|
||||||
|
LocalAction.TriggerSound(pguid, TriggeredSound.TREKSuccessful, tplayer.Position, 30, 0.49803925f)
|
||||||
|
)
|
||||||
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
zoneId,
|
||||||
|
LocalAction
|
||||||
|
.HackTemporarily(pguid, zone, target, installedVirusDuration, hackClearValue, installedVirusDuration, unk2=hackState)
|
||||||
|
)
|
||||||
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
LocalAction.SendResponse(GenericObjectActionMessage(target.GUID, 61))
|
||||||
|
)
|
||||||
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
LocalAction.SendResponse(GenericObjectActionMessage(target.GUID, 58))
|
||||||
|
)
|
||||||
|
//amenities if applicable
|
||||||
|
virus match {
|
||||||
|
case 0L =>
|
||||||
|
building.HackableAmenities.filter(d => d.Definition == GlobalDefinitions.lock_external).foreach{ iff =>
|
||||||
|
var setHacked = iff.asInstanceOf[PlanetSideServerObject with Hackable]
|
||||||
|
setHacked.HackedBy = tplayer
|
||||||
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
zoneId,
|
||||||
|
LocalAction.HackTemporarily(pguid, zone, iff, hackValue, hackClearValue, installedVirusDuration)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case 4L =>
|
||||||
|
building.HackableAmenities.filter(d => d.Definition == GlobalDefinitions.order_terminal).foreach{ term =>
|
||||||
|
var setHacked = term.asInstanceOf[PlanetSideServerObject with Hackable]
|
||||||
|
setHacked.HackedBy = tplayer
|
||||||
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
zoneId,
|
||||||
|
LocalAction.HackTemporarily(pguid, zone, term, hackValue, hackClearValue, installedVirusDuration)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
|
building.Actor ! BuildingActor.MapUpdate()
|
||||||
|
}
|
||||||
|
case Failure(_) =>
|
||||||
|
log.warn(s"Virus action failed on target: ${target.Definition.Name}@${target.GUID.guid}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the state of connected facilities has changed since the hack progress began. It accounts for a friendly facility
|
* Check if the state of connected facilities has changed since the hack progress began. It accounts for a friendly facility
|
||||||
* on the other side of a warpgate as well in case there are no friendly facilities in the same zone
|
* on the other side of a warpgate as well in case there are no friendly facilities in the same zone
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import net.psforever.objects.zones.Zone
|
||||||
import net.psforever.services.Service
|
import net.psforever.services.Service
|
||||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||||
import net.psforever.types.Vector3
|
import net.psforever.types.Vector3
|
||||||
|
import net.psforever.zones.Zones
|
||||||
|
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
@ -44,6 +45,12 @@ class VehicleSpawnControlLoadVehicle(pad: VehicleSpawnPad) extends VehicleSpawnC
|
||||||
) //appear below the trench and doors
|
) //appear below the trench and doors
|
||||||
vehicle.WhichSide = pad.WhichSide
|
vehicle.WhichSide = pad.WhichSide
|
||||||
vehicle.Cloaked = vehicle.Definition.CanCloak && driver.Cloaked
|
vehicle.Cloaked = vehicle.Definition.CanCloak && driver.Cloaked
|
||||||
|
// increase MaxHealth by 10% if driver has Cyssor empire armor benefit
|
||||||
|
if (Zones.zones.find(_.Number == 3).exists(_.benefitRecipient == driver.Faction)) {
|
||||||
|
val boosted = Math.round(vehicle.MaxHealth * 1.1).toInt
|
||||||
|
vehicle.MaxHealth = boosted
|
||||||
|
vehicle.Health = boosted
|
||||||
|
}
|
||||||
|
|
||||||
temp = Some(order)
|
temp = Some(order)
|
||||||
val result = ask(pad.Zone.Transport, Zone.Vehicle.Spawn(vehicle))
|
val result = ask(pad.Zone.Transport, Zone.Vehicle.Spawn(vehicle))
|
||||||
|
|
|
||||||
|
|
@ -270,20 +270,19 @@ trait AmenityAutoRepair
|
||||||
autoRepairTimer.cancel()
|
autoRepairTimer.cancel()
|
||||||
autoRepairQueueTask = Some(System.currentTimeMillis() + delay)
|
autoRepairQueueTask = Some(System.currentTimeMillis() + delay)
|
||||||
val modifiedDrain = drain * Config.app.game.amenityAutorepairDrainRate //doubled intentionally
|
val modifiedDrain = drain * Config.app.game.amenityAutorepairDrainRate //doubled intentionally
|
||||||
autoRepairTimer = if(AutoRepairObject.Owner == Building.NoBuilding) {
|
AutoRepairObject.Owner match {
|
||||||
//without an owner, auto-repair freely
|
case Building.NoBuilding =>
|
||||||
context.system.scheduler.scheduleOnce(
|
autoRepairTimer = context.system.scheduler.scheduleOnce(
|
||||||
delay milliseconds,
|
delay.milliseconds,
|
||||||
self,
|
self,
|
||||||
NtuCommand.Grant(null, modifiedDrain)
|
NtuCommand.Grant(null, modifiedDrain))
|
||||||
)
|
case b: Building =>
|
||||||
} else {
|
val doubledDrain = if (b.virusId == 2) modifiedDrain * 2 else modifiedDrain
|
||||||
//ask politely
|
autoRepairTimer = context.system.scheduler.scheduleOnce(
|
||||||
context.system.scheduler.scheduleOnce(
|
delay.milliseconds,
|
||||||
delay milliseconds,
|
b.Actor,
|
||||||
AutoRepairObject.Owner.Actor,
|
BuildingActor.Ntu(NtuCommand.Request(doubledDrain, ntuGrantActorRef)))
|
||||||
BuildingActor.Ntu(NtuCommand.Request(modifiedDrain, ntuGrantActorRef))
|
case _ => ()
|
||||||
)
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ trait RepairableEntity extends Repairable {
|
||||||
*/
|
*/
|
||||||
protected def CanPerformRepairs(target: Repairable.Target, player: Player, item: Tool): Boolean = {
|
protected def CanPerformRepairs(target: Repairable.Target, player: Player, item: Tool): Boolean = {
|
||||||
val definition = target.Definition
|
val definition = target.Definition
|
||||||
definition.Repairable && target.Health < definition.MaxHealth && (definition.RepairIfDestroyed || !target.Destroyed) &&
|
definition.Repairable && target.Health < target.MaxHealth && (definition.RepairIfDestroyed || !target.Destroyed) &&
|
||||||
(target.Faction == player.Faction || target.Faction == PlanetSideEmpire.NEUTRAL) && item.Magazine > 0 &&
|
(target.Faction == player.Faction || target.Faction == PlanetSideEmpire.NEUTRAL) && item.Magazine > 0 &&
|
||||||
player.isAlive && Vector3.Distance(target.Position, player.Position) < definition.RepairDistance
|
player.isAlive && Vector3.Distance(target.Position, player.Position) < definition.RepairDistance
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
|
||||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
import net.psforever.objects.zones.blockmap.BlockMapEntity
|
import net.psforever.objects.zones.blockmap.BlockMapEntity
|
||||||
import net.psforever.packet.game.{BuildingInfoUpdateMessage, DensityLevelUpdateMessage}
|
import net.psforever.packet.game.{Additional3, BuildingInfoUpdateMessage, DensityLevelUpdateMessage}
|
||||||
import net.psforever.types._
|
import net.psforever.types._
|
||||||
import scalax.collection.{Graph, GraphEdge}
|
import scalax.collection.{Graph, GraphEdge}
|
||||||
import akka.actor.typed.scaladsl.adapter._
|
import akka.actor.typed.scaladsl.adapter._
|
||||||
|
|
@ -34,6 +34,9 @@ class Building(
|
||||||
private var playersInSOI: List[Player] = List.empty
|
private var playersInSOI: List[Player] = List.empty
|
||||||
private var forceDomeActive: Boolean = false
|
private var forceDomeActive: Boolean = false
|
||||||
private var participationFunc: ParticipationLogic = NoParticipation
|
private var participationFunc: ParticipationLogic = NoParticipation
|
||||||
|
var virusId: Long = 8 // 8 default = no virus
|
||||||
|
var virusInstalledBy: Option[Int] = None // faction id
|
||||||
|
var hasCavernLockBenefit: Boolean = false
|
||||||
super.Zone_=(zone)
|
super.Zone_=(zone)
|
||||||
super.GUID_=(PlanetSideGUID(building_guid)) //set
|
super.GUID_=(PlanetSideGUID(building_guid)) //set
|
||||||
Invalidate() //unset; guid can be used during setup, but does not stop being registered properly later
|
Invalidate() //unset; guid can be used during setup, but does not stop being registered properly later
|
||||||
|
|
@ -204,13 +207,22 @@ class Building(
|
||||||
}
|
}
|
||||||
val cavernBenefit: Set[CavernBenefit] = if (
|
val cavernBenefit: Set[CavernBenefit] = if (
|
||||||
generatorState != PlanetSideGeneratorState.Destroyed &&
|
generatorState != PlanetSideGeneratorState.Destroyed &&
|
||||||
faction != PlanetSideEmpire.NEUTRAL &&
|
faction != PlanetSideEmpire.NEUTRAL && !CaptureTerminalIsHacked &&
|
||||||
connectedCavern().nonEmpty
|
connectedCavern().exists(_.Zone.lockedBy == faction)
|
||||||
) {
|
) {
|
||||||
Set(CavernBenefit.VehicleModule, CavernBenefit.EquipmentModule)
|
hasCavernLockBenefit = true
|
||||||
|
Set(CavernBenefit.VehicleModule, CavernBenefit.EquipmentModule, CavernBenefit.ShieldModule,
|
||||||
|
CavernBenefit.SpeedModule, CavernBenefit.HealthModule, CavernBenefit.PainModule)
|
||||||
} else {
|
} else {
|
||||||
|
hasCavernLockBenefit = false
|
||||||
Set(CavernBenefit.None)
|
Set(CavernBenefit.None)
|
||||||
}
|
}
|
||||||
|
val (installedVirus, installedByFac) = if (virusId == 8) {
|
||||||
|
(8, None)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
(virusId.toInt, Some(Additional3(inform_defenders=true, virusInstalledBy.getOrElse(3))))
|
||||||
|
}
|
||||||
|
|
||||||
BuildingInfoUpdateMessage(
|
BuildingInfoUpdateMessage(
|
||||||
Zone.Number,
|
Zone.Number,
|
||||||
|
|
@ -230,8 +242,8 @@ class Building(
|
||||||
unk4 = Nil,
|
unk4 = Nil,
|
||||||
unk5 = 0,
|
unk5 = 0,
|
||||||
unk6 = false,
|
unk6 = false,
|
||||||
unk7 = 8, // unk7 != 8 will cause malformed packet
|
installedVirus,
|
||||||
unk7x = None,
|
installedByFac,
|
||||||
boostSpawnPain,
|
boostSpawnPain,
|
||||||
boostGeneratorPain
|
boostGeneratorPain
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ trait FacilityHackParticipation extends ParticipationLogic {
|
||||||
.filterNot { p =>
|
.filterNot { p =>
|
||||||
playerContribution.exists { case (u, _) => p.CharId == u }
|
playerContribution.exists { case (u, _) => p.CharId == u }
|
||||||
}
|
}
|
||||||
|
informOfInstalledVirus(newParticipants)
|
||||||
playerContribution =
|
playerContribution =
|
||||||
vanguardParticipants.map { case (u, (p, d, _)) => (u, (p, d + 1, curr)) } ++
|
vanguardParticipants.map { case (u, (p, d, _)) => (u, (p, d + 1, curr)) } ++
|
||||||
newParticipants.map { p => (p.CharId, (p, 1, curr)) } ++
|
newParticipants.map { p => (p.CharId, (p, 1, curr)) } ++
|
||||||
|
|
@ -96,6 +97,27 @@ trait FacilityHackParticipation extends ParticipationLogic {
|
||||||
}) :+ newEntry
|
}) :+ newEntry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* send packet that makes building lights green in case a virus was installed before this player got there
|
||||||
|
* @param list new players to the SOI
|
||||||
|
*/
|
||||||
|
protected def informOfInstalledVirus(list : List[Player]): Unit = {
|
||||||
|
if (building.virusId != 8) {
|
||||||
|
import net.psforever.objects.serverobject.terminals.Terminal
|
||||||
|
import net.psforever.objects.GlobalDefinitions
|
||||||
|
import net.psforever.services.Service
|
||||||
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
|
val mainTerm = building.Amenities.filter(x => x.isInstanceOf[Terminal] && x.Definition == GlobalDefinitions.main_terminal).head.GUID
|
||||||
|
val msg1 = AvatarAction.GenericObjectAction(Service.defaultPlayerGUID, mainTerm, 61)
|
||||||
|
val msg2 = AvatarAction.GenericObjectAction(Service.defaultPlayerGUID, mainTerm, 58)
|
||||||
|
val events = building.Zone.AvatarEvents
|
||||||
|
list.foreach { p =>
|
||||||
|
events ! AvatarServiceMessage(p.Name, msg1)
|
||||||
|
events ! AvatarServiceMessage(p.Name, msg2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object FacilityHackParticipation {
|
object FacilityHackParticipation {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright (c) 2025 PSForever
|
||||||
|
package net.psforever.objects.serverobject.terminals
|
||||||
|
|
||||||
|
import akka.actor.ActorContext
|
||||||
|
import net.psforever.objects.{Default, Player}
|
||||||
|
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||||
|
import net.psforever.objects.serverobject.structures.Amenity
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The definition for any `Terminal` that is of a type "main_terminal".
|
||||||
|
* Main terminal objects are used to upload or remove a virus from a major facility
|
||||||
|
* @param objectId the object's identifier number
|
||||||
|
*/
|
||||||
|
class MainTerminalDefinition(objectId: Int) extends TerminalDefinition(objectId) {
|
||||||
|
def Request(player: Player, msg: Any): Terminal.Exchange = Terminal.NoDeal()
|
||||||
|
}
|
||||||
|
|
@ -247,6 +247,9 @@ object ProximityTerminalControl {
|
||||||
target: PlanetSideGameObject
|
target: PlanetSideGameObject
|
||||||
): Boolean = {
|
): Boolean = {
|
||||||
(terminal.Definition, target) match {
|
(terminal.Definition, target) match {
|
||||||
|
case (_: MedicalTerminalDefinition, p: Player)
|
||||||
|
if terminal.Definition ==
|
||||||
|
GlobalDefinitions.medical_terminal_healing_module => HealthModule(terminal, p)
|
||||||
case (_: MedicalTerminalDefinition, p: Player) => HealthAndArmorTerminal(terminal, p)
|
case (_: MedicalTerminalDefinition, p: Player) => HealthAndArmorTerminal(terminal, p)
|
||||||
case (_: WeaponRechargeTerminalDefinition, p: Player) => WeaponRechargeTerminal(terminal, p)
|
case (_: WeaponRechargeTerminalDefinition, p: Player) => WeaponRechargeTerminal(terminal, p)
|
||||||
case (_: MedicalTerminalDefinition, v: Vehicle) => VehicleRepairTerminal(terminal, v)
|
case (_: MedicalTerminalDefinition, v: Vehicle) => VehicleRepairTerminal(terminal, v)
|
||||||
|
|
@ -269,6 +272,16 @@ object ProximityTerminalControl {
|
||||||
fullHeal && fullRepair
|
fullHeal && fullRepair
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activated by a facility having a linked cavern lock or health module installed. Friendly players
|
||||||
|
* within the SOI receive constant healing as requested by the client
|
||||||
|
*/
|
||||||
|
def HealthModule(unit: Terminal with ProximityUnit, target: Player): Boolean = {
|
||||||
|
val medDef = unit.Definition.asInstanceOf[MedicalTerminalDefinition]
|
||||||
|
val fullHeal = HealthModuleAction(unit, target, medDef.HealAmount, PlayerHealthCallback)
|
||||||
|
fullHeal
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When driving a vehicle close to a rearm/repair silo,
|
* When driving a vehicle close to a rearm/repair silo,
|
||||||
* restore the vehicle's health points.
|
* restore the vehicle's health points.
|
||||||
|
|
@ -318,6 +331,35 @@ object ProximityTerminalControl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heals players and increases their health/max health up to 120 if they enter the SOI this benefit is active in.
|
||||||
|
*/
|
||||||
|
def HealthModuleAction(
|
||||||
|
terminal: Terminal,
|
||||||
|
target: PlanetSideGameObject with Vitality with ZoneAware,
|
||||||
|
healAmount: Int,
|
||||||
|
updateFunc: PlanetSideGameObject with Vitality with ZoneAware => Unit
|
||||||
|
): Boolean = {
|
||||||
|
val maxHealthCap = 120
|
||||||
|
val zone = target.Zone
|
||||||
|
val oldMax = target.MaxHealth
|
||||||
|
val newMax = math.min(oldMax + healAmount, maxHealthCap)
|
||||||
|
|
||||||
|
if (oldMax < maxHealthCap) {
|
||||||
|
target.MaxHealth = newMax
|
||||||
|
zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
AvatarAction.PlanetsideAttributeToAll(target.GUID, 1, newMax)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (target.Health < newMax) {
|
||||||
|
target.Health = math.min(target.Health + healAmount, newMax)
|
||||||
|
target.LogActivity(HealFromTerminal(AmenitySource(terminal), 1))
|
||||||
|
updateFunc(target)
|
||||||
|
}
|
||||||
|
target.Health == newMax
|
||||||
|
}
|
||||||
|
|
||||||
def PlayerHealthCallback(target: PlanetSideGameObject with Vitality with ZoneAware): Unit = {
|
def PlayerHealthCallback(target: PlanetSideGameObject with Vitality with ZoneAware): Unit = {
|
||||||
val zone = target.Zone
|
val zone = target.Zone
|
||||||
zone.AvatarEvents ! AvatarServiceMessage(
|
zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
package net.psforever.objects.serverobject.terminals
|
package net.psforever.objects.serverobject.terminals
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
import net.psforever.objects.{GlobalDefinitions, SimpleItem}
|
import net.psforever.objects.{GlobalDefinitions, SimpleItem, Tool}
|
||||||
import net.psforever.objects.serverobject.CommonMessages
|
import net.psforever.objects.serverobject.CommonMessages
|
||||||
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
|
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
|
||||||
import net.psforever.objects.serverobject.damage.Damageable.Target
|
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||||
|
|
@ -57,7 +57,28 @@ class TerminalControl(term: Terminal)
|
||||||
)
|
)
|
||||||
case _ => ()
|
case _ => ()
|
||||||
}
|
}
|
||||||
|
case CommonMessages.UploadVirus(player, Some(item: Tool), virus)
|
||||||
|
if item.Definition == GlobalDefinitions.trek =>
|
||||||
|
term.Owner match {
|
||||||
|
case _: Building =>
|
||||||
|
sender() ! CommonMessages.Progress(
|
||||||
|
1.66f,
|
||||||
|
GenericHackables.FinishVirusAction(term, player, hackValue = -1, hackClearValue = -1, virus),
|
||||||
|
GenericHackables.HackingTickAction(HackState1.Unk1, player, term, item.GUID)
|
||||||
|
)
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
|
case CommonMessages.RemoveVirus(player, Some(item: SimpleItem))
|
||||||
|
if item.Definition == GlobalDefinitions.remote_electronics_kit =>
|
||||||
|
term.Owner match {
|
||||||
|
case _: Building =>
|
||||||
|
sender() ! CommonMessages.Progress(
|
||||||
|
1.66f,
|
||||||
|
GenericHackables.FinishVirusAction(term, player, hackValue = -1, hackClearValue = -1, virus=8L),
|
||||||
|
GenericHackables.HackingTickAction(HackState1.Unk1, player, term, item.GUID)
|
||||||
|
)
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
case _ => ()
|
case _ => ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,5 +99,6 @@ object CollisionReason {
|
||||||
* Damage is considered `Direct`, however, which defines some resistance. */
|
* Damage is considered `Direct`, however, which defines some resistance. */
|
||||||
val noDamage = new DamageProperties {
|
val noDamage = new DamageProperties {
|
||||||
CausesDamageType = DamageType.Direct
|
CausesDamageType = DamageType.Direct
|
||||||
|
DamageToArmorFirst = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
// Copyright (c) 2020 PSForever
|
// Copyright (c) 2020 PSForever
|
||||||
package net.psforever.objects.vital.etc
|
package net.psforever.objects.vital.etc
|
||||||
|
|
||||||
|
import net.psforever.objects.Vehicle
|
||||||
import net.psforever.objects.serverobject.painbox.Painbox
|
import net.psforever.objects.serverobject.painbox.Painbox
|
||||||
|
import net.psforever.objects.serverobject.structures.Building
|
||||||
import net.psforever.objects.sourcing.SourceEntry
|
import net.psforever.objects.sourcing.SourceEntry
|
||||||
import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions}
|
import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions}
|
||||||
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
|
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
|
||||||
|
|
@ -13,7 +15,18 @@ final case class PainboxReason(entity: Painbox) extends DamageReason {
|
||||||
private val definition = entity.Definition
|
private val definition = entity.Definition
|
||||||
assert(definition.innateDamage.nonEmpty, s"causal entity '${definition.Name}' does not emit pain field")
|
assert(definition.innateDamage.nonEmpty, s"causal entity '${definition.Name}' does not emit pain field")
|
||||||
|
|
||||||
def source: DamageWithPosition = definition.innateDamage.get
|
def source: DamageWithPosition = {
|
||||||
|
val base = definition.innateDamage.get
|
||||||
|
entity.Owner match {
|
||||||
|
case b: Building if b.hasCavernLockBenefit =>
|
||||||
|
new DamageWithPosition {
|
||||||
|
Damage0 = 5
|
||||||
|
DamageRadius = 0
|
||||||
|
DamageToHealthOnly = true
|
||||||
|
}
|
||||||
|
case _ => base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def resolution: DamageResolution.Value = DamageResolution.Resolved
|
def resolution: DamageResolution.Value = DamageResolution.Resolved
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,11 @@ trait DamageProperties
|
||||||
private var damageTypeSecondary: DamageType.Value = DamageType.None
|
private var damageTypeSecondary: DamageType.Value = DamageType.None
|
||||||
/** against Infantry targets, damage does not apply to armor damage */
|
/** against Infantry targets, damage does not apply to armor damage */
|
||||||
private var damageToHealthOnly: Boolean = false
|
private var damageToHealthOnly: Boolean = false
|
||||||
/** against Vehicle targets, damage does not apply to vehicle shield */
|
/** against Infantry targets, damage does not apply to armor damage */
|
||||||
|
private var damageToArmorFirst: Boolean = false
|
||||||
|
/** against Infantry targets, damage applies to armor before it does health;
|
||||||
|
* regardless of other resistance conditions, non-zero armor is reduced before health;
|
||||||
|
* should not have priority over the flag for infantry health only */
|
||||||
private var damageToVehicleOnly: Boolean = false
|
private var damageToVehicleOnly: Boolean = false
|
||||||
/** against battleframe targets, damage does not apply to battleframe robotics shield;
|
/** against battleframe targets, damage does not apply to battleframe robotics shield;
|
||||||
* this is equivalent to the property "bfr_permeate_shield" */
|
* this is equivalent to the property "bfr_permeate_shield" */
|
||||||
|
|
@ -84,6 +88,13 @@ trait DamageProperties
|
||||||
DamageToHealthOnly
|
DamageToHealthOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def DamageToArmorFirst : Boolean = damageToArmorFirst
|
||||||
|
|
||||||
|
def DamageToArmorFirst_=(armorFirst: Boolean) : Boolean = {
|
||||||
|
damageToArmorFirst = armorFirst
|
||||||
|
DamageToArmorFirst
|
||||||
|
}
|
||||||
|
|
||||||
def DamageToVehicleOnly : Boolean = damageToVehicleOnly
|
def DamageToVehicleOnly : Boolean = damageToVehicleOnly
|
||||||
|
|
||||||
def DamageToVehicleOnly_=(vehicleOnly: Boolean) : Boolean = {
|
def DamageToVehicleOnly_=(vehicleOnly: Boolean) : Boolean = {
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,15 @@ import net.psforever.objects.serverobject.damage.Damageable
|
||||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
||||||
import net.psforever.objects.vehicles.VehicleSubsystemEntry
|
import net.psforever.objects.vehicles.VehicleSubsystemEntry
|
||||||
import net.psforever.objects.vital.base.DamageResolution
|
import net.psforever.objects.vital.base.DamageResolution
|
||||||
import net.psforever.objects.vital.{DamagingActivity, Vitality, InGameHistory}
|
import net.psforever.objects.vital.{DamagingActivity, InGameHistory, Vitality}
|
||||||
import net.psforever.objects.vital.damage.DamageCalculations
|
import net.psforever.objects.vital.damage.DamageCalculations
|
||||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||||
import net.psforever.objects.vital.projectile.ProjectileReason
|
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||||
import net.psforever.objects.vital.resistance.ResistanceSelection
|
import net.psforever.objects.vital.resistance.ResistanceSelection
|
||||||
import net.psforever.types.{ExoSuitType, ImplantType}
|
import net.psforever.types.{ExoSuitType, ImplantType}
|
||||||
|
|
||||||
|
import scala.annotation.unused
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base for the combining step of all projectile-induced damage calculation function literals.
|
* The base for the combining step of all projectile-induced damage calculation function literals.
|
||||||
*/
|
*/
|
||||||
|
|
@ -37,13 +39,16 @@ object ResolutionCalculations {
|
||||||
type Output = PlanetSideGameObject with FactionAffinity => DamageResult
|
type Output = PlanetSideGameObject with FactionAffinity => DamageResult
|
||||||
type Form = (DamageCalculations.Selector, ResistanceSelection.Format, DamageInteraction) => Output
|
type Form = (DamageCalculations.Selector, ResistanceSelection.Format, DamageInteraction) => Output
|
||||||
|
|
||||||
def NoDamage(data: DamageInteraction)(a: Int, b: Int): Int = 0
|
|
||||||
|
def NoDamage(@unused data: DamageInteraction)(@unused a: Int, @unused b: Int): Int = 0
|
||||||
|
|
||||||
def InfantryDamage(data: DamageInteraction): (Int, Int) => (Int, Int) = {
|
def InfantryDamage(data: DamageInteraction): (Int, Int) => (Int, Int) = {
|
||||||
data.target match {
|
data.target match {
|
||||||
case target: PlayerSource =>
|
case target: PlayerSource =>
|
||||||
if(data.cause.source.DamageToHealthOnly) {
|
if (data.cause.source.DamageToHealthOnly) {
|
||||||
DamageToHealthOnly(target.health)
|
DamageToHealthOnly(target.health)
|
||||||
|
} else if (data.cause.source.DamageToArmorFirst) {
|
||||||
|
InfantryArmorDamageFirst(target.health, target.armor)
|
||||||
} else {
|
} else {
|
||||||
InfantryDamageAfterResist(target.health, target.armor)
|
InfantryDamageAfterResist(target.health, target.armor)
|
||||||
}
|
}
|
||||||
|
|
@ -84,6 +89,25 @@ object ResolutionCalculations {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def InfantryArmorDamageFirst(currentHP: Int, currentArmor: Int)(damages: Int, resistance: Int): (Int, Int) = {
|
||||||
|
if (damages > 0 && currentHP > 0) {
|
||||||
|
if (currentArmor <= 0) {
|
||||||
|
(damages, 0) //no armor; health damage
|
||||||
|
} else if (damages > resistance) {
|
||||||
|
val resistedDam = damages - resistance
|
||||||
|
if (resistedDam <= currentArmor) {
|
||||||
|
(0, resistedDam) //armor damage
|
||||||
|
} else {
|
||||||
|
(resistedDam, currentArmor) //deplete armor; health damage
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(0, damages) //too weak; armor damage (less than resistance)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(0, 0) //no damage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def MaxDamage(data: DamageInteraction): (Int, Int) => (Int, Int) = {
|
def MaxDamage(data: DamageInteraction): (Int, Int) => (Int, Int) = {
|
||||||
data.target match {
|
data.target match {
|
||||||
case target: PlayerSource =>
|
case target: PlayerSource =>
|
||||||
|
|
@ -131,7 +155,7 @@ object ResolutionCalculations {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def NoApplication(damageValue: Int, data: DamageInteraction)(target: PlanetSideGameObject with FactionAffinity): DamageResult = {
|
def NoApplication(@unused damageValue: Int, data: DamageInteraction)(target: PlanetSideGameObject with FactionAffinity): DamageResult = {
|
||||||
val sameTarget = SourceEntry(target)
|
val sameTarget = SourceEntry(target)
|
||||||
DamageResult(sameTarget, sameTarget, data)
|
DamageResult(sameTarget, sameTarget, data)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,34 +51,16 @@ case object MapInfo extends StringEnum[MapInfo] {
|
||||||
scale = MapScale.Dim8192,
|
scale = MapScale.Dim8192,
|
||||||
hotSpotSpan = 80,
|
hotSpotSpan = 80,
|
||||||
environment = {
|
environment = {
|
||||||
//exclude parts of voltan and naum due to their generator rooms being below sealevel
|
List(SeaLevel(EnvironmentAttribute.Water, 11)) ++
|
||||||
val northVoltan = 3562.4844f
|
MapEnvironment.zoneMapEdgeKillPlane(
|
||||||
val southVoltan = 3401.6875f
|
MapScale.Dim8192,
|
||||||
val eastVoltan = 4556.703f
|
(400, 400, 200, 400),
|
||||||
val westVoltan = 4411.6875f
|
List(
|
||||||
val northNaum = 3575.8047f
|
(450, 450, 250, 450, 3),
|
||||||
val southNaum = 3539.5234f
|
(500, 500, 300, 500, 2),
|
||||||
val eastNaum = 5490.6875f
|
(600, 600, 400, 600, 1)
|
||||||
val westNaum = 5427.078f
|
)
|
||||||
List(
|
|
||||||
Pool(EnvironmentAttribute.Water, 11, 8192, westVoltan, 0, 0), //west of voltan
|
|
||||||
Pool(EnvironmentAttribute.Water, 11, 8192, westNaum, 0, eastVoltan), //between voltan and naum
|
|
||||||
Pool(EnvironmentAttribute.Water, 11, 8192, 8192, 0, eastNaum), //east of naum
|
|
||||||
Pool(EnvironmentAttribute.Water, 11, 8192, eastVoltan, northVoltan, westVoltan), //north of voltan
|
|
||||||
Pool(EnvironmentAttribute.Water, 11, southVoltan, eastVoltan, 0, westVoltan), //south of voltan
|
|
||||||
Pool(EnvironmentAttribute.Water, 11, 8192, eastNaum, northNaum, westNaum), //north of naum
|
|
||||||
Pool(EnvironmentAttribute.Water, 11, southNaum, eastNaum, 0, westNaum) //south of naum
|
|
||||||
//TODO voltan Killplane
|
|
||||||
//TODO naum Killplane
|
|
||||||
) ++ MapEnvironment.zoneMapEdgeKillPlane(
|
|
||||||
MapScale.Dim8192,
|
|
||||||
(400, 400, 200, 400),
|
|
||||||
List(
|
|
||||||
(450, 450, 250, 450, 3),
|
|
||||||
(500, 500, 300, 500, 2),
|
|
||||||
(600, 600, 400, 600, 1)
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -185,8 +167,7 @@ case object MapInfo extends StringEnum[MapInfo] {
|
||||||
hotSpotSpan = 80,
|
hotSpotSpan = 80,
|
||||||
environment = List(
|
environment = List(
|
||||||
SeaLevel(EnvironmentAttribute.Water, 10.03125f),
|
SeaLevel(EnvironmentAttribute.Water, 10.03125f),
|
||||||
Pool(EnvironmentAttribute.Water, 213.03125f, 3116.7266f, 4724.414f, 2685.8281f, 4363.461f), //east side of southwest of tootega
|
Pool(EnvironmentAttribute.Water, 213.03125f, 3116.7266f, 4724.414f, 2685.8281f, 4187.4375f) //southwest of tootega
|
||||||
Pool(EnvironmentAttribute.Water, 213.03125f, 2994.2969f, 4363.461f, 2685.8281f, 4187.4375f), //west side of southwest of tootega
|
|
||||||
) ++ MapEnvironment.zoneMapEdgeKillPlane(
|
) ++ MapEnvironment.zoneMapEdgeKillPlane(
|
||||||
MapScale.Dim8192,
|
MapScale.Dim8192,
|
||||||
(400, 400, 400, 400),
|
(400, 400, 400, 400),
|
||||||
|
|
@ -265,15 +246,16 @@ case object MapInfo extends StringEnum[MapInfo] {
|
||||||
checksum = 230810349L,
|
checksum = 230810349L,
|
||||||
scale = MapScale.Dim8192,
|
scale = MapScale.Dim8192,
|
||||||
hotSpotSpan = 80,
|
hotSpotSpan = 80,
|
||||||
environment = List(SeaLevel(EnvironmentAttribute.Water, 28)) ++ MapEnvironment.zoneMapEdgeKillPlane(
|
environment = List(SeaLevel(EnvironmentAttribute.Water, 28)) ++
|
||||||
MapScale.Dim8192,
|
MapEnvironment.zoneMapEdgeKillPlane(
|
||||||
(200, 200, 200, 200),
|
MapScale.Dim8192,
|
||||||
List(
|
(200, 200, 200, 200),
|
||||||
(250, 250, 250, 250, 3),
|
List(
|
||||||
(300, 300, 300, 300, 2),
|
(250, 250, 250, 250, 3),
|
||||||
(400, 400, 400, 400, 1)
|
(300, 300, 300, 300, 2),
|
||||||
|
(400, 400, 400, 400, 1)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
case object Map11
|
case object Map11
|
||||||
|
|
@ -370,13 +352,13 @@ case object MapInfo extends StringEnum[MapInfo] {
|
||||||
scale = MapScale.Dim2560,
|
scale = MapScale.Dim2560,
|
||||||
hotSpotSpan = 80,
|
hotSpotSpan = 80,
|
||||||
environment = List(
|
environment = List(
|
||||||
|
SeaLevel(EnvironmentAttribute.Death, 10),
|
||||||
Pool(EnvironmentAttribute.Water, 194.89062f, 1763.4141f, 1415.125f, 1333.9531f, 1280.4609f), //east, northern pool
|
Pool(EnvironmentAttribute.Water, 194.89062f, 1763.4141f, 1415.125f, 1333.9531f, 1280.4609f), //east, northern pool
|
||||||
Pool(EnvironmentAttribute.Water, 192.40625f, 1717.5703f, 1219.3359f, 1572.8828f, 1036.1328f), //bottom, northern pool
|
Pool(EnvironmentAttribute.Water, 192.40625f, 1717.5703f, 1219.3359f, 1572.8828f, 1036.1328f), //bottom, northern pool
|
||||||
Pool(EnvironmentAttribute.Water, 192.32812f, 1966.1562f, 1252.7344f, 1889.8047f, 1148.5312f), //top, northern pool
|
Pool(EnvironmentAttribute.Water, 192.32812f, 1966.1562f, 1252.7344f, 1889.8047f, 1148.5312f), //top, northern pool
|
||||||
Pool(EnvironmentAttribute.Water, 191.65625f, 1869.1484f, 1195.6406f, 1743.8125f, 1050.7344f), //middle, northern pool
|
Pool(EnvironmentAttribute.Water, 191.65625f, 1869.1484f, 1195.6406f, 1743.8125f, 1050.7344f), //middle, northern pool
|
||||||
Pool(EnvironmentAttribute.Water, 183.98438f, 914.33594f, 1369.5f, 626.03906f, 666.3047f), //upper southern pools
|
Pool(EnvironmentAttribute.Water, 183.98438f, 914.33594f, 1369.5f, 626.03906f, 666.3047f), //upper southern pools
|
||||||
Pool(EnvironmentAttribute.Water, 182.96875f, 580.7578f, 913.52344f, 520.4531f, 843.97656f), //lowest southern pool
|
Pool(EnvironmentAttribute.Water, 182.96875f, 580.7578f, 913.52344f, 520.4531f, 843.97656f) //lowest southern pool
|
||||||
SeaLevel(EnvironmentAttribute.Death, 10)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -449,14 +431,8 @@ case object MapInfo extends StringEnum[MapInfo] {
|
||||||
checksum = 3654267088L,
|
checksum = 3654267088L,
|
||||||
scale = MapScale.Dim4096,
|
scale = MapScale.Dim4096,
|
||||||
hotSpotSpan = 80,
|
hotSpotSpan = 80,
|
||||||
environment = List(
|
environment = List(SeaLevel(EnvironmentAttribute.Water, 3.5f)) ++
|
||||||
Pool(EnvironmentAttribute.Water, 3.5f, 2867f, 1228f, 1128f, 0f), //west
|
MapEnvironment.dim4096MapEdgeKillPlanes
|
||||||
Pool(EnvironmentAttribute.Water, 3.5f, 4096f, 4096f, 2867f, 0f), //north
|
|
||||||
Pool(EnvironmentAttribute.Water, 3.5f, 2867f, 4096f, 1227f, 2900f), //east
|
|
||||||
Pool(EnvironmentAttribute.Water, 3.5f, 1227f, 4096f, 0f, 2000f), //southeast
|
|
||||||
Pool(EnvironmentAttribute.Water, 3.5f, 1128f, 2000f, 0f, 0f), //southwest
|
|
||||||
Pool(EnvironmentAttribute.Death, 0.5f, 2867f, 2900f, 1128f, 1228f), //central, kill
|
|
||||||
) ++ MapEnvironment.dim4096MapEdgeKillPlanes
|
|
||||||
)
|
)
|
||||||
|
|
||||||
case object Map99
|
case object Map99
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,9 @@ import scalax.collection.GraphEdge._
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
import akka.actor.typed
|
import akka.actor.typed
|
||||||
import net.psforever.actors.session.AvatarActor
|
import net.psforever.actors.session.AvatarActor
|
||||||
import net.psforever.actors.zone.ZoneActor
|
import net.psforever.actors.zone.{BuildingActor, ZoneActor}
|
||||||
import net.psforever.actors.zone.building.WarpGateLogic
|
import net.psforever.actors.zone.building.WarpGateLogic
|
||||||
import net.psforever.objects.avatar.Avatar
|
import net.psforever.objects.avatar.{Avatar, PlayerControl}
|
||||||
import net.psforever.objects.definition.ObjectDefinition
|
import net.psforever.objects.definition.ObjectDefinition
|
||||||
import net.psforever.objects.geometry.d3.VolumetricGeometry
|
import net.psforever.objects.geometry.d3.VolumetricGeometry
|
||||||
import net.psforever.objects.guid.pool.NumberPool
|
import net.psforever.objects.guid.pool.NumberPool
|
||||||
|
|
@ -56,6 +56,8 @@ import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||||
import net.psforever.objects.vital.prop.DamageWithPosition
|
import net.psforever.objects.vital.prop.DamageWithPosition
|
||||||
import net.psforever.objects.vital.Vitality
|
import net.psforever.objects.vital.Vitality
|
||||||
import net.psforever.objects.zones.blockmap.{BlockMap, SectorPopulation}
|
import net.psforever.objects.zones.blockmap.{BlockMap, SectorPopulation}
|
||||||
|
import net.psforever.packet.game.EmpireBenefitsMessage.{ZoneBenefit, ZoneLock, ZoneLockBenefit, ZoneLockZone}
|
||||||
|
import net.psforever.packet.game.{EmpireBenefitsMessage, PropertyOverrideMessage}
|
||||||
import net.psforever.services.Service
|
import net.psforever.services.Service
|
||||||
import net.psforever.zones.Zones
|
import net.psforever.zones.Zones
|
||||||
|
|
||||||
|
|
@ -194,6 +196,16 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
||||||
*/
|
*/
|
||||||
private var zoneInitialized: Promise[Boolean] = Promise[Boolean]()
|
private var zoneInitialized: Promise[Boolean] = Promise[Boolean]()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For ContinentalLockUpdateMessage
|
||||||
|
*/
|
||||||
|
var lockedBy: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used with lockedBy, but persists until another empire locks the cont
|
||||||
|
*/
|
||||||
|
var benefitRecipient: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When the zone has completed initializing, this will be the future.
|
* When the zone has completed initializing, this will be the future.
|
||||||
* @see `init(ActorContext)`
|
* @see `init(ActorContext)`
|
||||||
|
|
@ -639,6 +651,142 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
||||||
}
|
}
|
||||||
output.toList
|
output.toList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def NotifyContinentalLockBenefits(zone: Zone, building: Building): Unit = {
|
||||||
|
building.Actor ! BuildingActor.ContinentalLock(zone)
|
||||||
|
ApplyHomeLockBenefits(building)
|
||||||
|
}
|
||||||
|
|
||||||
|
def ApplyHomeLockBenefits(building: Building): Unit = {
|
||||||
|
val homeSets: Map[PlanetSideEmpire.Value, Set[Int]] = Map(
|
||||||
|
PlanetSideEmpire.TR -> Set(1, 2),
|
||||||
|
PlanetSideEmpire.VS -> Set(5, 6),
|
||||||
|
PlanetSideEmpire.NC -> Set(7, 10)
|
||||||
|
)
|
||||||
|
val homePerks: Map[PlanetSideEmpire.Value, String] = Map(
|
||||||
|
PlanetSideEmpire.TR -> "battlewagon 15mmbullet prowler 105mmbullet threemanheavybuggy heavy_grenade_mortar apc_tr",
|
||||||
|
PlanetSideEmpire.VS -> "magrider pulse_battery heavy_rail_beam_battery twomanhoverbuggy flux_cannon_thresher_battery aurora fluxpod_ammo apc_vs",
|
||||||
|
PlanetSideEmpire.NC -> "thunderer gauss_cannon_ammo twomanheavybuggy firebird_missile vanguard 150mmbullet apc_nc"
|
||||||
|
)
|
||||||
|
|
||||||
|
def isLockedBy(homeSet: Set[Int], empire: PlanetSideEmpire.Value): Boolean =
|
||||||
|
Zones.zones.filter(z => homeSet.contains(z.Number)).forall(_.benefitRecipient == empire)
|
||||||
|
|
||||||
|
val perks: Map[PlanetSideEmpire.Value, String] =
|
||||||
|
PlanetSideEmpire.values.map { empire =>
|
||||||
|
val empirePerks = homeSets.collect {
|
||||||
|
case (owner, zoneSet) if owner != empire && isLockedBy(zoneSet, empire) =>
|
||||||
|
homePerks(owner)
|
||||||
|
}
|
||||||
|
empire -> empirePerks.mkString(" ")
|
||||||
|
}.toMap
|
||||||
|
|
||||||
|
if (perks.values.forall(_.isEmpty)) {/*do nothing*/}
|
||||||
|
else {
|
||||||
|
val overrideMsg = PropertyOverrideMessage(List(PropertyOverrideMessage.GamePropertyScope(0, List(PropertyOverrideMessage.GamePropertyTarget(343,
|
||||||
|
List(PropertyOverrideMessage.GameProperty("purchase_exempt_vs", perks(PlanetSideEmpire.VS)),
|
||||||
|
PropertyOverrideMessage.GameProperty("purchase_exempt_tr", perks(PlanetSideEmpire.TR)),
|
||||||
|
PropertyOverrideMessage.GameProperty("purchase_exempt_nc", perks(PlanetSideEmpire.NC))))))))
|
||||||
|
building.Actor ! BuildingActor.HomeLockBenefits(overrideMsg)
|
||||||
|
}
|
||||||
|
val benefitMsg = BuildEmpireBenefits()
|
||||||
|
if (benefitMsg.zoneLocks.nonEmpty) {
|
||||||
|
building.Actor ! BuildingActor.HomeLockBenefits(benefitMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def ApplyHomeLockBenefitsOnLogin(player: Player): Unit = {
|
||||||
|
val homeSets: Map[PlanetSideEmpire.Value, Set[Int]] = Map(
|
||||||
|
PlanetSideEmpire.TR -> Set(1, 2),
|
||||||
|
PlanetSideEmpire.VS -> Set(5, 6),
|
||||||
|
PlanetSideEmpire.NC -> Set(7, 10)
|
||||||
|
)
|
||||||
|
val homePerks: Map[PlanetSideEmpire.Value, String] = Map(
|
||||||
|
PlanetSideEmpire.TR -> "battlewagon 15mmbullet prowler 105mmbullet threemanheavybuggy heavy_grenade_mortar apc_tr",
|
||||||
|
PlanetSideEmpire.VS -> "magrider pulse_battery heavy_rail_beam_battery twomanhoverbuggy flux_cannon_thresher_battery aurora fluxpod_ammo apc_vs",
|
||||||
|
PlanetSideEmpire.NC -> "thunderer gauss_cannon_ammo twomanheavybuggy firebird_missile vanguard 150mmbullet apc_nc"
|
||||||
|
)
|
||||||
|
|
||||||
|
def isLockedBy(homeSet: Set[Int], empire: PlanetSideEmpire.Value): Boolean =
|
||||||
|
Zones.zones.filter(z => homeSet.contains(z.Number)).forall(_.benefitRecipient == empire)
|
||||||
|
|
||||||
|
val perks: Map[PlanetSideEmpire.Value, String] =
|
||||||
|
PlanetSideEmpire.values.map { empire =>
|
||||||
|
val empirePerks = homeSets.collect {
|
||||||
|
case (owner, zoneSet) if owner != empire && isLockedBy(zoneSet, empire) =>
|
||||||
|
homePerks(owner)
|
||||||
|
}
|
||||||
|
empire -> empirePerks.mkString(" ")
|
||||||
|
}.toMap
|
||||||
|
|
||||||
|
if (perks.values.forall(_.isEmpty)) {/*do nothing*/}
|
||||||
|
else {
|
||||||
|
val overrideMsg = PropertyOverrideMessage(List(PropertyOverrideMessage.GamePropertyScope(0, List(PropertyOverrideMessage.GamePropertyTarget(343,
|
||||||
|
List(PropertyOverrideMessage.GameProperty("purchase_exempt_vs", perks(PlanetSideEmpire.VS)),
|
||||||
|
PropertyOverrideMessage.GameProperty("purchase_exempt_tr", perks(PlanetSideEmpire.TR)),
|
||||||
|
PropertyOverrideMessage.GameProperty("purchase_exempt_nc", perks(PlanetSideEmpire.NC))))))))
|
||||||
|
PlayerControl.sendResponse(player.Zone, player.Name, overrideMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
val benefitMsg = BuildEmpireBenefits()
|
||||||
|
if (benefitMsg.zoneLocks.nonEmpty) {
|
||||||
|
PlayerControl.sendResponse(player.Zone, player.Name, benefitMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def BuildEmpireBenefits(): EmpireBenefitsMessage = {
|
||||||
|
val locks = scala.collection.mutable.ArrayBuffer[ZoneLock]()
|
||||||
|
val benefits = scala.collection.mutable.ArrayBuffer[ZoneBenefit]()
|
||||||
|
val homeSets: Map[PlanetSideEmpire.Value, Set[Int]] = Map(
|
||||||
|
PlanetSideEmpire.TR -> Set(1, 2),
|
||||||
|
PlanetSideEmpire.VS -> Set(5, 6),
|
||||||
|
PlanetSideEmpire.NC -> Set(7, 10)
|
||||||
|
)
|
||||||
|
val benefitOfZones: Map[Int, Int] = Map(
|
||||||
|
3 -> 6, // Cyssor gives benefit 6 (+10% armor bonus to vehicles)
|
||||||
|
4 -> 1, // Ishundar gives benefit 1 (vehicle shields)
|
||||||
|
9 -> 3 // Searhus gives benefit 3 (faster respawn)
|
||||||
|
)
|
||||||
|
val homePerkBenefits: Map[PlanetSideEmpire.Value, Int] = Map(
|
||||||
|
PlanetSideEmpire.TR -> 7,
|
||||||
|
PlanetSideEmpire.NC -> 8,
|
||||||
|
PlanetSideEmpire.VS -> 9
|
||||||
|
)
|
||||||
|
def isLockedBy(homeSet: Set[Int], empire: PlanetSideEmpire.Value): Boolean =
|
||||||
|
Zones.zones.filter(z => homeSet.contains(z.Number)).forall(_.benefitRecipient == empire)
|
||||||
|
|
||||||
|
// home zone perks
|
||||||
|
homeSets.foreach { case (owner, set) =>
|
||||||
|
PlanetSideEmpire.values.filterNot(_ == PlanetSideEmpire.NEUTRAL).foreach { empire =>
|
||||||
|
if (owner != empire && isLockedBy(set, empire)) {
|
||||||
|
locks += ZoneLock(empire, s"lock-${owner.toString.toLowerCase}-homes")
|
||||||
|
benefits += ZoneBenefit(empire, ZoneLockBenefit(homePerkBenefits(owner)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
benefitOfZones.foreach { case (zoneNum, benefitId) =>
|
||||||
|
Zones.zones.find(_.Number == zoneNum).foreach { z =>
|
||||||
|
z.benefitRecipient match {
|
||||||
|
case PlanetSideEmpire.NEUTRAL =>
|
||||||
|
//nothing
|
||||||
|
case empire =>
|
||||||
|
locks += ZoneLock(empire, s"lock-z$zoneNum")
|
||||||
|
benefits += ZoneBenefit(empire, ZoneLockBenefit(benefitId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// all four islands together give benefit 4 (vehicle repair)
|
||||||
|
val islandZones: Set[Int] = Set(29, 30, 31, 32)
|
||||||
|
val islandBenefit: Int = 4
|
||||||
|
PlanetSideEmpire.values.filterNot(_ == PlanetSideEmpire.NEUTRAL).foreach { empire =>
|
||||||
|
if (isLockedBy(islandZones, empire)) {
|
||||||
|
locks += ZoneLock(empire, ZoneLockZone.i1_i2_i3_i4)
|
||||||
|
benefits += ZoneBenefit(empire, ZoneLockBenefit(islandBenefit))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EmpireBenefitsMessage(locks.toVector, benefits.toVector)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -557,7 +557,7 @@ object GamePacketOpcode extends Enumeration {
|
||||||
case 0xd4 => game.GenericObjectActionAtPositionMessage.decode
|
case 0xd4 => game.GenericObjectActionAtPositionMessage.decode
|
||||||
case 0xd5 => game.PropertyOverrideMessage.decode
|
case 0xd5 => game.PropertyOverrideMessage.decode
|
||||||
case 0xd6 => game.WarpgateLinkOverrideMessage.decode
|
case 0xd6 => game.WarpgateLinkOverrideMessage.decode
|
||||||
case 0xd7 => noDecoder(EmpireBenefitsMessage)
|
case 0xd7 => game.EmpireBenefitsMessage.decode
|
||||||
// 0xd8
|
// 0xd8
|
||||||
case 0xd8 => noDecoder(ForceEmpireMessage)
|
case 0xd8 => noDecoder(ForceEmpireMessage)
|
||||||
case 0xd9 => game.BroadcastWarpgateUpdateMessage.decode
|
case 0xd9 => game.BroadcastWarpgateUpdateMessage.decode
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,12 @@ final case class Additional1(unk1: String, unk2: Int, unk3: Long)
|
||||||
final case class Additional2(unk1: Int, unk2: Long)
|
final case class Additional2(unk1: Int, unk2: Long)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* na
|
* Used for building information window on map. Tells the empire who installed the virus which one
|
||||||
* @param unk1 na
|
* and tells the defending faction generic "Infected"
|
||||||
* @param unk2 na
|
* @param inform_defenders na
|
||||||
|
* @param installed_by_id na
|
||||||
*/
|
*/
|
||||||
final case class Additional3(unk1: Boolean, unk2: Int)
|
final case class Additional3(inform_defenders: Boolean, installed_by_id: Int)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the state of map asset for a client's specific building's state.
|
* Update the state of map asset for a client's specific building's state.
|
||||||
|
|
@ -73,9 +74,14 @@ final case class Additional3(unk1: Boolean, unk2: Int)
|
||||||
* @param unk4 na
|
* @param unk4 na
|
||||||
* @param unk5 na
|
* @param unk5 na
|
||||||
* @param unk6 na
|
* @param unk6 na
|
||||||
* @param unk7 na;
|
* @param virus_id id of virus installed. value != 8 causes the next field to be defined.
|
||||||
* value != 8 causes the next field to be defined
|
* 0 - unlock all doors
|
||||||
* @param unk7x na
|
* 1 - disable linked benefits
|
||||||
|
* 2 - double ntu drain
|
||||||
|
* 3 - disable enemy radar
|
||||||
|
* 4 - access equipment terminals
|
||||||
|
* 8 - no virus installed - if 8, virus_installed_by is None
|
||||||
|
* @param virus_installed_by if virus_id = 8, None, else this has bool and id of the empire that installed the virus
|
||||||
* @param boost_spawn_pain if the building has spawn tubes, the (boosted) strength of its enemy pain field
|
* @param boost_spawn_pain if the building has spawn tubes, the (boosted) strength of its enemy pain field
|
||||||
* @param boost_generator_pain if the building has a generator, the (boosted) strength of its enemy pain field
|
* @param boost_generator_pain if the building has a generator, the (boosted) strength of its enemy pain field
|
||||||
*/
|
*/
|
||||||
|
|
@ -97,8 +103,8 @@ final case class BuildingInfoUpdateMessage(
|
||||||
unk4: List[Additional2],
|
unk4: List[Additional2],
|
||||||
unk5: Long,
|
unk5: Long,
|
||||||
unk6: Boolean,
|
unk6: Boolean,
|
||||||
unk7: Int,
|
virus_id: Int,
|
||||||
unk7x: Option[Additional3],
|
virus_installed_by: Option[Additional3],
|
||||||
boost_spawn_pain: Boolean,
|
boost_spawn_pain: Boolean,
|
||||||
boost_generator_pain: Boolean
|
boost_generator_pain: Boolean
|
||||||
) extends PlanetSideGamePacket {
|
) extends PlanetSideGamePacket {
|
||||||
|
|
@ -129,8 +135,8 @@ object BuildingInfoUpdateMessage extends Marshallable[BuildingInfoUpdateMessage]
|
||||||
* A `Codec` for a set of additional fields.
|
* A `Codec` for a set of additional fields.
|
||||||
*/
|
*/
|
||||||
private val additional3_codec: Codec[Additional3] = (
|
private val additional3_codec: Codec[Additional3] = (
|
||||||
("unk1" | bool) ::
|
("inform_defenders" | bool) ::
|
||||||
("unk2" | uint2L)
|
("installed_by_id" | uint2L)
|
||||||
).as[Additional3]
|
).as[Additional3]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -190,8 +196,8 @@ object BuildingInfoUpdateMessage extends Marshallable[BuildingInfoUpdateMessage]
|
||||||
("unk4" | listOfN(uint4L, additional2_codec)) ::
|
("unk4" | listOfN(uint4L, additional2_codec)) ::
|
||||||
("unk5" | uint32L) ::
|
("unk5" | uint32L) ::
|
||||||
("unk6" | bool) ::
|
("unk6" | bool) ::
|
||||||
(("unk7" | uint4L) >>:~ { unk7 =>
|
(("virus_id" | uint4L) >>:~ { virus_id =>
|
||||||
conditional(unk7 != 8, codec = "unk7x" | additional3_codec) ::
|
conditional(virus_id != 8, codec = "virus_installed_by" | additional3_codec) ::
|
||||||
("boost_spawn_pain" | bool) ::
|
("boost_spawn_pain" | bool) ::
|
||||||
("boost_generator_pain" | bool)
|
("boost_generator_pain" | bool)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
// Copyright (c) 2025 PSForever
|
||||||
|
package net.psforever.packet.game
|
||||||
|
|
||||||
|
import net.psforever.packet.game.EmpireBenefitsMessage.{ZoneBenefit, ZoneLock}
|
||||||
|
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||||
|
import net.psforever.types.PlanetSideEmpire
|
||||||
|
import scodec.Codec
|
||||||
|
import scodec.codecs._
|
||||||
|
|
||||||
|
import scala.language.implicitConversions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EmpireBenefitsMessage
|
||||||
|
*
|
||||||
|
* zoneLocks gives the client information about which empire locks what continent.
|
||||||
|
* This produces a chat message.
|
||||||
|
* zoneBenefits tells the client what empire has which benefits enabled.
|
||||||
|
* This has to match zoneLocks to work properly.
|
||||||
|
*/
|
||||||
|
final case class EmpireBenefitsMessage(
|
||||||
|
zoneLocks: Vector[ZoneLock],
|
||||||
|
zoneBenefits: Vector[ZoneBenefit]
|
||||||
|
) extends PlanetSideGamePacket {
|
||||||
|
type Packet = EmpireBenefitsMessage
|
||||||
|
def opcode = GamePacketOpcode.EmpireBenefitsMessage
|
||||||
|
def encode = EmpireBenefitsMessage.encode(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
object EmpireBenefitsMessage extends Marshallable[EmpireBenefitsMessage] {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ZoneLockZone
|
||||||
|
*
|
||||||
|
* Available Types of Zones
|
||||||
|
*
|
||||||
|
* These zones can be used to notify the client of a lock.
|
||||||
|
*/
|
||||||
|
object ZoneLockZone extends Enumeration {
|
||||||
|
type Type = String
|
||||||
|
|
||||||
|
val i1: ZoneLockZone.Value = Value("lock-i1") // Extinction Continental Lock
|
||||||
|
val i2: ZoneLockZone.Value = Value("lock-i2") // Ascension Continental Lock
|
||||||
|
val i3: ZoneLockZone.Value = Value("lock-i3") // Desolation Continental Lock
|
||||||
|
val i4: ZoneLockZone.Value = Value("lock-i4") // Nexus Continental Lock
|
||||||
|
val i1_i2_i3_i4: ZoneLockZone.Value = Value("lock-i1-i2-i3-i4") // Oshur Cluster Lock
|
||||||
|
val z3: ZoneLockZone.Value = Value("lock-z3") // Cyssor Continental Lock
|
||||||
|
val z4: ZoneLockZone.Value = Value("lock-z4") // Ishundar Continental Lock
|
||||||
|
val z9: ZoneLockZone.Value = Value("lock-z9") // Searhus Continental Lock
|
||||||
|
val tr_homes: ZoneLockZone.Value = Value("lock-tr-homes") // TR Home Continent Lock
|
||||||
|
val nc_homes: ZoneLockZone.Value = Value("lock-nc-homes") // NC Home Continent Lock
|
||||||
|
val vs_homes: ZoneLockZone.Value = Value("lock-vs-homes") // VS Home Continent Lock
|
||||||
|
|
||||||
|
implicit def valueToType(v: ZoneLockZone.Value): Type = v.toString
|
||||||
|
implicit val codec: Codec[Type] = PacketHelpers.encodedStringAligned(6)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ZoneLockBenefit
|
||||||
|
*
|
||||||
|
* Available Types of Benefits
|
||||||
|
*
|
||||||
|
* Benefits 0, 2 and 5 are unknown. Benefits for i1 to i4 are unknown and mapped incorrectly here.
|
||||||
|
*/
|
||||||
|
object ZoneLockBenefit extends Enumeration {
|
||||||
|
type Type = Value
|
||||||
|
|
||||||
|
val i1: ZoneLockBenefit.Value = Value(-1) // Extinction Continental Lock
|
||||||
|
val i2: ZoneLockBenefit.Value = Value(-2) // Ascension Continental Lock
|
||||||
|
val i3: ZoneLockBenefit.Value = Value(-3) // Desolation Continental Lock
|
||||||
|
val i4: ZoneLockBenefit.Value = Value(-4) // Nexus Continental Lock
|
||||||
|
|
||||||
|
// val unk0: ZoneLockBenefit.Value = Value(0)
|
||||||
|
val z4: ZoneLockBenefit.Value = Value(1) // Ishundar Continental Lock
|
||||||
|
// val unk2: ZoneLockBenefit.Value = Value(2)
|
||||||
|
val z9: ZoneLockBenefit.Value = Value(3) // Searhus Continental Lock
|
||||||
|
val i1_i2_i3_i4: ZoneLockBenefit.Value = Value(4) // Oshur Cluster Lock
|
||||||
|
// val unk5: ZoneLockBenefit.Value = Value(5)
|
||||||
|
val z3: ZoneLockBenefit.Value = Value(6) // Cyssor Continental Lock
|
||||||
|
val tr_homes: ZoneLockBenefit.Value = Value(7) // TR Home Continent Lock
|
||||||
|
val nc_homes: ZoneLockBenefit.Value = Value(8) // NC Home Continent Lock
|
||||||
|
val vs_homes: ZoneLockBenefit.Value = Value(9) // VS Home Continent Lock
|
||||||
|
|
||||||
|
implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uint16L)
|
||||||
|
}
|
||||||
|
|
||||||
|
final case class ZoneLock(
|
||||||
|
empire: PlanetSideEmpire.Type,
|
||||||
|
zone: ZoneLockZone.Type,
|
||||||
|
)
|
||||||
|
|
||||||
|
final case class ZoneBenefit(
|
||||||
|
empire: PlanetSideEmpire.Type,
|
||||||
|
value: ZoneLockBenefit.Type
|
||||||
|
)
|
||||||
|
|
||||||
|
private implicit val zoneLockCodec: Codec[ZoneLock] = (
|
||||||
|
("empire" | PlanetSideEmpire.codec) ::
|
||||||
|
("zone" | ZoneLockZone.codec)
|
||||||
|
).as[ZoneLock]
|
||||||
|
|
||||||
|
private implicit val zoneBenefitCodec: Codec[ZoneBenefit] = (
|
||||||
|
("empire" | PlanetSideEmpire.codec) ::
|
||||||
|
("benefit" | ZoneLockBenefit.codec)
|
||||||
|
).as[ZoneBenefit]
|
||||||
|
|
||||||
|
implicit val codec: Codec[EmpireBenefitsMessage] = (
|
||||||
|
("zoneLocks" | vectorOfN(uint32L.xmap(_.toInt, _.toLong), zoneLockCodec)) ::
|
||||||
|
("zoneBenefits" | vectorOfN(uint32L.xmap(_.toInt, _.toLong), zoneBenefitCodec))
|
||||||
|
).as[EmpireBenefitsMessage]
|
||||||
|
}
|
||||||
|
|
@ -50,6 +50,9 @@ import shapeless.{::, HNil}
|
||||||
* 53 - Put down an FDU<br/>
|
* 53 - Put down an FDU<br/>
|
||||||
* 56 - Sets vehicle or player to be black ops<br/>
|
* 56 - Sets vehicle or player to be black ops<br/>
|
||||||
* 57 - Reverts player from black ops<br/>
|
* 57 - Reverts player from black ops<br/>
|
||||||
|
* 58 - Virus installed, changes lighting in facility to green
|
||||||
|
* 60 - Virus purged
|
||||||
|
* 61 - Virus recently installed. Counts down from 2 minutes until a new virus can be uploaded
|
||||||
* <br>
|
* <br>
|
||||||
* What are these values?<br>
|
* What are these values?<br>
|
||||||
* 90? - for observed driven BFR's, model pitches up slightly and stops idle animation<br>
|
* 90? - for observed driven BFR's, model pitches up slightly and stops idle animation<br>
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,11 @@ sealed abstract class HackState7(val value: Int) extends IntEnumEntry
|
||||||
object HackState7 extends IntEnum[HackState7] {
|
object HackState7 extends IntEnum[HackState7] {
|
||||||
val values: IndexedSeq[HackState7] = findValues
|
val values: IndexedSeq[HackState7] = findValues
|
||||||
|
|
||||||
case object Unk0 extends HackState7(value = 0)
|
case object UnlockDoors extends HackState7(value = 0)
|
||||||
case object Unk1 extends HackState7(value = 1)
|
case object DisableLatticeBenefits extends HackState7(value = 1)
|
||||||
case object Unk2 extends HackState7(value = 2)
|
case object NTUDrain extends HackState7(value = 2)
|
||||||
case object Unk3 extends HackState7(value = 3)
|
case object DisableRadar extends HackState7(value = 3)
|
||||||
case object Unk4 extends HackState7(value = 4)
|
case object AccessEquipmentTerms extends HackState7(value = 4)
|
||||||
case object Unk5 extends HackState7(value = 5)
|
case object Unk5 extends HackState7(value = 5)
|
||||||
case object Unk6 extends HackState7(value = 6)
|
case object Unk6 extends HackState7(value = 6)
|
||||||
case object Unk7 extends HackState7(value = 7)
|
case object Unk7 extends HackState7(value = 7)
|
||||||
|
|
|
||||||
|
|
@ -201,7 +201,7 @@ import scodec.codecs._
|
||||||
* `68 - Vehicle shield health`<br>
|
* `68 - Vehicle shield health`<br>
|
||||||
* `79 - ???`<br>
|
* `79 - ???`<br>
|
||||||
* `80 - Damage vehicle (unknown value)`<br>
|
* `80 - Damage vehicle (unknown value)`<br>
|
||||||
* `81 - ???`<br>
|
* `81 - Player bailed from vehicle, causes bail animation `<br>
|
||||||
* `113 - Vehicle capacitor - e.g. Leviathan EMP charge`
|
* `113 - Vehicle capacitor - e.g. Leviathan EMP charge`
|
||||||
* @param guid the object
|
* @param guid the object
|
||||||
* @param attribute_type the field
|
* @param attribute_type the field
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ final case class VariantVehicleData(unk: Int) extends SpecificVehicleData(Vehicl
|
||||||
* -jammered - vehicles will not be jammered by setting this field<br>
|
* -jammered - vehicles will not be jammered by setting this field<br>
|
||||||
* -player_guid the vehicle's (official) owner;
|
* -player_guid the vehicle's (official) owner;
|
||||||
* a living player in the game world on the same continent as the vehicle who may mount the driver mount
|
* a living player in the game world on the same continent as the vehicle who may mount the driver mount
|
||||||
* @param unk3 na
|
* @param boostMaxHealth vehicle gets 10% more armor from vehicle armor benefit given by Cyssor empire lock
|
||||||
* @param health the amount of health the vehicle has, as a percentage of a filled bar (255)
|
* @param health the amount of health the vehicle has, as a percentage of a filled bar (255)
|
||||||
* @param unk4 na
|
* @param unk4 na
|
||||||
* @param no_mount_points do not display entry points for the seats
|
* @param no_mount_points do not display entry points for the seats
|
||||||
|
|
@ -65,7 +65,7 @@ final case class VariantVehicleData(unk: Int) extends SpecificVehicleData(Vehicl
|
||||||
final case class VehicleData(
|
final case class VehicleData(
|
||||||
pos: PlacementData,
|
pos: PlacementData,
|
||||||
data: CommonFieldData,
|
data: CommonFieldData,
|
||||||
unk3: Boolean,
|
boostMaxHealth: Boolean,
|
||||||
health: Int,
|
health: Int,
|
||||||
unk4: Boolean,
|
unk4: Boolean,
|
||||||
no_mount_points: Boolean,
|
no_mount_points: Boolean,
|
||||||
|
|
@ -106,7 +106,7 @@ object VehicleData extends Marshallable[VehicleData] {
|
||||||
cloak: Boolean,
|
cloak: Boolean,
|
||||||
inventory: Option[InventoryData]
|
inventory: Option[InventoryData]
|
||||||
): VehicleData = {
|
): VehicleData = {
|
||||||
VehicleData(pos, basic, unk3 = false, health, unk4 = false, no_mount_points = false, driveState, unk5 = false, unk6 = false, cloak = cloak, None, inventory)(
|
VehicleData(pos, basic, boostMaxHealth = false, health, unk4 = false, no_mount_points = false, driveState, unk5 = false, unk6 = false, cloak = cloak, None, inventory)(
|
||||||
VehicleFormat.Normal
|
VehicleFormat.Normal
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -128,7 +128,7 @@ object VehicleData extends Marshallable[VehicleData] {
|
||||||
format: UtilityVehicleData,
|
format: UtilityVehicleData,
|
||||||
inventory: Option[InventoryData]
|
inventory: Option[InventoryData]
|
||||||
): VehicleData = {
|
): VehicleData = {
|
||||||
VehicleData(pos, basic, unk3 = false, health, unk4 = false, no_mount_points = false, driveState, unk5 = false, unk6 = false, cloak = cloak, Some(format), inventory)(
|
VehicleData(pos, basic, boostMaxHealth = false, health, unk4 = false, no_mount_points = false, driveState, unk5 = false, unk6 = false, cloak = cloak, Some(format), inventory)(
|
||||||
VehicleFormat.Utility
|
VehicleFormat.Utility
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +150,7 @@ object VehicleData extends Marshallable[VehicleData] {
|
||||||
format: VariantVehicleData,
|
format: VariantVehicleData,
|
||||||
inventory: Option[InventoryData]
|
inventory: Option[InventoryData]
|
||||||
): VehicleData = {
|
): VehicleData = {
|
||||||
VehicleData(pos, basic, unk3 = false, health, unk4 = false, no_mount_points = false, driveState, unk5 = false, unk6 = false, cloak = cloak, Some(format), inventory)(
|
VehicleData(pos, basic, boostMaxHealth = false, health, unk4 = false, no_mount_points = false, driveState, unk5 = false, unk6 = false, cloak = cloak, Some(format), inventory)(
|
||||||
VehicleFormat.Variant
|
VehicleFormat.Variant
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -555,15 +555,37 @@ class CavernRotationService(
|
||||||
def sendCavernRotationUpdates(sendToSession: ActorRef): Unit = {
|
def sendCavernRotationUpdates(sendToSession: ActorRef): Unit = {
|
||||||
val curr = System.currentTimeMillis()
|
val curr = System.currentTimeMillis()
|
||||||
val (lockedZones, unlockedZones) = managedZones.partition(_.locked)
|
val (lockedZones, unlockedZones) = managedZones.partition(_.locked)
|
||||||
//borrow GalaxyService response structure, but send to the specific endpoint
|
//borrow GalaxyService response structure, but send to the specific endpoint math.max(0, monitor.start + monitor.duration - curr)
|
||||||
lockedZones.foreach { monitor =>
|
unlockedZones.foreach { monitor =>
|
||||||
|
sendToSession ! GalaxyServiceResponse("", GalaxyResponse.UnlockedZoneUpdate(monitor.zone))
|
||||||
|
}
|
||||||
|
val sortedLocked = lockedZones.sortBy(z => z.start)
|
||||||
|
sortedLocked.take(2).foreach { monitor =>
|
||||||
sendToSession ! GalaxyServiceResponse(
|
sendToSession ! GalaxyServiceResponse(
|
||||||
"",
|
"",
|
||||||
GalaxyResponse.LockedZoneUpdate(monitor.zone, math.max(0, monitor.start + monitor.duration - curr))
|
GalaxyResponse.LockedZoneUpdate(monitor.zone, math.max(0, monitor.start + monitor.duration - curr))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
unlockedZones.foreach { monitor =>
|
sortedLocked.takeRight(2).foreach { monitor =>
|
||||||
sendToSession ! GalaxyServiceResponse("", GalaxyResponse.UnlockedZoneUpdate(monitor.zone))
|
sendToSession ! GalaxyServiceResponse(
|
||||||
|
"",
|
||||||
|
GalaxyResponse.LockedZoneUpdate(monitor.zone, 0L)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def sendCavernRotationUpdatesToAll(galaxyService: ActorRef): Unit = {
|
||||||
|
val curr = System.currentTimeMillis()
|
||||||
|
val (lockedZones, unlockedZones) = managedZones.partition(_.locked)
|
||||||
|
unlockedZones.foreach { z =>
|
||||||
|
galaxyService ! GalaxyServiceMessage(GalaxyAction.UnlockedZoneUpdate(z.zone))
|
||||||
|
}
|
||||||
|
val sortedLocked = lockedZones.sortBy(z => z.start)
|
||||||
|
sortedLocked.take(2).foreach { z =>
|
||||||
|
galaxyService ! GalaxyServiceMessage(GalaxyAction.LockedZoneUpdate(z.zone, z.start + z.duration - curr))
|
||||||
|
}
|
||||||
|
sortedLocked.takeRight(2).foreach { z =>
|
||||||
|
galaxyService ! GalaxyServiceMessage(GalaxyAction.LockedZoneUpdate(z.zone, 0L))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -595,9 +617,9 @@ class CavernRotationService(
|
||||||
//zone transition immediately
|
//zone transition immediately
|
||||||
lockTimer.cancel()
|
lockTimer.cancel()
|
||||||
unlockTimer.cancel()
|
unlockTimer.cancel()
|
||||||
|
retimeZonesUponForcedRotation(galaxyService)
|
||||||
zoneRotationFunc(galaxyService)
|
zoneRotationFunc(galaxyService)
|
||||||
lockTimerToDisplayWarning(timeBetweenRotationsHours.hours - firstClosingWarningAtMinutes.minutes)
|
lockTimerToDisplayWarning(timeBetweenRotationsHours.hours - firstClosingWarningAtMinutes.minutes)
|
||||||
retimeZonesUponForcedRotation(galaxyService)
|
|
||||||
} else {
|
} else {
|
||||||
//instead of transitioning immediately, jump to the 5 minute rotation warning for the benefit of players
|
//instead of transitioning immediately, jump to the 5 minute rotation warning for the benefit of players
|
||||||
lockTimer.cancel() //won't need to retime until zone change
|
lockTimer.cancel() //won't need to retime until zone change
|
||||||
|
|
@ -651,7 +673,6 @@ class CavernRotationService(
|
||||||
galaxyService ! GalaxyServiceMessage(GalaxyAction.SendResponse(
|
galaxyService ! GalaxyServiceMessage(GalaxyAction.SendResponse(
|
||||||
ChatMsg(ChatMessageType.UNK_229, s"@cavern_switched^@${lockingZone.id}~^@${unlockingZone.id}")
|
ChatMsg(ChatMessageType.UNK_229, s"@cavern_switched^@${lockingZone.id}~^@${unlockingZone.id}")
|
||||||
))
|
))
|
||||||
galaxyService ! GalaxyServiceMessage(GalaxyAction.UnlockedZoneUpdate(unlockingZone))
|
|
||||||
//change warp gate statuses to reflect zone lock state
|
//change warp gate statuses to reflect zone lock state
|
||||||
CavernRotationService.disableLatticeLinksAndWarpGateAccessibility(
|
CavernRotationService.disableLatticeLinksAndWarpGateAccessibility(
|
||||||
((prevToLock until managedZones.size) ++ (0 until prevToLock))
|
((prevToLock until managedZones.size) ++ (0 until prevToLock))
|
||||||
|
|
@ -664,7 +685,7 @@ class CavernRotationService(
|
||||||
.map(managedZones(_).zone)
|
.map(managedZones(_).zone)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
galaxyService ! GalaxyServiceMessage(GalaxyAction.LockedZoneUpdate(locking.zone, fullHoursBetweenRotationsAsMillis))
|
sendCavernRotationUpdatesToAll(galaxyService)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -689,9 +710,6 @@ class CavernRotationService(
|
||||||
val zone = managedZones(monitorIndex)
|
val zone = managedZones(monitorIndex)
|
||||||
val newStart = startingInThePast + (index * timeBetweenRotationsHours).hours.toMillis
|
val newStart = startingInThePast + (index * timeBetweenRotationsHours).hours.toMillis
|
||||||
zone.start = newStart
|
zone.start = newStart
|
||||||
if (zone.locked) {
|
|
||||||
galaxyService ! GalaxyServiceMessage(GalaxyAction.LockedZoneUpdate(zone.zone, newStart + fullDurationAsMillis - curr))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//println(managedZones.flatMap { z => s"[${z.start + z.duration - curr}]"}.mkString(""))
|
//println(managedZones.flatMap { z => s"[${z.start + z.duration - curr}]"}.mkString(""))
|
||||||
}
|
}
|
||||||
|
|
@ -715,10 +733,8 @@ class CavernRotationService(
|
||||||
val advanceByTimeAsMillis = advanceTimeBy.toMillis
|
val advanceByTimeAsMillis = advanceTimeBy.toMillis
|
||||||
managedZones.foreach { zone =>
|
managedZones.foreach { zone =>
|
||||||
zone.start = zone.start - advanceByTimeAsMillis
|
zone.start = zone.start - advanceByTimeAsMillis
|
||||||
if (zone.locked) {
|
|
||||||
galaxyService ! GalaxyServiceMessage(GalaxyAction.LockedZoneUpdate(zone.zone, zone.start + zone.duration - curr))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
sendCavernRotationUpdatesToAll(galaxyService)
|
||||||
//println(managedZones.flatMap { z => s"[${z.start + z.duration - curr}]"}.mkString(""))
|
//println(managedZones.flatMap { z => s"[${z.start + z.duration - curr}]"}.mkString(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -259,14 +259,18 @@ class HackCaptureActor extends Actor {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// Push map update to clients
|
// Push map update to clients
|
||||||
owner.Zone.actor ! ZoneActor.ZoneMapUpdate()
|
if (owner.BuildingType == StructureType.Tower)
|
||||||
|
owner.Actor ! BuildingActor.MapUpdate()
|
||||||
|
else
|
||||||
|
owner.Zone.actor ! ZoneActor.ZoneMapUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
private def HackCompleted(terminal: CaptureTerminal with Hackable, hackedByFaction: PlanetSideEmpire.Value): Unit = {
|
private def HackCompleted(terminal: CaptureTerminal with Hackable, hackedByFaction: PlanetSideEmpire.Value): Unit = {
|
||||||
val building = terminal.Owner.asInstanceOf[Building]
|
val building = terminal.Owner.asInstanceOf[Building]
|
||||||
if (building.NtuLevel > 0) {
|
if (building.NtuLevel > 0) {
|
||||||
|
building.virusId = 8
|
||||||
|
building.virusInstalledBy = None
|
||||||
log.info(s"Setting base ${building.GUID} / MapId: ${building.MapId} as owned by $hackedByFaction")
|
log.info(s"Setting base ${building.GUID} / MapId: ${building.MapId} as owned by $hackedByFaction")
|
||||||
building.Actor! BuildingActor.SetFaction(hackedByFaction)
|
|
||||||
//dispatch to players aligned with the capturing faction within the SOI
|
//dispatch to players aligned with the capturing faction within the SOI
|
||||||
val events = building.Zone.LocalEvents
|
val events = building.Zone.LocalEvents
|
||||||
val msg = LocalAction.SendGenericActionMessage(Service.defaultPlayerGUID, GenericAction.FacilityCaptureFanfare)
|
val msg = LocalAction.SendGenericActionMessage(Service.defaultPlayerGUID, GenericAction.FacilityCaptureFanfare)
|
||||||
|
|
@ -275,19 +279,34 @@ class HackCaptureActor extends Actor {
|
||||||
.collect { case p if p.Faction == hackedByFaction =>
|
.collect { case p if p.Faction == hackedByFaction =>
|
||||||
events ! LocalServiceMessage(p.Name, msg)
|
events ! LocalServiceMessage(p.Name, msg)
|
||||||
}
|
}
|
||||||
val zoneBases = building.Zone.Buildings.filter(base =>
|
val buildings = building.Zone.Buildings.values
|
||||||
base._2.BuildingType == StructureType.Facility)
|
val hackedBaseId = building.GUID
|
||||||
val ownedBases = building.Zone.Buildings.filter(base =>
|
val facilities = if (building.Zone.id.startsWith("c")) {
|
||||||
base._2.BuildingType == StructureType.Facility && base._2.Faction == hackedByFaction
|
buildings.filter(b =>
|
||||||
&& base._2.GUID != building.GUID)
|
b.Name.startsWith("N") || b.Name.startsWith("S")).toSeq
|
||||||
val zoneTowers = building.Zone.Buildings.filter(tower =>
|
|
||||||
tower._2.BuildingType == StructureType.Tower && tower._2.Faction != hackedByFaction)
|
|
||||||
// All major facilities in zone are now owned by the hacking faction. Capture all towers in the zone
|
|
||||||
// Base that was just hacked is not counted (hence the size - 1) because it wasn't always in ownedBases (async?)
|
|
||||||
if (zoneBases.size - 1 == ownedBases.size && zoneTowers.nonEmpty)
|
|
||||||
{
|
|
||||||
processBuildingsWithDelay(zoneTowers.values.toSeq, hackedByFaction, 1000)
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
buildings.filter(_.BuildingType == StructureType.Facility).toSeq
|
||||||
|
}
|
||||||
|
val ownedFacilities = facilities.filter(b =>
|
||||||
|
b.Faction == hackedByFaction && b.GUID != hackedBaseId
|
||||||
|
)
|
||||||
|
val towersToCapture = buildings.filter(b =>
|
||||||
|
b.BuildingType == StructureType.Tower && b.Faction != hackedByFaction
|
||||||
|
).toSeq
|
||||||
|
if (ownedFacilities.size == facilities.size - 1) {
|
||||||
|
building.Zone.lockedBy = hackedByFaction
|
||||||
|
building.Zone.benefitRecipient = hackedByFaction
|
||||||
|
building.Zone.NotifyContinentalLockBenefits(building.Zone, building)
|
||||||
|
if (towersToCapture.nonEmpty) {
|
||||||
|
processBuildingsWithDelay(towersToCapture, hackedByFaction, 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (building.Zone.lockedBy != PlanetSideEmpire.NEUTRAL) {
|
||||||
|
building.Zone.lockedBy = PlanetSideEmpire.NEUTRAL
|
||||||
|
building.Zone.NotifyContinentalLockBenefits(building.Zone, building)
|
||||||
|
}
|
||||||
|
building.Actor ! BuildingActor.SetFaction(hackedByFaction)
|
||||||
} else {
|
} else {
|
||||||
log.info("Base hack completed, but base was out of NTU.")
|
log.info("Base hack completed, but base was out of NTU.")
|
||||||
}
|
}
|
||||||
|
|
@ -317,23 +336,10 @@ class HackCaptureActor extends Actor {
|
||||||
if (buildingIterator.hasNext) {
|
if (buildingIterator.hasNext) {
|
||||||
val building = buildingIterator.next()
|
val building = buildingIterator.next()
|
||||||
val terminal = building.CaptureTerminal.get
|
val terminal = building.CaptureTerminal.get
|
||||||
val zone = building.Zone
|
|
||||||
val zoneActor = zone.actor
|
|
||||||
val buildingActor = building.Actor
|
val buildingActor = building.Actor
|
||||||
//clear any previous hack
|
|
||||||
if (building.CaptureTerminalIsHacked) {
|
|
||||||
zone.LocalEvents ! LocalServiceMessage(
|
|
||||||
zone.id,
|
|
||||||
LocalAction.ResecureCaptureTerminal(terminal, PlayerSource.Nobody)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
//push any updates this might cause
|
|
||||||
zoneActor ! ZoneActor.ZoneMapUpdate()
|
|
||||||
//convert faction affiliation
|
|
||||||
buildingActor ! BuildingActor.SetFaction(faction)
|
buildingActor ! BuildingActor.SetFaction(faction)
|
||||||
buildingActor ! BuildingActor.AmenityStateChange(terminal, Some(false))
|
buildingActor ! BuildingActor.AmenityStateChange(terminal, Some(false))
|
||||||
//push for map updates again
|
buildingActor ! BuildingActor.MapUpdate()
|
||||||
zoneActor ! ZoneActor.ZoneMapUpdate()
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
0,
|
0,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package net.psforever.services.local.support
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import akka.actor.{Actor, Cancellable}
|
import akka.actor.{Actor, Cancellable}
|
||||||
import net.psforever.objects.Default
|
import net.psforever.objects.{Default, GlobalDefinitions}
|
||||||
import net.psforever.objects.serverobject.hackable.Hackable
|
import net.psforever.objects.serverobject.hackable.Hackable
|
||||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
|
|
@ -30,8 +30,12 @@ class HackClearActor() extends Actor {
|
||||||
def receive: Receive = {
|
def receive: Receive = {
|
||||||
case HackClearActor.ObjectIsHacked(target, zone, unk1, unk2, duration, time) =>
|
case HackClearActor.ObjectIsHacked(target, zone, unk1, unk2, duration, time) =>
|
||||||
val durationMillis = TimeUnit.MILLISECONDS.convert(duration, TimeUnit.SECONDS)
|
val durationMillis = TimeUnit.MILLISECONDS.convert(duration, TimeUnit.SECONDS)
|
||||||
hackedObjects = hackedObjects :+ HackClearActor.HackEntry(target, zone, unk1, unk2, time, durationMillis)
|
val newEntry = HackClearActor.HackEntry(target, zone, unk1, unk2, time, durationMillis)
|
||||||
|
// Remove any existing entry for this GUID + zone in case of virus adding an entry for same target
|
||||||
|
hackedObjects = hackedObjects.filterNot(e => e.target.GUID == target.GUID && e.zone.id == zone.id)
|
||||||
|
hackedObjects = newEntry :: hackedObjects
|
||||||
|
// Sort so they are removed in the correct order
|
||||||
|
hackedObjects = hackedObjects.sortBy(e => e.time + e.duration)
|
||||||
// Restart the timer, in case this is the first object in the hacked objects list
|
// Restart the timer, in case this is the first object in the hacked objects list
|
||||||
RestartTimer()
|
RestartTimer()
|
||||||
|
|
||||||
|
|
@ -49,6 +53,9 @@ class HackClearActor() extends Actor {
|
||||||
entry.unk1,
|
entry.unk1,
|
||||||
entry.unk2
|
entry.unk2
|
||||||
) //call up to the main event system
|
) //call up to the main event system
|
||||||
|
if (entry.target.Definition == GlobalDefinitions.main_terminal) {
|
||||||
|
ClearVirusFromBuilding(entry.target)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
RestartTimer()
|
RestartTimer()
|
||||||
|
|
@ -93,6 +100,29 @@ class HackClearActor() extends Actor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the hack timer expires on a main_terminal, clear the virus from the building and
|
||||||
|
* inform the players in the area
|
||||||
|
* @param target main_terminal object
|
||||||
|
*/
|
||||||
|
private def ClearVirusFromBuilding(target: PlanetSideServerObject): Unit = {
|
||||||
|
import net.psforever.objects.serverobject.structures.Building
|
||||||
|
import net.psforever.objects.serverobject.terminals.Terminal
|
||||||
|
import net.psforever.actors.zone.BuildingActor
|
||||||
|
import net.psforever.services.Service
|
||||||
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
|
|
||||||
|
val building = target.asInstanceOf[Terminal].Owner.asInstanceOf[Building]
|
||||||
|
building.virusId = 8
|
||||||
|
building.virusInstalledBy = None
|
||||||
|
val msg = AvatarAction.GenericObjectAction(Service.defaultPlayerGUID, target.GUID, 60)
|
||||||
|
val events = building.Zone.AvatarEvents
|
||||||
|
building.PlayersInSOI.foreach { player =>
|
||||||
|
events ! AvatarServiceMessage(player.Name, msg)
|
||||||
|
}
|
||||||
|
building.Actor ! BuildingActor.MapUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterate over entries in a `List` until an entry that does not exceed the time limit is discovered.
|
* Iterate over entries in a `List` until an entry that does not exceed the time limit is discovered.
|
||||||
* Separate the original `List` into two:
|
* Separate the original `List` into two:
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ object Zones {
|
||||||
"vanu_vehicle_station"
|
"vanu_vehicle_station"
|
||||||
)
|
)
|
||||||
private val basicTerminalTypes =
|
private val basicTerminalTypes =
|
||||||
Seq("order_terminal", "spawn_terminal", "cert_terminal", "order_terminal", "vanu_equipment_term")
|
Seq("order_terminal", "spawn_terminal", "cert_terminal", "order_terminal", "vanu_equipment_term", "main_terminal")
|
||||||
private val spawnPadTerminalTypes = Seq(
|
private val spawnPadTerminalTypes = Seq(
|
||||||
"ground_vehicle_terminal",
|
"ground_vehicle_terminal",
|
||||||
"air_vehicle_terminal",
|
"air_vehicle_terminal",
|
||||||
|
|
@ -345,6 +345,15 @@ object Zones {
|
||||||
),
|
),
|
||||||
owningBuildingGuid = buildingGuid
|
owningBuildingGuid = buildingGuid
|
||||||
)
|
)
|
||||||
|
//health module slowly heals friendly players in the soi
|
||||||
|
zoneMap.addLocalObject(
|
||||||
|
buildingGuid + 2,
|
||||||
|
ProximityTerminal.Constructor(
|
||||||
|
structure.position,
|
||||||
|
GlobalDefinitions.medical_terminal_healing_module
|
||||||
|
),
|
||||||
|
owningBuildingGuid = buildingGuid
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val filteredZoneEntities =
|
val filteredZoneEntities =
|
||||||
|
|
@ -557,7 +566,7 @@ object Zones {
|
||||||
|
|
||||||
case "adv_med_terminal" | "repair_silo" | "pad_landing_frame" | "pad_landing_tower_frame" | "medical_terminal" |
|
case "adv_med_terminal" | "repair_silo" | "pad_landing_frame" | "pad_landing_tower_frame" | "medical_terminal" |
|
||||||
"crystals_health_a" | "crystals_health_b" | "crystals_repair_a" | "crystals_repair_b" | "crystals_vehicle_a" |
|
"crystals_health_a" | "crystals_health_b" | "crystals_repair_a" | "crystals_repair_b" | "crystals_vehicle_a" |
|
||||||
"crystals_vehicle_b" | "crystals_energy_a" | "crystals_energy_b" =>
|
"crystals_vehicle_b" | "crystals_energy_a" | "crystals_energy_b" | "medical_terminal_healing_module" =>
|
||||||
zoneMap.addLocalObject(
|
zoneMap.addLocalObject(
|
||||||
obj.guid,
|
obj.guid,
|
||||||
ProximityTerminal
|
ProximityTerminal
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@ class BuildingInfoUpdateMessageTest extends Specification {
|
||||||
unk4,
|
unk4,
|
||||||
unk5,
|
unk5,
|
||||||
unk6,
|
unk6,
|
||||||
unk7,
|
virus_id,
|
||||||
unk7x,
|
virus_installed_by,
|
||||||
boost_spawn_pain,
|
boost_spawn_pain,
|
||||||
boost_generator_pain
|
boost_generator_pain
|
||||||
) =>
|
) =>
|
||||||
|
|
@ -53,8 +53,8 @@ class BuildingInfoUpdateMessageTest extends Specification {
|
||||||
unk4.isEmpty mustEqual true
|
unk4.isEmpty mustEqual true
|
||||||
unk5 mustEqual 0
|
unk5 mustEqual 0
|
||||||
unk6 mustEqual false
|
unk6 mustEqual false
|
||||||
unk7 mustEqual 8
|
virus_id mustEqual 8
|
||||||
unk7x.isEmpty mustEqual true
|
virus_installed_by.isEmpty mustEqual true
|
||||||
boost_spawn_pain mustEqual false
|
boost_spawn_pain mustEqual false
|
||||||
boost_generator_pain mustEqual false
|
boost_generator_pain mustEqual false
|
||||||
case _ =>
|
case _ =>
|
||||||
|
|
|
||||||
182
src/test/scala/game/EmpireBenefitsMessageTest.scala
Normal file
182
src/test/scala/game/EmpireBenefitsMessageTest.scala
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
// Copyright (c) 2025 PSForever
|
||||||
|
package game
|
||||||
|
|
||||||
|
import net.psforever.packet._
|
||||||
|
import net.psforever.packet.game.EmpireBenefitsMessage
|
||||||
|
import net.psforever.packet.game.EmpireBenefitsMessage.{ZoneBenefit, ZoneLock, ZoneLockBenefit, ZoneLockZone}
|
||||||
|
import net.psforever.types.PlanetSideEmpire
|
||||||
|
import org.specs2.mutable._
|
||||||
|
import scodec.bits._
|
||||||
|
|
||||||
|
class EmpireBenefitsMessageTest extends Specification {
|
||||||
|
|
||||||
|
val sample1: ByteVector = ByteVector.fromValidHex(
|
||||||
|
"d7" + // header
|
||||||
|
"04000000" + // count uint32L
|
||||||
|
"21c06c6f636b2d7a3321c06c6f636b2d7a3464006c6f636b2d69312d69322d69332d6934a1c06c6f636b2d7a39" +
|
||||||
|
"04000000" + // count uint32L
|
||||||
|
"004000600410020300"
|
||||||
|
)
|
||||||
|
|
||||||
|
val sample2: ByteVector = ByteVector.fromValidHex(
|
||||||
|
"d7" +
|
||||||
|
"05000000 23406c6f636b2d76732d686f6d657321c06c6f636b2d7a3321c06c6f636b2d7a3461c06c6f636b2d7a3964006c6f636b2d69312d69322d69332d6934" +
|
||||||
|
"05000000 004000600024010300410000"
|
||||||
|
)
|
||||||
|
|
||||||
|
val sample3: ByteVector = ByteVector.fromValidHex(
|
||||||
|
"d7" +
|
||||||
|
"05000000 21c06c6f636b2d7a3321c06c6f636b2d7a3423406c6f636b2d6e632d686f6d657361c06c6f636b2d7a39a4006c6f636b2d69312d69322d69332d6934" +
|
||||||
|
"05000000 004000600020010300810000"
|
||||||
|
)
|
||||||
|
|
||||||
|
val sample4: ByteVector = ByteVector.fromValidHex(
|
||||||
|
"d7" +
|
||||||
|
"06000000 a3406c6f636b2d6e632d686f6d6573a3406c6f636b2d74722d686f6d6573a1c06c6f636b2d7a33a1c06c6f636b2d7a34a1c06c6f636b2d7a39a4006c6f636b2d69312d69322d69332d6934" +
|
||||||
|
"06000000 80402030081002060081c0208000"
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sample1_expectedLocks = Vector(
|
||||||
|
ZoneLock(PlanetSideEmpire.TR, ZoneLockZone.z3),
|
||||||
|
ZoneLock(PlanetSideEmpire.TR, ZoneLockZone.z4),
|
||||||
|
ZoneLock(PlanetSideEmpire.NC, ZoneLockZone.i1_i2_i3_i4),
|
||||||
|
ZoneLock(PlanetSideEmpire.VS, ZoneLockZone.z9)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sample1_expectedBenefits = Vector(
|
||||||
|
ZoneBenefit(PlanetSideEmpire.TR, ZoneLockBenefit.z4),
|
||||||
|
ZoneBenefit(PlanetSideEmpire.TR, ZoneLockBenefit.z3),
|
||||||
|
ZoneBenefit(PlanetSideEmpire.NC, ZoneLockBenefit.i1_i2_i3_i4),
|
||||||
|
ZoneBenefit(PlanetSideEmpire.VS, ZoneLockBenefit.z9)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sample2_expectedLocks = Vector(
|
||||||
|
ZoneLock(PlanetSideEmpire.TR, ZoneLockZone.vs_homes),
|
||||||
|
ZoneLock(PlanetSideEmpire.TR, ZoneLockZone.z3),
|
||||||
|
ZoneLock(PlanetSideEmpire.TR, ZoneLockZone.z4),
|
||||||
|
ZoneLock(PlanetSideEmpire.NC, ZoneLockZone.z9),
|
||||||
|
ZoneLock(PlanetSideEmpire.NC, ZoneLockZone.i1_i2_i3_i4)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sample2_expectedBenefits = Vector(
|
||||||
|
ZoneBenefit(PlanetSideEmpire.TR, ZoneLockBenefit.z4),
|
||||||
|
ZoneBenefit(PlanetSideEmpire.TR, ZoneLockBenefit.z3),
|
||||||
|
ZoneBenefit(PlanetSideEmpire.TR, ZoneLockBenefit.vs_homes),
|
||||||
|
ZoneBenefit(PlanetSideEmpire.NC, ZoneLockBenefit.z9),
|
||||||
|
ZoneBenefit(PlanetSideEmpire.NC, ZoneLockBenefit.i1_i2_i3_i4)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sample3_expectedLocks = Vector(
|
||||||
|
ZoneLock(PlanetSideEmpire.TR, ZoneLockZone.z3),
|
||||||
|
ZoneLock(PlanetSideEmpire.TR, ZoneLockZone.z4),
|
||||||
|
ZoneLock(PlanetSideEmpire.TR, ZoneLockZone.nc_homes),
|
||||||
|
ZoneLock(PlanetSideEmpire.NC, ZoneLockZone.z9),
|
||||||
|
ZoneLock(PlanetSideEmpire.VS, ZoneLockZone.i1_i2_i3_i4)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sample3_expectedBenefits = Vector(
|
||||||
|
ZoneBenefit(PlanetSideEmpire.TR, ZoneLockBenefit.z4),
|
||||||
|
ZoneBenefit(PlanetSideEmpire.TR, ZoneLockBenefit.z3),
|
||||||
|
ZoneBenefit(PlanetSideEmpire.TR, ZoneLockBenefit.nc_homes),
|
||||||
|
ZoneBenefit(PlanetSideEmpire.NC, ZoneLockBenefit.z9),
|
||||||
|
ZoneBenefit(PlanetSideEmpire.VS, ZoneLockBenefit.i1_i2_i3_i4)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sample4_expectedLocks = Vector(
|
||||||
|
ZoneLock(PlanetSideEmpire.VS, ZoneLockZone.nc_homes),
|
||||||
|
ZoneLock(PlanetSideEmpire.VS, ZoneLockZone.tr_homes),
|
||||||
|
ZoneLock(PlanetSideEmpire.VS, ZoneLockZone.z3),
|
||||||
|
ZoneLock(PlanetSideEmpire.VS, ZoneLockZone.z4),
|
||||||
|
ZoneLock(PlanetSideEmpire.VS, ZoneLockZone.z9),
|
||||||
|
ZoneLock(PlanetSideEmpire.VS, ZoneLockZone.i1_i2_i3_i4)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sample4_expectedBenefits = Vector(
|
||||||
|
ZoneBenefit(PlanetSideEmpire.VS, ZoneLockBenefit.z4),
|
||||||
|
ZoneBenefit(PlanetSideEmpire.VS, ZoneLockBenefit.z9),
|
||||||
|
ZoneBenefit(PlanetSideEmpire.VS, ZoneLockBenefit.i1_i2_i3_i4),
|
||||||
|
ZoneBenefit(PlanetSideEmpire.VS, ZoneLockBenefit.z3),
|
||||||
|
ZoneBenefit(PlanetSideEmpire.VS, ZoneLockBenefit.tr_homes),
|
||||||
|
ZoneBenefit(PlanetSideEmpire.VS, ZoneLockBenefit.nc_homes)
|
||||||
|
)
|
||||||
|
|
||||||
|
"decode sample1" in {
|
||||||
|
PacketCoding.decodePacket(sample1).require match {
|
||||||
|
case EmpireBenefitsMessage(a, b) =>
|
||||||
|
a mustEqual sample1_expectedLocks
|
||||||
|
b mustEqual sample1_expectedBenefits
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode sample1" in {
|
||||||
|
val msg = EmpireBenefitsMessage(
|
||||||
|
zoneLocks = sample1_expectedLocks,
|
||||||
|
zoneBenefits = sample1_expectedBenefits
|
||||||
|
)
|
||||||
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
|
pkt mustEqual sample1
|
||||||
|
}
|
||||||
|
|
||||||
|
"decode sample2" in {
|
||||||
|
PacketCoding.decodePacket(sample2).require match {
|
||||||
|
case EmpireBenefitsMessage(a, b) =>
|
||||||
|
a mustEqual sample2_expectedLocks
|
||||||
|
b mustEqual sample2_expectedBenefits
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode sample2" in {
|
||||||
|
val msg = EmpireBenefitsMessage(
|
||||||
|
zoneLocks = sample2_expectedLocks,
|
||||||
|
zoneBenefits = sample2_expectedBenefits
|
||||||
|
)
|
||||||
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
|
pkt mustEqual sample2
|
||||||
|
}
|
||||||
|
|
||||||
|
"decode sample3" in {
|
||||||
|
PacketCoding.decodePacket(sample3).require match {
|
||||||
|
case EmpireBenefitsMessage(a, b) =>
|
||||||
|
a mustEqual sample3_expectedLocks
|
||||||
|
b mustEqual sample3_expectedBenefits
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode sample3" in {
|
||||||
|
val msg = EmpireBenefitsMessage(
|
||||||
|
zoneLocks = sample3_expectedLocks,
|
||||||
|
zoneBenefits = sample3_expectedBenefits
|
||||||
|
)
|
||||||
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
|
pkt mustEqual sample3
|
||||||
|
}
|
||||||
|
|
||||||
|
"decode sample4" in {
|
||||||
|
PacketCoding.decodePacket(sample4).require match {
|
||||||
|
case EmpireBenefitsMessage(a, b) =>
|
||||||
|
a mustEqual sample4_expectedLocks
|
||||||
|
b mustEqual sample4_expectedBenefits
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode sample4" in {
|
||||||
|
val msg = EmpireBenefitsMessage(
|
||||||
|
zoneLocks = sample4_expectedLocks,
|
||||||
|
zoneBenefits = sample4_expectedBenefits
|
||||||
|
)
|
||||||
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
|
pkt mustEqual sample4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -39,7 +39,7 @@ class MountedVehiclesTest extends Specification {
|
||||||
vdata.no_mount_points mustEqual false
|
vdata.no_mount_points mustEqual false
|
||||||
vdata.driveState mustEqual DriveState.Mobile
|
vdata.driveState mustEqual DriveState.Mobile
|
||||||
vdata.cloak mustEqual false
|
vdata.cloak mustEqual false
|
||||||
vdata.unk3 mustEqual false
|
vdata.boostMaxHealth mustEqual false
|
||||||
vdata.unk4 mustEqual false
|
vdata.unk4 mustEqual false
|
||||||
vdata.unk5 mustEqual false
|
vdata.unk5 mustEqual false
|
||||||
vdata.unk6 mustEqual false
|
vdata.unk6 mustEqual false
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ class UtilityVehiclesTest extends Specification {
|
||||||
ant.driveState mustEqual DriveState.Mobile
|
ant.driveState mustEqual DriveState.Mobile
|
||||||
ant.health mustEqual 255
|
ant.health mustEqual 255
|
||||||
ant.cloak mustEqual false
|
ant.cloak mustEqual false
|
||||||
ant.unk3 mustEqual false
|
ant.boostMaxHealth mustEqual false
|
||||||
ant.unk4 mustEqual false
|
ant.unk4 mustEqual false
|
||||||
ant.unk5 mustEqual false
|
ant.unk5 mustEqual false
|
||||||
ant.unk6 mustEqual false
|
ant.unk6 mustEqual false
|
||||||
|
|
@ -67,7 +67,7 @@ class UtilityVehiclesTest extends Specification {
|
||||||
ams.vehicle_format_data mustEqual Some(UtilityVehicleData(60))
|
ams.vehicle_format_data mustEqual Some(UtilityVehicleData(60))
|
||||||
ams.health mustEqual 236
|
ams.health mustEqual 236
|
||||||
ams.cloak mustEqual true
|
ams.cloak mustEqual true
|
||||||
ams.unk3 mustEqual false
|
ams.boostMaxHealth mustEqual false
|
||||||
ams.unk4 mustEqual false
|
ams.unk4 mustEqual false
|
||||||
ams.unk5 mustEqual false
|
ams.unk5 mustEqual false
|
||||||
ams.unk6 mustEqual true
|
ams.unk6 mustEqual true
|
||||||
|
|
@ -120,7 +120,7 @@ class UtilityVehiclesTest extends Specification {
|
||||||
case _ =>
|
case _ =>
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
ams.unk3 mustEqual false
|
ams.boostMaxHealth mustEqual false
|
||||||
ams.health mustEqual 255
|
ams.health mustEqual 255
|
||||||
ams.unk4 mustEqual false
|
ams.unk4 mustEqual false
|
||||||
ams.no_mount_points mustEqual false
|
ams.no_mount_points mustEqual false
|
||||||
|
|
@ -320,7 +320,7 @@ class UtilityVehiclesTest extends Specification {
|
||||||
Some(Vector3(27.3375f, -0.78749996f, 0.1125f))
|
Some(Vector3(27.3375f, -0.78749996f, 0.1125f))
|
||||||
),
|
),
|
||||||
CommonFieldData(PlanetSideEmpire.TR, false, false, false, None, false, Some(false), None, PlanetSideGUID(3087)),
|
CommonFieldData(PlanetSideEmpire.TR, false, false, false, None, false, Some(false), None, PlanetSideGUID(3087)),
|
||||||
unk3 = false,
|
boostMaxHealth = false,
|
||||||
health = 255,
|
health = 255,
|
||||||
unk4 = false,
|
unk4 = false,
|
||||||
no_mount_points = false,
|
no_mount_points = false,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue