From d03954d6a6d7b4079f5266bfd97861a1d1715add Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Mon, 11 Nov 2024 15:59:31 -0500 Subject: [PATCH] field turrets are neutral-neutral when constructed by a csr; custom bang command 'setempire' will adjust the faction affiliation of a variety of game objects --- .../actors/session/csr/ChatLogic.scala | 84 ++++++++++++++++++- .../actors/session/csr/GeneralLogic.scala | 19 +++-- .../session/support/ChatOperations.scala | 36 +++++--- .../session/support/GeneralOperations.scala | 25 ++++-- .../session/support/ZoningOperations.scala | 19 +++-- 5 files changed, 149 insertions(+), 34 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/csr/ChatLogic.scala b/src/main/scala/net/psforever/actors/session/csr/ChatLogic.scala index 3ec8216ab..972474ffb 100644 --- a/src/main/scala/net/psforever/actors/session/csr/ChatLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/ChatLogic.scala @@ -5,13 +5,20 @@ import akka.actor.ActorContext import net.psforever.actors.session.SessionActor import net.psforever.actors.session.normal.NormalMode import net.psforever.actors.session.support.{ChatFunctions, ChatOperations, SessionData} -import net.psforever.objects.Session +import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Session, TurretDeployable} +import net.psforever.objects.ce.{Deployable, DeployableCategory} +import net.psforever.objects.serverobject.affinity.FactionAffinity +import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} +import net.psforever.objects.serverobject.hackable.Hackable +import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.zones.Zone import net.psforever.packet.game.{ChatMsg, SetChatFilterMessage} import net.psforever.services.Service import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.chat.{ChatChannel, DefaultChannel, SpectatorChannel} -import net.psforever.types.ChatMessageType +import net.psforever.types.{ChatMessageType, PlanetSideEmpire} + +import scala.util.Success object ChatLogic { def apply(ops: ChatOperations): ChatLogic = { @@ -207,6 +214,7 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext case "showspectators" => customCommandShowSpectators() case "hidespectators" => customCommandHideSpectators() case "sayspectator" => customCommandSpeakAsSpectator(params, message) + case "setempire" => customCommandSetEmpire(params) case _ => // command was not handled sendResponse( @@ -309,6 +317,78 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext true } + private def customCommandSetEmpire(params: Seq[String]): Boolean = { + var postUsage: Boolean = false + val (entityOpt, foundFaction) = (params.headOption, params.lift(1)) match { + case (Some(guid), Some(faction)) if guid.toIntOption.nonEmpty => + try { + (continent.GUID(guid.toInt), PlanetSideEmpire.apply(faction)) + } catch { + case _: Exception => + (None, PlanetSideEmpire.NEUTRAL) + } + case (Some(guid), None) if guid.toIntOption.nonEmpty => + (continent.GUID(guid.toInt), player.Faction) + case _ => + postUsage = true + (None, PlanetSideEmpire.NEUTRAL) + } + entityOpt + .collect { + case f: FactionAffinity if f.Faction != foundFaction && foundFaction != PlanetSideEmpire.NEUTRAL => f + } + .collect { + case o: TurretDeployable + if o.Definition.DeployCategory == DeployableCategory.FieldTurrets => + //remove prior turret and construct new one + import scala.concurrent.ExecutionContext.Implicits.global + import scala.concurrent.duration._ + o.Actor ! Deployable.Deconstruct(Some(2.seconds)) + sessionLogic.general.handleDeployObject( + continent, + GlobalDefinitions.PortableMannedTurret(foundFaction).Item, + o.Position, + o.Orientation, + o.WhichSide, + foundFaction + ).onComplete { + case Success(obj2) => sendResponse(ChatMsg(ChatMessageType.UNK_227, s"${obj2.GUID.guid}")) + case _ => () + } + true + case o: Deployable => + o.Faction = foundFaction + continent.AvatarEvents ! AvatarServiceMessage( + continent.id, + AvatarAction.SetEmpire(Service.defaultPlayerGUID, o.GUID, foundFaction) + ) + true + case o: Building => + ops.commandCaptureBaseProcessResults(Some(Seq(o)), Some(foundFaction), Some(1)) + true + case o: PlanetSideServerObject with Hackable => + o.Actor ! CommonMessages.Hack(player, o) + true + case o: PlanetSideGameObject with FactionAffinity => + o.Faction = foundFaction + continent.AvatarEvents ! AvatarServiceMessage( + continent.id, + AvatarAction.SetEmpire(Service.defaultPlayerGUID, o.GUID, foundFaction) + ) + true + } + .getOrElse { + if (postUsage) { + sendResponse(ChatMsg(ChatMessageType.UNK_227, "!setempire guid [faction]")) + } else if (entityOpt.nonEmpty) { + sendResponse(ChatMsg(ChatMessageType.UNK_227, "set empire entity not supported")) + } else { + sendResponse(ChatMsg(ChatMessageType.UNK_227, "set empire entity not found")) + } + true + } + } + override def stop(): Unit = { super.stop() seeSpectatorsIn.foreach(_ => customCommandHideSpectators()) diff --git a/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala b/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala index 7692a4501..68f59b434 100644 --- a/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala @@ -7,7 +7,7 @@ import net.psforever.actors.session.support.{GeneralFunctions, GeneralOperations import net.psforever.objects.{Account, BoomerDeployable, BoomerTrigger, ConstructionItem, GlobalDefinitions, LivePlayerList, Player, SensorDeployable, ShieldGeneratorDeployable, SpecialEmp, TelepadDeployable, Tool, TrapDeployable, TurretDeployable, Vehicle} import net.psforever.objects.avatar.{Avatar, PlayerControl} import net.psforever.objects.ballistics.Projectile -import net.psforever.objects.ce.{Deployable, DeployedItem} +import net.psforever.objects.ce.Deployable import net.psforever.objects.definition.{BasicDefinition, KitDefinition, SpecialExoSuitDefinition} import net.psforever.objects.entity.WorldEntity import net.psforever.objects.equipment.Equipment @@ -30,10 +30,12 @@ import net.psforever.objects.vehicles.Utility import net.psforever.objects.vital.Vitality import net.psforever.objects.zones.{ZoneProjectile, Zoning} import net.psforever.packet.PlanetSideGamePacket -import net.psforever.packet.game.{ActionCancelMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, CollisionIs, ConnectToWorldRequestMessage, CreateShortcutMessage, DeadState, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, RequestDestroyMessage, TargetingImplantRequest, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage} +import net.psforever.packet.game.{ActionCancelMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, ChatMsg, CollisionIs, ConnectToWorldRequestMessage, CreateShortcutMessage, DeadState, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, RequestDestroyMessage, TargetingImplantRequest, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage} import net.psforever.services.RemoverActor import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} -import net.psforever.types.{CapacitorStateType, Cosmetic, ExoSuitType, PlanetSideEmpire, PlanetSideGUID, Vector3} +import net.psforever.types.{CapacitorStateType, ChatMessageType, Cosmetic, ExoSuitType, PlanetSideEmpire, PlanetSideGUID, Vector3} + +import scala.util.Success object GeneralLogic { def apply(ops: GeneralOperations): GeneralLogic = { @@ -354,15 +356,16 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex def handleDeployObject(pkt: DeployObjectMessage): Unit = { if (!player.spectator) { + import scala.concurrent.ExecutionContext.Implicits.global val DeployObjectMessage(guid, _, pos, orient, _) = pkt player.Holsters().find(slot => slot.Equipment.nonEmpty && slot.Equipment.get.GUID == guid).flatMap { slot => slot.Equipment } match { case Some(obj: ConstructionItem) => - val ammoType = obj.AmmoType match { - case DeployedItem.portable_manned_turret => GlobalDefinitions.PortableMannedTurret(player.Faction).Item - case dtype => dtype - } sessionLogic.zoning.CancelZoningProcess() - ops.handleDeployObject(continent, ammoType, pos, orient, player.WhichSide, PlanetSideEmpire.NEUTRAL) + val result = ops.handleDeployObject(continent, obj.AmmoType, pos, orient, player.WhichSide, PlanetSideEmpire.NEUTRAL) + result.onComplete { + case Success(obj) => sendResponse(ChatMsg(ChatMessageType.UNK_227, s"${obj.GUID.guid}")) + case _ => () + } case Some(obj) => log.warn(s"DeployObject: what is $obj, ${player.Name}? It's not a construction tool!") case None => diff --git a/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala b/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala index 2eaad6e9a..a1e705014 100644 --- a/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala @@ -327,6 +327,28 @@ class ChatOperations( } } //evaluate results + if (!commandCaptureBaseProcessResults(resolvedFacilities, resolvedFaction, resolvedTimer)) { + if (usageMessage) { + sendResponse( + message.copy(messageType = UNK_229, contents = "@CMT_CAPTUREBASE_usage") + ) + } else { + val msg = if (facilityError == 1) { "can not contextually determine building target" } + else if (facilityError == 2) { s"\'${foundFacilitiesTag.get}\' is not a valid building name" } + else if (factionError) { s"\'${foundFactionTag.get}\' is not a valid faction designation" } + else if (timerError) { s"\'${foundTimerTag.get}\' is not a valid timer value" } + else { "malformed params; check usage" } + sendResponse(ChatMsg(UNK_229, wideContents=true, "", s"\\#FF4040ERROR - $msg", None)) + } + } + } + + def commandCaptureBaseProcessResults( + resolvedFacilities: Option[Seq[Building]], + resolvedFaction: Option[PlanetSideEmpire.Value], + resolvedTimer: Option[Int] + ): Boolean = { + //evaluate results (resolvedFacilities, resolvedFaction, resolvedTimer) match { case (Some(buildings), Some(faction), Some(_)) => buildings.foreach { building => @@ -350,19 +372,9 @@ class ChatOperations( //push for map updates again zoneActor ! ZoneActor.ZoneMapUpdate() } + true case _ => - if (usageMessage) { - sendResponse( - message.copy(messageType = UNK_229, contents = "@CMT_CAPTUREBASE_usage") - ) - } else { - val msg = if (facilityError == 1) { "can not contextually determine building target" } - else if (facilityError == 2) { s"\'${foundFacilitiesTag.get}\' is not a valid building name" } - else if (factionError) { s"\'${foundFactionTag.get}\' is not a valid faction designation" } - else if (timerError) { s"\'${foundTimerTag.get}\' is not a valid timer value" } - else { "malformed params; check usage" } - sendResponse(ChatMsg(UNK_229, wideContents=true, "", s"\\#FF4040ERROR - $msg", None)) - } + false } } diff --git a/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala b/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala index e39619e13..181a1e746 100644 --- a/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala @@ -20,7 +20,9 @@ import net.psforever.services.local.{LocalAction, LocalServiceMessage} import scala.collection.mutable import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.{Future, Promise} import scala.concurrent.duration._ +import scala.util.Success // import net.psforever.actors.session.{AvatarActor, SessionActor} import net.psforever.login.WorldSession._ @@ -1060,16 +1062,22 @@ class GeneralOperations( faction: PlanetSideEmpire.Value, owner: Player, builtWith: ConstructionItem - ): Unit = { + ): Future[Deployable] = { val (deployableEntity, tasking) = commonHandleDeployObjectSetup(zone, deployableType, position, orientation, side, faction) deployableEntity.AssignOwnership(owner) + val promisedDeployable: Promise[Deployable] = Promise() //execute - TaskWorkflow.execute(CallBackForTask( + val result = TaskWorkflow.execute(CallBackForTask( tasking, zone.Deployables, Zone.Deployable.BuildByOwner(deployableEntity, owner, builtWith), context.self )) + result.onComplete { + case Success(_) => promisedDeployable.success(deployableEntity) + case _ => () + } + promisedDeployable.future } def handleDeployObject( @@ -1079,16 +1087,18 @@ class GeneralOperations( orientation: Vector3, side: Sidedness, faction: PlanetSideEmpire.Value - ): Unit = { + ): Future[Deployable] = { val (deployableEntity, tasking) = commonHandleDeployObjectSetup(zone, deployableType, position, orientation, side, faction) + val promisedDeployable: Promise[Deployable] = Promise() //execute - TaskWorkflow.execute(CallBackForTask( + val result = TaskWorkflow.execute(CallBackForTask( tasking, zone.Deployables, Zone.Deployable.Build(deployableEntity), context.self - )).onComplete { - _ => + )) + result.onComplete { + case Success(_) => Players.buildCooldownReset(zone, player.Name, deployableEntity.GUID) deployableEntity.Actor ! Deployable.Deconstruct(Some(20.minutes)) if (deployableType == DeployedItem.boomer) { @@ -1101,7 +1111,10 @@ class GeneralOperations( Zone.Ground.DropItem(trigger, position + Vector3.z(value = 0.5f), Vector3.z(orientation.z)) )) } + promisedDeployable.success(deployableEntity) + case _ => () } + promisedDeployable.future } def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = { diff --git a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala index cf6c0f1ea..ddae723f0 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -158,6 +158,18 @@ object ZoningOperations { events ! LocalServiceMessage(target, soundMessage) } } + + def findBuildingsBySoiOccupancy(zone: Zone, position: Vector3): List[Building] = { + val positionxy = position.xy + zone + .blockMap + .sector(positionxy, range=5) + .buildingList + .filter { building => + val radius = building.Definition.SOIRadius + Vector3.DistanceSquared(building.Position.xy, positionxy) < radius * radius + } + } } class ZoningOperations( @@ -868,12 +880,7 @@ class ZoningOperations( val location = if (Zones.sanctuaryZoneNumber(player.Faction) == continent.Number) { Zoning.Time.Sanctuary } else { - val playerPosition = player.Position.xy - continent.Buildings.values - .filter { building => - val radius = building.Definition.SOIRadius - Vector3.DistanceSquared(building.Position.xy, playerPosition) < radius * radius - } match { + ZoningOperations.findBuildingsBySoiOccupancy(continent, player.Position) match { case Nil => Zoning.Time.None case List(building: FactionAffinity) =>