Merge pull request #1332 from ScrawnyRonnie/cont-lock
Some checks failed
Publish Docs / docs (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Test / test (push) Has been cancelled

Continental Locks and Benefits
This commit is contained in:
Dethdeath 2025-12-12 20:21:34 +01:00 committed by GitHub
commit 3358a2f0ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 176 additions and 53 deletions

View file

@ -60,10 +60,6 @@ add_property maelstrom equiptime 1000
add_property maelstrom holstertime 1000
add_property magcutter equiptime 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 holstertime 750
add_property nano_dispenser equiptime 750

View file

@ -150,6 +150,11 @@ object MiddlewareActor {
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.
* Do nothing.
@ -259,7 +264,8 @@ class MiddlewareActor(
MiddlewareActor.keepAliveMessageGuard,
MiddlewareActor.characterInfoMessageGuard,
MiddlewareActor.squadDetailDefinitionMessageGuard,
MiddlewareActor.chatMsgGuard
MiddlewareActor.chatMsgGuard,
MiddlewareActor.propertyOverrideMessageGuard
)
private val smpHistoryLength: Int = 100

View file

@ -2218,12 +2218,12 @@ class AvatarActor(
.Holsters()
.foreach(holster =>
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)
val item = SimpleItem(GlobalDefinitions.flail_targeting_laser)
holster.Equipment = None
holster.Equipment = item
item.GUID = PlanetSideGUID(gen.getAndIncrement)
item.GUID = PlanetSideGUID(gen.getAndIncrement)*/
case Some(tool: Tool) =>
tool.AmmoSlots.foreach(slot => {
slot.Box.GUID = PlanetSideGUID(gen.getAndIncrement)

View file

@ -12,7 +12,7 @@ import net.psforever.actors.session.{AvatarActor, SessionActor}
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.LivePlayerList
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.services.chat.{DefaultChannel, OutfitChannel, SquadChannel}
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 org.log4s.Logger
import java.util.concurrent.{Executors, TimeUnit}
import java.util.concurrent.{Executors, ScheduledFuture, TimeUnit}
import scala.annotation.unused
import scala.collection.{Seq, mutable}
import scala.concurrent.ExecutionContext.Implicits.global
@ -368,7 +368,9 @@ class ChatOperations(
case (Some(buildings), Some(faction), Some(_)) =>
//TODO implement timer
//schedule processing of buildings with a delay
processBuildingsWithDelay(buildings, faction, 1000) //delay of 1000ms between each building operation
processBuildingsWithDelay(buildings, faction, 1000) { zone =>
zone.actor ! ZoneActor.AssignLockedBy(zone, notifyPlayers=true)
}
true
case _ =>
false
@ -379,30 +381,30 @@ class ChatOperations(
buildings: Seq[Building],
faction: PlanetSideEmpire.Value,
delayMillis: Long
): Unit = {
val buildingIterator = buildings.iterator
scheduler.scheduleAtFixedRate(
)(onComplete: Zone => Unit): Unit = {
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) {
val building = buildingIterator.next()
if (iterator.hasNext) {
val building = iterator.next()
val terminal = building.CaptureTerminal.get
val zone = building.Zone
val zoneActor = zone.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.AmenityStateChange(terminal, Some(false))
//push for map updates again
building.Actor ! BuildingActor.SetFaction(faction)
building.Actor ! BuildingActor.AmenityStateChange(terminal, Some(false))
zoneActor ! ZoneActor.ZoneMapUpdate()
} else {
handle.cancel(false)
onComplete(zone)
}
},
0,

View file

@ -54,7 +54,7 @@ import net.psforever.objects.serverobject.turret.FacilityTurret
import net.psforever.objects.vehicles._
import net.psforever.objects.zones.{Zone, ZoneHotSpotProjector, Zoning}
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.{BeginZoningMessage, DroppodLaunchRequestMessage, ReleaseAvatarRequestMessage, SpawnRequestMessage, WarpgateRequest}
import net.psforever.packet.game.DeathStatistic
@ -559,18 +559,12 @@ class ZoningOperations(
val popTR = zone.Players.count(_.faction == PlanetSideEmpire.TR)
val popNC = zone.Players.count(_.faction == PlanetSideEmpire.NC)
val popVS = zone.Players.count(_.faction == PlanetSideEmpire.VS)
zone.Buildings.foreach({ case (_, building) => initBuilding(continentNumber, building.MapId, building) })
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)
sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NC))
else if (continentNumber == 12)
sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.TR))
else if (continentNumber == 13)
sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.VS))
else
if (continentNumber == 11 || continentNumber == 12 || continentNumber == 13)
sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NEUTRAL))
else
sendResponse(ContinentalLockUpdateMessage(continentNumber, zone.lockedBy))
//CaptureFlagUpdateMessage()
//VanuModuleUpdateMessage()
//ModuleLimitsMessage()
@ -640,6 +634,8 @@ class ZoningOperations(
)
}
}
player.Zone.ApplyHomeLockBenefitsOnLogin(player)
SessionOutfitHandlers.HandleLoginOutfitCheck(player, sessionLogic)
}
def handleZoneResponse(foundZone: Zone): Unit = {
@ -2092,7 +2088,7 @@ class ZoningOperations(
session = session.copy(player = p, avatar = a)
sessionLogic.persist()
setupAvatarFunc = AvatarRejoin
dropMedicalApplicators(p)
//dropMedicalApplicators(p)
avatarActor ! AvatarActor.ReplaceAvatar(a)
avatarLoginResponse(a)
@ -2102,7 +2098,7 @@ class ZoningOperations(
deadState = DeadState.Dead
session = session.copy(player = p, avatar = a)
sessionLogic.persist()
dropMedicalApplicators(p)
//dropMedicalApplicators(p)
HandleReleaseAvatar(p, inZone)
avatarActor ! AvatarActor.ReplaceAvatar(a)
avatarLoginResponse(a)
@ -2546,9 +2542,6 @@ class ZoningOperations(
sessionLogic.general.toggleTeleportSystem(obj, TelepadLike.AppraiseTeleportationSystem(obj, continent))
}
}
if (player.outfit_id == 0) {
SessionOutfitHandlers.HandleLoginOutfitCheck(player, sessionLogic)
}
/*make weather happen
sendResponse(WeatherMessage(List(),List(
StormInfo(Vector3(0.1f, 0.15f, 0.0f), 240, 217),
@ -2672,7 +2665,6 @@ class ZoningOperations(
log.debug(s"AvatarRejoin: ${player.Name} - $guid -> $data")
}
setupAvatarFunc = AvatarCreate
SessionOutfitHandlers.HandleLoginOutfitCheck(player, sessionLogic)
/*make weather happen
sendResponse(WeatherMessage(List(),List(
StormInfo(Vector3(0.1f, 0.15f, 0.0f), 240, 217),

View file

@ -8,6 +8,8 @@ import net.psforever.actors.commands.NtuCommand
import net.psforever.actors.zone.building._
import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate}
import net.psforever.objects.zones.Zone
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.ContinentalLockUpdateMessage
import net.psforever.persistence
import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
@ -76,6 +78,9 @@ object BuildingActor {
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.
* @param details building and event system references
@ -252,6 +257,14 @@ class BuildingActor(
case DensityLevelUpdate(building) =>
details.galaxyService ! GalaxyServiceMessage(GalaxyAction.SendResponse(details.building.densityLevelUpdateMessage(building)))
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
}
}
}

View file

@ -78,6 +78,8 @@ object ZoneActor {
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 AssignLockedBy(zone: Zone, notifyPlayers: Boolean) extends Command
}
class ZoneActor(
@ -115,6 +117,7 @@ class ZoneActor(
// 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)
}
@ -187,10 +190,29 @@ class ZoneActor(
.values
.foreach(_.Actor ! BuildingActor.MapUpdate())
Behaviors.same
case AssignLockedBy(zone, notifyPlayers) =>
AssignLockedBy(zone, notifyPlayers)
Behaviors.same
}
.receiveSignal {
case (_, PostStop) =>
Behaviors.same
}
}
def AssignLockedBy(zone: Zone, notifyPlayers: Boolean): Unit = {
val buildings = zone.Buildings.values
val facilities = 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) }
}
}

View file

@ -32,9 +32,9 @@ import scalax.collection.GraphEdge._
import scala.util.Try
import akka.actor.typed
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.objects.avatar.Avatar
import net.psforever.objects.avatar.{Avatar, PlayerControl}
import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.geometry.d3.VolumetricGeometry
import net.psforever.objects.guid.pool.NumberPool
@ -56,6 +56,7 @@ import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
import net.psforever.objects.vital.prop.DamageWithPosition
import net.psforever.objects.vital.Vitality
import net.psforever.objects.zones.blockmap.{BlockMap, SectorPopulation}
import net.psforever.packet.game.PropertyOverrideMessage
import net.psforever.services.Service
import net.psforever.zones.Zones
@ -194,6 +195,16 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
*/
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.
* @see `init(ActorContext)`
@ -639,6 +650,79 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
}
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 prowler threemanheavybuggy",
PlanetSideEmpire.VS -> "magrider twomanhoverbuggy aurora",
PlanetSideEmpire.NC -> "thunderer twomanheavybuggy vanguard"
)
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 msg = 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(msg)
}
}
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 prowler threemanheavybuggy",
PlanetSideEmpire.VS -> "magrider twomanhoverbuggy aurora",
PlanetSideEmpire.NC -> "thunderer twomanheavybuggy vanguard"
)
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 msg = 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, msg)
}
}
}
/**

View file

@ -275,19 +275,27 @@ class HackCaptureActor extends Actor {
.collect { case p if p.Faction == hackedByFaction =>
events ! LocalServiceMessage(p.Name, msg)
}
val zoneBases = building.Zone.Buildings.filter(base =>
base._2.BuildingType == StructureType.Facility)
val ownedBases = building.Zone.Buildings.filter(base =>
base._2.BuildingType == StructureType.Facility && base._2.Faction == hackedByFaction
&& base._2.GUID != building.GUID)
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)
val buildings = building.Zone.Buildings.values
val hackedBaseId = building.GUID
val facilities = 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, 1000)
}
}
else if (building.Zone.lockedBy != PlanetSideEmpire.NEUTRAL) {
building.Zone.lockedBy = PlanetSideEmpire.NEUTRAL
building.Zone.NotifyContinentalLockBenefits(building.Zone, building)
}
} else {
log.info("Base hack completed, but base was out of NTU.")
}