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

This commit is contained in:
Jason_DiDonato@yahoo.com 2020-10-18 10:06:11 -04:00
parent e1c5cd90a7
commit 88684aa1a2
6 changed files with 213 additions and 104 deletions

View file

@ -7,7 +7,7 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer}
import net.psforever.actors.zone.BuildingActor import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.avatar.{BattleRank, Certification, CommandRank, Cosmetic} import net.psforever.objects.avatar.{BattleRank, Certification, CommandRank, Cosmetic}
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} 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.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.serverobject.structures.Building
import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurrets} import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurrets}
@ -342,20 +342,55 @@ class ChatActor(
) )
} }
case (_, _, contents) if contents.startsWith("!ntu") && session.account.gm => case (_, _, contents) if contents.startsWith("!ntu") && gmCommandAllowed =>
session.zone.Buildings.values.foreach(building => val buffer = contents.toLowerCase.split("\\s+")
building.Amenities.foreach(amenity => val (facility, customNtuValue) = (buffer.lift(1), buffer.lift(2)) match {
amenity.Definition match { case (Some(x), Some(y)) if y.toIntOption.nonEmpty => (Some(x), Some(y.toInt))
case GlobalDefinitions.resource_silo => case (Some(x), None) if x.toIntOption.nonEmpty => (None, Some(x.toInt))
val r = new scala.util.Random case _ => (None, None)
val silo = amenity.asInstanceOf[ResourceSilo] }
val ntu = 900f + r.nextFloat() * 100f - silo.NtuCapacitor val silos = (facility match {
silo.Actor ! ResourceSilo.UpdateChargeLevel(ntu) case Some(cur) if cur.toLowerCase().startsWith("curr") =>
val position = session.player.Position
case _ => () 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 _ => case _ =>
// unknown ! commands are ignored // unknown ! commands are ignored

View file

@ -9,7 +9,6 @@ import net.psforever.objects.{CommonNtuContainer, NtuContainer}
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.generator.Generator import net.psforever.objects.serverobject.generator.Generator
import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate} 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.objects.zones.Zone
import net.psforever.persistence import net.psforever.persistence
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
@ -118,55 +117,14 @@ class BuildingActor(
): Behavior[Command] = { ): Behavior[Command] = {
Behaviors.receiveMessagePartial { Behaviors.receiveMessagePartial {
case SetFaction(faction) => case SetFaction(faction) =>
import ctx._ setFactionTo(faction, galaxyService)
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))
Behaviors.same Behaviors.same
case MapUpdate() => case MapUpdate() =>
galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage()))
Behaviors.same 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 //TODO when parameter object is finally immutable, perform analysis on it to determine specific actions
data match { data match {
case Some("overloaded") => case Some("overloaded") =>
@ -176,7 +134,8 @@ class BuildingActor(
building.PlayersInSOI.foreach { player => building.PlayersInSOI.foreach { player =>
zone.AvatarEvents ! AvatarServiceMessage(player.Name, msg) zone.AvatarEvents ! AvatarServiceMessage(player.Name, msg)
} //??? } //???
case Some("repaired") => case Some("restored") =>
// Power restored. Reactor Online. Sensors Online. Weapons Online. All systems nominal.
powerRestored() powerRestored()
val zone = building.Zone val zone = building.Zone
building.PlayersInSOI.foreach { player => building.PlayersInSOI.foreach { player =>
@ -204,12 +163,15 @@ class BuildingActor(
Behaviors.same Behaviors.same
case msg @ NtuDepleted() => case msg @ NtuDepleted() =>
// Oops, someone let the base run out of power. Shut it all down.
building.Amenities.foreach { amenity => building.Amenities.foreach { amenity =>
amenity.Actor ! msg amenity.Actor ! msg
} }
setFactionTo(PlanetSideEmpire.NEUTRAL, galaxyService)
Behaviors.same Behaviors.same
case msg @ SuppliedWithNtu() => case msg @ SuppliedWithNtu() =>
// Auto-repair, mainly. If the Generator works, power is restored too.
building.Amenities.foreach { amenity => building.Amenities.foreach { amenity =>
amenity.Actor ! msg 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 = { def powerLost(): Unit = {
val zone = building.Zone val zone = building.Zone
val zoneId = zone.id val zoneId = zone.id

View file

@ -1,9 +1,9 @@
// Copyright (c) 2020 PSForever // Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.generator package net.psforever.objects.serverobject.generator
import akka.actor.Actor import akka.actor.{Actor, Cancellable}
import net.psforever.actors.zone.BuildingActor 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.ballistics._
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
@ -35,15 +35,40 @@ class GeneratorControl(gen: Generator)
def RepairableObject = gen def RepairableObject = gen
def AutoRepairObject = gen def AutoRepairObject = gen
var imminentExplosion: Boolean = false var imminentExplosion: Boolean = false
var queuedExplosion: Cancellable = Default.Cancellable
var alarmCooldownPeriod: Boolean = false var alarmCooldownPeriod: Boolean = false
private[this] val log = org.log4s.getLogger
def receive: Receive = def receive: Receive = withNtu
val commonBehavior: Receive =
checkBehavior checkBehavior
.orElse(takesDamage) .orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser) .orElse(canBeRepairedByNanoDispenser)
.orElse(autoRepairBehavior) .orElse(autoRepairBehavior)
.orElse { .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 val zone = gen.Zone
gen.Health = 0 gen.Health = 0
super.DestructionAwareness(gen, gen.LastShot.get) super.DestructionAwareness(gen, gen.LastShot.get)
@ -57,6 +82,8 @@ class GeneratorControl(gen: Generator)
TriggerEffectMessage(gen.GUID, "explosion_generator", None, None) TriggerEffectMessage(gen.GUID, "explosion_generator", None, None)
) )
) )
queuedExplosion.cancel()
queuedExplosion = Default.Cancellable
imminentExplosion = false imminentExplosion = false
//kill everyone within 14m //kill everyone within 14m
gen.Owner match { gen.Owner match {
@ -71,33 +98,35 @@ class GeneratorControl(gen: Generator)
} }
gen.ClearHistory() gen.ClearHistory()
case GeneratorControl.UnderThreatAlarm() => case GeneratorControl.Restored() =>
if (!alarmCooldownPeriod) { GeneratorControl.UpdateOwner(gen, Some("restored"))
alarmCooldownPeriod = true GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.Online)
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 _ => ; 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 = { override protected def CanPerformRepairs(obj: Target, player: Player, item: Tool): Boolean = {
!imminentExplosion && super.CanPerformRepairs(obj, player, item) !imminentExplosion && super.CanPerformRepairs(obj, player, item)
} }
@ -120,10 +149,8 @@ class GeneratorControl(gen: Generator)
tryAutoRepair() tryAutoRepair()
if (!target.Destroyed) { if (!target.Destroyed) {
target.Health = 1 //temporary target.Health = 1 //temporary
imminentExplosion = true
context.system.scheduler.scheduleOnce(10 seconds, self, GeneratorControl.GeneratorExplodes())
GeneratorControl.UpdateOwner(gen, Some("overloaded")) 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 = { override def Restoration(obj: Repairable.Target): Unit = {
super.Restoration(obj) super.Restoration(obj)
gen.Condition = PlanetSideGeneratorState.Normal gen.Condition = PlanetSideGeneratorState.Normal
GeneratorControl.UpdateOwner(gen, Some("repaired")) self ! GeneratorControl.Restored()
GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.Online) }
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 { object GeneratorControl {
/**
* na
*/
private case class Destabilized()
/** /**
* na * na
*/ */
@ -160,6 +212,11 @@ object GeneratorControl {
*/ */
private case class AlarmReset() private case class AlarmReset()
/**
* na
*/
private case class Restored()
/** /**
* na * na
*/ */

View file

@ -113,18 +113,9 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
LowNtuWarning(enabled = true) LowNtuWarning(enabled = true)
} }
if (resourceSilo.NtuCapacitor == 0 && siloChargeBeforeChange > 0) { 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.NtuDepleted()
building.Actor ! BuildingActor.AmenityStateChange(resourceSilo) building.Actor ! BuildingActor.AmenityStateChange(resourceSilo)
building.Actor ! BuildingActor.SetFaction(PlanetSideEmpire.NEUTRAL)
} else if (siloChargeBeforeChange == 0 && resourceSilo.NtuCapacitor > 0) { } 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.SuppliedWithNtu()
building.Actor ! BuildingActor.AmenityStateChange(resourceSilo) building.Actor ! BuildingActor.AmenityStateChange(resourceSilo)
} }

View file

@ -5,9 +5,10 @@ import akka.actor.{ActorRef, Cancellable}
import net.psforever.objects._ import net.psforever.objects._
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.DamageableAmenity import net.psforever.objects.serverobject.damage.DamageableAmenity
import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior} 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.objects.serverobject.structures.{Building, PoweredAmenityControl}
import net.psforever.services.Service import net.psforever.services.Service
import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage}
@ -26,12 +27,14 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit)
with FactionAffinityBehavior.Check with FactionAffinityBehavior.Check
with HackableBehavior.GenericHackable with HackableBehavior.GenericHackable
with DamageableAmenity with DamageableAmenity
with RepairableAmenity { with RepairableAmenity
with AmenityAutoRepair {
def FactionObject = term def FactionObject = term
def HackableObject = term def HackableObject = term
def TerminalObject = term def TerminalObject = term
def DamageableObject = term def DamageableObject = term
def RepairableObject = term def RepairableObject = term
def AutoRepairObject = term
var terminalAction: Cancellable = Default.Cancellable var terminalAction: Cancellable = Default.Cancellable
val callbacks: mutable.ListBuffer[ActorRef] = new mutable.ListBuffer[ActorRef]() val callbacks: mutable.ListBuffer[ActorRef] = new mutable.ListBuffer[ActorRef]()
@ -40,6 +43,7 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit)
val commonBehavior: Receive = checkBehavior val commonBehavior: Receive = checkBehavior
.orElse(takesDamage) .orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser) .orElse(canBeRepairedByNanoDispenser)
.orElse(autoRepairBehavior)
.orElse { .orElse {
case CommonMessages.Unuse(_, Some(target: PlanetSideGameObject)) => case CommonMessages.Unuse(_, Some(target: PlanetSideGameObject)) =>
Unuse(target, term.Continent) Unuse(target, term.Continent)
@ -117,6 +121,18 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit)
case _ => ; 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 = { def Use(target: PlanetSideGameObject, zone: String, callback: ActorRef): Unit = {
val hadNoUsers = term.NumberUsers == 0 val hadNoUsers = term.NumberUsers == 0
if (term.AddUser(target)) { if (term.AddUser(target)) {
@ -164,6 +180,7 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit)
} }
def powerTurnOffCallback() : Unit = { def powerTurnOffCallback() : Unit = {
stopAutoRepair()
//clear effect callbacks //clear effect callbacks
terminalAction.cancel() terminalAction.cancel()
if (callbacks.nonEmpty) { 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 override def toString: String = term.Definition.Name
} }

View file

@ -58,11 +58,11 @@ class FacilityTurretControl(turret: FacilityTurret)
.orElse(dismountBehavior) .orElse(dismountBehavior)
.orElse(takesDamage) .orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser) .orElse(canBeRepairedByNanoDispenser)
.orElse(autoRepairBehavior)
def poweredStateLogic: Receive = def poweredStateLogic: Receive =
commonBehavior commonBehavior
.orElse(mountBehavior) .orElse(mountBehavior)
.orElse(autoRepairBehavior)
.orElse { .orElse {
case CommonMessages.Use(player, Some((item: Tool, upgradeValue: Int))) case CommonMessages.Use(player, Some((item: Tool, upgradeValue: Int)))
if player.Faction == turret.Faction && if player.Faction == turret.Faction &&