From 88684aa1a23439146dc19d1b9cdb3b09e9500875 Mon Sep 17 00:00:00 2001 From: "Jason_DiDonato@yahoo.com" Date: Sun, 18 Oct 2020 10:06:11 -0400 Subject: [PATCH] modified !ntu command to improve effect and control; generator has context for ntu rupply and when lacking ntu supply; the resource silo no longer directly controls facility conditions --- .../psforever/actors/session/ChatActor.scala | 63 +++++++-- .../psforever/actors/zone/BuildingActor.scala | 97 +++++++------- .../generator/GeneratorControl.scala | 121 +++++++++++++----- .../resourcesilo/ResourceSiloControl.scala | 9 -- .../terminals/ProximityTerminalControl.scala | 25 +++- .../turret/FacilityTurretControl.scala | 2 +- 6 files changed, 213 insertions(+), 104 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/ChatActor.scala b/src/main/scala/net/psforever/actors/session/ChatActor.scala index 9a1c2719..8afe468e 100644 --- a/src/main/scala/net/psforever/actors/session/ChatActor.scala +++ b/src/main/scala/net/psforever/actors/session/ChatActor.scala @@ -7,7 +7,7 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} import net.psforever.actors.zone.BuildingActor import net.psforever.objects.avatar.{BattleRank, Certification, CommandRank, Cosmetic} import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} -import net.psforever.objects.{Default, GlobalDefinitions, Player, Session} +import net.psforever.objects.{Default, Player, Session} import net.psforever.objects.serverobject.resourcesilo.ResourceSilo import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurrets} @@ -342,20 +342,55 @@ class ChatActor( ) } - case (_, _, contents) if contents.startsWith("!ntu") && session.account.gm => - session.zone.Buildings.values.foreach(building => - building.Amenities.foreach(amenity => - amenity.Definition match { - case GlobalDefinitions.resource_silo => - val r = new scala.util.Random - val silo = amenity.asInstanceOf[ResourceSilo] - val ntu = 900f + r.nextFloat() * 100f - silo.NtuCapacitor - silo.Actor ! ResourceSilo.UpdateChargeLevel(ntu) - - case _ => () - } + case (_, _, contents) if contents.startsWith("!ntu") && gmCommandAllowed => + val buffer = contents.toLowerCase.split("\\s+") + val (facility, customNtuValue) = (buffer.lift(1), buffer.lift(2)) match { + case (Some(x), Some(y)) if y.toIntOption.nonEmpty => (Some(x), Some(y.toInt)) + case (Some(x), None) if x.toIntOption.nonEmpty => (None, Some(x.toInt)) + case _ => (None, None) + } + val silos = (facility match { + case Some(cur) if cur.toLowerCase().startsWith("curr") => + val position = session.player.Position + session.zone.Buildings.values + .filter { building => + val soi2 = building.Definition.SOIRadius * building.Definition.SOIRadius + Vector3.DistanceSquared(building.Position, position) < soi2 + } + case Some(x) => + session.zone.Buildings.values.find { _.Name.equalsIgnoreCase(x) }.toList + case _ => + session.zone.Buildings.values + }) + .flatMap { building => building.Amenities.filter { _.isInstanceOf[ResourceSilo] } } + if(silos.isEmpty) { + sessionActor ! SessionActor.SendResponse( + ChatMsg(UNK_229, true, "Server", s"no targets for ntu found with parameters $facility", None) ) - ) + } + customNtuValue match { + // x = n0% of maximum capacitance + case Some(value) if value > -1 && value < 11 => + silos.collect { case silo: ResourceSilo => + silo.Actor ! ResourceSilo.UpdateChargeLevel(value * silo.MaxNtuCapacitor * 0.1f - silo.NtuCapacitor) + } + // capacitance set to x (where x > 10) exactly, within limits + case Some(value) => + silos.collect { case silo: ResourceSilo => + silo.Actor ! ResourceSilo.UpdateChargeLevel(value - silo.NtuCapacitor) + } + case None => + // x >= n0% of maximum capacitance and x <= maximum capacitance + val rand = new scala.util.Random + silos.collect { case silo: ResourceSilo => + val a = 7 + val b = 10 - a + val tenth = silo.MaxNtuCapacitor * 0.1f + silo.Actor ! ResourceSilo.UpdateChargeLevel( + a * tenth + rand.nextFloat() * b * tenth - silo.NtuCapacitor + ) + } + } case _ => // unknown ! commands are ignored diff --git a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala index 13201790..fc7b699a 100644 --- a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala +++ b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala @@ -9,7 +9,6 @@ import net.psforever.objects.{CommonNtuContainer, NtuContainer} import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.generator.Generator import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate} -import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.zones.Zone import net.psforever.persistence import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} @@ -118,55 +117,14 @@ class BuildingActor( ): Behavior[Command] = { Behaviors.receiveMessagePartial { case SetFaction(faction) => - import ctx._ - ctx - .run( - query[persistence.Building] - .filter(_.localId == lift(building.MapId)) - .filter(_.zoneId == lift(zone.Number)) - ) - .onComplete { - case Success(res) => - res.headOption match { - case Some(_) => - ctx - .run( - query[persistence.Building] - .filter(_.localId == lift(building.MapId)) - .filter(_.zoneId == lift(zone.Number)) - .update(_.factionId -> lift(building.Faction.id)) - ) - .onComplete { - case Success(_) => - case Failure(e) => log.error(e.getMessage) - } - case _ => - ctx - .run( - query[persistence.Building] - .insert( - _.localId -> lift(building.MapId), - _.factionId -> lift(building.Faction.id), - _.zoneId -> lift(zone.Number) - ) - ) - .onComplete { - case Success(_) => - case Failure(e) => log.error(e.getMessage) - } - } - case Failure(e) => log.error(e.getMessage) - } - building.Faction = faction - galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) - zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SetEmpire(building.GUID, faction)) + setFactionTo(faction, galaxyService) Behaviors.same case MapUpdate() => galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) Behaviors.same - case AmenityStateChange(obj: Generator, data) => + case AmenityStateChange(_: Generator, data) => //TODO when parameter object is finally immutable, perform analysis on it to determine specific actions data match { case Some("overloaded") => @@ -176,7 +134,8 @@ class BuildingActor( building.PlayersInSOI.foreach { player => zone.AvatarEvents ! AvatarServiceMessage(player.Name, msg) } //??? - case Some("repaired") => + case Some("restored") => + // Power restored. Reactor Online. Sensors Online. Weapons Online. All systems nominal. powerRestored() val zone = building.Zone building.PlayersInSOI.foreach { player => @@ -204,12 +163,15 @@ class BuildingActor( Behaviors.same case msg @ NtuDepleted() => + // Oops, someone let the base run out of power. Shut it all down. building.Amenities.foreach { amenity => amenity.Actor ! msg } + setFactionTo(PlanetSideEmpire.NEUTRAL, galaxyService) Behaviors.same case msg @ SuppliedWithNtu() => + // Auto-repair, mainly. If the Generator works, power is restored too. building.Amenities.foreach { amenity => amenity.Actor ! msg } @@ -220,6 +182,51 @@ class BuildingActor( } } + def setFactionTo(faction: PlanetSideEmpire.Value, galaxy: classic.ActorRef): Unit = { + import ctx._ + ctx + .run( + query[persistence.Building] + .filter(_.localId == lift(building.MapId)) + .filter(_.zoneId == lift(zone.Number)) + ) + .onComplete { + case Success(res) => + res.headOption match { + case Some(_) => + ctx + .run( + query[persistence.Building] + .filter(_.localId == lift(building.MapId)) + .filter(_.zoneId == lift(zone.Number)) + .update(_.factionId -> lift(building.Faction.id)) + ) + .onComplete { + case Success(_) => + case Failure(e) => log.error(e.getMessage) + } + case _ => + ctx + .run( + query[persistence.Building] + .insert( + _.localId -> lift(building.MapId), + _.factionId -> lift(building.Faction.id), + _.zoneId -> lift(zone.Number) + ) + ) + .onComplete { + case Success(_) => + case Failure(e) => log.error(e.getMessage) + } + } + case Failure(e) => log.error(e.getMessage) + } + building.Faction = faction + galaxy ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) + zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SetEmpire(building.GUID, faction)) + } + def powerLost(): Unit = { val zone = building.Zone val zoneId = zone.id 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 23a99c67..12795d97 100644 --- a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala @@ -1,9 +1,9 @@ // Copyright (c) 2020 PSForever package net.psforever.objects.serverobject.generator -import akka.actor.Actor +import akka.actor.{Actor, Cancellable} import net.psforever.actors.zone.BuildingActor -import net.psforever.objects.{Player, Tool} +import net.psforever.objects.{Default, Player, Tool} import net.psforever.objects.ballistics._ import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior import net.psforever.objects.serverobject.damage.Damageable.Target @@ -35,15 +35,40 @@ class GeneratorControl(gen: Generator) def RepairableObject = gen def AutoRepairObject = gen var imminentExplosion: Boolean = false + var queuedExplosion: Cancellable = Default.Cancellable var alarmCooldownPeriod: Boolean = false + private[this] val log = org.log4s.getLogger - def receive: Receive = + def receive: Receive = withNtu + + val commonBehavior: Receive = checkBehavior .orElse(takesDamage) .orElse(canBeRepairedByNanoDispenser) .orElse(autoRepairBehavior) .orElse { - case GeneratorControl.GeneratorExplodes() => //TODO this only works with projectiles right now! + case GeneratorControl.UnderThreatAlarm() => + if (!alarmCooldownPeriod) { + alarmCooldownPeriod = true + GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.UnderAttack) + queuedExplosion = context.system.scheduler.scheduleOnce(delay = 5 seconds, self, GeneratorControl.AlarmReset()) + } + + case GeneratorControl.AlarmReset() => + alarmCooldownPeriod = false + } + + def withNtu: Receive = + commonBehavior + .orElse { + case GeneratorControl.Destabilized() => + imminentExplosion = true + GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.Overloaded) + queuedExplosion.cancel() + queuedExplosion = context.system.scheduler.scheduleOnce(10 seconds, self, GeneratorControl.GeneratorExplodes()) + + case GeneratorControl.GeneratorExplodes() => + //TODO this only works with projectiles right now! val zone = gen.Zone gen.Health = 0 super.DestructionAwareness(gen, gen.LastShot.get) @@ -57,6 +82,8 @@ class GeneratorControl(gen: Generator) TriggerEffectMessage(gen.GUID, "explosion_generator", None, None) ) ) + queuedExplosion.cancel() + queuedExplosion = Default.Cancellable imminentExplosion = false //kill everyone within 14m gen.Owner match { @@ -71,33 +98,35 @@ class GeneratorControl(gen: Generator) } gen.ClearHistory() - case GeneratorControl.UnderThreatAlarm() => - if (!alarmCooldownPeriod) { - alarmCooldownPeriod = true - GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.UnderAttack) - context.system.scheduler.scheduleOnce(delay = 5 seconds, self, GeneratorControl.AlarmReset()) - } - - case GeneratorControl.AlarmReset() => - alarmCooldownPeriod = false - - case BuildingActor.NtuDepleted() => - gen.Owner match { - case b: Building if !gen.Destroyed => - b.Actor ! BuildingActor.PowerOff() - case _ => ; - } - - case BuildingActor.SuppliedWithNtu() => - gen.Owner match { - case b: Building if !gen.Destroyed => - b.Actor ! BuildingActor.PowerOn() - case _ => ; - } + case GeneratorControl.Restored() => + GeneratorControl.UpdateOwner(gen, Some("restored")) + GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.Online) case _ => ; } + def withoutNtu: Receive = + commonBehavior + .orElse { + case GeneratorControl.GeneratorExplodes() => + queuedExplosion.cancel() + queuedExplosion = Default.Cancellable + imminentExplosion = false + + case GeneratorControl.Destabilized() => + //if the generator is destabilized but has no ntu, it will not explode + gen.Health = 0 + super.DestructionAwareness(gen, gen.LastShot.get) + queuedExplosion.cancel() + queuedExplosion = Default.Cancellable + imminentExplosion = false + gen.Condition = PlanetSideGeneratorState.Destroyed + GeneratorControl.UpdateOwner(gen, Some("destroyed")) + gen.ClearHistory() + + case _ => + } + override protected def CanPerformRepairs(obj: Target, player: Player, item: Tool): Boolean = { !imminentExplosion && super.CanPerformRepairs(obj, player, item) } @@ -120,10 +149,8 @@ class GeneratorControl(gen: Generator) tryAutoRepair() if (!target.Destroyed) { target.Health = 1 //temporary - imminentExplosion = true - context.system.scheduler.scheduleOnce(10 seconds, self, GeneratorControl.GeneratorExplodes()) GeneratorControl.UpdateOwner(gen, Some("overloaded")) - GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.Overloaded) + self ! GeneratorControl.Destabilized() } } @@ -138,13 +165,38 @@ class GeneratorControl(gen: Generator) override def Restoration(obj: Repairable.Target): Unit = { super.Restoration(obj) gen.Condition = PlanetSideGeneratorState.Normal - GeneratorControl.UpdateOwner(gen, Some("repaired")) - GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.Online) + self ! GeneratorControl.Restored() + } + + override def withNtuSupplyCallback() : Unit = { + context.become(withNtu) + super.withNtuSupplyCallback() + if(!gen.Destroyed) { + self ! GeneratorControl.Restored() + } + } + + override def noNtuSupplyCallback() : Unit = { + //auto-repair must stop naturally + context.become(withoutNtu) + super.noNtuSupplyCallback() + if(!gen.Destroyed) { + GeneratorControl.UpdateOwner(gen, Some("overloaded")) + } + if(!queuedExplosion.isCancelled) { + queuedExplosion.cancel() + self ! GeneratorControl.Destabilized() + } } } object GeneratorControl { + /** + * na + */ + private case class Destabilized() + /** * na */ @@ -160,6 +212,11 @@ object GeneratorControl { */ private case class AlarmReset() + /** + * na + */ + private case class Restored() + /** * na */ diff --git a/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala b/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala index f726b57d..673829f8 100644 --- a/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala @@ -113,18 +113,9 @@ class ResourceSiloControl(resourceSilo: ResourceSilo) LowNtuWarning(enabled = true) } if (resourceSilo.NtuCapacitor == 0 && siloChargeBeforeChange > 0) { - // Oops, someone let the base run out of power. Shut it all down. - zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.PlanetsideAttribute(building.GUID, 48, 1)) building.Actor ! BuildingActor.NtuDepleted() building.Actor ! BuildingActor.AmenityStateChange(resourceSilo) - building.Actor ! BuildingActor.SetFaction(PlanetSideEmpire.NEUTRAL) } else if (siloChargeBeforeChange == 0 && resourceSilo.NtuCapacitor > 0) { - // Power restored. Reactor Online. Sensors Online. Weapons Online. All systems nominal. - //todo: Check generator is online before starting up - zone.AvatarEvents ! AvatarServiceMessage( - zone.id, - AvatarAction.PlanetsideAttribute(building.GUID, 48, 0) - ) building.Actor ! BuildingActor.SuppliedWithNtu() building.Actor ! BuildingActor.AmenityStateChange(resourceSilo) } diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala index 7773a846..04ded0e6 100644 --- a/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala @@ -5,9 +5,10 @@ import akka.actor.{ActorRef, Cancellable} import net.psforever.objects._ import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior +import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.serverobject.damage.DamageableAmenity import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior} -import net.psforever.objects.serverobject.repair.RepairableAmenity +import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableAmenity} import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl} import net.psforever.services.Service import net.psforever.services.local.{LocalAction, LocalServiceMessage} @@ -26,12 +27,14 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit) with FactionAffinityBehavior.Check with HackableBehavior.GenericHackable with DamageableAmenity - with RepairableAmenity { + with RepairableAmenity + with AmenityAutoRepair { def FactionObject = term def HackableObject = term def TerminalObject = term def DamageableObject = term def RepairableObject = term + def AutoRepairObject = term var terminalAction: Cancellable = Default.Cancellable val callbacks: mutable.ListBuffer[ActorRef] = new mutable.ListBuffer[ActorRef]() @@ -40,6 +43,7 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit) val commonBehavior: Receive = checkBehavior .orElse(takesDamage) .orElse(canBeRepairedByNanoDispenser) + .orElse(autoRepairBehavior) .orElse { case CommonMessages.Unuse(_, Some(target: PlanetSideGameObject)) => Unuse(target, term.Continent) @@ -117,6 +121,18 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit) case _ => ; } + override def PerformRepairs(target : Target, amount : Int) : Int = { + val newHealth = super.PerformRepairs(target, amount) + if(newHealth == target.Definition.MaxHealth) { + stopAutoRepair() + } + newHealth + } + + override def tryAutoRepair() : Boolean = { + isPowered && super.tryAutoRepair() + } + def Use(target: PlanetSideGameObject, zone: String, callback: ActorRef): Unit = { val hadNoUsers = term.NumberUsers == 0 if (term.AddUser(target)) { @@ -164,6 +180,7 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit) } def powerTurnOffCallback() : Unit = { + stopAutoRepair() //clear effect callbacks terminalAction.cancel() if (callbacks.nonEmpty) { @@ -177,7 +194,9 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit) } } - def powerTurnOnCallback() : Unit = { } + def powerTurnOnCallback() : Unit = { + tryAutoRepair() + } override def toString: String = term.Definition.Name } diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala index 9734810b..3ae7f160 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala @@ -58,11 +58,11 @@ class FacilityTurretControl(turret: FacilityTurret) .orElse(dismountBehavior) .orElse(takesDamage) .orElse(canBeRepairedByNanoDispenser) + .orElse(autoRepairBehavior) def poweredStateLogic: Receive = commonBehavior .orElse(mountBehavior) - .orElse(autoRepairBehavior) .orElse { case CommonMessages.Use(player, Some((item: Tool, upgradeValue: Int))) if player.Faction == turret.Faction &&