From d1e8da208b75b3a5955affe94194ece7f56f89db Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Sun, 8 Jun 2025 19:48:28 -0400 Subject: [PATCH 1/6] bail malfunction, gen normal state, stop vehicle momentum --- .../session/csr/MountHandlerLogic.scala | 4 +++ .../session/normal/MountHandlerLogic.scala | 4 +++ .../support/SessionMountHandlers.scala | 10 +++--- .../generator/GeneratorControl.scala | 4 +++ .../vehicles/control/VehicleControl.scala | 33 ++++++++++++++++++- 5 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/csr/MountHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/csr/MountHandlerLogic.scala index 5ca48d68..1846f069 100644 --- a/src/main/scala/net/psforever/actors/session/csr/MountHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/MountHandlerLogic.scala @@ -297,6 +297,10 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act sendResponse(ChatMsg(ChatMessageType.UNK_224, "@SA_CannotBailAtThisTime")) } + case Mountable.CanNotDismount(obj: Vehicle, _, BailType.Bailed) + if obj.Health <= (obj.MaxHealth * .35).round && GlobalDefinitions.isFlightVehicle(obj.Definition) => + sendResponse(ChatMsg(ChatMessageType.UNK_224, "@BailingMechanismFailure_Pilot")) + case Mountable.CanNotDismount(obj: Vehicle, _, BailType.Bailed) if { continent diff --git a/src/main/scala/net/psforever/actors/session/normal/MountHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/MountHandlerLogic.scala index 2a6249d6..ccd99554 100644 --- a/src/main/scala/net/psforever/actors/session/normal/MountHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/MountHandlerLogic.scala @@ -312,6 +312,10 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act if obj.DeploymentState == DriveState.AutoPilot => sendResponse(ChatMsg(ChatMessageType.UNK_224, "@SA_CannotBailAtThisTime")) + case Mountable.CanNotDismount(obj: Vehicle, _, BailType.Bailed) + if obj.Health <= (obj.MaxHealth * .35).round && GlobalDefinitions.isFlightVehicle(obj.Definition) => + sendResponse(ChatMsg(ChatMessageType.UNK_224, "@BailingMechanismFailure_Pilot")) + case Mountable.CanNotDismount(obj: Vehicle, _, BailType.Bailed) if { continent diff --git a/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala index 3ee687b1..905d2ca0 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala @@ -202,10 +202,10 @@ class SessionMountHandlers( //until vehicles maintain synchronized momentum without a driver obj match { case v: Vehicle - if seatNum == 0 && Vector3.MagnitudeSquared(v.Velocity.getOrElse(Vector3.Zero)) > 0f => - sessionLogic.vehicles.serverVehicleControlVelocity.collect { _ => + if seatNum == 0 => + /*sessionLogic.vehicles.serverVehicleControlVelocity.collect { _ => sessionLogic.vehicles.ServerVehicleOverrideStop(v) - } + }*/ v.Velocity = Vector3.Zero continent.VehicleEvents ! VehicleServiceMessage( continent.id, @@ -213,9 +213,9 @@ class SessionMountHandlers( tplayer.GUID, v.GUID, unk1 = 0, - v.Position, + tplayer.Position, v.Orientation, - vel = None, + v.Velocity, v.Flying, unk3 = 0, unk4 = 0, diff --git a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala index 5ff17689..07e2b9d9 100644 --- a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala @@ -192,6 +192,10 @@ class GeneratorControl(gen: Generator) if(newHealth == target.Definition.MaxHealth) { stopAutoRepair() } + if(gen.Condition == PlanetSideGeneratorState.Critical && newHealth > (target.MaxHealth / 2)) { + gen.Condition = PlanetSideGeneratorState.Normal + GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Normal)) + } newHealth } diff --git a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala index 66b6f270..4a4f97b6 100644 --- a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala @@ -27,7 +27,7 @@ import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, VehicleSource} import net.psforever.objects.vehicles._ import net.psforever.objects.vehicles.interaction.WithWater import net.psforever.objects.vital.interaction.DamageResult -import net.psforever.objects.vital.{InGameActivity, ShieldCharge, SpawningActivity, VehicleDismountActivity, VehicleMountActivity} +import net.psforever.objects.vital.{DamagingActivity, InGameActivity, ShieldCharge, SpawningActivity, VehicleDismountActivity, VehicleMountActivity} import net.psforever.objects.zones._ import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.game._ @@ -39,6 +39,7 @@ import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ +import scala.util.Random /** * An `Actor` that handles messages being dispatched to a specific `Vehicle`.
@@ -140,6 +141,36 @@ class VehicleControl(vehicle: Vehicle) }) => sender() ! Mountable.MountMessages(user, Mountable.CanNotDismount(vehicle, seat_num, bailType)) + case Mountable.TryDismount(user, seat_num, bailType) + if vehicle.Health <= (vehicle.Definition.MaxHealth * .1).round && bailType == BailType.Bailed + && GlobalDefinitions.isFlightVehicle(vehicle.Definition) + && (seat_num == 0 || vehicle.SeatPermissionGroup(seat_num).getOrElse(0) == AccessPermissionGroup.Gunner) + && (vehicle.History.findLast { entry => entry.isInstanceOf[DamagingActivity] } match { + case Some(entry) if System.currentTimeMillis() - entry.time < 4000L => true + case _ if Random.nextInt(10) == 1 => false + case _ => true }) => + sender() ! Mountable.MountMessages(user, Mountable.CanNotDismount(vehicle, seat_num, bailType)) + + case Mountable.TryDismount(user, seat_num, bailType) + if vehicle.Health <= (vehicle.Definition.MaxHealth * .2).round && bailType == BailType.Bailed + && GlobalDefinitions.isFlightVehicle(vehicle.Definition) + && (seat_num == 0 || vehicle.SeatPermissionGroup(seat_num).getOrElse(0) == AccessPermissionGroup.Gunner) + && (vehicle.History.findLast { entry => entry.isInstanceOf[DamagingActivity] } match { + case Some(entry) if System.currentTimeMillis() - entry.time < 3500L => true + case _ if Random.nextInt(5) == 1 => false + case _ => true }) => + sender() ! Mountable.MountMessages(user, Mountable.CanNotDismount(vehicle, seat_num, bailType)) + + case Mountable.TryDismount(user, seat_num, bailType) + if vehicle.Health <= (vehicle.Definition.MaxHealth * .35).round && bailType == BailType.Bailed + && GlobalDefinitions.isFlightVehicle(vehicle.Definition) + && (seat_num == 0 || vehicle.SeatPermissionGroup(seat_num).getOrElse(0) == AccessPermissionGroup.Gunner) + && (vehicle.History.findLast { entry => entry.isInstanceOf[DamagingActivity] } match { + case Some(entry) if System.currentTimeMillis() - entry.time < 3000L => true + case _ if Random.nextInt(4) == 1 => false + case _ => true }) => + sender() ! Mountable.MountMessages(user, Mountable.CanNotDismount(vehicle, seat_num, bailType)) + case Mountable.TryDismount(user, seat_num, bailType) if vehicle.DeploymentState == DriveState.AutoPilot => sender() ! Mountable.MountMessages(user, Mountable.CanNotDismount(vehicle, seat_num, bailType)) From 4cf54d09373b997b28e7d7de52efa9702d7923c3 Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Mon, 9 Jun 2025 19:49:17 -0400 Subject: [PATCH 2/6] claim the cont --- .../local/support/HackCaptureActor.scala | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala index d561cd75..dccc0dd7 100644 --- a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala +++ b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala @@ -18,6 +18,8 @@ import net.psforever.services.local.support.HackCaptureActor.GetHackingFaction import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGUID} +import java.util.concurrent.{Executors, TimeUnit} +import scala.collection.Seq import scala.concurrent.duration.{FiniteDuration, _} import scala.util.Random @@ -30,6 +32,7 @@ class HackCaptureActor extends Actor { private var clearTrigger: Cancellable = Default.Cancellable /** list of currently hacked server objects */ private var hackedObjects: List[HackCaptureActor.HackEntry] = Nil + private val scheduler = Executors.newScheduledThreadPool(2) def receive: Receive = { case HackCaptureActor.StartCaptureTerminalHack(target, _, _, _, _) @@ -266,6 +269,20 @@ 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?) + println(s"bases: ${zoneBases.size} owned: ${ownedBases.size} towers: ${zoneTowers.size}") + if (zoneBases.size - 1 == ownedBases.size && zoneTowers.nonEmpty) + { + processBuildingsWithDelay(zoneTowers.values.toSeq, hackedByFaction, 1000) + } } else { log.info("Base hack completed, but base was out of NTU.") } @@ -283,6 +300,43 @@ class HackCaptureActor extends Actor { clearTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, HackCaptureActor.ProcessCompleteHacks()) } } + + def processBuildingsWithDelay( + buildings: Seq[Building], + faction: PlanetSideEmpire.Value, + delayMillis: Long + ): Unit = { + val buildingIterator = buildings.iterator + scheduler.scheduleAtFixedRate( + () => { + if (buildingIterator.hasNext) { + val building = buildingIterator.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 + zoneActor ! ZoneActor.ZoneMapUpdate() + } + }, + 0, + delayMillis, + TimeUnit.MILLISECONDS + ) + } + } object HackCaptureActor { From acb9cae3fcb347000cfe94c76211bfcdf1224c37 Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Mon, 9 Jun 2025 20:34:45 -0400 Subject: [PATCH 3/6] oops --- .../net/psforever/services/local/support/HackCaptureActor.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala index dccc0dd7..89443524 100644 --- a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala +++ b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala @@ -278,7 +278,6 @@ class HackCaptureActor extends Actor { 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?) - println(s"bases: ${zoneBases.size} owned: ${ownedBases.size} towers: ${zoneTowers.size}") if (zoneBases.size - 1 == ownedBases.size && zoneTowers.nonEmpty) { processBuildingsWithDelay(zoneTowers.values.toSeq, hackedByFaction, 1000) From e32e67a00dc86a9eedd71e79c3de9feb1a86834a Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Mon, 9 Jun 2025 20:37:38 -0400 Subject: [PATCH 4/6] oops --- .../net/psforever/services/local/support/HackCaptureActor.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala index 89443524..db33a6a3 100644 --- a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala +++ b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala @@ -276,7 +276,7 @@ class HackCaptureActor extends Actor { && 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 + // All major facilities in zone are now owned by the hacking faction. Capture all remaining 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) { From 895ed5331cf7674fb04cf0b970ad6e984b26d2e2 Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Mon, 9 Jun 2025 20:39:13 -0400 Subject: [PATCH 5/6] oops --- .../net/psforever/services/local/support/HackCaptureActor.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala index db33a6a3..89443524 100644 --- a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala +++ b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala @@ -276,7 +276,7 @@ class HackCaptureActor extends Actor { && 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 remaining towers in the zone + // 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) { From c634aea786d5b1d0e0dc8557df021e2de155d5d4 Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Sun, 15 Jun 2025 17:19:11 -0400 Subject: [PATCH 6/6] heal & repair messages --- .../scala/net/psforever/objects/Players.scala | 2 ++ .../objects/avatar/PlayerControl.scala | 19 +++++++++++++ .../repair/RepairableEntity.scala | 27 +++++++++++++++++-- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/main/scala/net/psforever/objects/Players.scala b/src/main/scala/net/psforever/objects/Players.scala index 7ec07f3c..241078f2 100644 --- a/src/main/scala/net/psforever/objects/Players.scala +++ b/src/main/scala/net/psforever/objects/Players.scala @@ -80,6 +80,8 @@ object Players { ) ) target.Zone.AvatarEvents ! AvatarServiceMessage(name, AvatarAction.Revive(target.GUID)) + val reviveMessage = s"@YouHaveBeenMessage^revived~^$medicName~" + PlayerControl.sendResponse(target.Zone, name, ChatMsg(ChatMessageType.UNK_227, reviveMessage)) } /** diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index ee5fef56..375f4cec 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -5,6 +5,7 @@ import akka.actor.{Actor, ActorRef, Props, typed} import net.psforever.actors.session.AvatarActor import net.psforever.login.WorldSession.{DropEquipmentFromInventory, HoldNewEquipmentUp, PutNewEquipmentInInventoryOrDrop, RemoveOldEquipmentFromInventory} import net.psforever.objects._ +import net.psforever.objects.avatar.PlayerControl.sendResponse import net.psforever.objects.ce.Deployable import net.psforever.objects.definition.DeployAnimation import net.psforever.objects.definition.converter.OCM @@ -147,6 +148,15 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm newHealth - originalHealth ) ) + val amount = newHealth - originalHealth + val healMessageSelf = s"@SelfHitHealedMessage^healed~^$amount~^health~" + val healMessageOther = s"@WereHitByHealedMessage^healed~^$amount~^health~^$uname~" + if (player == user) { + sendResponse(user.Zone, user.Name, ChatMsg(ChatMessageType.UNK_227, healMessageSelf)) + } + else { + sendResponse(player.Zone, player.Name, ChatMsg(ChatMessageType.UNK_227, healMessageOther)) + } } if (player != user) { //"Someone is trying to heal you" @@ -210,6 +220,15 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm newArmor - originalArmor ) ) + val amount = newArmor - originalArmor + val repairMessageSelf = s"@SelfHitHealedMessage^repaired~^$amount~^armor~" + val repairMessageOther = s"@WereHitByHealedMessage^repaired~^$amount~^armor~^$uname~" + if (player == user) { + sendResponse(user.Zone, user.Name, ChatMsg(ChatMessageType.UNK_227, repairMessageSelf)) + } + else { + sendResponse(player.Zone, player.Name, ChatMsg(ChatMessageType.UNK_227, repairMessageOther)) + } } if (player != user) { if (player.isAlive) { diff --git a/src/main/scala/net/psforever/objects/serverobject/repair/RepairableEntity.scala b/src/main/scala/net/psforever/objects/serverobject/repair/RepairableEntity.scala index 11b51ad8..3efb93c9 100644 --- a/src/main/scala/net/psforever/objects/serverobject/repair/RepairableEntity.scala +++ b/src/main/scala/net/psforever/objects/serverobject/repair/RepairableEntity.scala @@ -1,11 +1,13 @@ //Copyright (c) 2020 PSForever package net.psforever.objects.serverobject.repair +import net.psforever.objects.avatar.PlayerControl.sendResponse +import net.psforever.objects.ce.DeployableCategory import net.psforever.objects.sourcing.PlayerSource import net.psforever.objects.vital.RepairFromEquipment import net.psforever.objects.{Player, Tool} -import net.psforever.packet.game.{InventoryStateMessage, RepairMessage} -import net.psforever.types.{PlanetSideEmpire, Vector3} +import net.psforever.packet.game.{ChatMsg, InventoryStateMessage, RepairMessage} +import net.psforever.types.{ChatMessageType, PlanetSideEmpire, Vector3} import net.psforever.services.Service import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} @@ -113,6 +115,27 @@ trait RepairableEntity extends Repairable { RepairMessage(target.GUID, updatedHealth * 100 / definition.MaxHealth) ) ) + //if vehicle and vehicle is owned by another player, send repair chat message to the vehicle's owner + if (target.Zone.Vehicles.exists(_.GUID == target.GUID)) { + val vehicle = target.Zone.Vehicles.filter(_.GUID == target.GUID).head + val vehicleOwner = vehicle.OwnerName.getOrElse("someone") + val amount = updatedHealth - originalHealth + if (vehicleOwner != "someone" && vehicle.OwnerGuid.get != player.GUID && amount > 0) { + val repairMessageOther = s"@YourWasHealedMessage^@${vehicle.Definition.Name}~^repaired~^$amount~^armor~^${player.Name}~" + sendResponse(vehicle.Zone, vehicleOwner, ChatMsg(ChatMessageType.UNK_227, repairMessageOther)) + } + } + //same check for field turret + if (target.Zone.DeployableList.exists(_.GUID == target.GUID)) { + val turret = target.Zone.DeployableList.filter(_.GUID == target.GUID).head + val turretOwner = turret.OwnerName.getOrElse("someone") + val amount = updatedHealth - originalHealth + if (turret.Definition.DeployCategory == DeployableCategory.FieldTurrets && turretOwner != "someone" + && turret.OwnerGuid.get != player.GUID && amount > 0) { + val repairMessageOther = s"@YourWasHealedMessage^@${turret.Definition.Name}~^repaired~^$amount~^armor~^${player.Name}~" + sendResponse(turret.Zone, turretOwner, ChatMsg(ChatMessageType.UNK_227, repairMessageOther)) + } + } } protected def PerformRepairs(target: Repairable.Target, amount: Int): Int = {