mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +00:00
Persistent Vehicle Loadouts (#863)
* persisting vehicle loadouts in between sessions, saving to db and loading from db * reusing refactored code for clob; initial loadout without empty entries; reverting method of stopping session child actors
This commit is contained in:
parent
e2a81d68c1
commit
7c64b23e1f
|
|
@ -0,0 +1,8 @@
|
|||
CREATE TABLE IF NOT EXISTS "vehicleloadout" (
|
||||
"id" SERIAL PRIMARY KEY NOT NULL,
|
||||
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
||||
"loadout_number" INT NOT NULL,
|
||||
"name" VARCHAR(36) NOT NULL,
|
||||
"vehicle" SMALLINT NOT NULL,
|
||||
"items" TEXT NOT NULL
|
||||
);
|
||||
|
|
@ -8,9 +8,10 @@ import akka.actor.typed.{ActorRef, Behavior, PostStop, SupervisorStrategy}
|
|||
import net.psforever.objects.avatar._
|
||||
import net.psforever.objects.definition.converter.CharacterSelectConverter
|
||||
import net.psforever.objects.definition._
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.inventory.{Container, InventoryItem}
|
||||
import net.psforever.objects.loadouts.{InfantryLoadout, Loadout}
|
||||
import net.psforever.objects.inventory.Container
|
||||
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
|
||||
import net.psforever.objects.inventory.InventoryItem
|
||||
import net.psforever.objects.loadouts.{InfantryLoadout, Loadout, VehicleLoadout}
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.locker.LockerContainer
|
||||
import net.psforever.packet.game.objectcreate.ObjectClass
|
||||
|
|
@ -96,7 +97,10 @@ object AvatarActor {
|
|||
/** Delete a loadout */
|
||||
final case class DeleteLoadout(player: Player, loadoutType: LoadoutType.Value, number: Int) extends Command
|
||||
|
||||
/** Refresh the client's loadouts */
|
||||
/** Refresh the client's loadouts, excluding empty entries */
|
||||
final case class InitialRefreshLoadouts() extends Command
|
||||
|
||||
/** Refresh all of the client's loadouts */
|
||||
final case class RefreshLoadouts() extends Command
|
||||
|
||||
/** Take all the entries in the player's locker and write it to the database */
|
||||
|
|
@ -349,7 +353,7 @@ class AvatarActor(
|
|||
.filter(_.id == lift(avatar.id))
|
||||
.update(_.lastLogin -> lift(LocalDateTime.now()))
|
||||
)
|
||||
loadouts <- loadLoadouts()
|
||||
loadouts <- initializeAllLoadouts()
|
||||
implants <- ctx.run(query[persistence.Implant].filter(_.avatarId == lift(avatar.id)))
|
||||
certs <- ctx.run(query[persistence.Certification].filter(_.avatarId == lift(avatar.id)))
|
||||
locker <- loadLocker()
|
||||
|
|
@ -374,12 +378,7 @@ class AvatarActor(
|
|||
Behaviors.same
|
||||
|
||||
case ReplaceAvatar(newAvatar) =>
|
||||
avatar = newAvatar
|
||||
avatar.deployables.UpdateMaxCounts(avatar.certifications)
|
||||
updateDeployableUIElements(
|
||||
avatar.deployables.UpdateUI()
|
||||
)
|
||||
|
||||
replaceAvatar(newAvatar)
|
||||
Behaviors.same
|
||||
|
||||
case AddFirstTimeEvent(event) =>
|
||||
|
|
@ -434,7 +433,7 @@ class AvatarActor(
|
|||
sessionActor ! SessionActor.SendResponse(
|
||||
PlanetsideAttributeMessage(session.get.player.GUID, 24, certification.value)
|
||||
)
|
||||
context.self ! ReplaceAvatar(
|
||||
replaceAvatar(
|
||||
avatar.copy(certifications = avatar.certifications.diff(replace) + certification)
|
||||
)
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
|
|
@ -487,7 +486,7 @@ class AvatarActor(
|
|||
)
|
||||
case Success(certs) =>
|
||||
val player = session.get.player
|
||||
context.self ! ReplaceAvatar(avatar.copy(certifications = avatar.certifications.diff(certs)))
|
||||
replaceAvatar(avatar.copy(certifications = avatar.certifications.diff(certs)))
|
||||
certs.foreach { cert =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
PlanetsideAttributeMessage(player.GUID, 25, cert.value)
|
||||
|
|
@ -548,7 +547,7 @@ class AvatarActor(
|
|||
)
|
||||
.onComplete {
|
||||
case Success(_) =>
|
||||
context.self ! ReplaceAvatar(avatar.copy(certifications = certifications))
|
||||
replaceAvatar(avatar.copy(certifications = certifications))
|
||||
case Failure(exception) =>
|
||||
log.error(exception)("db failure")
|
||||
}
|
||||
|
|
@ -584,9 +583,7 @@ class AvatarActor(
|
|||
.run(query[persistence.Implant].insert(_.name -> lift(definition.Name), _.avatarId -> lift(avatar.id)))
|
||||
.onComplete {
|
||||
case Success(_) =>
|
||||
context.self ! ReplaceAvatar(
|
||||
avatar.copy(implants = avatar.implants.updated(_index, Some(Implant(definition))))
|
||||
)
|
||||
replaceAvatar(avatar.copy(implants = avatar.implants.updated(_index, Some(Implant(definition)))))
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
AvatarImplantMessage(
|
||||
session.get.player.GUID,
|
||||
|
|
@ -627,7 +624,7 @@ class AvatarActor(
|
|||
)
|
||||
.onComplete {
|
||||
case Success(_) =>
|
||||
context.self ! ReplaceAvatar(avatar.copy(implants = avatar.implants.updated(_index, None)))
|
||||
replaceAvatar(avatar.copy(implants = avatar.implants.updated(_index, None)))
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
AvatarImplantMessage(session.get.player.GUID, ImplantAction.Remove, _index, 0)
|
||||
)
|
||||
|
|
@ -647,59 +644,69 @@ class AvatarActor(
|
|||
Behaviors.same
|
||||
|
||||
case SaveLoadout(player, loadoutType, label, number) =>
|
||||
log.info(s"${player.Name} wishes to save a favorite $loadoutType loadout as #${number+1}")
|
||||
val name = label.getOrElse(s"missing_loadout_${number + 1}")
|
||||
loadoutType match {
|
||||
val (lineNo, result): (Int, Future[Loadout]) = loadoutType match {
|
||||
case LoadoutType.Infantry =>
|
||||
storeLoadout(player, name, number).onComplete {
|
||||
case Success(_) =>
|
||||
loadLoadouts().onComplete {
|
||||
case Success(loadouts) =>
|
||||
context.self ! ReplaceAvatar(avatar.copy(loadouts = loadouts))
|
||||
context.self ! RefreshLoadouts()
|
||||
case Failure(exception) => log.error(exception)("db failure")
|
||||
}
|
||||
|
||||
case Failure(exception) => log.error(exception)("db failure")
|
||||
}
|
||||
(
|
||||
number,
|
||||
storeLoadout(player, name, number)
|
||||
)
|
||||
|
||||
case LoadoutType.Vehicle =>
|
||||
// TODO
|
||||
// storeLoadout(player, name, 10 + number)
|
||||
sessionActor ! SessionActor.SendResponse(FavoritesMessage(loadoutType, player.GUID, number, name))
|
||||
(
|
||||
number + 10,
|
||||
player.Zone.GUID(avatar.vehicle) match {
|
||||
case Some(vehicle: Vehicle) =>
|
||||
storeVehicleLoadout(player, name, number, vehicle)
|
||||
case _ =>
|
||||
throwLoadoutFailure(s"no owned vehicle found for ${player.Name}")
|
||||
}
|
||||
)
|
||||
}
|
||||
result.onComplete {
|
||||
case Success(loadout) =>
|
||||
replaceAvatar(avatar.copy(loadouts = avatar.loadouts.updated(lineNo, Some(loadout))))
|
||||
refreshLoadout(lineNo)
|
||||
case Failure(exception) =>
|
||||
log.error(exception)("db failure (?)")
|
||||
}
|
||||
Behaviors.same
|
||||
|
||||
case DeleteLoadout(player, loadoutType, number) =>
|
||||
log.info(s"${player.Name} wishes to delete a favorite $loadoutType loadout - #${number+1}")
|
||||
import ctx._
|
||||
ctx
|
||||
.run(
|
||||
query[persistence.Loadout]
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.filter(_.loadoutNumber == lift(number))
|
||||
.delete
|
||||
)
|
||||
.onComplete {
|
||||
case Success(_) =>
|
||||
context.self ! ReplaceAvatar(avatar.copy(loadouts = avatar.loadouts.updated(number, None)))
|
||||
sessionActor ! SessionActor.SendResponse(FavoritesMessage(loadoutType, player.GUID, number, ""))
|
||||
case Failure(exception) =>
|
||||
log.error(exception)("db failure")
|
||||
}
|
||||
Behaviors.same
|
||||
|
||||
case RefreshLoadouts() =>
|
||||
avatar.loadouts.zipWithIndex.foreach {
|
||||
case (Some(loadout: InfantryLoadout), index) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
FavoritesMessage(
|
||||
LoadoutType.Infantry,
|
||||
session.get.player.GUID,
|
||||
index,
|
||||
loadout.label,
|
||||
InfantryLoadout.DetermineSubtypeB(loadout.exosuit, loadout.subtype)
|
||||
val (lineNo, result) = loadoutType match {
|
||||
case LoadoutType.Infantry if avatar.loadouts(number).nonEmpty =>
|
||||
(
|
||||
number,
|
||||
ctx.run(
|
||||
query[persistence.Loadout]
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.filter(_.loadoutNumber == lift(number))
|
||||
.delete
|
||||
)
|
||||
)
|
||||
case _ => ;
|
||||
case LoadoutType.Vehicle if avatar.loadouts(number + 10).nonEmpty =>
|
||||
val lineNo = number + 10
|
||||
(
|
||||
lineNo,
|
||||
ctx.run(
|
||||
query[persistence.Vehicleloadout]
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.filter(_.loadoutNumber == lift(number))
|
||||
.delete
|
||||
)
|
||||
)
|
||||
case _ =>
|
||||
(number, throwLoadoutFailure("unhandled loadout type or no loadout"))
|
||||
}
|
||||
result.onComplete {
|
||||
case Success(_) =>
|
||||
replaceAvatar(avatar.copy(loadouts = avatar.loadouts.updated(lineNo, None)))
|
||||
sessionActor ! SessionActor.SendResponse(FavoritesMessage(loadoutType, player.GUID, number, ""))
|
||||
case Failure(exception) =>
|
||||
log.error(exception)("db failure (?)")
|
||||
}
|
||||
Behaviors.same
|
||||
|
||||
|
|
@ -707,6 +714,14 @@ class AvatarActor(
|
|||
saveLockerFunc()
|
||||
Behaviors.same
|
||||
|
||||
case InitialRefreshLoadouts() =>
|
||||
refreshLoadouts(avatar.loadouts.zipWithIndex)
|
||||
Behaviors.same
|
||||
|
||||
case RefreshLoadouts() =>
|
||||
refreshLoadouts(avatar.loadouts.zipWithIndex.collect { case out @ (Some(_), _) => out })
|
||||
Behaviors.same
|
||||
|
||||
case UpdatePurchaseTime(definition, time) =>
|
||||
// TODO save to db
|
||||
var newTimes = avatar.purchaseTimes
|
||||
|
|
@ -955,6 +970,22 @@ class AvatarActor(
|
|||
}
|
||||
}
|
||||
|
||||
def throwLoadoutFailure(msg: String): Future[Loadout] = {
|
||||
throwLoadoutFailure(new Exception(msg))
|
||||
}
|
||||
|
||||
def throwLoadoutFailure(ex: Throwable): Future[Loadout] = {
|
||||
Future.failed(ex).asInstanceOf[Future[Loadout]]
|
||||
}
|
||||
|
||||
def replaceAvatar(newAvatar: Avatar): Unit = {
|
||||
avatar = newAvatar
|
||||
avatar.deployables.UpdateMaxCounts(avatar.certifications)
|
||||
updateDeployableUIElements(
|
||||
avatar.deployables.UpdateUI()
|
||||
)
|
||||
}
|
||||
|
||||
def setCosmetics(cosmetics: Set[Cosmetic]): Future[Unit] = {
|
||||
val p = Promise[Unit]()
|
||||
|
||||
|
|
@ -1183,9 +1214,8 @@ class AvatarActor(
|
|||
}
|
||||
}
|
||||
|
||||
def storeLoadout(owner: Player, label: String, line: Int): Future[Unit] = {
|
||||
def storeLoadout(owner: Player, label: String, line: Int): Future[Loadout] = {
|
||||
import ctx._
|
||||
|
||||
val items: String = {
|
||||
val clobber: StringBuilder = new StringBuilder()
|
||||
//encode holsters
|
||||
|
|
@ -1226,7 +1256,53 @@ class AvatarActor(
|
|||
)
|
||||
)
|
||||
}
|
||||
} yield ()
|
||||
} yield Loadout.Create(owner, label)
|
||||
}
|
||||
|
||||
def storeVehicleLoadout(owner: Player, label: String, line: Int, vehicle: Vehicle): Future[Loadout] = {
|
||||
import ctx._
|
||||
val items: String = {
|
||||
val clobber: StringBuilder = new StringBuilder()
|
||||
//encode holsters
|
||||
vehicle
|
||||
.Weapons
|
||||
.collect {
|
||||
case (index, slot: EquipmentSlot) if slot.Equipment.nonEmpty =>
|
||||
clobber.append(encodeLoadoutClobFragment(slot.Equipment.get, index))
|
||||
}
|
||||
//encode inventory
|
||||
vehicle.Inventory.Items.foreach {
|
||||
case InventoryItem(obj, index) =>
|
||||
clobber.append(encodeLoadoutClobFragment(obj, index))
|
||||
}
|
||||
clobber.mkString.drop(1)
|
||||
}
|
||||
|
||||
for {
|
||||
loadouts <- ctx.run(
|
||||
query[persistence.Vehicleloadout]
|
||||
.filter(_.avatarId == lift(owner.CharId))
|
||||
.filter(_.loadoutNumber == lift(line))
|
||||
)
|
||||
_ <- loadouts.headOption match {
|
||||
case Some(loadout) =>
|
||||
ctx.run(
|
||||
query[persistence.Vehicleloadout]
|
||||
.filter(_.id == lift(loadout.id))
|
||||
.update(_.name -> lift(label), _.vehicle -> lift(vehicle.Definition.ObjectId), _.items -> lift(items))
|
||||
)
|
||||
case None =>
|
||||
ctx.run(
|
||||
query[persistence.Vehicleloadout].insert(
|
||||
_.avatarId -> lift(owner.avatar.id),
|
||||
_.loadoutNumber -> lift(line),
|
||||
_.name -> lift(label),
|
||||
_.vehicle -> lift(vehicle.Definition.ObjectId),
|
||||
_.items -> lift(items)
|
||||
)
|
||||
)
|
||||
}
|
||||
} yield Loadout.Create(vehicle, label)
|
||||
}
|
||||
|
||||
def storeNewLocker(): Unit = {
|
||||
|
|
@ -1293,6 +1369,19 @@ class AvatarActor(
|
|||
s"/${equipment.getClass.getSimpleName},$index,${equipment.Definition.ObjectId},$ammoInfo"
|
||||
}
|
||||
|
||||
def initializeAllLoadouts(): Future[Seq[Option[Loadout]]] = {
|
||||
for {
|
||||
infantry <- loadLoadouts().andThen {
|
||||
case out @ Success(_) => out
|
||||
case Failure(_) => Future(Array.fill[Option[Loadout]](10)(None).toSeq)
|
||||
}
|
||||
vehicles <- loadVehicleLoadouts().andThen {
|
||||
case out @ Success(_) => out
|
||||
case Failure(_) => Future(Array.fill[Option[Loadout]](5)(None).toSeq)
|
||||
}
|
||||
} yield infantry ++ vehicles
|
||||
}
|
||||
|
||||
def loadLoadouts(): Future[Seq[Option[Loadout]]] = {
|
||||
import ctx._
|
||||
ctx
|
||||
|
|
@ -1311,7 +1400,102 @@ class AvatarActor(
|
|||
result
|
||||
}
|
||||
}
|
||||
.map { loadouts => (0 until 15).map { index => loadouts.find(_._1 == index).map(_._2) } }
|
||||
.map { loadouts => (0 until 10).map { index => loadouts.find(_._1 == index).map(_._2) } }
|
||||
}
|
||||
|
||||
def loadVehicleLoadouts(): Future[Seq[Option[Loadout]]] = {
|
||||
import ctx._
|
||||
ctx
|
||||
.run(query[persistence.Vehicleloadout].filter(_.avatarId == lift(avatar.id)))
|
||||
.map { loadouts =>
|
||||
loadouts.map { loadout =>
|
||||
val toy = new Vehicle(DefinitionUtil.idToDefinition(loadout.vehicle).asInstanceOf[VehicleDefinition])
|
||||
buildContainedEquipmentFromClob(toy, loadout.items)
|
||||
|
||||
val result = (loadout.loadoutNumber, Loadout.Create(toy, loadout.name))
|
||||
toy.Weapons.values.foreach(slot => {
|
||||
slot.Equipment = None
|
||||
})
|
||||
toy.Inventory.Clear()
|
||||
result
|
||||
}
|
||||
}
|
||||
.map { loadouts => (0 until 5).map { index => loadouts.find(_._1 == index).map(_._2) } }
|
||||
}
|
||||
|
||||
def refreshLoadouts(loadouts: Iterable[(Option[Loadout], Int)]): Unit = {
|
||||
loadouts.map {
|
||||
case (Some(loadout: InfantryLoadout), index) =>
|
||||
FavoritesMessage(
|
||||
LoadoutType.Infantry,
|
||||
session.get.player.GUID,
|
||||
index,
|
||||
loadout.label,
|
||||
InfantryLoadout.DetermineSubtypeB(loadout.exosuit, loadout.subtype)
|
||||
)
|
||||
case (Some(loadout: VehicleLoadout), index) =>
|
||||
FavoritesMessage(
|
||||
LoadoutType.Vehicle,
|
||||
session.get.player.GUID,
|
||||
index - 10,
|
||||
loadout.label,
|
||||
0
|
||||
)
|
||||
case (_, index) =>
|
||||
val (mtype, lineNo) = if (index < 10) {
|
||||
(LoadoutType.Infantry, index)
|
||||
} else {
|
||||
(LoadoutType.Vehicle, index - 10)
|
||||
}
|
||||
FavoritesMessage(
|
||||
mtype,
|
||||
session.get.player.GUID,
|
||||
lineNo,
|
||||
"",
|
||||
0
|
||||
)
|
||||
}.foreach { sessionActor ! SessionActor.SendResponse(_) }
|
||||
}
|
||||
|
||||
def refreshLoadout(line: Int): Unit = {
|
||||
avatar.loadouts.lift(line) match {
|
||||
case Some(Some(loadout: InfantryLoadout)) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
FavoritesMessage(
|
||||
LoadoutType.Infantry,
|
||||
session.get.player.GUID,
|
||||
line,
|
||||
loadout.label,
|
||||
InfantryLoadout.DetermineSubtypeB(loadout.exosuit, loadout.subtype)
|
||||
)
|
||||
)
|
||||
case Some(Some(loadout: VehicleLoadout)) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
FavoritesMessage(
|
||||
LoadoutType.Vehicle,
|
||||
session.get.player.GUID,
|
||||
line - 10,
|
||||
loadout.label,
|
||||
0
|
||||
)
|
||||
)
|
||||
case Some(None) =>
|
||||
val (mtype, lineNo) = if (line < 10) {
|
||||
(LoadoutType.Infantry, line)
|
||||
} else {
|
||||
(LoadoutType.Vehicle, line - 10)
|
||||
}
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
FavoritesMessage(
|
||||
mtype,
|
||||
session.get.player.GUID,
|
||||
lineNo,
|
||||
"",
|
||||
0
|
||||
)
|
||||
)
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
def loadLocker(): Future[LockerContainer] = {
|
||||
|
|
|
|||
|
|
@ -332,8 +332,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
}
|
||||
}
|
||||
// when going from classic -> typed this seems necessary
|
||||
akka.actor.TypedActor(context.system).poisonPill(avatarActor)
|
||||
akka.actor.TypedActor(context.system).poisonPill(chatActor)
|
||||
context.stop(avatarActor)
|
||||
context.stop(chatActor)
|
||||
}
|
||||
|
||||
def ValidObject(id: Int): Option[PlanetSideGameObject] = ValidObject(Some(PlanetSideGUID(id)))
|
||||
|
|
@ -3078,7 +3078,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
sendResponse(CreateShortcutMessage(guid, 1, 0, true, Shortcut.Medkit))
|
||||
sendResponse(ChangeShortcutBankMessage(guid, 0))
|
||||
//Favorites lists
|
||||
avatarActor ! AvatarActor.RefreshLoadouts()
|
||||
avatarActor ! AvatarActor.InitialRefreshLoadouts()
|
||||
|
||||
sendResponse(
|
||||
SetChatFilterMessage(ChatChannel.Platoon, false, ChatChannel.values.toList)
|
||||
|
|
@ -5105,7 +5105,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
|
||||
case msg @ FavoritesRequest(player_guid, loadoutType, action, line, label) =>
|
||||
CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
log.info(s"${player.Name} wishes to load a saved favorite loadout")
|
||||
action match {
|
||||
case FavoritesAction.Save => avatarActor ! AvatarActor.SaveLoadout(player, loadoutType, label, line)
|
||||
case FavoritesAction.Delete => avatarActor ! AvatarActor.DeleteLoadout(player, loadoutType, line)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package net.psforever.persistence
|
||||
|
||||
case class Vehicleloadout(id: Int, avatarId: Int, loadoutNumber: Int, name: String, vehicle: Int, items: String)
|
||||
|
|
@ -218,7 +218,50 @@ object DefinitionUtil {
|
|||
case 39 => advanced_ace
|
||||
case 148 => boomer
|
||||
case 149 => boomer_trigger
|
||||
case _ => frag_grenade
|
||||
//vehicles
|
||||
case 67 => apc_tr
|
||||
case 66 => apc_nc
|
||||
case 68 => apc_vs
|
||||
case 46 => ams
|
||||
case 60 => ant
|
||||
//case 83 => aphelion_flight
|
||||
//case 84 => aphelion_gunner
|
||||
case 118 => aurora
|
||||
case 135 => battlewagon
|
||||
//case 199 => colossus_flight
|
||||
//case 200 => colossus_gunner
|
||||
case 258 => droppod
|
||||
case 259 => dropship
|
||||
case 294 => flail
|
||||
case 335 => fury
|
||||
case 338 => galaxy_gunship
|
||||
case 432 => liberator
|
||||
case 441 => lightgunship
|
||||
case 446 => lightning
|
||||
case 459 => lodestar
|
||||
case 470 => magrider
|
||||
case 572 => mosquito
|
||||
case 532 => mediumtransport
|
||||
case 608 => orbital_shuttle
|
||||
//case 642 => peregrine_flight
|
||||
//case 643 => peregrine_gunner
|
||||
case 671 => phantasm
|
||||
case 697 => prowler
|
||||
case 707 => quadassault
|
||||
case 710 => quadstealth
|
||||
case 741 => router
|
||||
case 784 => skyguard
|
||||
case 847 => switchblade
|
||||
case 862 => threemanheavybuggy
|
||||
case 865 => thunderer
|
||||
case 896 => two_man_assault_buggy
|
||||
case 898 => twomanheavybuggy
|
||||
case 900 => twomanhoverbuggy
|
||||
case 923 => vanguard
|
||||
case 986 => vulture
|
||||
case 997 => wasp
|
||||
//default
|
||||
case _ => throw new IllegalArgumentException(s"you can not build $id")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue