Battle Island Facility Names (#1173)

* alias the internal facility names to their continental map names (battle islands)

* capturebase, but easier to follow

* sraosha(whitespace) is no longer with (whitespace)
This commit is contained in:
Fate-JH 2024-03-02 23:12:28 -05:00 committed by GitHub
parent 34ac1e5266
commit 44f1560a94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 257 additions and 160 deletions

View file

@ -7,9 +7,11 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer}
import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.zone.ZoneActor import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.sourcing.PlayerSource import net.psforever.objects.sourcing.PlayerSource
import net.psforever.objects.zones.ZoneInfo
import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import scala.collection.mutable import scala.annotation.unused
import scala.collection.{Seq, mutable}
import scala.concurrent.ExecutionContextExecutor import scala.concurrent.ExecutionContextExecutor
import scala.concurrent.duration._ import scala.concurrent.duration._
// //
@ -204,7 +206,8 @@ class ChatActor(
Behaviors Behaviors
.receiveMessagePartial[Command] { .receiveMessagePartial[Command] {
case SetSession(newSession) => case SetSession(newSession) =>
active(newSession, chatService,cluster) this.session = Some(newSession)
active(newSession, chatService, cluster)
case JoinChannel(channel) => case JoinChannel(channel) =>
chatService ! ChatService.JoinChannel(chatServiceAdapter, session, channel) chatService ! ChatService.JoinChannel(chatServiceAdapter, session, channel)
@ -398,133 +401,114 @@ class ChatActor(
case (U_CMT_ZONEROTATE, _, contents) if gmCommandAllowed => case (U_CMT_ZONEROTATE, _, contents) if gmCommandAllowed =>
cluster ! InterstellarClusterService.CavernRotation(CavernRotationService.HurryNextRotation) cluster ! InterstellarClusterService.CavernRotation(CavernRotationService.HurryNextRotation)
/** Messages starting with ! are custom chat commands */
case (_, _, contents) if contents.startsWith("!") &&
customCommandMessages(message, session, chatService, cluster, gmCommandAllowed) => ;
case (CMT_CAPTUREBASE, _, contents) if gmCommandAllowed => case (CMT_CAPTUREBASE, _, contents) if gmCommandAllowed =>
val args = contents.split(" ").filter(_ != "") val buffer = contents.split(" ").filterNot(_ == "").take(3)
val (faction, factionPos): (PlanetSideEmpire.Value, Option[Int]) = args.zipWithIndex //walk through the param buffer
.map { case (factionName, pos) => (factionName.toLowerCase, pos) } val (foundFacilities, foundFacilitiesTag, factionBuffer) = firstParam(session, buffer, captureBaseParamFacilities)
.flatMap { val (foundFaction, foundFactionTag, timerBuffer) = firstParam(session, factionBuffer, captureBaseParamFaction)
case ("tr", pos) => Some(PlanetSideEmpire.TR, pos) val (foundTimer, foundTimerTag, _) = firstParam(session, timerBuffer, captureBaseParamTimer)
case ("nc", pos) => Some(PlanetSideEmpire.NC, pos) //resolve issues with the initial params
case ("vs", pos) => Some(PlanetSideEmpire.VS, pos) var facilityError: Int = 0
case ("none", pos) => Some(PlanetSideEmpire.NEUTRAL, pos) var factionError: Boolean = false
case ("bo", pos) => Some(PlanetSideEmpire.NEUTRAL, pos) var timerError: Boolean = false
case ("neutral", pos) => Some(PlanetSideEmpire.NEUTRAL, pos) var usageMessage: Boolean = false
case _ => None val resolvedFacilities = foundFacilities
} .orElse {
.headOption match { if (foundFacilitiesTag.nonEmpty) {
case Some((isFaction, pos)) => (isFaction, Some(pos)) if (foundFaction.isEmpty) {
case None => (session.player.Faction, None) /* /capturebase <bad_facility> OR /capturebase <bad_facility> <no_faction> */
} //malformed facility tag error
val (buildingsOption, buildingPos): (Option[Seq[Building]], Option[Int]) = args.zipWithIndex.flatMap { facilityError = 2
case (_, pos) if factionPos.isDefined && factionPos.get == pos => None None
case ("all", pos) => } else if (!foundFacilitiesTag.contains("curr")) { //did we do this next check already
Some( /* /capturebase <faction>, potentially */
Some( val buildings = captureBaseCurrSoi(session)
session.zone.Buildings if (buildings.nonEmpty) {
.filter { //convert facilities to faction
case (_, building) => building.CaptureTerminal.isDefined Some(buildings.toSeq)
} } else {
.values //no facilities error
.toSeq facilityError = 1
),
Some(pos)
)
case (name: String, pos) =>
session.zone.Buildings.find {
case (_, building) => name.equalsIgnoreCase(building.Name) && building.CaptureTerminal.isDefined
} match {
case Some((_, building)) => Some(Some(Seq(building)), Some(pos))
case None =>
try {
// check if we have a timer
name.toInt
None None
} catch {
case _: Throwable =>
Some(None, Some(pos))
} }
} } else {
}.headOption match { //no facilities error
case Some((buildings, pos)) => (buildings, pos) facilityError = 1
case None => (None, None) None
}
val (timerOption, timerPos): (Option[Int], Option[Int]) = args.zipWithIndex.flatMap {
case (_, pos)
if factionPos.isDefined && factionPos.get == pos || buildingPos.isDefined && buildingPos.get == pos =>
None
case (timer: String, pos) =>
try {
val t = timer.toInt // TODO what is the timer format supposed to be?
Some(Some(t), Some(pos))
} catch {
case _: Throwable =>
Some(None, Some(pos))
}
}.headOption match {
case Some((timer, posOption)) => (timer, posOption)
case None => (None, None)
}
(factionPos, buildingPos, timerPos, buildingsOption, timerOption) match {
case // [[<empire>|none [<timer>]]
(Some(0), None, Some(1), None, Some(_)) | (Some(0), None, None, None, None) |
(None, None, None, None, None) |
// [<building name> [<empire>|none [timer]]]
(None | Some(1), Some(0), None, Some(_), None) | (Some(1), Some(0), Some(2), Some(_), Some(_)) |
// [all [<empire>|none]]
(Some(1) | None, Some(0), None, Some(_), None) =>
val buildings: Seq[Building] = buildingsOption.getOrElse(
session.zone.Buildings.values.filter { building =>
building.PlayersInSOI.exists { soiPlayer =>
session.player.CharId == soiPlayer.CharId
}
}.toSeq
)
buildings foreach { building =>
// TODO implement timer
val terminal = building.CaptureTerminal.get
building.Actor ! BuildingActor.SetFaction(faction)
building.Actor ! BuildingActor.AmenityStateChange(terminal, Some(false))
// clear any previous hack via "resecure"
if (building.CaptureTerminalIsHacked) {
building.Zone.LocalEvents ! LocalServiceMessage(terminal.Zone.id,LocalAction.ResecureCaptureTerminal(terminal, PlayerSource.Nobody))
} }
} else {
// push any updates this might cause to clients //no params; post command usage reminder
building.Zone.actor ! ZoneActor.ZoneMapUpdate() usageMessage = true
None
} }
}
case (_, Some(0), _, None, _) => val resolvedFaction = foundFaction
sessionActor ! SessionActor.SendResponse( .orElse {
ChatMsg( if (resolvedFacilities.nonEmpty) {
UNK_229, /* /capturebase <facility> OR /capturebase <facility> <timer> */
wideContents=true, if (foundFactionTag.isEmpty || foundTimer.nonEmpty) {
"", //convert facilities to OUR PLAYER'S faction
s"\\#FF4040ERROR - \'${args(0)}\' is not a valid building name.", Some(session.player.Faction)
} else {
//malformed faction tag error
factionError = true
None None
) }
) } else {
case (Some(0), _, Some(1), _, None) | (Some(1), Some(0), Some(2), _, None) => //incorrect params; already posted an error message
sessionActor ! SessionActor.SendResponse( None
ChatMsg( }
UNK_229, }
wideContents=true, val resolvedTimer = foundTimer
"", .orElse {
s"\\#FF4040ERROR - \'${args(timerPos.get)}\' is not a valid timer value.", //todo stop command execution? post command usage reminder?
None if (resolvedFaction.nonEmpty && foundTimerTag.nonEmpty) {
) /* /capturebase <?> <?> <bad_timer> */
) //malformed timer tag error
timerError = true
None
} else {
//eh
Some(1)
}
}
//evaluate results
(resolvedFacilities, resolvedFaction, resolvedTimer) match {
case (Some(buildings), Some(faction), Some(_)) =>
buildings.foreach { building =>
//TODO implement timer
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()
}
case _ => case _ =>
sessionActor ! SessionActor.SendResponse( if (usageMessage) {
message.copy(messageType = UNK_229, contents = "@CMT_CAPTUREBASE_usage") sessionActor ! SessionActor.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" }
sessionActor ! SessionActor.SendResponse(ChatMsg(UNK_229, wideContents=true, "", s"\\#FF4040ERROR - $msg", None))
}
} }
case (CMT_GMBROADCAST | CMT_GMBROADCAST_NC | CMT_GMBROADCAST_VS | CMT_GMBROADCAST_TR, _, _) case (CMT_GMBROADCAST | CMT_GMBROADCAST_NC | CMT_GMBROADCAST_VS | CMT_GMBROADCAST_TR, _, _)
@ -549,26 +533,6 @@ class ChatActor(
ChatChannel.Default() ChatChannel.Default()
) )
case (_, "tr", contents) =>
sessionActor ! SessionActor.SendResponse(
ZonePopulationUpdateMessage(4, 414, 138, contents.toInt, 138, contents.toInt / 2, 138, 0, 138, 0)
)
case (_, "nc", contents) =>
sessionActor ! SessionActor.SendResponse(
ZonePopulationUpdateMessage(4, 414, 138, 0, 138, contents.toInt, 138, contents.toInt / 3, 138, 0)
)
case (_, "vs", contents) =>
sessionActor ! SessionActor.SendResponse(
ZonePopulationUpdateMessage(4, 414, 138, contents.toInt * 2, 138, 0, 138, contents.toInt, 138, 0)
)
case (_, "bo", contents) =>
sessionActor ! SessionActor.SendResponse(
ZonePopulationUpdateMessage(4, 414, 138, 0, 138, 0, 138, 0, 138, contents.toInt)
)
case (CMT_OPEN, _, _) if !session.player.silenced => case (CMT_OPEN, _, _) if !session.player.silenced =>
chatService ! ChatService.Message( chatService ! ChatService.Message(
session, session,
@ -915,6 +879,30 @@ class ChatActor(
) )
} }
case (_, "tr", contents) =>
sessionActor ! SessionActor.SendResponse(
ZonePopulationUpdateMessage(4, 414, 138, contents.toInt, 138, contents.toInt / 2, 138, 0, 138, 0)
)
case (_, "nc", contents) =>
sessionActor ! SessionActor.SendResponse(
ZonePopulationUpdateMessage(4, 414, 138, 0, 138, contents.toInt, 138, contents.toInt / 3, 138, 0)
)
case (_, "vs", contents) =>
sessionActor ! SessionActor.SendResponse(
ZonePopulationUpdateMessage(4, 414, 138, contents.toInt * 2, 138, 0, 138, contents.toInt, 138, 0)
)
case (_, "bo", contents) =>
sessionActor ! SessionActor.SendResponse(
ZonePopulationUpdateMessage(4, 414, 138, 0, 138, 0, 138, 0, 138, contents.toInt)
)
/** Messages starting with ! are custom chat commands */
case (_, _, contents) if contents.startsWith("!") &&
customCommandMessages(message, session, chatService, cluster, gmCommandAllowed) => ;
case _ => case _ =>
log.warn(s"Unhandled chat message $message") log.warn(s"Unhandled chat message $message")
} }
@ -1386,4 +1374,83 @@ class ChatActor(
false false
} }
} }
private def captureBaseParamFacilities(session: Session, token: Option[String]): Option[Seq[Building]] = {
token.collect {
case "curr" =>
val list = captureBaseCurrSoi(session)
if (list.nonEmpty) {
Some(list.toSeq)
} else {
None
}
case "all" =>
val list = session.zone.Buildings.values.filter(_.CaptureTerminal.isDefined)
if (list.nonEmpty) {
Some(list.toSeq)
} else {
None
}
case name =>
val trueName = ZoneInfo
.values
.find(_.id.equals(session.zone.id))
.flatMap { info =>
info.aliases
.facilities
.collectFirst { case (key, internalName) if key.equalsIgnoreCase(name) => internalName }
}
.getOrElse(name)
session.zone.Buildings
.values
.find {
building => trueName.equalsIgnoreCase(building.Name) && building.CaptureTerminal.isDefined
}
.map(b => Seq(b))
}
.flatten
}
private def captureBaseCurrSoi(session: Session): Iterable[Building] = {
val charId = session.player.CharId
session.zone.Buildings.values.filter { building =>
building.PlayersInSOI.exists(_.CharId == charId)
}
}
private def captureBaseParamFaction(@unused session: Session, token: Option[String]): Option[PlanetSideEmpire.Value] = {
token.collect {
case faction =>
faction.toLowerCase() match {
case "tr" => Some(PlanetSideEmpire.TR)
case "nc" => Some(PlanetSideEmpire.NC)
case "vs" => Some(PlanetSideEmpire.VS)
case "none" => Some(PlanetSideEmpire.NEUTRAL)
case "bo" => Some(PlanetSideEmpire.NEUTRAL)
case "neutral" => Some(PlanetSideEmpire.NEUTRAL)
case _ => None
}
}.flatten
}
private def captureBaseParamTimer(@unused session: Session, token: Option[String]): Option[Int] = {
token.collect {
case n if n.forall(Character.isDigit) => n.toInt
}
}
private def firstParam[T](
session: Session,
buffer: Array[String],
func: (Session, Option[String])=>Option[T]
): (Option[T], Option[String], Array[String]) = {
val tokenOpt = buffer.headOption
val valueOpt = func(session, tokenOpt)
val outBuffer = if (valueOpt.nonEmpty) {
buffer.drop(1)
} else {
buffer
}
(valueOpt, tokenOpt, outBuffer)
}
} }

View file

@ -656,7 +656,6 @@ class ZoningOperations(
spawn.handleNewPlayerLoaded(player) spawn.handleNewPlayerLoaded(player)
} else { } else {
//alive but doesn't have a GUID; probably logging in? //alive but doesn't have a GUID; probably logging in?
session = session.copy(zone = Zone.Nowhere)
context.self ! ICS.ZoneResponse(Some(player.Zone)) context.self ! ICS.ZoneResponse(Some(player.Zone))
} }
} else { } else {

View file

@ -2,15 +2,21 @@ package net.psforever.objects.zones
import enumeratum.values.{IntEnum, IntEnumEntry} import enumeratum.values.{IntEnum, IntEnumEntry}
final case class AliasLookup(
zone: Seq[String] = Seq(),
facilities: Map[String, String] = Map()
)
sealed abstract class ZoneInfo( sealed abstract class ZoneInfo(
val value: Int, val value: Int,
val name: String, val name: String,
val id: String, val id: String,
val map: MapInfo, val map: MapInfo,
val aliases: Seq[String] = Seq() val aliases: AliasLookup = ZoneInfo.defaultAliases,
) extends IntEnumEntry {} ) extends IntEnumEntry {}
case object ZoneInfo extends IntEnum[ZoneInfo] { case object ZoneInfo extends IntEnum[ZoneInfo] {
private val defaultAliases = AliasLookup(Nil, Map.empty[String, String])
case object Solsar case object Solsar
extends ZoneInfo( extends ZoneInfo(
@ -98,7 +104,7 @@ case object ZoneInfo extends IntEnum[ZoneInfo] {
name = "NC Sanctuary", name = "NC Sanctuary",
id = "home1", id = "home1",
map = MapInfo.Map11, map = MapInfo.Map11,
aliases = Seq("nc-sanctuary") aliases = AliasLookup(zone = Seq("nc-sanctuary"))
) )
case object TrSanctuary case object TrSanctuary
@ -107,7 +113,7 @@ case object ZoneInfo extends IntEnum[ZoneInfo] {
name = "TR Sanctuary", name = "TR Sanctuary",
id = "home2", id = "home2",
map = MapInfo.Map12, map = MapInfo.Map12,
aliases = Seq("tr-sanctuary") aliases = AliasLookup(zone = Seq("tr-sanctuary"))
) )
case object VsSanctuary case object VsSanctuary
@ -116,7 +122,7 @@ case object ZoneInfo extends IntEnum[ZoneInfo] {
name = "VS Sanctuary", name = "VS Sanctuary",
id = "home3", id = "home3",
map = MapInfo.Map13, map = MapInfo.Map13,
aliases = Seq("vs-sanctuary") aliases = AliasLookup(zone = Seq("vs-sanctuary"))
) )
case object tzshtr case object tzshtr
@ -124,7 +130,8 @@ case object ZoneInfo extends IntEnum[ZoneInfo] {
value = 14, value = 14,
name = "tzshtr", name = "tzshtr",
id = "tzshtr", id = "tzshtr",
map = MapInfo.Map14 map = MapInfo.Map14,
aliases = AliasLookup(zone = Seq("tr-shooting"))
) )
case object tzdrtr case object tzdrtr
@ -132,7 +139,8 @@ case object ZoneInfo extends IntEnum[ZoneInfo] {
value = 15, value = 15,
name = "tzdrtr", name = "tzdrtr",
id = "tzdrtr", id = "tzdrtr",
map = MapInfo.Map15 map = MapInfo.Map15,
aliases = AliasLookup(zone = Seq("tr-driving"))
) )
case object tzcotr case object tzcotr
@ -148,7 +156,8 @@ case object ZoneInfo extends IntEnum[ZoneInfo] {
value = 17, value = 17,
name = "tzshnc", name = "tzshnc",
id = "tzshnc", id = "tzshnc",
map = MapInfo.Map14 map = MapInfo.Map14,
aliases = AliasLookup(zone = Seq("nc-shooting"))
) )
case object tzdrnc case object tzdrnc
@ -156,7 +165,8 @@ case object ZoneInfo extends IntEnum[ZoneInfo] {
value = 18, value = 18,
name = "tzdrnc", name = "tzdrnc",
id = "tzdrnc", id = "tzdrnc",
map = MapInfo.Map15 map = MapInfo.Map15,
aliases = AliasLookup(zone = Seq("nc-driving"))
) )
case object tzconc case object tzconc
@ -172,7 +182,8 @@ case object ZoneInfo extends IntEnum[ZoneInfo] {
value = 20, value = 20,
name = "tzshvs", name = "tzshvs",
id = "tzshvs", id = "tzshvs",
map = MapInfo.Map14 map = MapInfo.Map14,
aliases = AliasLookup(zone = Seq("vs-shooting"))
) )
case object tzdrvs case object tzdrvs
@ -180,7 +191,8 @@ case object ZoneInfo extends IntEnum[ZoneInfo] {
value = 21, value = 21,
name = "tzdrvs", name = "tzdrvs",
id = "tzdrvs", id = "tzdrvs",
map = MapInfo.Map15 map = MapInfo.Map15,
aliases = AliasLookup(zone = Seq("vs-driving"))
) )
case object tzcovs case object tzcovs
@ -244,7 +256,12 @@ case object ZoneInfo extends IntEnum[ZoneInfo] {
value = 29, value = 29,
name = "Extinction", name = "Extinction",
id = "i1", id = "i1",
map = MapInfo.Map99 map = MapInfo.Map99,
aliases = AliasLookup(facilities = Map(
("Mithra", "Blue_Base"),
("Yazata", "Red_Base"),
("Hvar", "Indigo_Base")
))
) )
case object Ascension case object Ascension
@ -252,7 +269,12 @@ case object ZoneInfo extends IntEnum[ZoneInfo] {
value = 30, value = 30,
name = "Ascension", name = "Ascension",
id = "i2", id = "i2",
map = MapInfo.Map98 map = MapInfo.Map98,
aliases = AliasLookup(facilities = Map(
("Zal", "Base_Alpha"),
("Rashnu", "Base_Bravo"),
("Sraosha", "Base_Charlie")
))
) )
case object Desolation case object Desolation
@ -260,7 +282,12 @@ case object ZoneInfo extends IntEnum[ZoneInfo] {
value = 31, value = 31,
name = "Desolation", name = "Desolation",
id = "i3", id = "i3",
map = MapInfo.Map97 map = MapInfo.Map97,
aliases = AliasLookup(facilities = Map(
("Dahaka", "Red_Base_97"),
("Jamshid", "Blue_Base_97"),
("Izha", "Indigo_Base_97")
))
) )
case object Nexus case object Nexus
@ -268,16 +295,20 @@ case object ZoneInfo extends IntEnum[ZoneInfo] {
value = 32, value = 32,
name = "Nexus", name = "Nexus",
id = "i4", id = "i4",
map = MapInfo.Map96 map = MapInfo.Map96,
aliases = AliasLookup(facilities = Map(
("Atar", "Nexus_base")
))
) )
val values: IndexedSeq[ZoneInfo] = findValues val values: IndexedSeq[ZoneInfo] = findValues
def findName(name: String): ZoneInfo = findNameOpt(name).get def findName(name: String): ZoneInfo = findNameOpt(name).get
def findNameOpt(name: String): Option[ZoneInfo] = def findNameOpt(name: String): Option[ZoneInfo] = {
val lowerName = name.toLowerCase()
values.find(v => values.find(v =>
v.name.toLowerCase() == name.toLowerCase() || v.aliases.map(_.toLowerCase()).contains(name.toLowerCase()) v.name.toLowerCase().equals(lowerName) || v.aliases.zone.map(_.toLowerCase()).contains(lowerName)
) )
}
} }