mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-07-14 16:04:41 +00:00
Lump of Coal (#982)
* preliminary elements needed to battle frame robotics; mostly from previous branch * introduction of FrameVehicleStateMessage and anticipated event system paths for BFR's; spawning amenities for BFR's are parsed and built from the zonemap files, but their coordinates are currently incorrect, and the resulting entity will not function atm * bfr's spawn correctly; default arm weapons will spawn correctly; bfr rearm terminal added but arm swap not working correctly; bfr shields charge if not full; proper separation of vehicle spawn pad types * arm weapon swapping in bfr's; swapped weapons switch, contextually, to either *_left or to *_right depending on the mounting; partial support for entities that do not have an OCDM packet form * crouching improves shield regeneration * some projectiles damage the bfr regardless of its shield * delay the final vehicle explosion; start of vehicle subsystems * handling for bfr shield ui updates; more of vehicle subsystems; corrections to TradeMessage packet; clarifications for FrameVehicleStateMessage package; report on flight status of bfr's * control agency support for vehicle subsystems for arm weapon fire control * vehicle capacitor, for what it's worth; shield and capacitor are influenced by recharge freeze and drain * initial packet and tests for AvatarAwardMessage; update the fields of FreindsResponse, DetailedCharacterData, and LoadoutType for FavoritesMessage; corrections to intiailization packets in SessionActor; players start as imprinted by default * support for GOAM and GAM integration into vehicle control agencies using a basic actor superclass; addition of vehicle subsystems; modifications to bfr control agency to allow for weapon handiness and subsystem control; fixed Fit mapping for vehicle override; made mountable seat transcoders independent * delayed explosions to accompany the delayed death for the bfr; bfr terminal window closes on successful purchase * the bfr armor siphon works * clarification for bfr inventory item manipulation; corrections to length of bfr transcoder for flight variants; everything else in in support of the various arm weapons that can be assigned to the bfr, including damage proxy support for causing/interacting with/cleaning up after radiation cloud projectiles * fixed the apc emp burst; fixed bfr arm weapon manipulation for activated subsystem; armor and ntu siphon support * battleframe loadouts available upon vehicle spawn (vs and tr only) * adb values for siphons; subsystem update message; some repairs * cargo vehicles are subject to radiation damage; damage for battleframes are different depending on shield evasion status; battleframe loadout deleting supported; bfr kill box; automatically wire bfr sheds, includeing the ones in sanctuary * proper bfr spawn angles; bfr vehicle timers; projectiles are no longer radiation clouds by default; better remote projectile cleanup; resolving incorrect weapon arm enabled states for bfrs * added tests for FrameVehicleState and GenericObjectActionAtPosition; pass around maximum sector for zone interactions * changed the triggers for the stamina regeneration timer * potential fix for issue related to finding arm weapon mounts * modifications to how vehicle subsystems are automated; jammer field updates; support and passing around custom block map ranges; does include activated dev tests for battleframe PAM, which will need to be stripped out later * commit while working on subsystems mk2 * subsystems fail when jammed; an unoccupied bfr does not have shields active; pulling a bfr of one variant should block the other variant too * fix distance check with radiation clouds; blocked bfr weaponry from anywhere but bfr arm mounts and cursor; ammunition depletion of aphelion laser; bfr shields deactivates when unoccupied * significant modifications to vehicle subsystem operations; disambiguation of weapon subsystems; debuffs to charge rate and use rate for the capacitor and shield of bfr; test for ComponentDamageMessage; somewhat proper jammering operations for bfr
This commit is contained in:
parent
46763b7877
commit
6ae0b44848
140 changed files with 10426 additions and 2462 deletions
|
|
@ -694,7 +694,7 @@ class PacketCodingActorKTest extends ActorTest {
|
||||||
0L,
|
0L,
|
||||||
0L,
|
0L,
|
||||||
0L,
|
0L,
|
||||||
Some(DCDExtra2(0, 0)),
|
Some(ImprintingProgress(0, 0)),
|
||||||
Nil,
|
Nil,
|
||||||
Nil,
|
Nil,
|
||||||
false,
|
false,
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,7 @@ object VehicleSpawnPadControlTest {
|
||||||
|
|
||||||
val terminal = Terminal(GlobalDefinitions.vehicle_terminal_combined)
|
val terminal = Terminal(GlobalDefinitions.vehicle_terminal_combined)
|
||||||
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
|
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
|
||||||
val weapon = vehicle.WeaponControlledFromSeat(1).get.asInstanceOf[Tool]
|
val weapon = vehicle.WeaponControlledFromSeat(1).head.asInstanceOf[Tool]
|
||||||
val guid: NumberPoolHub = new NumberPoolHub(MaxNumberSource(5))
|
val guid: NumberPoolHub = new NumberPoolHub(MaxNumberSource(5))
|
||||||
guid.AddPool("test-pool", (0 to 5).toList)
|
guid.AddPool("test-pool", (0 to 5).toList)
|
||||||
guid.register(vehicle, "test-pool")
|
guid.register(vehicle, "test-pool")
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,12 @@
|
||||||
"Max": 39500,
|
"Max": 39500,
|
||||||
"Selector": "random"
|
"Selector": "random"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Name": "rc-projectiles",
|
||||||
|
"Start": 39701,
|
||||||
|
"Max": 40000,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Name": "projectiles",
|
"Name": "projectiles",
|
||||||
"Start": 40100,
|
"Start": 40100,
|
||||||
|
|
|
||||||
|
|
@ -40,5 +40,11 @@
|
||||||
"Start": 37501,
|
"Start": 37501,
|
||||||
"Max": 40099,
|
"Max": 40099,
|
||||||
"Selector": "random"
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "rc-projectiles",
|
||||||
|
"Start": 64001,
|
||||||
|
"Max": 64300,
|
||||||
|
"Selector": "random"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -40,5 +40,11 @@
|
||||||
"Start": 36601,
|
"Start": 36601,
|
||||||
"Max": 39600,
|
"Max": 39600,
|
||||||
"Selector": "random"
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "rc-projectiles",
|
||||||
|
"Start": 64001,
|
||||||
|
"Max": 64300,
|
||||||
|
"Selector": "random"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -377,7 +377,12 @@ class AvatarActor(
|
||||||
implants = implants.map(implant => Some(Implant(implant.toImplantDefinition))).padTo(3, None),
|
implants = implants.map(implant => Some(Implant(implant.toImplantDefinition))).padTo(3, None),
|
||||||
locker = locker
|
locker = locker
|
||||||
))
|
))
|
||||||
defaultStaminaRegen(initialDelay = 0.5f seconds)
|
// if we need to start stamina regeneration
|
||||||
|
tryRestoreStaminaForSession(stamina = 1) match {
|
||||||
|
case Some(sess) =>
|
||||||
|
defaultStaminaRegen(initialDelay = 0.5f seconds)
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
replyTo ! AvatarLoginResponse(avatar)
|
replyTo ! AvatarLoginResponse(avatar)
|
||||||
case Failure(e) =>
|
case Failure(e) =>
|
||||||
log.error(e)("db failure")
|
log.error(e)("db failure")
|
||||||
|
|
@ -386,8 +391,7 @@ class AvatarActor(
|
||||||
|
|
||||||
case ReplaceAvatar(newAvatar) =>
|
case ReplaceAvatar(newAvatar) =>
|
||||||
replaceAvatar(newAvatar)
|
replaceAvatar(newAvatar)
|
||||||
staminaRegenTimer.cancel()
|
startIfStoppedStaminaRegen(initialDelay = 0.5f seconds)
|
||||||
defaultStaminaRegen(initialDelay = 0.5f seconds)
|
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
case AddFirstTimeEvent(event) =>
|
case AddFirstTimeEvent(event) =>
|
||||||
|
|
@ -672,6 +676,18 @@ class AvatarActor(
|
||||||
throwLoadoutFailure(s"no owned vehicle found for ${player.Name}")
|
throwLoadoutFailure(s"no owned vehicle found for ${player.Name}")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
case LoadoutType.Battleframe =>
|
||||||
|
(
|
||||||
|
number + 15,
|
||||||
|
player.Zone.GUID(avatar.vehicle) match {
|
||||||
|
case Some(vehicle: Vehicle)
|
||||||
|
if GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition) =>
|
||||||
|
storeVehicleLoadout(player, name, number + 5, vehicle)
|
||||||
|
case _ =>
|
||||||
|
throwLoadoutFailure(s"no owned battleframe found for ${player.Name}")
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
result.onComplete {
|
result.onComplete {
|
||||||
case Success(loadout) =>
|
case Success(loadout) =>
|
||||||
|
|
@ -697,9 +713,8 @@ class AvatarActor(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
case LoadoutType.Vehicle if avatar.loadouts(number + 10).nonEmpty =>
|
case LoadoutType.Vehicle if avatar.loadouts(number + 10).nonEmpty =>
|
||||||
val lineNo = number + 10
|
|
||||||
(
|
(
|
||||||
lineNo,
|
number + 10,
|
||||||
ctx.run(
|
ctx.run(
|
||||||
query[persistence.Vehicleloadout]
|
query[persistence.Vehicleloadout]
|
||||||
.filter(_.avatarId == lift(avatar.id))
|
.filter(_.avatarId == lift(avatar.id))
|
||||||
|
|
@ -707,8 +722,18 @@ class AvatarActor(
|
||||||
.delete
|
.delete
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
case LoadoutType.Battleframe if avatar.loadouts(number + 15).nonEmpty =>
|
||||||
|
(
|
||||||
|
number + 15,
|
||||||
|
ctx.run(
|
||||||
|
query[persistence.Vehicleloadout]
|
||||||
|
.filter(_.avatarId == lift(avatar.id))
|
||||||
|
.filter(_.loadoutNumber == lift(number + 5))
|
||||||
|
.delete
|
||||||
|
)
|
||||||
|
)
|
||||||
case _ =>
|
case _ =>
|
||||||
(number, throwLoadoutFailure("unhandled loadout type or no loadout"))
|
(number, throwLoadoutFailure(msg = "unhandled loadout type or no loadout"))
|
||||||
}
|
}
|
||||||
result.onComplete {
|
result.onComplete {
|
||||||
case Success(_) =>
|
case Success(_) =>
|
||||||
|
|
@ -739,7 +764,7 @@ class AvatarActor(
|
||||||
Avatar.purchaseCooldowns.get(item) match {
|
Avatar.purchaseCooldowns.get(item) match {
|
||||||
case Some(cooldown) =>
|
case Some(cooldown) =>
|
||||||
//only send for items with cooldowns
|
//only send for items with cooldowns
|
||||||
newTimes = newTimes.updated(item.Name, time)
|
newTimes = newTimes.updated(name, time)
|
||||||
updatePurchaseTimer(name, cooldown.toSeconds, unk1 = true)
|
updatePurchaseTimer(name, cooldown.toSeconds, unk1 = true)
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
|
@ -782,14 +807,8 @@ class AvatarActor(
|
||||||
implants = avatar.implants.updated(slot, Some(implant.copy(active = true)))
|
implants = avatar.implants.updated(slot, Some(implant.copy(active = true)))
|
||||||
))
|
))
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
AvatarImplantMessage(
|
AvatarImplantMessage(session.get.player.GUID, ImplantAction.Activation, slot, 1)
|
||||||
session.get.player.GUID,
|
|
||||||
ImplantAction.Activation,
|
|
||||||
slot,
|
|
||||||
1
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Activation sound / effect
|
// Activation sound / effect
|
||||||
session.get.zone.AvatarEvents ! AvatarServiceMessage(
|
session.get.zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
session.get.zone.id,
|
session.get.zone.id,
|
||||||
|
|
@ -799,9 +818,7 @@ class AvatarActor(
|
||||||
implant.definition.implantType.value * 2 + 1
|
implant.definition.implantType.value * 2 + 1
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
implantTimers.get(slot).foreach(_.cancel())
|
implantTimers.get(slot).foreach(_.cancel())
|
||||||
|
|
||||||
val interval = implant.definition.GetCostIntervalByExoSuit(session.get.player.ExoSuit).milliseconds
|
val interval = implant.definition.GetCostIntervalByExoSuit(session.get.player.ExoSuit).milliseconds
|
||||||
// TODO costInterval should be an option ^
|
// TODO costInterval should be an option ^
|
||||||
if (interval.toMillis > 0) {
|
if (interval.toMillis > 0) {
|
||||||
|
|
@ -880,26 +897,17 @@ class AvatarActor(
|
||||||
tryRestoreStaminaForSession(stamina) match {
|
tryRestoreStaminaForSession(stamina) match {
|
||||||
case Some(sess) =>
|
case Some(sess) =>
|
||||||
actuallyRestoreStamina(stamina, sess)
|
actuallyRestoreStamina(stamina, sess)
|
||||||
defaultStaminaRegen(initialDelay = 0.5f seconds)
|
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
case RestoreStaminaPeriodically(stamina) =>
|
case RestoreStaminaPeriodically(stamina) =>
|
||||||
tryRestoreStaminaForSession(stamina) match {
|
restoreStaminaPeriodically(stamina)
|
||||||
case Some(sess) =>
|
|
||||||
actuallyRestoreStaminaIfStationary(stamina, sess)
|
|
||||||
case _ => ;
|
|
||||||
}
|
|
||||||
defaultStaminaRegen(initialDelay = 0.5f seconds)
|
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
case ConsumeStamina(stamina) =>
|
case ConsumeStamina(stamina) =>
|
||||||
if (stamina > 0) {
|
if (stamina > 0) {
|
||||||
consumeThisMuchStamina(stamina)
|
consumeThisMuchStamina(stamina)
|
||||||
if(staminaRegenTimer.isCancelled) {
|
|
||||||
defaultStaminaRegen(initialDelay = 0.5f seconds)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
log.warn(s"consumed stamina must be larger than 0, but is: $stamina")
|
log.warn(s"consumed stamina must be larger than 0, but is: $stamina")
|
||||||
}
|
}
|
||||||
|
|
@ -1053,35 +1061,52 @@ class AvatarActor(
|
||||||
}
|
}
|
||||||
|
|
||||||
def tryRestoreStaminaForSession(stamina: Int): Option[Session] = {
|
def tryRestoreStaminaForSession(stamina: Int): Option[Session] = {
|
||||||
session match {
|
(session, _avatar) match {
|
||||||
case out @ Some(_) if !avatar.staminaFull && stamina > 0 => out
|
case (out @ Some(_), Some(a)) if !a.staminaFull && stamina > 0 => out
|
||||||
case _ => None
|
case _ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def actuallyRestoreStaminaIfStationary(stamina: Int, session: Session): Unit = {
|
def actuallyRestoreStaminaIfStationary(stamina: Int, session: Session): Unit = {
|
||||||
if (session.player.VehicleSeated.nonEmpty || !(session.player.isMoving || session.player.Jumping)) {
|
val player = session.player
|
||||||
|
if (player.VehicleSeated.nonEmpty || !(player.isMoving || player.Jumping)) {
|
||||||
actuallyRestoreStamina(stamina, session)
|
actuallyRestoreStamina(stamina, session)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def actuallyRestoreStamina(stamina: Int, session: Session): Unit = {
|
def actuallyRestoreStamina(stamina: Int, session: Session): Unit = {
|
||||||
val totalStamina = math.min(avatar.maxStamina, avatar.stamina + stamina)
|
val originalStamina = avatar.stamina
|
||||||
val isFatigued = if (avatar.fatigued && totalStamina >= 20) {
|
val maxStamina = avatar.maxStamina
|
||||||
val pguid = session.player.GUID
|
val totalStamina = math.min(maxStamina, originalStamina + stamina)
|
||||||
avatar.implants.zipWithIndex.foreach {
|
if (originalStamina < totalStamina) {
|
||||||
case (Some(_), slot) =>
|
val originalFatigued = avatar.fatigued
|
||||||
sessionActor ! SessionActor.SendResponse(
|
val isFatigued = totalStamina < 20
|
||||||
AvatarImplantMessage(pguid, ImplantAction.OutOfStamina, slot, 0)
|
avatar = avatar.copy(stamina = totalStamina, fatigued = isFatigued)
|
||||||
)
|
if (totalStamina == maxStamina) {
|
||||||
case _ => ()
|
staminaRegenTimer.cancel()
|
||||||
|
staminaRegenTimer = Default.Cancellable
|
||||||
|
}
|
||||||
|
if (session.player.HasGUID) {
|
||||||
|
val guid = session.player.GUID
|
||||||
|
if (originalFatigued && !isFatigued) {
|
||||||
|
avatar.implants.zipWithIndex.foreach {
|
||||||
|
case (Some(_), slot) =>
|
||||||
|
sessionActor ! SessionActor.SendResponse(AvatarImplantMessage(guid, ImplantAction.OutOfStamina, slot, 0))
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sessionActor ! SessionActor.SendResponse(PlanetsideAttributeMessage(guid, 2, totalStamina))
|
||||||
}
|
}
|
||||||
false
|
|
||||||
} else {
|
|
||||||
avatar.fatigued
|
|
||||||
}
|
}
|
||||||
avatarCopy(avatar.copy(stamina = totalStamina, fatigued = isFatigued))
|
}
|
||||||
sessionActor ! SessionActor.SendResponse(PlanetsideAttributeMessage(session.player.GUID, 2, totalStamina))
|
|
||||||
|
def restoreStaminaPeriodically(stamina: Int): Unit = {
|
||||||
|
tryRestoreStaminaForSession(stamina) match {
|
||||||
|
case Some(sess) =>
|
||||||
|
actuallyRestoreStaminaIfStationary(stamina, sess)
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
startIfStoppedStaminaRegen(initialDelay = 0.5f seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1102,6 +1127,7 @@ class AvatarActor(
|
||||||
val alreadyFatigued = avatar.fatigued
|
val alreadyFatigued = avatar.fatigued
|
||||||
val becomeFatigued = !alreadyFatigued && totalStamina == 0
|
val becomeFatigued = !alreadyFatigued && totalStamina == 0
|
||||||
avatarCopy(avatar.copy(stamina = totalStamina, fatigued = alreadyFatigued || becomeFatigued))
|
avatarCopy(avatar.copy(stamina = totalStamina, fatigued = alreadyFatigued || becomeFatigued))
|
||||||
|
startIfStoppedStaminaRegen(initialDelay = 0.5f seconds)
|
||||||
val player = session.get.player
|
val player = session.get.player
|
||||||
if (player.HasGUID) {
|
if (player.HasGUID) {
|
||||||
if (becomeFatigued) {
|
if (becomeFatigued) {
|
||||||
|
|
@ -1227,20 +1253,13 @@ class AvatarActor(
|
||||||
avatarCopy(avatar.copy(
|
avatarCopy(avatar.copy(
|
||||||
implants = avatar.implants.updated(slot, Some(implant.copy(active = false)))
|
implants = avatar.implants.updated(slot, Some(implant.copy(active = false)))
|
||||||
))
|
))
|
||||||
|
|
||||||
// Deactivation sound / effect
|
// Deactivation sound / effect
|
||||||
session.get.zone.AvatarEvents ! AvatarServiceMessage(
|
session.get.zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
session.get.zone.id,
|
session.get.zone.id,
|
||||||
AvatarAction.PlanetsideAttribute(session.get.player.GUID, 28, implant.definition.implantType.value * 2)
|
AvatarAction.PlanetsideAttribute(session.get.player.GUID, 28, implant.definition.implantType.value * 2)
|
||||||
)
|
)
|
||||||
|
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
AvatarImplantMessage(
|
AvatarImplantMessage(session.get.player.GUID, ImplantAction.Activation, slot, 0)
|
||||||
session.get.player.GUID,
|
|
||||||
ImplantAction.Activation,
|
|
||||||
slot,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
case None => log.error(s"requested deactivation of unknown implant $implantType")
|
case None => log.error(s"requested deactivation of unknown implant $implantType")
|
||||||
}
|
}
|
||||||
|
|
@ -1501,7 +1520,7 @@ class AvatarActor(
|
||||||
}
|
}
|
||||||
vehicles <- loadVehicleLoadouts().andThen {
|
vehicles <- loadVehicleLoadouts().andThen {
|
||||||
case out @ Success(_) => out
|
case out @ Success(_) => out
|
||||||
case Failure(_) => Future(Array.fill[Option[Loadout]](5)(None).toSeq)
|
case Failure(_) => Future(Array.fill[Option[Loadout]](10)(None).toSeq)
|
||||||
}
|
}
|
||||||
} yield infantry ++ vehicles
|
} yield infantry ++ vehicles
|
||||||
}
|
}
|
||||||
|
|
@ -1533,7 +1552,8 @@ class AvatarActor(
|
||||||
.run(query[persistence.Vehicleloadout].filter(_.avatarId == lift(avatar.id)))
|
.run(query[persistence.Vehicleloadout].filter(_.avatarId == lift(avatar.id)))
|
||||||
.map { loadouts =>
|
.map { loadouts =>
|
||||||
loadouts.map { loadout =>
|
loadouts.map { loadout =>
|
||||||
val toy = new Vehicle(DefinitionUtil.idToDefinition(loadout.vehicle).asInstanceOf[VehicleDefinition])
|
val definition = DefinitionUtil.idToDefinition(loadout.vehicle).asInstanceOf[VehicleDefinition]
|
||||||
|
val toy = new Vehicle(definition)
|
||||||
buildContainedEquipmentFromClob(toy, loadout.items)
|
buildContainedEquipmentFromClob(toy, loadout.items)
|
||||||
|
|
||||||
val result = (loadout.loadoutNumber, Loadout.Create(toy, loadout.name))
|
val result = (loadout.loadoutNumber, Loadout.Create(toy, loadout.name))
|
||||||
|
|
@ -1544,29 +1564,36 @@ class AvatarActor(
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.map { loadouts => (0 until 5).map { index => loadouts.find(_._1 == index).map(_._2) } }
|
.map { loadouts => (0 until 10).map { index => loadouts.find(_._1 == index).map(_._2) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
def refreshLoadouts(loadouts: Iterable[(Option[Loadout], Int)]): Unit = {
|
def refreshLoadouts(loadouts: Iterable[(Option[Loadout], Int)]): Unit = {
|
||||||
loadouts.map {
|
loadouts.map {
|
||||||
case (Some(loadout: InfantryLoadout), index) =>
|
case (Some(loadout: InfantryLoadout), index) =>
|
||||||
FavoritesMessage(
|
FavoritesMessage.Infantry(
|
||||||
LoadoutType.Infantry,
|
|
||||||
session.get.player.GUID,
|
session.get.player.GUID,
|
||||||
index,
|
index,
|
||||||
loadout.label,
|
loadout.label,
|
||||||
InfantryLoadout.DetermineSubtypeB(loadout.exosuit, loadout.subtype)
|
InfantryLoadout.DetermineSubtypeB(loadout.exosuit, loadout.subtype)
|
||||||
)
|
)
|
||||||
|
case (Some(loadout: VehicleLoadout), index)
|
||||||
|
if GlobalDefinitions.isBattleFrameVehicle(loadout.vehicle_definition) =>
|
||||||
|
FavoritesMessage.Battleframe(
|
||||||
|
session.get.player.GUID,
|
||||||
|
index - 15,
|
||||||
|
loadout.label,
|
||||||
|
VehicleLoadout.DetermineBattleframeSubtype(loadout.vehicle_definition)
|
||||||
|
)
|
||||||
case (Some(loadout: VehicleLoadout), index) =>
|
case (Some(loadout: VehicleLoadout), index) =>
|
||||||
FavoritesMessage(
|
FavoritesMessage.Vehicle(
|
||||||
LoadoutType.Vehicle,
|
|
||||||
session.get.player.GUID,
|
session.get.player.GUID,
|
||||||
index - 10,
|
index - 10,
|
||||||
loadout.label,
|
loadout.label
|
||||||
0
|
|
||||||
)
|
)
|
||||||
case (_, index) =>
|
case (_, index) =>
|
||||||
val (mtype, lineNo) = if (index < 10) {
|
val (mtype, lineNo) = if (index > 14) {
|
||||||
|
(LoadoutType.Battleframe, index - 15)
|
||||||
|
} else if (index < 10) {
|
||||||
(LoadoutType.Infantry, index)
|
(LoadoutType.Infantry, index)
|
||||||
} else {
|
} else {
|
||||||
(LoadoutType.Vehicle, index - 10)
|
(LoadoutType.Vehicle, index - 10)
|
||||||
|
|
@ -1585,29 +1612,38 @@ class AvatarActor(
|
||||||
avatar.loadouts.lift(line) match {
|
avatar.loadouts.lift(line) match {
|
||||||
case Some(Some(loadout: InfantryLoadout)) =>
|
case Some(Some(loadout: InfantryLoadout)) =>
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
FavoritesMessage(
|
FavoritesMessage.Infantry(
|
||||||
LoadoutType.Infantry,
|
|
||||||
session.get.player.GUID,
|
session.get.player.GUID,
|
||||||
line,
|
line,
|
||||||
loadout.label,
|
loadout.label,
|
||||||
InfantryLoadout.DetermineSubtypeB(loadout.exosuit, loadout.subtype)
|
InfantryLoadout.DetermineSubtypeB(loadout.exosuit, loadout.subtype)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
case Some(Some(loadout: VehicleLoadout))
|
||||||
|
if GlobalDefinitions.isBattleFrameVehicle(loadout.vehicle_definition) =>
|
||||||
|
sessionActor ! SessionActor.SendResponse(
|
||||||
|
FavoritesMessage.Battleframe(
|
||||||
|
session.get.player.GUID,
|
||||||
|
line - 15,
|
||||||
|
loadout.label,
|
||||||
|
VehicleLoadout.DetermineBattleframeSubtype(loadout.vehicle_definition)
|
||||||
|
)
|
||||||
|
)
|
||||||
case Some(Some(loadout: VehicleLoadout)) =>
|
case Some(Some(loadout: VehicleLoadout)) =>
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
FavoritesMessage(
|
FavoritesMessage.Vehicle(
|
||||||
LoadoutType.Vehicle,
|
|
||||||
session.get.player.GUID,
|
session.get.player.GUID,
|
||||||
line - 10,
|
line - 10,
|
||||||
loadout.label,
|
loadout.label
|
||||||
0
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
case Some(None) =>
|
case Some(None) =>
|
||||||
val (mtype, lineNo) = if (line < 10) {
|
val (mtype, lineNo, subtype) = if (line > 14) {
|
||||||
(LoadoutType.Infantry, line)
|
(LoadoutType.Battleframe, line - 15, Some(0))
|
||||||
|
} else if (line < 10) {
|
||||||
|
(LoadoutType.Infantry, line, Some(0))
|
||||||
} else {
|
} else {
|
||||||
(LoadoutType.Vehicle, line - 10)
|
(LoadoutType.Vehicle, line - 10, None)
|
||||||
}
|
}
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
FavoritesMessage(
|
FavoritesMessage(
|
||||||
|
|
@ -1615,7 +1651,7 @@ class AvatarActor(
|
||||||
session.get.player.GUID,
|
session.get.player.GUID,
|
||||||
lineNo,
|
lineNo,
|
||||||
"",
|
"",
|
||||||
0
|
subtype
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
case _ => ;
|
case _ => ;
|
||||||
|
|
@ -1679,15 +1715,18 @@ class AvatarActor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def startIfStoppedStaminaRegen(initialDelay: FiniteDuration): Unit = {
|
||||||
|
if (staminaRegenTimer.isCancelled) {
|
||||||
|
defaultStaminaRegen(initialDelay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def defaultStaminaRegen(initialDelay: FiniteDuration): Unit = {
|
def defaultStaminaRegen(initialDelay: FiniteDuration): Unit = {
|
||||||
staminaRegenTimer.cancel()
|
staminaRegenTimer.cancel()
|
||||||
staminaRegenTimer = if (!avatar.staminaFull) {
|
val restoreStaminaFunc: Int => Unit = restoreStaminaPeriodically
|
||||||
context.system.scheduler.scheduleWithFixedDelay(initialDelay, 0.5 seconds)(() => {
|
staminaRegenTimer = context.system.scheduler.scheduleWithFixedDelay(initialDelay, delay = 0.5 seconds)(() => {
|
||||||
context.self ! RestoreStaminaPeriodically(1)
|
restoreStaminaFunc(1)
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
Default.Cancellable
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// same as in SA, this really doesn't belong here
|
// same as in SA, this really doesn't belong here
|
||||||
|
|
@ -1723,7 +1762,7 @@ class AvatarActor(
|
||||||
}
|
}
|
||||||
|
|
||||||
def resolveSharedPurchaseTimeNames(pair: (BasicDefinition, String)): Seq[(BasicDefinition, String)] = {
|
def resolveSharedPurchaseTimeNames(pair: (BasicDefinition, String)): Seq[(BasicDefinition, String)] = {
|
||||||
val (_, name) = pair
|
val (definition, name) = pair
|
||||||
if (name.matches("(tr|nc|vs)hev_.+") && Config.app.game.sharedMaxCooldown) {
|
if (name.matches("(tr|nc|vs)hev_.+") && Config.app.game.sharedMaxCooldown) {
|
||||||
val faction = name.take(2)
|
val faction = name.take(2)
|
||||||
(if (faction.equals("nc")) {
|
(if (faction.equals("nc")) {
|
||||||
|
|
@ -1738,7 +1777,22 @@ class AvatarActor(
|
||||||
Seq(s"${faction}hev_antipersonnel", s"${faction}hev_antivehicular", s"${faction}hev_antiaircraft")
|
Seq(s"${faction}hev_antipersonnel", s"${faction}hev_antivehicular", s"${faction}hev_antiaircraft")
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Seq(pair)
|
definition match {
|
||||||
|
case vdef: VehicleDefinition
|
||||||
|
if GlobalDefinitions.isBattleFrameFlightVehicle(vdef) =>
|
||||||
|
val bframe = name.substring(0, name.indexOf('_'))
|
||||||
|
val gunner = bframe+"_gunner"
|
||||||
|
Seq((DefinitionUtil.fromString(gunner), gunner), (vdef, name))
|
||||||
|
|
||||||
|
case vdef: VehicleDefinition
|
||||||
|
if GlobalDefinitions.isBattleFrameGunnerVehicle(vdef) =>
|
||||||
|
val bframe = name.substring(0, name.indexOf('_'))
|
||||||
|
val flight = bframe+"_flight"
|
||||||
|
Seq((vdef, name), (DefinitionUtil.fromString(flight), flight))
|
||||||
|
|
||||||
|
case _ =>
|
||||||
|
Seq(pair)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -487,8 +487,9 @@ class BuildingActor(
|
||||||
class FakeNtuSource(private val building: Building)
|
class FakeNtuSource(private val building: Building)
|
||||||
extends PlanetSideServerObject
|
extends PlanetSideServerObject
|
||||||
with NtuContainer {
|
with NtuContainer {
|
||||||
override def NtuCapacitor = Float.MaxValue
|
override def NtuCapacitor = Int.MaxValue.toFloat
|
||||||
override def NtuCapacitor_=(a: Float) = Float.MaxValue
|
override def NtuCapacitor_=(a: Float) = Int.MaxValue.toFloat
|
||||||
|
override def MaxNtuCapacitor = Int.MaxValue.toFloat
|
||||||
override def Faction = building.Faction
|
override def Faction = building.Faction
|
||||||
override def Zone = building.Zone
|
override def Zone = building.Zone
|
||||||
override def Definition = null
|
override def Definition = null
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -3,6 +3,7 @@ package net.psforever.objects
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorRef}
|
import akka.actor.{Actor, ActorRef}
|
||||||
import net.psforever.actors.commands.NtuCommand
|
import net.psforever.actors.commands.NtuCommand
|
||||||
|
import net.psforever.objects.definition.ObjectDefinition
|
||||||
import net.psforever.objects.serverobject.transfer.{TransferBehavior, TransferContainer}
|
import net.psforever.objects.serverobject.transfer.{TransferBehavior, TransferContainer}
|
||||||
|
|
||||||
object Ntu {
|
object Ntu {
|
||||||
|
|
@ -34,12 +35,26 @@ object Ntu {
|
||||||
final case class Grant(src: NtuContainer, amount: Float)
|
final case class Grant(src: NtuContainer, amount: Float)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait NtuContainerOwner {
|
||||||
|
def getNtuContainer: Option[NtuContainer]
|
||||||
|
}
|
||||||
|
|
||||||
trait NtuContainer extends TransferContainer {
|
trait NtuContainer extends TransferContainer {
|
||||||
def NtuCapacitor: Float
|
def NtuCapacitor: Float
|
||||||
|
|
||||||
def NtuCapacitor_=(value: Float): Float
|
def NtuCapacitor_=(value: Float): Float
|
||||||
|
|
||||||
def Definition: NtuContainerDefinition
|
def NtuCapacitorScaled: Int = {
|
||||||
|
if (Definition.MaxNtuCapacitor > 0) {
|
||||||
|
scala.math.ceil((NtuCapacitor / Definition.MaxNtuCapacitor) * 10).toInt
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def MaxNtuCapacitor: Float
|
||||||
|
|
||||||
|
def Definition: ObjectDefinition with NtuContainerDefinition
|
||||||
}
|
}
|
||||||
|
|
||||||
trait CommonNtuContainer extends NtuContainer {
|
trait CommonNtuContainer extends NtuContainer {
|
||||||
|
|
@ -51,8 +66,6 @@ trait CommonNtuContainer extends NtuContainer {
|
||||||
ntuCapacitor = scala.math.max(0, scala.math.min(value, Definition.MaxNtuCapacitor))
|
ntuCapacitor = scala.math.max(0, scala.math.min(value, Definition.MaxNtuCapacitor))
|
||||||
NtuCapacitor
|
NtuCapacitor
|
||||||
}
|
}
|
||||||
|
|
||||||
def Definition: NtuContainerDefinition
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trait NtuContainerDefinition {
|
trait NtuContainerDefinition {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
package net.psforever.objects
|
package net.psforever.objects
|
||||||
|
|
||||||
import net.psforever.objects.avatar.{Avatar, LoadoutManager, SpecialCarry}
|
import net.psforever.objects.avatar.{Avatar, LoadoutManager, SpecialCarry}
|
||||||
|
import net.psforever.objects.ballistics.InteractWithRadiationClouds
|
||||||
import net.psforever.objects.ce.{Deployable, InteractWithMines}
|
import net.psforever.objects.ce.{Deployable, InteractWithMines}
|
||||||
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition}
|
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition}
|
||||||
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
|
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
|
||||||
|
|
@ -15,7 +16,7 @@ import net.psforever.objects.vital.resistance.ResistanceProfile
|
||||||
import net.psforever.objects.vital.Vitality
|
import net.psforever.objects.vital.Vitality
|
||||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||||
import net.psforever.objects.vital.resolution.DamageResistanceModel
|
import net.psforever.objects.vital.resolution.DamageResistanceModel
|
||||||
import net.psforever.objects.zones.blockmap.BlockMapEntity
|
import net.psforever.objects.zones.blockmap.{BlockMapEntity, SectorPopulation}
|
||||||
import net.psforever.objects.zones.{InteractsWithZone, ZoneAware, Zoning}
|
import net.psforever.objects.zones.{InteractsWithZone, ZoneAware, Zoning}
|
||||||
import net.psforever.types.{PlanetSideGUID, _}
|
import net.psforever.types.{PlanetSideGUID, _}
|
||||||
|
|
||||||
|
|
@ -36,6 +37,7 @@ class Player(var avatar: Avatar)
|
||||||
with MountableEntity {
|
with MountableEntity {
|
||||||
interaction(new InteractWithEnvironment())
|
interaction(new InteractWithEnvironment())
|
||||||
interaction(new InteractWithMinesUnlessSpectating(obj = this, range = 10))
|
interaction(new InteractWithMinesUnlessSpectating(obj = this, range = 10))
|
||||||
|
interaction(new InteractWithRadiationClouds(range = 10f, Some(this)))
|
||||||
|
|
||||||
private var backpack: Boolean = false
|
private var backpack: Boolean = false
|
||||||
private var released: Boolean = false
|
private var released: Boolean = false
|
||||||
|
|
@ -599,11 +601,11 @@ object Player {
|
||||||
|
|
||||||
private class InteractWithMinesUnlessSpectating(
|
private class InteractWithMinesUnlessSpectating(
|
||||||
private val obj: Player,
|
private val obj: Player,
|
||||||
range: Float
|
override val range: Float
|
||||||
) extends InteractWithMines(range) {
|
) extends InteractWithMines(range) {
|
||||||
override def interaction(target: InteractsWithZone): Unit = {
|
override def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = {
|
||||||
if (!obj.spectator) {
|
if (!obj.spectator) {
|
||||||
super.interaction(target)
|
super.interaction(sector, target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package net.psforever.objects
|
||||||
|
|
||||||
import net.psforever.objects.ballistics.SourceEntry
|
import net.psforever.objects.ballistics.SourceEntry
|
||||||
import net.psforever.objects.definition.ObjectDefinition
|
import net.psforever.objects.definition.ObjectDefinition
|
||||||
|
import net.psforever.objects.equipment.{EffectTarget, TargetValidation}
|
||||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||||
import net.psforever.objects.vital.{Vitality, VitalityDefinition}
|
import net.psforever.objects.vital.{Vitality, VitalityDefinition}
|
||||||
|
|
@ -28,6 +29,30 @@ object SpecialEmp {
|
||||||
DamageAtEdge = 1.0f
|
DamageAtEdge = 1.0f
|
||||||
DamageRadius = 5f
|
DamageRadius = 5f
|
||||||
AdditionalEffect = true
|
AdditionalEffect = true
|
||||||
|
JammedEffectDuration += TargetValidation(
|
||||||
|
EffectTarget.Category.Player,
|
||||||
|
EffectTarget.Validation.Player
|
||||||
|
) -> 1000
|
||||||
|
JammedEffectDuration += TargetValidation(
|
||||||
|
EffectTarget.Category.Vehicle,
|
||||||
|
EffectTarget.Validation.AMS
|
||||||
|
) -> 5000
|
||||||
|
JammedEffectDuration += TargetValidation(
|
||||||
|
EffectTarget.Category.Deployable,
|
||||||
|
EffectTarget.Validation.MotionSensor
|
||||||
|
) -> 30000
|
||||||
|
JammedEffectDuration += TargetValidation(
|
||||||
|
EffectTarget.Category.Deployable,
|
||||||
|
EffectTarget.Validation.Spitfire
|
||||||
|
) -> 30000
|
||||||
|
JammedEffectDuration += TargetValidation(
|
||||||
|
EffectTarget.Category.Turret,
|
||||||
|
EffectTarget.Validation.Turret
|
||||||
|
) -> 30000
|
||||||
|
JammedEffectDuration += TargetValidation(
|
||||||
|
EffectTarget.Category.Vehicle,
|
||||||
|
EffectTarget.Validation.VehicleNotAMS
|
||||||
|
) -> 10000
|
||||||
Modifiers = MaxDistanceCutoff
|
Modifiers = MaxDistanceCutoff
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,7 +62,6 @@ object SpecialEmp {
|
||||||
MaxHealth = 1
|
MaxHealth = 1
|
||||||
Damageable = false
|
Damageable = false
|
||||||
Repairable = false
|
Repairable = false
|
||||||
explodes = true
|
|
||||||
innateDamage = emp
|
innateDamage = emp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,15 +21,14 @@ class Tool(private val toolDef: ToolDefinition)
|
||||||
extends Equipment
|
extends Equipment
|
||||||
with FireModeSwitch[FireModeDefinition]
|
with FireModeSwitch[FireModeDefinition]
|
||||||
with JammableUnit {
|
with JammableUnit {
|
||||||
|
private var tdef = toolDef
|
||||||
/** index of the current fire mode on the `ToolDefinition`'s list of fire modes */
|
/** index of the current fire mode on the `ToolDefinition`'s list of fire modes */
|
||||||
private var fireModeIndex: Int = toolDef.DefaultFireModeIndex
|
private var fireModeIndex: Int = tdef.DefaultFireModeIndex
|
||||||
|
|
||||||
/** current ammunition slot being used by this fire mode */
|
/** current ammunition slot being used by this fire mode */
|
||||||
private var ammoSlots: List[Tool.FireModeSlot] = List.empty
|
private var ammoSlots: List[Tool.FireModeSlot] = List.empty
|
||||||
var lastDischarge: Long = 0
|
var lastDischarge: Long = 0
|
||||||
|
|
||||||
Tool.LoadDefinition(this)
|
Tool.LoadDefinition(tool = this)
|
||||||
|
|
||||||
def FireModeIndex: Int = fireModeIndex
|
def FireModeIndex: Int = fireModeIndex
|
||||||
|
|
||||||
|
|
@ -114,7 +113,7 @@ class Tool(private val toolDef: ToolDefinition)
|
||||||
|
|
||||||
def MaxAmmoSlot: Int = ammoSlots.length
|
def MaxAmmoSlot: Int = ammoSlots.length
|
||||||
|
|
||||||
def Definition: ToolDefinition = toolDef
|
def Definition: ToolDefinition = tdef
|
||||||
|
|
||||||
override def toString: String = Tool.toString(this)
|
override def toString: String = Tool.toString(this)
|
||||||
}
|
}
|
||||||
|
|
@ -131,9 +130,20 @@ object Tool {
|
||||||
* @param tool the `Tool` being initialized
|
* @param tool the `Tool` being initialized
|
||||||
*/
|
*/
|
||||||
def LoadDefinition(tool: Tool): Unit = {
|
def LoadDefinition(tool: Tool): Unit = {
|
||||||
val tdef: ToolDefinition = tool.Definition
|
val tdef = tool.Definition
|
||||||
val maxSlot = tdef.FireModes.maxBy(fmode => fmode.AmmoSlotIndex).AmmoSlotIndex
|
val maxSlot = tdef.FireModes.maxBy(fmode => fmode.AmmoSlotIndex).AmmoSlotIndex
|
||||||
tool.ammoSlots = buildFireModes(tdef, (0 to maxSlot).iterator, tdef.FireModes.toList)
|
tool.ammoSlots = buildFireModes(tdef, (0 to maxSlot).iterator, tdef.FireModes.toList)
|
||||||
|
tool.fireModeIndex = tdef.DefaultFireModeIndex
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Substitute this `Definition` for the one that was originally provided for this entity.
|
||||||
|
* Calling this will not reconstruct the internal fields of the entity.
|
||||||
|
* @param tool the `Tool` being modified
|
||||||
|
* @param tdef the definition used to override the definition that was previously assigned this `Tool`;
|
||||||
|
* WILL override the assignment in the original constructor
|
||||||
|
*/
|
||||||
|
def LoadDefinition(tool: Tool, tdef: ToolDefinition): Unit = {
|
||||||
|
tool.tdef = tdef
|
||||||
}
|
}
|
||||||
|
|
||||||
@tailrec private def buildFireModes(
|
@tailrec private def buildFireModes(
|
||||||
|
|
@ -226,7 +236,7 @@ object Tool {
|
||||||
def MaxMagazine(): Int = {
|
def MaxMagazine(): Int = {
|
||||||
fdef.CustomMagazine.get(AmmoType) match {
|
fdef.CustomMagazine.get(AmmoType) match {
|
||||||
case Some(value) => value
|
case Some(value) => value
|
||||||
case None => fdef.Magazine
|
case None => fdef.DefaultMagazine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package net.psforever.objects
|
||||||
|
|
||||||
import net.psforever.objects.ce.InteractWithMines
|
import net.psforever.objects.ce.InteractWithMines
|
||||||
import net.psforever.objects.definition.{ToolDefinition, VehicleDefinition}
|
import net.psforever.objects.definition.{ToolDefinition, VehicleDefinition}
|
||||||
import net.psforever.objects.equipment.{EquipmentSize, EquipmentSlot, JammableUnit}
|
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
|
||||||
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem, InventoryTile}
|
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem, InventoryTile}
|
||||||
import net.psforever.objects.serverobject.mount.{MountableEntity, Seat, SeatDefinition}
|
import net.psforever.objects.serverobject.mount.{MountableEntity, Seat, SeatDefinition}
|
||||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||||
|
|
@ -19,8 +19,10 @@ import net.psforever.objects.vital.Vitality
|
||||||
import net.psforever.objects.vital.resolution.DamageResistanceModel
|
import net.psforever.objects.vital.resolution.DamageResistanceModel
|
||||||
import net.psforever.objects.zones.InteractsWithZone
|
import net.psforever.objects.zones.InteractsWithZone
|
||||||
import net.psforever.objects.zones.blockmap.BlockMapEntity
|
import net.psforever.objects.zones.blockmap.BlockMapEntity
|
||||||
|
import net.psforever.packet.PlanetSideGamePacket
|
||||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
|
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||||
|
|
||||||
|
import scala.annotation.tailrec
|
||||||
import scala.concurrent.duration.FiniteDuration
|
import scala.concurrent.duration.FiniteDuration
|
||||||
import scala.util.{Success, Try}
|
import scala.util.{Success, Try}
|
||||||
|
|
||||||
|
|
@ -89,13 +91,13 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
with AuraContainer
|
with AuraContainer
|
||||||
with MountableEntity {
|
with MountableEntity {
|
||||||
interaction(new InteractWithEnvironment())
|
interaction(new InteractWithEnvironment())
|
||||||
interaction(new InteractWithMines(range = 30))
|
interaction(new InteractWithMines(range = 20))
|
||||||
|
interaction(new InteractWithRadiationCloudsSeatedInVehicle(obj = this, range = 20))
|
||||||
|
|
||||||
private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
|
private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
|
||||||
private var shields: Int = 0
|
private var shields: Int = 0
|
||||||
private var decal: Int = 0
|
private var decal: Int = 0
|
||||||
private var trunkAccess: Option[PlanetSideGUID] = None
|
private var trunkAccess: Option[PlanetSideGUID] = None
|
||||||
private var jammered: Boolean = false
|
|
||||||
|
|
||||||
private var cloaked: Boolean = false
|
private var cloaked: Boolean = false
|
||||||
private var flying: Option[Int] = None
|
private var flying: Option[Int] = None
|
||||||
|
|
@ -107,9 +109,10 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
*/
|
*/
|
||||||
private val groupPermissions: Array[VehicleLockState.Value] =
|
private val groupPermissions: Array[VehicleLockState.Value] =
|
||||||
Array(VehicleLockState.Locked, VehicleLockState.Empire, VehicleLockState.Empire, VehicleLockState.Locked)
|
Array(VehicleLockState.Locked, VehicleLockState.Empire, VehicleLockState.Empire, VehicleLockState.Locked)
|
||||||
private var cargoHolds: Map[Int, Cargo] = Map.empty
|
private var cargoHolds: Map[Int, Cargo] = Map.empty
|
||||||
private var utilities: Map[Int, Utility] = Map()
|
private var utilities: Map[Int, Utility] = Map.empty
|
||||||
private val trunk: GridInventory = GridInventory()
|
private var subsystems: List[VehicleSubsystem] = Nil
|
||||||
|
private val trunk: GridInventory = GridInventory()
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Records the GUID of the cargo vehicle (galaxy/lodestar) this vehicle is stored in for DismountVehicleCargoMsg use
|
* Records the GUID of the cargo vehicle (galaxy/lodestar) this vehicle is stored in for DismountVehicleCargoMsg use
|
||||||
|
|
@ -128,7 +131,7 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
* @see `Vehicle.LoadDefinition`
|
* @see `Vehicle.LoadDefinition`
|
||||||
*/
|
*/
|
||||||
protected def LoadDefinition(): Unit = {
|
protected def LoadDefinition(): Unit = {
|
||||||
Vehicle.LoadDefinition(this)
|
Vehicle.LoadDefinition(vehicle = this)
|
||||||
}
|
}
|
||||||
|
|
||||||
def Faction: PlanetSideEmpire.Value = {
|
def Faction: PlanetSideEmpire.Value = {
|
||||||
|
|
@ -173,13 +176,6 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
Decal
|
Decal
|
||||||
}
|
}
|
||||||
|
|
||||||
def Jammered: Boolean = jammered
|
|
||||||
|
|
||||||
def Jammered_=(jamState: Boolean): Boolean = {
|
|
||||||
jammered = jamState
|
|
||||||
Jammered
|
|
||||||
}
|
|
||||||
|
|
||||||
def Cloaked: Boolean = cloaked
|
def Cloaked: Boolean = cloaked
|
||||||
|
|
||||||
def Cloaked_=(isCloaked: Boolean): Boolean = {
|
def Cloaked_=(isCloaked: Boolean): Boolean = {
|
||||||
|
|
@ -198,14 +194,6 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
Flying
|
Flying
|
||||||
}
|
}
|
||||||
|
|
||||||
def NtuCapacitorScaled: Int = {
|
|
||||||
if (Definition.MaxNtuCapacitor > 0) {
|
|
||||||
scala.math.ceil((NtuCapacitor / Definition.MaxNtuCapacitor) * 10).toInt
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def Capacitor: Int = capacitor
|
def Capacitor: Int = capacitor
|
||||||
|
|
||||||
def Capacitor_=(value: Int): Int = {
|
def Capacitor_=(value: Int): Int = {
|
||||||
|
|
@ -291,7 +279,7 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
} else {
|
} else {
|
||||||
Seat(seatNumber) match {
|
Seat(seatNumber) match {
|
||||||
case Some(_) =>
|
case Some(_) =>
|
||||||
Definition.controlledWeapons.get(seatNumber) match {
|
Definition.controlledWeapons().get(seatNumber) match {
|
||||||
case Some(_) =>
|
case Some(_) =>
|
||||||
Some(AccessPermissionGroup.Gunner)
|
Some(AccessPermissionGroup.Gunner)
|
||||||
case None =>
|
case None =>
|
||||||
|
|
@ -341,6 +329,42 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def Subsystems(): List[VehicleSubsystem] = subsystems
|
||||||
|
|
||||||
|
def Subsystems(sys: VehicleSubsystemEntry): Option[VehicleSubsystem] = subsystems.find { _.sys == sys }
|
||||||
|
|
||||||
|
def Subsystems(sys: String): Option[VehicleSubsystem] = subsystems.find { _.sys.name.contains(sys) }
|
||||||
|
|
||||||
|
def SubsystemMessages(): List[PlanetSideGamePacket] = {
|
||||||
|
subsystems
|
||||||
|
.filter { sub => sub.Enabled != sub.sys.defaultState }
|
||||||
|
.flatMap { _.getMessage(vehicle = this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
def SubsystemStatus(sys: String): Option[Boolean] = {
|
||||||
|
val elems = sys.split("\\.")
|
||||||
|
if (elems.length < 2) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Subsystems(elems.head) match {
|
||||||
|
case Some(sub) => sub.stateOfStatus(elems(1))
|
||||||
|
case None => Some(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def SubsystemStatusMultiplier(sys: String): Float = {
|
||||||
|
val elems = sys.split("\\.")
|
||||||
|
if (elems.length < 2) {
|
||||||
|
1f
|
||||||
|
} else {
|
||||||
|
Subsystems(elems.head) match {
|
||||||
|
case Some(sub) => sub.multiplierOfStatus(elems(1))
|
||||||
|
case None => 1f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override def DeployTime = Definition.DeployTime
|
override def DeployTime = Definition.DeployTime
|
||||||
|
|
||||||
override def UndeployTime = Definition.UndeployTime
|
override def UndeployTime = Definition.UndeployTime
|
||||||
|
|
@ -352,35 +376,58 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
override def Slot(slotNum: Int): EquipmentSlot = {
|
override def Slot(slotNum: Int): EquipmentSlot = {
|
||||||
weapons
|
weapons
|
||||||
.get(slotNum)
|
.get(slotNum)
|
||||||
// .orElse(utilities.get(slotNum) match {
|
|
||||||
// case Some(_) =>
|
|
||||||
// //TODO what do now?
|
|
||||||
// None
|
|
||||||
// case None => ;
|
|
||||||
// None
|
|
||||||
// })
|
|
||||||
.orElse(Some(Inventory.Slot(slotNum)))
|
.orElse(Some(Inventory.Slot(slotNum)))
|
||||||
.get
|
.get
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def SlotMapResolution(slot: Int): Int = {
|
||||||
|
if (GlobalDefinitions.isBattleFrameVehicle(vehicleDef)) {
|
||||||
|
//for the benefit of BFR equipment slots interacting with MoveItemMessage
|
||||||
|
if (VisibleSlots.size == 2) {
|
||||||
|
if (slot == 0) 1 else if (slot == 1) 2 else slot //*_flight
|
||||||
|
} else {
|
||||||
|
if (slot == 0) 2 else if (slot == 1) 3 else if (slot == 2) 4 else slot //*_gunner
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
slot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override def Find(guid: PlanetSideGUID): Option[Int] = {
|
override def Find(guid: PlanetSideGUID): Option[Int] = {
|
||||||
weapons.find({
|
weapons.find({
|
||||||
case (_, obj) =>
|
case (_, obj) =>
|
||||||
obj.Equipment match {
|
obj.Equipment match {
|
||||||
case Some(item) =>
|
case Some(item) => item.HasGUID && item.GUID == guid
|
||||||
if (item.HasGUID && item.GUID == guid) {
|
case None => false
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
case None =>
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}) match {
|
}) match {
|
||||||
case Some((index, _)) =>
|
case Some((index, _)) => Some(index)
|
||||||
|
case None => Inventory.Find(guid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def Fit(obj: Equipment): Option[Int] = {
|
||||||
|
recursiveSlotFit(weapons.iterator, obj.Size) match {
|
||||||
|
case Some(index) =>
|
||||||
Some(index)
|
Some(index)
|
||||||
case None =>
|
case None =>
|
||||||
Inventory.Find(guid)
|
trunk.Fit(obj.Definition.Tile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@tailrec private def recursiveSlotFit(
|
||||||
|
iter: Iterator[(Int, EquipmentSlot)],
|
||||||
|
objSize: EquipmentSize.Value
|
||||||
|
): Option[Int] = {
|
||||||
|
if (!iter.hasNext) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
val (index, slot) = iter.next()
|
||||||
|
if (slot.Equipment.isEmpty && slot.Size.equals(objSize)) {
|
||||||
|
Some(index)
|
||||||
|
} else {
|
||||||
|
recursiveSlotFit(iter, objSize)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -388,13 +435,10 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
weapons.get(dest) match {
|
weapons.get(dest) match {
|
||||||
case Some(slot) =>
|
case Some(slot) =>
|
||||||
slot.Equipment match {
|
slot.Equipment match {
|
||||||
case Some(item) =>
|
case Some(item) => Success(List(InventoryItem(item, dest)))
|
||||||
Success(List(InventoryItem(item, dest)))
|
case None => Success(List())
|
||||||
case None =>
|
|
||||||
Success(List())
|
|
||||||
}
|
}
|
||||||
case None =>
|
case None => super.Collisions(dest, width, height)
|
||||||
super.Collisions(dest, width, height)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -514,6 +558,8 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
override def toString: String = {
|
override def toString: String = {
|
||||||
Vehicle.toString(this)
|
Vehicle.toString(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def MaxNtuCapacitor: Float = Definition.MaxNtuCapacitor
|
||||||
}
|
}
|
||||||
|
|
||||||
object Vehicle {
|
object Vehicle {
|
||||||
|
|
@ -558,6 +604,8 @@ object Vehicle {
|
||||||
*/
|
*/
|
||||||
final case class UpdateShieldsCharge(vehicle: Vehicle)
|
final case class UpdateShieldsCharge(vehicle: Vehicle)
|
||||||
|
|
||||||
|
final case class UpdateSubsystemStates(toChannel: String, stateToUpdateFor: Option[Boolean] = None)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change a vehicle's internal ownership property to match that of the target player.
|
* Change a vehicle's internal ownership property to match that of the target player.
|
||||||
* @param player the person who will own the vehicle, or `None` if the vehicle will go unowned
|
* @param player the person who will own the vehicle, or `None` if the vehicle will go unowned
|
||||||
|
|
@ -602,10 +650,12 @@ object Vehicle {
|
||||||
val vdef: VehicleDefinition = vehicle.Definition
|
val vdef: VehicleDefinition = vehicle.Definition
|
||||||
//general stuff
|
//general stuff
|
||||||
vehicle.Health = vdef.DefaultHealth
|
vehicle.Health = vdef.DefaultHealth
|
||||||
|
vehicle.Shields = vdef.DefaultShields
|
||||||
|
vehicle.Capacitor = vdef.DefaultCapacitor
|
||||||
//create weapons
|
//create weapons
|
||||||
vehicle.weapons = vdef.Weapons.map[Int, EquipmentSlot] {
|
vehicle.weapons = vdef.Weapons.map[Int, EquipmentSlot] {
|
||||||
case (num: Int, definition: ToolDefinition) =>
|
case (num: Int, definition: ToolDefinition) =>
|
||||||
val slot = EquipmentSlot(EquipmentSize.VehicleWeapon)
|
val slot = EquipmentSlot(definition.Size)
|
||||||
slot.Equipment = Tool(definition)
|
slot.Equipment = Tool(definition)
|
||||||
num -> slot
|
num -> slot
|
||||||
}.toMap
|
}.toMap
|
||||||
|
|
@ -628,6 +678,8 @@ object Vehicle {
|
||||||
utilObj.LocationOffset = vdef.UtilityOffset.get(num)
|
utilObj.LocationOffset = vdef.UtilityOffset.get(num)
|
||||||
num -> obj
|
num -> obj
|
||||||
}.toMap
|
}.toMap
|
||||||
|
//subsystems
|
||||||
|
vehicle.subsystems = vdef.subsystems.map { entry => new VehicleSubsystem(entry) }
|
||||||
//trunk
|
//trunk
|
||||||
vdef.TrunkSize match {
|
vdef.TrunkSize match {
|
||||||
case InventoryTile.None => ;
|
case InventoryTile.None => ;
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@ package net.psforever.objects
|
||||||
import net.psforever.objects.ce.TelepadLike
|
import net.psforever.objects.ce.TelepadLike
|
||||||
import net.psforever.objects.serverobject.CommonMessages
|
import net.psforever.objects.serverobject.CommonMessages
|
||||||
import net.psforever.objects.serverobject.deploy.Deployment
|
import net.psforever.objects.serverobject.deploy.Deployment
|
||||||
|
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
|
||||||
import net.psforever.objects.serverobject.transfer.TransferContainer
|
import net.psforever.objects.serverobject.transfer.TransferContainer
|
||||||
import net.psforever.objects.serverobject.structures.{StructureType, WarpGate}
|
import net.psforever.objects.serverobject.structures.WarpGate
|
||||||
import net.psforever.objects.vehicles._
|
import net.psforever.objects.vehicles._
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
import net.psforever.packet.game.TriggeredSound
|
import net.psforever.packet.game.TriggeredSound
|
||||||
|
|
@ -210,11 +211,12 @@ object Vehicles {
|
||||||
* The orientation of a cargo vehicle as it is being loaded into and contained by a carrier vehicle.
|
* The orientation of a cargo vehicle as it is being loaded into and contained by a carrier vehicle.
|
||||||
* The type of carrier is not an important consideration in determining the orientation, oddly enough.
|
* The type of carrier is not an important consideration in determining the orientation, oddly enough.
|
||||||
* @param vehicle the cargo vehicle
|
* @param vehicle the cargo vehicle
|
||||||
* @return the orientation as an `Integer` value;
|
* @return the orientation;
|
||||||
* `0` for almost all cases
|
* `1` is for unique sideways mounting;
|
||||||
|
* `0` is or straight-on mounting, valid for almost all cases
|
||||||
*/
|
*/
|
||||||
def CargoOrientation(vehicle: Vehicle): Int = {
|
def CargoOrientation(vehicle: Vehicle): Int = {
|
||||||
if (vehicle.Definition == GlobalDefinitions.router) {
|
if (vehicle.Definition == GlobalDefinitions.router || GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition)) {
|
||||||
1
|
1
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
|
|
@ -232,7 +234,7 @@ object Vehicles {
|
||||||
log.info(s"${hacker.Name} has jacked a ${target.Definition.Name}")
|
log.info(s"${hacker.Name} has jacked a ${target.Definition.Name}")
|
||||||
val zone = target.Zone
|
val zone = target.Zone
|
||||||
// Forcefully dismount any cargo
|
// Forcefully dismount any cargo
|
||||||
target.CargoHolds.foreach { case (index, cargoHold) =>
|
target.CargoHolds.foreach { case (_, cargoHold) =>
|
||||||
cargoHold.occupant match {
|
cargoHold.occupant match {
|
||||||
case Some(cargo: Vehicle) =>
|
case Some(cargo: Vehicle) =>
|
||||||
cargo.Actor ! CargoBehavior.StartCargoDismounting(bailed = false)
|
cargo.Actor ! CargoBehavior.StartCargoDismounting(bailed = false)
|
||||||
|
|
@ -339,33 +341,66 @@ object Vehicles {
|
||||||
}
|
}
|
||||||
|
|
||||||
def FindANTDischargingTarget(
|
def FindANTDischargingTarget(
|
||||||
obj: TransferContainer,
|
obj: TransferContainer,
|
||||||
ntuChargingTarget: Option[TransferContainer]
|
ntuChargingTarget: Option[TransferContainer]
|
||||||
): Option[TransferContainer] = {
|
): Option[TransferContainer] = {
|
||||||
(ntuChargingTarget match {
|
FindResourceSiloToDischargeInto(obj, ntuChargingTarget, radius = 20)
|
||||||
case out @ Some(target: NtuContainer) if {
|
}
|
||||||
Vector3.DistanceSquared(obj.Position.xy, target.Position.xy) < 400 //20m is generous ...
|
|
||||||
} =>
|
def FindBfrChargingSource(
|
||||||
|
obj: TransferContainer,
|
||||||
|
ntuChargingTarget: Option[TransferContainer]
|
||||||
|
): Option[TransferContainer] = {
|
||||||
|
//determine if we are close enough to charge from something
|
||||||
|
val position = obj.Position.xy
|
||||||
|
ntuChargingTarget.orElse(
|
||||||
|
obj.Zone
|
||||||
|
.blockMap
|
||||||
|
.sector(position, range = 20f).buildingList
|
||||||
|
.sortBy { b => Vector3.DistanceSquared(position, b.Position.xy) }
|
||||||
|
.flatMap { _.NtuSource }
|
||||||
|
.headOption
|
||||||
|
) match {
|
||||||
|
case out @ Some(_: WarpGate) =>
|
||||||
|
out
|
||||||
|
case Some(silo: ResourceSilo) if {
|
||||||
|
val radius = 20f//3.6135f
|
||||||
|
Vector3.DistanceSquared(position, silo.Position.xy) < radius * radius && obj.Faction != silo.Faction
|
||||||
|
} =>
|
||||||
|
Some(silo)
|
||||||
|
case _ =>
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def FindBfrDischargingTarget(
|
||||||
|
obj: TransferContainer,
|
||||||
|
ntuChargingTarget: Option[TransferContainer]
|
||||||
|
): Option[TransferContainer] = {
|
||||||
|
FindResourceSiloToDischargeInto(obj, ntuChargingTarget, radius = 20) //3.6135f?
|
||||||
|
}
|
||||||
|
|
||||||
|
def FindResourceSiloToDischargeInto(
|
||||||
|
obj: TransferContainer,
|
||||||
|
ntuChargingTarget: Option[TransferContainer],
|
||||||
|
radius: Float
|
||||||
|
): Option[TransferContainer] = {
|
||||||
|
//determine if we are close enough to charge from something
|
||||||
|
val position = obj.Position.xy
|
||||||
|
ntuChargingTarget.orElse(
|
||||||
|
obj.Zone
|
||||||
|
.blockMap
|
||||||
|
.sector(position, range = 20f)
|
||||||
|
.buildingList
|
||||||
|
.sortBy { b => Vector3.DistanceSquared(position, b.Position.xy) }
|
||||||
|
.flatMap { _.NtuSource }
|
||||||
|
.headOption
|
||||||
|
) match {
|
||||||
|
case out @ Some(silo: ResourceSilo)
|
||||||
|
if Vector3.DistanceSquared(position, silo.Position.xy) < radius * radius && obj.Faction == silo.Faction =>
|
||||||
out
|
out
|
||||||
case _ =>
|
case _ =>
|
||||||
None
|
None
|
||||||
}).orElse {
|
|
||||||
val position = obj.Position.xy
|
|
||||||
obj.Zone.Buildings.values
|
|
||||||
.find { building =>
|
|
||||||
building.BuildingType == StructureType.Facility && {
|
|
||||||
val soiRadius = building.Definition.SOIRadius
|
|
||||||
Vector3.DistanceSquared(position, building.Position.xy) < soiRadius * soiRadius
|
|
||||||
}
|
|
||||||
} match {
|
|
||||||
case Some(building) =>
|
|
||||||
building.Amenities
|
|
||||||
.collect { case obj: NtuContainer => obj }
|
|
||||||
.sortBy { o => Vector3.DistanceSquared(position, o.Position.xy) < 400 } //20m is generous ...
|
|
||||||
.headOption
|
|
||||||
case None =>
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,19 @@ import scala.concurrent.duration._
|
||||||
object Avatar {
|
object Avatar {
|
||||||
val purchaseCooldowns: Map[BasicDefinition, FiniteDuration] = Map(
|
val purchaseCooldowns: Map[BasicDefinition, FiniteDuration] = Map(
|
||||||
GlobalDefinitions.ams -> 5.minutes,
|
GlobalDefinitions.ams -> 5.minutes,
|
||||||
GlobalDefinitions.ant -> 5.minutes,
|
GlobalDefinitions.ant -> 4.minutes,
|
||||||
GlobalDefinitions.apc_nc -> 5.minutes,
|
GlobalDefinitions.apc_nc -> 5.minutes,
|
||||||
GlobalDefinitions.apc_tr -> 5.minutes,
|
GlobalDefinitions.apc_tr -> 5.minutes,
|
||||||
GlobalDefinitions.apc_vs -> 5.minutes,
|
GlobalDefinitions.apc_vs -> 5.minutes,
|
||||||
|
GlobalDefinitions.aphelion_flight -> 15.minutes, //Temporarily - Default is 25 minutes
|
||||||
|
GlobalDefinitions.aphelion_gunner -> 15.minutes, //Temporarily - Default is 25 minutes
|
||||||
GlobalDefinitions.aurora -> 5.minutes,
|
GlobalDefinitions.aurora -> 5.minutes,
|
||||||
GlobalDefinitions.battlewagon -> 5.minutes,
|
GlobalDefinitions.battlewagon -> 5.minutes,
|
||||||
|
GlobalDefinitions.colossus_flight -> 15.minutes, //Temporarily - Default is 25 minutes
|
||||||
|
GlobalDefinitions.colossus_gunner -> 15.minutes, //Temporarily - Default is 25 minutes
|
||||||
GlobalDefinitions.dropship -> 5.minutes,
|
GlobalDefinitions.dropship -> 5.minutes,
|
||||||
GlobalDefinitions.flail -> 5.minutes,
|
GlobalDefinitions.flail -> 5.minutes,
|
||||||
GlobalDefinitions.fury -> 5.minutes,
|
GlobalDefinitions.fury -> 2.minutes,
|
||||||
GlobalDefinitions.galaxy_gunship -> 15.minutes, //Temporary - Default is 10 minutes
|
GlobalDefinitions.galaxy_gunship -> 15.minutes, //Temporary - Default is 10 minutes
|
||||||
GlobalDefinitions.lodestar -> 5.minutes,
|
GlobalDefinitions.lodestar -> 5.minutes,
|
||||||
GlobalDefinitions.liberator -> 5.minutes,
|
GlobalDefinitions.liberator -> 5.minutes,
|
||||||
|
|
@ -32,18 +36,20 @@ object Avatar {
|
||||||
GlobalDefinitions.magrider -> 5.minutes,
|
GlobalDefinitions.magrider -> 5.minutes,
|
||||||
GlobalDefinitions.mediumtransport -> 5.minutes,
|
GlobalDefinitions.mediumtransport -> 5.minutes,
|
||||||
GlobalDefinitions.mosquito -> 5.minutes,
|
GlobalDefinitions.mosquito -> 5.minutes,
|
||||||
|
GlobalDefinitions.peregrine_flight -> 15.minutes, //Temporarily - Default is 25 minutes
|
||||||
|
GlobalDefinitions.peregrine_gunner -> 15.minutes, //Temporarily - Default is 25 minutes
|
||||||
GlobalDefinitions.phantasm -> 5.minutes,
|
GlobalDefinitions.phantasm -> 5.minutes,
|
||||||
GlobalDefinitions.prowler -> 5.minutes,
|
GlobalDefinitions.prowler -> 5.minutes,
|
||||||
GlobalDefinitions.quadassault -> 5.minutes,
|
GlobalDefinitions.quadassault -> 2.minutes,
|
||||||
GlobalDefinitions.quadstealth -> 5.minutes,
|
GlobalDefinitions.quadstealth -> 2.minutes,
|
||||||
GlobalDefinitions.router -> 5.minutes,
|
GlobalDefinitions.router -> 5.minutes,
|
||||||
GlobalDefinitions.switchblade -> 5.minutes,
|
GlobalDefinitions.switchblade -> 5.minutes,
|
||||||
GlobalDefinitions.skyguard -> 5.minutes,
|
GlobalDefinitions.skyguard -> 2.minutes,
|
||||||
GlobalDefinitions.threemanheavybuggy -> 5.minutes,
|
GlobalDefinitions.threemanheavybuggy -> 2.minutes,
|
||||||
GlobalDefinitions.thunderer -> 5.minutes,
|
GlobalDefinitions.thunderer -> 5.minutes,
|
||||||
GlobalDefinitions.two_man_assault_buggy -> 5.minutes,
|
GlobalDefinitions.two_man_assault_buggy -> 2.minutes,
|
||||||
GlobalDefinitions.twomanhoverbuggy -> 5.minutes,
|
GlobalDefinitions.twomanhoverbuggy -> 2.minutes,
|
||||||
GlobalDefinitions.twomanheavybuggy -> 5.minutes,
|
GlobalDefinitions.twomanheavybuggy -> 2.minutes,
|
||||||
GlobalDefinitions.vanguard -> 5.minutes,
|
GlobalDefinitions.vanguard -> 5.minutes,
|
||||||
GlobalDefinitions.vulture -> 5.minutes,
|
GlobalDefinitions.vulture -> 5.minutes,
|
||||||
GlobalDefinitions.wasp -> 5.minutes,
|
GlobalDefinitions.wasp -> 5.minutes,
|
||||||
|
|
@ -89,7 +95,7 @@ case class Avatar(
|
||||||
fatigued: Boolean = false,
|
fatigued: Boolean = false,
|
||||||
cosmetics: Option[Set[Cosmetic]] = None,
|
cosmetics: Option[Set[Cosmetic]] = None,
|
||||||
certifications: Set[Certification] = Set(),
|
certifications: Set[Certification] = Set(),
|
||||||
loadouts: Seq[Option[Loadout]] = Seq.fill(15)(None),
|
loadouts: Seq[Option[Loadout]] = Seq.fill(20)(None),
|
||||||
squadLoadouts: Seq[Option[SquadLoadout]] = Seq.fill(10)(None),
|
squadLoadouts: Seq[Option[SquadLoadout]] = Seq.fill(10)(None),
|
||||||
implants: Seq[Option[Implant]] = Seq(None, None, None),
|
implants: Seq[Option[Implant]] = Seq(None, None, None),
|
||||||
locker: LockerContainer = Avatar.makeLocker(),
|
locker: LockerContainer = Avatar.makeLocker(),
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ case object Certification extends IntEnum[Certification] {
|
||||||
case object GroundSupport extends Certification(value = 17, name = "ground_support", cost = 2)
|
case object GroundSupport extends Certification(value = 17, name = "ground_support", cost = 2)
|
||||||
|
|
||||||
case object BattleFrameRobotics
|
case object BattleFrameRobotics
|
||||||
extends Certification(value = 18, name = "TODO2", cost = 4, requires = Set(ArmoredAssault2)) // TODO name
|
extends Certification(value = 18, name = "bfr_basic", cost = 4, requires = Set(ArmoredAssault2))
|
||||||
|
|
||||||
case object Flail extends Certification(value = 19, name = "flail", cost = 1, requires = Set(ArmoredAssault2))
|
case object Flail extends Certification(value = 19, name = "flail", cost = 1, requires = Set(ArmoredAssault2))
|
||||||
|
|
||||||
|
|
@ -87,10 +87,10 @@ case object Certification extends IntEnum[Certification] {
|
||||||
case object GalaxyGunship extends Certification(value = 23, name = "gunship", cost = 2, requires = Set(AirSupport))
|
case object GalaxyGunship extends Certification(value = 23, name = "gunship", cost = 2, requires = Set(AirSupport))
|
||||||
|
|
||||||
case object BFRAntiAircraft
|
case object BFRAntiAircraft
|
||||||
extends Certification(value = 24, name = "TODO3", cost = 1, requires = Set(BattleFrameRobotics))
|
extends Certification(value = 24, name = "bfr_aa_gunnery", cost = 1, requires = Set(BattleFrameRobotics))
|
||||||
|
|
||||||
case object BFRAntiInfantry
|
case object BFRAntiInfantry
|
||||||
extends Certification(value = 25, name = "TODO4", cost = 1, requires = Set(BattleFrameRobotics)) // TODO name
|
extends Certification(value = 25, name = "bfr_ai_gunnery", cost = 1, requires = Set(BattleFrameRobotics))
|
||||||
|
|
||||||
case object StandardExoSuit extends Certification(value = 26, name = "standard_armor", cost = 0)
|
case object StandardExoSuit extends Certification(value = 26, name = "standard_armor", cost = 0)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import net.psforever.objects.{Player, _}
|
||||||
import net.psforever.objects.ballistics.PlayerSource
|
import net.psforever.objects.ballistics.PlayerSource
|
||||||
import net.psforever.objects.ce.Deployable
|
import net.psforever.objects.ce.Deployable
|
||||||
import net.psforever.objects.definition.DeployAnimation
|
import net.psforever.objects.definition.DeployAnimation
|
||||||
|
import net.psforever.objects.definition.converter.OCM
|
||||||
import net.psforever.objects.equipment._
|
import net.psforever.objects.equipment._
|
||||||
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||||
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
|
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
|
||||||
|
|
@ -1111,7 +1112,6 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
||||||
val zone = obj.Zone
|
val zone = obj.Zone
|
||||||
val events = zone.AvatarEvents
|
val events = zone.AvatarEvents
|
||||||
val name = player.Name
|
val name = player.Name
|
||||||
val definition = item.Definition
|
|
||||||
val faction = obj.Faction
|
val faction = obj.Faction
|
||||||
val toChannel = if (player.isBackpack) { self.toString } else { name }
|
val toChannel = if (player.isBackpack) { self.toString } else { name }
|
||||||
val willBeVisible = obj.VisibleSlots.contains(slot)
|
val willBeVisible = obj.VisibleSlots.contains(slot)
|
||||||
|
|
@ -1143,12 +1143,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
||||||
toChannel,
|
toChannel,
|
||||||
AvatarAction.SendResponse(
|
AvatarAction.SendResponse(
|
||||||
Service.defaultPlayerGUID,
|
Service.defaultPlayerGUID,
|
||||||
ObjectCreateDetailedMessage(
|
OCM.detailed(item, ObjectCreateMessageParent(guid, slot))
|
||||||
definition.ObjectId,
|
|
||||||
item.GUID,
|
|
||||||
ObjectCreateMessageParent(guid, slot),
|
|
||||||
definition.Packet.DetailedConstructorData(item).get
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if (!player.isBackpack && willBeVisible) {
|
if (!player.isBackpack && willBeVisible) {
|
||||||
|
|
@ -1328,10 +1323,10 @@ object PlayerControl {
|
||||||
*/
|
*/
|
||||||
private def auraEffectToAttributeValue(effect: Aura): Int = effect match {
|
private def auraEffectToAttributeValue(effect: Aura): Int = effect match {
|
||||||
case Aura.Plasma => 1
|
case Aura.Plasma => 1
|
||||||
case Aura.Comet => 2
|
case Aura.Comet => 2
|
||||||
case Aura.Napalm => 4
|
case Aura.Napalm => 4
|
||||||
case Aura.Fire => 8
|
case Aura.Fire => 8
|
||||||
case _ => 0
|
case _ => 0
|
||||||
}
|
}
|
||||||
|
|
||||||
def sendResponse(zone: Zone, channel: String, msg: PlanetSideGamePacket): Unit = {
|
def sendResponse(zone: Zone, channel: String, msg: PlanetSideGamePacket): Unit = {
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,8 @@ object AggravatedDamage {
|
||||||
DamageType.Direct
|
DamageType.Direct
|
||||||
case DamageResolution.AggravatedSplash | DamageResolution.AggravatedSplashBurn =>
|
case DamageResolution.AggravatedSplash | DamageResolution.AggravatedSplashBurn =>
|
||||||
DamageType.Splash
|
DamageType.Splash
|
||||||
|
case DamageResolution.Radiation =>
|
||||||
|
DamageType.Splash
|
||||||
case _ =>
|
case _ =>
|
||||||
DamageType.None
|
DamageType.None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,16 @@ final case class DeployableSource(
|
||||||
position: Vector3,
|
position: Vector3,
|
||||||
orientation: Vector3
|
orientation: Vector3
|
||||||
) extends SourceEntry {
|
) extends SourceEntry {
|
||||||
override def Name = SourceEntry.NameFormat(obj_def.Name)
|
override def Name = obj_def.Descriptor
|
||||||
override def Faction = faction
|
override def Faction = faction
|
||||||
def Definition: ObjectDefinition with DeployableDefinition = obj_def
|
def Definition: ObjectDefinition with DeployableDefinition = obj_def
|
||||||
def Health = health
|
def Health = health
|
||||||
def Shields = shields
|
def Shields = shields
|
||||||
def OwnerName = owner.Name
|
def OwnerName = owner.Name
|
||||||
def Position = position
|
def Position = position
|
||||||
def Orientation = orientation
|
def Orientation = orientation
|
||||||
def Velocity = None
|
def Velocity = None
|
||||||
def Modifiers = obj_def.asInstanceOf[ResistanceProfile]
|
def Modifiers = obj_def.asInstanceOf[ResistanceProfile]
|
||||||
}
|
}
|
||||||
|
|
||||||
object DeployableSource {
|
object DeployableSource {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.ballistics
|
||||||
|
|
||||||
|
import net.psforever.objects.Player
|
||||||
|
import net.psforever.objects.vital.Vitality
|
||||||
|
import net.psforever.objects.vital.base.DamageResolution
|
||||||
|
import net.psforever.objects.vital.etc.RadiationReason
|
||||||
|
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||||
|
import net.psforever.objects.zones.blockmap.SectorPopulation
|
||||||
|
import net.psforever.objects.zones.{InteractsWithZone, Zone, ZoneInteraction, ZoneInteractionType}
|
||||||
|
import net.psforever.types.PlanetSideGUID
|
||||||
|
|
||||||
|
case object RadiationInteraction extends ZoneInteractionType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This game entity may infrequently test whether it may interact with radiation cloud projectiles
|
||||||
|
* that may be emitted in the game environment for a limited amount of time.
|
||||||
|
*/
|
||||||
|
class InteractWithRadiationClouds(
|
||||||
|
val range: Float,
|
||||||
|
private val user: Option[Player]
|
||||||
|
) extends ZoneInteraction {
|
||||||
|
/**
|
||||||
|
* radiation clouds that, though detected, are skipped from affecting the target;
|
||||||
|
* in between interaction tests, a memory of the clouds that were tested last are retained and
|
||||||
|
* are excluded from being tested this next time;
|
||||||
|
* clouds that are detected a second time are cleared from the list and are available to be tested next time
|
||||||
|
*/
|
||||||
|
private var skipTargets: List[PlanetSideGUID] = List()
|
||||||
|
|
||||||
|
def Type = RadiationInteraction
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wander into a radiation cloud and suffer the consequences.
|
||||||
|
* @param sector the portion of the block map being tested
|
||||||
|
* @param target the fixed element in this test
|
||||||
|
*/
|
||||||
|
def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = {
|
||||||
|
target match {
|
||||||
|
case t: Vitality =>
|
||||||
|
val position = target.Position
|
||||||
|
//collect all projectiles in sector/range
|
||||||
|
val projectiles = sector
|
||||||
|
.projectileList
|
||||||
|
.filter { cloud =>
|
||||||
|
val radius = cloud.Definition.DamageRadius
|
||||||
|
cloud.Definition.radiation_cloud && Zone.distanceCheck(target, cloud, radius * radius)
|
||||||
|
}
|
||||||
|
.distinct
|
||||||
|
val notSkipped = projectiles.filterNot { t => skipTargets.contains(t.GUID) }
|
||||||
|
skipTargets = notSkipped.map { _.GUID }
|
||||||
|
if (notSkipped.nonEmpty) {
|
||||||
|
//isolate one of each type of projectile
|
||||||
|
notSkipped
|
||||||
|
.foldLeft(Nil: List[Projectile]) {
|
||||||
|
(acc, next) => if (acc.exists { _.profile == next.profile }) acc else next :: acc
|
||||||
|
}
|
||||||
|
.foreach { projectile =>
|
||||||
|
t.Actor ! Vitality.Damage(
|
||||||
|
DamageInteraction(
|
||||||
|
SourceEntry(target),
|
||||||
|
RadiationReason(
|
||||||
|
ProjectileQuality.modifiers(projectile, DamageResolution.Radiation, t, t.Position, user),
|
||||||
|
t.DamageModel,
|
||||||
|
1f
|
||||||
|
),
|
||||||
|
position
|
||||||
|
).calculate()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any radiation clouds blocked from being tested should be cleared.
|
||||||
|
* All that can be done is blanking our retained previous effect targets.
|
||||||
|
* @param target the fixed element in this test
|
||||||
|
*/
|
||||||
|
def resetInteraction(target: InteractsWithZone): Unit = {
|
||||||
|
skipTargets = List()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ import net.psforever.objects.entity.SimpleWorldEntity
|
||||||
import net.psforever.objects.equipment.FireModeDefinition
|
import net.psforever.objects.equipment.FireModeDefinition
|
||||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||||
import net.psforever.objects.vital.base.DamageResolution
|
import net.psforever.objects.vital.base.DamageResolution
|
||||||
|
import net.psforever.objects.zones.blockmap.BlockMapEntity
|
||||||
import net.psforever.types.Vector3
|
import net.psforever.types.Vector3
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -47,7 +48,8 @@ final case class Projectile(
|
||||||
quality: ProjectileQuality = ProjectileQuality.Normal,
|
quality: ProjectileQuality = ProjectileQuality.Normal,
|
||||||
id: Long = Projectile.idGenerator.getAndIncrement(),
|
id: Long = Projectile.idGenerator.getAndIncrement(),
|
||||||
fire_time: Long = System.currentTimeMillis()
|
fire_time: Long = System.currentTimeMillis()
|
||||||
) extends PlanetSideGameObject {
|
) extends PlanetSideGameObject
|
||||||
|
with BlockMapEntity {
|
||||||
Position = shot_origin
|
Position = shot_origin
|
||||||
Orientation = shot_angle
|
Orientation = shot_angle
|
||||||
Velocity = shot_velocity.getOrElse {
|
Velocity = shot_velocity.getOrElse {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
//Copyright (c) 2020 PSForever
|
//Copyright (c) 2020 PSForever
|
||||||
package net.psforever.objects.ballistics
|
package net.psforever.objects.ballistics
|
||||||
|
|
||||||
|
import net.psforever.objects.{PlanetSideGameObject, Player}
|
||||||
|
import net.psforever.objects.equipment.EquipmentSize
|
||||||
|
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||||
|
import net.psforever.objects.vital.Vitality
|
||||||
|
import net.psforever.objects.vital.base.{DamageResolution, DamageType}
|
||||||
|
import net.psforever.types.{ImplantType, Vector3}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Projectile quality is an external aspect of projectiles
|
* Projectile quality is an external aspect of projectiles
|
||||||
* that is not dependent on hard-coded definitions of the entities
|
* that is not dependent on hard-coded definitions of the entities
|
||||||
|
|
@ -33,4 +40,48 @@ object ProjectileQuality {
|
||||||
|
|
||||||
/** Assign a custom numeric qualifier value, usually to be applied to damage calculations. */
|
/** Assign a custom numeric qualifier value, usually to be applied to damage calculations. */
|
||||||
case class Modified(mod: Float) extends ProjectileQuality
|
case class Modified(mod: Float) extends ProjectileQuality
|
||||||
|
|
||||||
|
/**
|
||||||
|
* na
|
||||||
|
* @param projectile the projectile object
|
||||||
|
* @param resolution the resolution status to promote the projectile
|
||||||
|
* @return a copy of the projectile
|
||||||
|
*/
|
||||||
|
def modifiers(
|
||||||
|
projectile: Projectile,
|
||||||
|
resolution: DamageResolution.Value,
|
||||||
|
target: PlanetSideGameObject with FactionAffinity with Vitality,
|
||||||
|
pos: Vector3,
|
||||||
|
user: Option[Player]
|
||||||
|
): Projectile = {
|
||||||
|
projectile.Resolve() //if not yet resolved once
|
||||||
|
if (projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated)) {
|
||||||
|
//aggravated
|
||||||
|
val quality = projectile.profile.Aggravated match {
|
||||||
|
case Some(aggravation)
|
||||||
|
if aggravation.targets.exists(validation => validation.test(target)) &&
|
||||||
|
aggravation.info.exists(_.damage_type == AggravatedDamage.basicDamageType(resolution)) =>
|
||||||
|
ProjectileQuality.AggravatesTarget
|
||||||
|
case _ =>
|
||||||
|
ProjectileQuality.Normal
|
||||||
|
}
|
||||||
|
projectile.quality(quality)
|
||||||
|
} else if (projectile.tool_def.Size == EquipmentSize.Melee) {
|
||||||
|
//melee
|
||||||
|
user match {
|
||||||
|
case Some(player) =>
|
||||||
|
val quality = player.avatar.implants.flatten.find { entry => entry.definition.implantType == ImplantType.MeleeBooster } match {
|
||||||
|
case Some(booster) if booster.active && player.avatar.stamina > 9 =>
|
||||||
|
ProjectileQuality.Modified(25f)
|
||||||
|
case _ =>
|
||||||
|
ProjectileQuality.Normal
|
||||||
|
}
|
||||||
|
projectile.quality(quality)
|
||||||
|
case None =>
|
||||||
|
projectile
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
projectile
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ object Projectiles extends Enumeration {
|
||||||
final val anniversary_projectileb = Value(59)
|
final val anniversary_projectileb = Value(59)
|
||||||
final val aphelion_immolation_cannon_projectile = Value(87)
|
final val aphelion_immolation_cannon_projectile = Value(87)
|
||||||
final val aphelion_laser_projectile = Value(91)
|
final val aphelion_laser_projectile = Value(91)
|
||||||
|
final val aphelion_plasma_cloud = Value(96) //radiation cloud
|
||||||
final val aphelion_plasma_rocket_projectile = Value(99)
|
final val aphelion_plasma_rocket_projectile = Value(99)
|
||||||
final val aphelion_ppa_projectile = Value(103)
|
final val aphelion_ppa_projectile = Value(103)
|
||||||
final val aphelion_starfire_projectile = Value(108)
|
final val aphelion_starfire_projectile = Value(108)
|
||||||
|
|
@ -48,6 +49,7 @@ object Projectiles extends Enumeration {
|
||||||
final val falcon_projectile = Value(286)
|
final val falcon_projectile = Value(286)
|
||||||
final val firebird_missile_projectile = Value(288)
|
final val firebird_missile_projectile = Value(288)
|
||||||
final val flail_projectile = Value(296)
|
final val flail_projectile = Value(296)
|
||||||
|
final val flamethrower_fire_cloud = Value(301)
|
||||||
final val flamethrower_fireball = Value(302)
|
final val flamethrower_fireball = Value(302)
|
||||||
final val flamethrower_projectile = Value(303)
|
final val flamethrower_projectile = Value(303)
|
||||||
final val flux_cannon_apc_projectile = Value(305)
|
final val flux_cannon_apc_projectile = Value(305)
|
||||||
|
|
@ -79,6 +81,7 @@ object Projectiles extends Enumeration {
|
||||||
final val liberator_bomb_cluster_bomblet_projectile = Value(436)
|
final val liberator_bomb_cluster_bomblet_projectile = Value(436)
|
||||||
final val liberator_bomb_cluster_projectile = Value(437)
|
final val liberator_bomb_cluster_projectile = Value(437)
|
||||||
final val liberator_bomb_projectile = Value(438)
|
final val liberator_bomb_projectile = Value(438)
|
||||||
|
final val maelstrom_grenade_damager = Value(464)
|
||||||
final val maelstrom_grenade_projectile = Value(465)
|
final val maelstrom_grenade_projectile = Value(465)
|
||||||
final val maelstrom_grenade_projectile_contact = Value(466)
|
final val maelstrom_grenade_projectile_contact = Value(466)
|
||||||
final val maelstrom_stream_projectile = Value(467)
|
final val maelstrom_stream_projectile = Value(467)
|
||||||
|
|
@ -94,12 +97,14 @@ object Projectiles extends Enumeration {
|
||||||
final val mine_projectile = Value(551)
|
final val mine_projectile = Value(551)
|
||||||
final val mine_sweeper_projectile = Value(554)
|
final val mine_sweeper_projectile = Value(554)
|
||||||
final val mine_sweeper_projectile_enh = Value(555)
|
final val mine_sweeper_projectile_enh = Value(555)
|
||||||
|
final val ntu_siphon_emp = Value(596)
|
||||||
final val oicw_little_buddy = Value(601)
|
final val oicw_little_buddy = Value(601)
|
||||||
final val oicw_projectile = Value(602)
|
final val oicw_projectile = Value(602)
|
||||||
final val pellet_gun_projectile = Value(631)
|
final val pellet_gun_projectile = Value(631)
|
||||||
final val peregrine_dual_machine_gun_projectile = Value(639)
|
final val peregrine_dual_machine_gun_projectile = Value(639)
|
||||||
final val peregrine_mechhammer_projectile = Value(647)
|
final val peregrine_mechhammer_projectile = Value(647)
|
||||||
final val peregrine_particle_cannon_projectile = Value(654)
|
final val peregrine_particle_cannon_projectile = Value(654)
|
||||||
|
final val peregrine_particle_cannon_radiation_cloud = Value(655) //radiation cloud
|
||||||
final val peregrine_rocket_pod_projectile = Value(657)
|
final val peregrine_rocket_pod_projectile = Value(657)
|
||||||
final val peregrine_sparrow_projectile = Value(661)
|
final val peregrine_sparrow_projectile = Value(661)
|
||||||
final val phalanx_av_projectile = Value(665)
|
final val phalanx_av_projectile = Value(665)
|
||||||
|
|
@ -117,6 +122,7 @@ object Projectiles extends Enumeration {
|
||||||
final val pulsar_ap_projectile = Value(702)
|
final val pulsar_ap_projectile = Value(702)
|
||||||
final val pulsar_projectile = Value(703)
|
final val pulsar_projectile = Value(703)
|
||||||
final val quasar_projectile = Value(713)
|
final val quasar_projectile = Value(713)
|
||||||
|
final val radiator_cloud = Value(717) //radiation cloud
|
||||||
final val radiator_grenade_projectile = Value(718)
|
final val radiator_grenade_projectile = Value(718)
|
||||||
final val radiator_sticky_projectile = Value(719)
|
final val radiator_sticky_projectile = Value(719)
|
||||||
final val reaver_rocket_projectile = Value(723)
|
final val reaver_rocket_projectile = Value(723)
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
// Copyright (c) 2021 PSForever
|
// Copyright (c) 2021 PSForever
|
||||||
package net.psforever.objects.ce
|
package net.psforever.objects.ce
|
||||||
|
|
||||||
import net.psforever.objects.zones.{InteractsWithZone, Zone, ZoneInteraction}
|
import net.psforever.objects.zones.blockmap.SectorPopulation
|
||||||
|
import net.psforever.objects.zones.{InteractsWithZone, Zone, ZoneInteraction, ZoneInteractionType}
|
||||||
import net.psforever.objects.{BoomerDeployable, ExplosiveDeployable}
|
import net.psforever.objects.{BoomerDeployable, ExplosiveDeployable}
|
||||||
import net.psforever.types.PlanetSideGUID
|
import net.psforever.types.PlanetSideGUID
|
||||||
|
|
||||||
|
case object MineInteraction extends ZoneInteractionType
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This game entity may infrequently test whether it may interact with game world deployable extra-territorial munitions.
|
* This game entity may infrequently test whether it may interact with game world deployable extra-territorial munitions.
|
||||||
* "Interact", here, is a graceful word for "trample upon" and the consequence should be an explosion
|
* "Interact", here, is a graceful word for "trample upon" and the consequence should be an explosion
|
||||||
* and maybe death.
|
* and maybe death.
|
||||||
*/
|
*/
|
||||||
class InteractWithMines(range: Float)
|
class InteractWithMines(val range: Float)
|
||||||
extends ZoneInteraction {
|
extends ZoneInteraction {
|
||||||
/**
|
/**
|
||||||
* mines that, though detected, are skipped from being alerted;
|
* mines that, though detected, are skipped from being alerted;
|
||||||
|
|
@ -20,14 +23,16 @@ class InteractWithMines(range: Float)
|
||||||
*/
|
*/
|
||||||
private var skipTargets: List[PlanetSideGUID] = List()
|
private var skipTargets: List[PlanetSideGUID] = List()
|
||||||
|
|
||||||
|
def Type = MineInteraction
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trample upon active mines in our current detection sector and alert those mines.
|
* Trample upon active mines in our current detection sector and alert those mines.
|
||||||
|
* @param sector the portion of the block map being tested
|
||||||
* @param target the fixed element in this test
|
* @param target the fixed element in this test
|
||||||
*/
|
*/
|
||||||
def interaction(target: InteractsWithZone): Unit = {
|
def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = {
|
||||||
val faction = target.Faction
|
val faction = target.Faction
|
||||||
val targets = target.Zone.blockMap
|
val targets = sector
|
||||||
.sector(target.Position, range)
|
|
||||||
.deployableList
|
.deployableList
|
||||||
.filter {
|
.filter {
|
||||||
case _: BoomerDeployable => false //boomers are specific types of ExplosiveDeployable but do not count here
|
case _: BoomerDeployable => false //boomers are specific types of ExplosiveDeployable but do not count here
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@ class ProjectileDefinition(objectId: Int)
|
||||||
/** projectile takes the form of a type of "grenade";
|
/** projectile takes the form of a type of "grenade";
|
||||||
* grenades arc with gravity rather than travel in a relatively straight path */
|
* grenades arc with gravity rather than travel in a relatively straight path */
|
||||||
private var grenade_projectile: Boolean = false
|
private var grenade_projectile: Boolean = false
|
||||||
|
/** radiation clouds create independent damage-dealing areas in a zone that last for the projectile's lifespan */
|
||||||
|
var radiation_cloud: Boolean = false
|
||||||
//derived calculations
|
//derived calculations
|
||||||
/** the calculated distance at which the projectile have traveled far enough to despawn (m);
|
/** the calculated distance at which the projectile have traveled far enough to despawn (m);
|
||||||
* typically handled as the projectile no longer performing damage;
|
* typically handled as the projectile no longer performing damage;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import net.psforever.objects.{Default, NtuContainerDefinition, Vehicle}
|
||||||
import net.psforever.objects.definition.converter.VehicleConverter
|
import net.psforever.objects.definition.converter.VehicleConverter
|
||||||
import net.psforever.objects.inventory.InventoryTile
|
import net.psforever.objects.inventory.InventoryTile
|
||||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||||
import net.psforever.objects.vehicles.{DestroyedVehicle, MountableWeaponsDefinition, UtilityType}
|
import net.psforever.objects.vehicles.{DestroyedVehicle, MountableWeaponsDefinition, UtilityType, VehicleSubsystemEntry}
|
||||||
import net.psforever.objects.vital._
|
import net.psforever.objects.vital._
|
||||||
import net.psforever.objects.vital.damage.DamageCalculations
|
import net.psforever.objects.vital.damage.DamageCalculations
|
||||||
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
|
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
|
||||||
|
|
@ -27,20 +27,39 @@ class VehicleDefinition(objectId: Int)
|
||||||
with NtuContainerDefinition
|
with NtuContainerDefinition
|
||||||
with ResistanceProfileMutators
|
with ResistanceProfileMutators
|
||||||
with DamageResistanceModel {
|
with DamageResistanceModel {
|
||||||
/** vehicle shields offered through amp station facility benefits (generally: 20% of health + 1) */
|
/** ... */
|
||||||
|
var shieldUiAttribute: Int = 68
|
||||||
|
/** how many points of shield the vehicle starts with (should default to 0 if unset through the accessor) */
|
||||||
|
private var defaultShields : Option[Int] = None
|
||||||
|
/** maximum vehicle shields (generally: 20% of health)
|
||||||
|
* for normal vehicles, offered through amp station facility benefits
|
||||||
|
* for BFR's, it charges naturally
|
||||||
|
**/
|
||||||
private var maxShields: Int = 0
|
private var maxShields: Int = 0
|
||||||
|
/** the minimum amount of time that must elapse in between damage and shield charge activities (ms) */
|
||||||
|
private var shieldChargeDamageCooldown : Long = 5000L
|
||||||
|
/** the minimum amount of time that must elapse in between distinct shield charge activities (ms) */
|
||||||
|
private var shieldChargePeriodicCooldown : Long = 1000L
|
||||||
|
/** if the shield recharges on its own, this value will be non-`None` and indicate by how much */
|
||||||
|
private var autoShieldRecharge : Option[Int] = None
|
||||||
|
private var autoShieldRechargeSpecial : Option[Int] = None
|
||||||
|
/** shield drain is what happens to the shield under special conditions, e.g., bfr flight;
|
||||||
|
* the drain interval is 250ms which is convenient for us
|
||||||
|
* we can skip needing to define is explicitly */
|
||||||
|
private var shieldDrain : Option[Int] = None
|
||||||
private val cargo: mutable.HashMap[Int, CargoDefinition] = mutable.HashMap[Int, CargoDefinition]()
|
private val cargo: mutable.HashMap[Int, CargoDefinition] = mutable.HashMap[Int, CargoDefinition]()
|
||||||
private var deployment: Boolean = false
|
private var deployment: Boolean = false
|
||||||
private val utilities: mutable.HashMap[Int, UtilityType.Value] = mutable.HashMap()
|
private val utilities: mutable.HashMap[Int, UtilityType.Value] = mutable.HashMap()
|
||||||
private val utilityOffsets: mutable.HashMap[Int, Vector3] = mutable.HashMap()
|
private val utilityOffsets: mutable.HashMap[Int, Vector3] = mutable.HashMap()
|
||||||
|
var subsystems: List[VehicleSubsystemEntry] = Nil
|
||||||
private var deploymentTime_Deploy: Int = 0 //ms
|
private var deploymentTime_Deploy: Int = 0 //ms
|
||||||
private var deploymentTime_Undeploy: Int = 0 //ms
|
private var deploymentTime_Undeploy: Int = 0 //ms
|
||||||
private var trunkSize: InventoryTile = InventoryTile.None
|
private var trunkSize: InventoryTile = InventoryTile.None
|
||||||
private var trunkOffset: Int = 0
|
private var trunkOffset: Int = 0
|
||||||
/* The position offset of the trunk, orientation as East = 0 */
|
/* The position offset of the trunk, orientation as East = 0 */
|
||||||
private var trunkLocation: Vector3 = Vector3.Zero
|
private var trunkLocation: Vector3 = Vector3.Zero
|
||||||
private var canCloak: Boolean = false
|
private var canCloak: Boolean = false
|
||||||
private var canFly: Boolean = false
|
private var canFly: Boolean = false
|
||||||
/** whether the vehicle gains and/or maintains ownership based on access to the driver seat<br>
|
/** whether the vehicle gains and/or maintains ownership based on access to the driver seat<br>
|
||||||
* `Some(true)` - assign ownership upon the driver mount, maintains ownership after the driver dismounts<br>
|
* `Some(true)` - assign ownership upon the driver mount, maintains ownership after the driver dismounts<br>
|
||||||
* `Some(false)` - assign ownership upon the driver mount, becomes unowned after the driver dismounts<br>
|
* `Some(false)` - assign ownership upon the driver mount, becomes unowned after the driver dismounts<br>
|
||||||
|
|
@ -48,12 +67,22 @@ class VehicleDefinition(objectId: Int)
|
||||||
* Be cautious about using `None` as the client tends to equate the driver seat as the owner's seat for many vehicles
|
* Be cautious about using `None` as the client tends to equate the driver seat as the owner's seat for many vehicles
|
||||||
* and breaking from the client's convention either requires additional fields or just doesn't work.
|
* and breaking from the client's convention either requires additional fields or just doesn't work.
|
||||||
*/
|
*/
|
||||||
private var canBeOwned: Option[Boolean] = Some(true)
|
private var canBeOwned: Option[Boolean] = Some(true)
|
||||||
private var serverVehicleOverrideSpeeds: (Int, Int) = (0, 0)
|
private var serverVehicleOverrideSpeeds: (Int, Int) = (0, 0)
|
||||||
var undergoesDecay: Boolean = true
|
var undergoesDecay: Boolean = true
|
||||||
private var deconTime: Option[FiniteDuration] = None
|
private var deconTime: Option[FiniteDuration] = None
|
||||||
private var maxCapacitor: Int = 0
|
private var defaultCapacitor: Int = 0
|
||||||
private var destroyedModel: Option[DestroyedVehicle.Value] = None
|
private var maxCapacitor: Int = 0
|
||||||
|
private var capacitorRecharge: Int = 0
|
||||||
|
private var capacitorDrain: Int = 0
|
||||||
|
private var capacitorDrainSpecial: Int = 0
|
||||||
|
/**
|
||||||
|
* extend the time of the final scrapping and explosion further beyond when the vehicle is functionally rendered destroyed;
|
||||||
|
* see `innateDamage` for explosion information;
|
||||||
|
* for BFR's, the ADB field is `death_large_explosion_interval`
|
||||||
|
*/
|
||||||
|
var destructionDelay: Option[Long] = None
|
||||||
|
private var destroyedModel: Option[DestroyedVehicle.Value] = None
|
||||||
Name = "vehicle"
|
Name = "vehicle"
|
||||||
Packet = VehicleDefinition.converter
|
Packet = VehicleDefinition.converter
|
||||||
DamageUsing = DamageCalculations.AgainstVehicle
|
DamageUsing = DamageCalculations.AgainstVehicle
|
||||||
|
|
@ -63,6 +92,15 @@ class VehicleDefinition(objectId: Int)
|
||||||
RepairRestoresAt = 1
|
RepairRestoresAt = 1
|
||||||
registerAs = "vehicles"
|
registerAs = "vehicles"
|
||||||
|
|
||||||
|
def DefaultShields: Int = defaultShields.getOrElse(0)
|
||||||
|
|
||||||
|
def DefaultShields_=(shield: Int): Int = DefaultShields_=(Some(shield))
|
||||||
|
|
||||||
|
def DefaultShields_=(shield: Option[Int]): Int = {
|
||||||
|
defaultShields = shield
|
||||||
|
DefaultShields
|
||||||
|
}
|
||||||
|
|
||||||
def MaxShields: Int = maxShields
|
def MaxShields: Int = maxShields
|
||||||
|
|
||||||
def MaxShields_=(shields: Int): Int = {
|
def MaxShields_=(shields: Int): Int = {
|
||||||
|
|
@ -70,6 +108,47 @@ class VehicleDefinition(objectId: Int)
|
||||||
MaxShields
|
MaxShields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def ShieldPeriodicDelay : Long = shieldChargePeriodicCooldown
|
||||||
|
|
||||||
|
def ShieldPeriodicDelay_=(cooldown: Long): Long = {
|
||||||
|
shieldChargePeriodicCooldown = cooldown
|
||||||
|
ShieldPeriodicDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
def ShieldDamageDelay: Long = shieldChargeDamageCooldown
|
||||||
|
|
||||||
|
def ShieldDamageDelay_=(cooldown: Long): Long = {
|
||||||
|
shieldChargeDamageCooldown = cooldown
|
||||||
|
ShieldDamageDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
def ShieldAutoRecharge: Option[Int] = autoShieldRecharge
|
||||||
|
|
||||||
|
def ShieldAutoRecharge_=(charge: Int): Option[Int] = ShieldAutoRecharge_=(Some(charge))
|
||||||
|
|
||||||
|
def ShieldAutoRecharge_=(charge: Option[Int]): Option[Int] = {
|
||||||
|
autoShieldRecharge = charge
|
||||||
|
ShieldAutoRecharge
|
||||||
|
}
|
||||||
|
|
||||||
|
def ShieldAutoRechargeSpecial: Option[Int] = autoShieldRechargeSpecial.orElse(ShieldAutoRecharge)
|
||||||
|
|
||||||
|
def ShieldAutoRechargeSpecial_=(charge: Int): Option[Int] = ShieldAutoRechargeSpecial_=(Some(charge))
|
||||||
|
|
||||||
|
def ShieldAutoRechargeSpecial_=(charge: Option[Int]): Option[Int] = {
|
||||||
|
autoShieldRechargeSpecial = charge
|
||||||
|
ShieldAutoRechargeSpecial
|
||||||
|
}
|
||||||
|
|
||||||
|
def ShieldDrain: Option[Int] = shieldDrain
|
||||||
|
|
||||||
|
def ShieldDrain_=(drain: Int): Option[Int] = ShieldDrain_=(Some(drain))
|
||||||
|
|
||||||
|
def ShieldDrain_=(drain: Option[Int]): Option[Int] = {
|
||||||
|
shieldDrain = drain
|
||||||
|
ShieldDrain
|
||||||
|
}
|
||||||
|
|
||||||
def Cargo: mutable.HashMap[Int, CargoDefinition] = cargo
|
def Cargo: mutable.HashMap[Int, CargoDefinition] = cargo
|
||||||
|
|
||||||
def CanBeOwned: Option[Boolean] = canBeOwned
|
def CanBeOwned: Option[Boolean] = canBeOwned
|
||||||
|
|
@ -164,6 +243,13 @@ class VehicleDefinition(objectId: Int)
|
||||||
|
|
||||||
def AutoPilotSpeed2: Int = serverVehicleOverrideSpeeds._2
|
def AutoPilotSpeed2: Int = serverVehicleOverrideSpeeds._2
|
||||||
|
|
||||||
|
def DefaultCapacitor: Int = defaultCapacitor
|
||||||
|
|
||||||
|
def DefaultCapacitor_=(defValue: Int): Int = {
|
||||||
|
defaultCapacitor = defValue
|
||||||
|
DefaultCapacitor
|
||||||
|
}
|
||||||
|
|
||||||
def MaxCapacitor : Int = maxCapacitor
|
def MaxCapacitor : Int = maxCapacitor
|
||||||
|
|
||||||
def MaxCapacitor_=(max: Int) : Int = {
|
def MaxCapacitor_=(max: Int) : Int = {
|
||||||
|
|
@ -171,6 +257,27 @@ class VehicleDefinition(objectId: Int)
|
||||||
MaxCapacitor
|
MaxCapacitor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def CapacitorRecharge: Int = capacitorRecharge
|
||||||
|
|
||||||
|
def CapacitorRecharge_=(charge: Int): Int = {
|
||||||
|
capacitorRecharge = charge
|
||||||
|
CapacitorRecharge
|
||||||
|
}
|
||||||
|
|
||||||
|
def CapacitorDrain: Int = capacitorDrain
|
||||||
|
|
||||||
|
def CapacitorDrain_=(charge: Int): Int = {
|
||||||
|
capacitorDrain = charge
|
||||||
|
CapacitorDrain
|
||||||
|
}
|
||||||
|
|
||||||
|
def CapacitorDrainSpecial: Int = capacitorDrainSpecial
|
||||||
|
|
||||||
|
def CapacitorDrainSpecial_=(charge: Int): Int = {
|
||||||
|
capacitorDrainSpecial = charge
|
||||||
|
CapacitorDrainSpecial
|
||||||
|
}
|
||||||
|
|
||||||
private var jackDuration = Array(0, 0, 0, 0)
|
private var jackDuration = Array(0, 0, 0, 0)
|
||||||
def JackingDuration: Array[Int] = jackDuration
|
def JackingDuration: Array[Int] = jackDuration
|
||||||
def JackingDuration_=(arr: Array[Int]): Array[Int] = {
|
def JackingDuration_=(arr: Array[Int]): Array[Int] = {
|
||||||
|
|
@ -253,6 +360,36 @@ object VehicleDefinition {
|
||||||
*/
|
*/
|
||||||
def Apc(objectId: Int): VehicleDefinition = new ApcDefinition(objectId)
|
def Apc(objectId: Int): VehicleDefinition = new ApcDefinition(objectId)
|
||||||
|
|
||||||
|
protected class BfrDefinition(objectId: Int) extends VehicleDefinition(objectId) {
|
||||||
|
import net.psforever.objects.vehicles.control.BfrControl
|
||||||
|
override def Initialize(obj: Vehicle, context: ActorContext): Unit = {
|
||||||
|
obj.Actor = context.actorOf(
|
||||||
|
Props(classOf[BfrControl], obj),
|
||||||
|
PlanetSideServerObject.UniqueActorName(obj)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Vehicle definition(s) for the battle frame robotics vehicles.
|
||||||
|
* @param objectId the object id that is associated with this sort of `Vehicle`
|
||||||
|
*/
|
||||||
|
def Bfr(objectId: Int): VehicleDefinition = new BfrDefinition(objectId)
|
||||||
|
|
||||||
|
protected class BfrFlightDefinition(objectId: Int) extends VehicleDefinition(objectId) {
|
||||||
|
import net.psforever.objects.vehicles.control.BfrFlightControl
|
||||||
|
override def Initialize(obj: Vehicle, context: ActorContext): Unit = {
|
||||||
|
obj.Actor = context.actorOf(
|
||||||
|
Props(classOf[BfrFlightControl], obj),
|
||||||
|
PlanetSideServerObject.UniqueActorName(obj)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Vehicle definition(s) for the flight variant of the battle frame robotics vehicles.
|
||||||
|
* @param objectId the object id that is associated with this sort of `Vehicle`
|
||||||
|
*/
|
||||||
|
def BfrFlight(objectId: Int): VehicleDefinition = new BfrFlightDefinition(objectId)
|
||||||
|
|
||||||
protected class CarrierDefinition(objectId: Int) extends VehicleDefinition(objectId) {
|
protected class CarrierDefinition(objectId: Int) extends VehicleDefinition(objectId) {
|
||||||
import net.psforever.objects.vehicles.control.CargoCarrierControl
|
import net.psforever.objects.vehicles.control.CargoCarrierControl
|
||||||
override def Initialize(obj: Vehicle, context: ActorContext): Unit = {
|
override def Initialize(obj: Vehicle, context: ActorContext): Unit = {
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ object AvatarConverter {
|
||||||
0L,
|
0L,
|
||||||
0L,
|
0L,
|
||||||
0L,
|
0L,
|
||||||
Some(DCDExtra2(0, 0)),
|
None, //Some(ImprintingProgress(0, 0)),
|
||||||
Nil,
|
Nil,
|
||||||
Nil,
|
Nil,
|
||||||
unkC = false,
|
unkC = false,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.definition.converter
|
||||||
|
|
||||||
|
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
|
||||||
|
import net.psforever.objects.vehicles.VehicleSubsystemEntry
|
||||||
|
import net.psforever.objects.{PlanetSideGameObject, Vehicle}
|
||||||
|
import net.psforever.types.PlanetSideGUID
|
||||||
|
import net.psforever.packet.game.objectcreate._
|
||||||
|
|
||||||
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
||||||
|
class BattleFrameFlightConverter extends ObjectCreateConverter[Vehicle]() {
|
||||||
|
override def DetailedConstructorData(obj: Vehicle): Try[BattleFrameRoboticsData] =
|
||||||
|
Failure(new Exception("BattleFrameFlightConverter should not be used to generate detailed BattleFrameRoboticsData (nothing should)"))
|
||||||
|
|
||||||
|
override def ConstructorData(obj: Vehicle): Try[BattleFrameRoboticsData] = {
|
||||||
|
val health = StatConverter.Health(obj.Health, obj.MaxHealth)
|
||||||
|
if(health > 0) { //active
|
||||||
|
Success(
|
||||||
|
BattleFrameRoboticsData(
|
||||||
|
PlacementData(obj.Position, obj.Orientation, obj.Velocity),
|
||||||
|
CommonFieldData(
|
||||||
|
obj.Faction,
|
||||||
|
bops = false,
|
||||||
|
alternate = false,
|
||||||
|
v1 = true,
|
||||||
|
v2 = None,
|
||||||
|
jammered = false,
|
||||||
|
v4 = None,
|
||||||
|
v5 = None,
|
||||||
|
obj.Owner match {
|
||||||
|
case Some(owner) => owner
|
||||||
|
case None => PlanetSideGUID(0)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
health,
|
||||||
|
StatConverter.Health(obj.Shields, obj.MaxShields),
|
||||||
|
unk1 = 0,
|
||||||
|
unk2 = false,
|
||||||
|
no_mount_points = false,
|
||||||
|
driveState = 60,
|
||||||
|
proper_anim = true,
|
||||||
|
unk3 = 0,
|
||||||
|
show_bfr_shield = showBfrShield(obj),
|
||||||
|
unk4 = Some(false),
|
||||||
|
Some(InventoryData(MakeDriverSeat(obj) ++ MakeUtilities(obj) ++ MakeMountings(obj)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else { //destroyed
|
||||||
|
Success(
|
||||||
|
BattleFrameRoboticsData(
|
||||||
|
PlacementData(obj.Position, obj.Orientation),
|
||||||
|
CommonFieldData(
|
||||||
|
obj.Faction,
|
||||||
|
bops = false,
|
||||||
|
alternate = false,
|
||||||
|
v1 = true,
|
||||||
|
v2 = None,
|
||||||
|
jammered = false,
|
||||||
|
v4 = None,
|
||||||
|
v5 = None,
|
||||||
|
guid = PlanetSideGUID(0)
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
unk1 = 0,
|
||||||
|
unk2 = false,
|
||||||
|
no_mount_points = false,
|
||||||
|
driveState = 0,
|
||||||
|
proper_anim = true,
|
||||||
|
unk3 = 0,
|
||||||
|
show_bfr_shield = false,
|
||||||
|
unk4 = Some(false),
|
||||||
|
inventory = None
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def MakeDriverSeat(obj: Vehicle): List[InventoryItemData.InventoryItem] = {
|
||||||
|
val offset: Long = MountableInventory.InitialStreamLengthToSeatEntries(obj.Velocity.nonEmpty, VehicleFormat.BattleframeFlight)
|
||||||
|
obj.Seats(0).occupant match {
|
||||||
|
case Some(player) =>
|
||||||
|
List(InventoryItemData(ObjectClass.avatar, player.GUID, 0, SeatConverter.MakeSeat(player, offset)))
|
||||||
|
case None =>
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def MakeMountings(obj: Vehicle): List[InventoryItemData.InventoryItem] = {
|
||||||
|
obj.Weapons.collect {
|
||||||
|
case (index, slot: EquipmentSlot) if slot.Equipment.nonEmpty =>
|
||||||
|
val equip: Equipment = slot.Equipment.get
|
||||||
|
val equipDef = equip.Definition
|
||||||
|
InventoryItemData(equipDef.ObjectId, equip.GUID, index, equipDef.Packet.ConstructorData(equip).get)
|
||||||
|
}.toList
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def MakeUtilities(obj: Vehicle): List[InventoryItemData.InventoryItem] = {
|
||||||
|
Vehicle
|
||||||
|
.EquipmentUtilities(obj.Utilities)
|
||||||
|
.map({
|
||||||
|
case (index, utilContainer) =>
|
||||||
|
val util: PlanetSideGameObject = utilContainer()
|
||||||
|
val utilDef = util.Definition
|
||||||
|
InventoryItemData(utilDef.ObjectId, util.GUID, index, utilDef.Packet.ConstructorData(util).get)
|
||||||
|
})
|
||||||
|
.toList
|
||||||
|
}
|
||||||
|
|
||||||
|
def showBfrShield(obj: Vehicle): Boolean = {
|
||||||
|
obj.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator).get.Enabled && obj.Shields > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.definition.converter
|
||||||
|
|
||||||
|
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
|
||||||
|
import net.psforever.objects.vehicles.VehicleSubsystemEntry
|
||||||
|
import net.psforever.objects.{PlanetSideGameObject, Vehicle}
|
||||||
|
import net.psforever.types.PlanetSideGUID
|
||||||
|
import net.psforever.packet.game.objectcreate._
|
||||||
|
|
||||||
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
||||||
|
class BattleFrameRoboticsConverter extends ObjectCreateConverter[Vehicle]() {
|
||||||
|
override def DetailedConstructorData(obj: Vehicle): Try[BattleFrameRoboticsData] =
|
||||||
|
Failure(new Exception("BattleFrameRoboticsConverter should not be used to generate detailed BattleFrameRoboticsData (nothing should)"))
|
||||||
|
|
||||||
|
override def ConstructorData(obj: Vehicle): Try[BattleFrameRoboticsData] = {
|
||||||
|
val health = StatConverter.Health(obj.Health, obj.MaxHealth)
|
||||||
|
if(health > 0) { //active
|
||||||
|
Success(
|
||||||
|
BattleFrameRoboticsData(
|
||||||
|
PlacementData(obj.Position, obj.Orientation, obj.Velocity),
|
||||||
|
CommonFieldData(
|
||||||
|
obj.Faction,
|
||||||
|
bops = false,
|
||||||
|
alternate = false,
|
||||||
|
v1 = true,
|
||||||
|
v2 = None,
|
||||||
|
jammered = obj.Jammed,
|
||||||
|
v4 = None,
|
||||||
|
v5 = None,
|
||||||
|
obj.Owner match {
|
||||||
|
case Some(owner) => owner
|
||||||
|
case None => PlanetSideGUID(0)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
health,
|
||||||
|
StatConverter.Health(obj.Shields, obj.MaxShields),
|
||||||
|
unk1 = 0,
|
||||||
|
unk2 = false,
|
||||||
|
no_mount_points = false,
|
||||||
|
driveState = 60,
|
||||||
|
proper_anim = true,
|
||||||
|
unk3 = 0,
|
||||||
|
show_bfr_shield = showBfrShield(obj),
|
||||||
|
unk4 = None,
|
||||||
|
Some(InventoryData(MakeDriverSeat(obj) ++ MakeUtilities(obj) ++ MakeMountings(obj)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else { //destroyed
|
||||||
|
Success(
|
||||||
|
BattleFrameRoboticsData(
|
||||||
|
PlacementData(obj.Position, obj.Orientation),
|
||||||
|
CommonFieldData(
|
||||||
|
obj.Faction,
|
||||||
|
bops = false,
|
||||||
|
alternate = false,
|
||||||
|
v1 = true,
|
||||||
|
v2 = None,
|
||||||
|
jammered = false,
|
||||||
|
v4 = None,
|
||||||
|
v5 = None,
|
||||||
|
guid = PlanetSideGUID(0)
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
unk1 = 0,
|
||||||
|
unk2 = false,
|
||||||
|
no_mount_points = false,
|
||||||
|
driveState = 0,
|
||||||
|
proper_anim = true,
|
||||||
|
unk3 = 0,
|
||||||
|
show_bfr_shield = false,
|
||||||
|
unk4 = None,
|
||||||
|
inventory = None
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def MakeDriverSeat(obj: Vehicle): List[InventoryItemData.InventoryItem] = {
|
||||||
|
val offset: Long = MountableInventory.InitialStreamLengthToSeatEntries(obj.Velocity.nonEmpty, VehicleFormat.Battleframe)
|
||||||
|
obj.Seats(0).occupant match {
|
||||||
|
case Some(player) =>
|
||||||
|
List(InventoryItemData(ObjectClass.avatar, player.GUID, 0, SeatConverter.MakeSeat(player, offset)))
|
||||||
|
case None =>
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def MakeMountings(obj: Vehicle): List[InventoryItemData.InventoryItem] = {
|
||||||
|
obj.Weapons.collect {
|
||||||
|
case (index, slot: EquipmentSlot) if slot.Equipment.nonEmpty =>
|
||||||
|
val equip: Equipment = slot.Equipment.get
|
||||||
|
val equipDef = equip.Definition
|
||||||
|
InventoryItemData(equipDef.ObjectId, equip.GUID, index, equipDef.Packet.ConstructorData(equip).get)
|
||||||
|
}.toList
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def MakeUtilities(obj: Vehicle): List[InventoryItemData.InventoryItem] = {
|
||||||
|
Vehicle
|
||||||
|
.EquipmentUtilities(obj.Utilities)
|
||||||
|
.map({
|
||||||
|
case (index, utilContainer) =>
|
||||||
|
val util: PlanetSideGameObject = utilContainer()
|
||||||
|
val utilDef = util.Definition
|
||||||
|
InventoryItemData(utilDef.ObjectId, util.GUID, index, utilDef.Packet.ConstructorData(util).get)
|
||||||
|
})
|
||||||
|
.toList
|
||||||
|
}
|
||||||
|
|
||||||
|
def showBfrShield(obj: Vehicle): Boolean = {
|
||||||
|
obj.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator).get.Enabled && obj.Shields > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.definition.converter
|
||||||
|
|
||||||
|
import net.psforever.objects.Tool
|
||||||
|
import net.psforever.packet.game.objectcreate.{CommonFieldData, DetailedWeaponData, InternalSlot, WeaponData}
|
||||||
|
import net.psforever.types.PlanetSideGUID
|
||||||
|
|
||||||
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
||||||
|
class BattleFrameToolConverter extends ObjectCreateConverter[Tool]() {
|
||||||
|
override def ConstructorData(obj: Tool): Try[WeaponData] = {
|
||||||
|
val slots: List[InternalSlot] = (0 until obj.MaxAmmoSlot).map(index => {
|
||||||
|
val box = obj.AmmoSlots(index).Box
|
||||||
|
InternalSlot(box.Definition.ObjectId, box.GUID, index, box.Definition.Packet.ConstructorData(box).get)
|
||||||
|
}).toList
|
||||||
|
Success(
|
||||||
|
WeaponData(
|
||||||
|
CommonFieldData(
|
||||||
|
obj.Faction,
|
||||||
|
bops = false,
|
||||||
|
alternate = false,
|
||||||
|
true,
|
||||||
|
None,
|
||||||
|
obj.Jammed,
|
||||||
|
Some(false),
|
||||||
|
None,
|
||||||
|
PlanetSideGUID(0)
|
||||||
|
),
|
||||||
|
obj.FireModeIndex,
|
||||||
|
slots
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def DetailedConstructorData(obj: Tool): Try[DetailedWeaponData] =
|
||||||
|
Failure(new Exception("BattleFrameToolConverter should not be used to generate detailed BattleFrameRToolData (nothing should)"))
|
||||||
|
}
|
||||||
|
|
@ -119,7 +119,7 @@ class CharacterSelectConverter extends AvatarConverter {
|
||||||
0L,
|
0L,
|
||||||
0L,
|
0L,
|
||||||
0L,
|
0L,
|
||||||
Some(DCDExtra2(0, 0)),
|
Some(ImprintingProgress(0, 0)),
|
||||||
Nil,
|
Nil,
|
||||||
Nil,
|
Nil,
|
||||||
unkC = false,
|
unkC = false,
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ class CorpseConverter extends AvatarConverter {
|
||||||
0L,
|
0L,
|
||||||
0L,
|
0L,
|
||||||
0L,
|
0L,
|
||||||
Some(DCDExtra2(0, 0)),
|
Some(ImprintingProgress(0, 0)),
|
||||||
Nil,
|
Nil,
|
||||||
Nil,
|
Nil,
|
||||||
unkC = false,
|
unkC = false,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.definition.converter
|
||||||
|
|
||||||
|
import net.psforever.objects.PlanetSideGameObject
|
||||||
|
import net.psforever.packet.PlanetSideGamePacket
|
||||||
|
import net.psforever.packet.game.{ObjectCreateDetailedMessage, ObjectCreateMessage}
|
||||||
|
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose an `ObjectCreateMessage` packet or, if requesting and allowing, an `ObjectCreateDetailedMessage` packet.
|
||||||
|
*/
|
||||||
|
object OCM {
|
||||||
|
/**
|
||||||
|
* Compose an `ObjectCreateMessage` packet of an entity.
|
||||||
|
* @param obj the entity being converted into a packet
|
||||||
|
* @return an `ObjectCreateMessage` packet
|
||||||
|
*/
|
||||||
|
def apply(obj: PlanetSideGameObject): PlanetSideGamePacket = {
|
||||||
|
val definition = obj.Definition
|
||||||
|
ObjectCreateMessage(
|
||||||
|
definition.ObjectId,
|
||||||
|
obj.GUID,
|
||||||
|
definition.Packet.ConstructorData(obj).get
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose a contained `ObjectCreateMessage` packet of an entity.
|
||||||
|
* @param obj the entity being converted into a packet
|
||||||
|
* @param parent information about the container for this entity
|
||||||
|
* @return an `ObjectCreateMessage` packet
|
||||||
|
*/
|
||||||
|
def apply(obj: PlanetSideGameObject, parent: Option[ObjectCreateMessageParent]): PlanetSideGamePacket = {
|
||||||
|
parent match {
|
||||||
|
case Some(info) => apply(obj, info)
|
||||||
|
case _ => apply(obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Compose a contained `ObjectCreateMessage` packet of an entity.
|
||||||
|
* @param obj the entity being converted into a packet
|
||||||
|
* @param parent information about the container for this entity
|
||||||
|
* @return an `ObjectCreateMessage` packet
|
||||||
|
*/
|
||||||
|
def apply(obj: PlanetSideGameObject, parent: ObjectCreateMessageParent): PlanetSideGamePacket = {
|
||||||
|
val definition = obj.Definition
|
||||||
|
ObjectCreateMessage(
|
||||||
|
definition.ObjectId,
|
||||||
|
obj.GUID,
|
||||||
|
parent,
|
||||||
|
definition.Packet.ConstructorData(obj).get
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def detailed(obj: PlanetSideGameObject): PlanetSideGamePacket = {
|
||||||
|
val definition = obj.Definition
|
||||||
|
val packet = definition.Packet
|
||||||
|
if (packet.noDetailedForm(obj)) {
|
||||||
|
apply(obj) //fall back
|
||||||
|
} else {
|
||||||
|
ObjectCreateDetailedMessage(
|
||||||
|
definition.ObjectId,
|
||||||
|
obj.GUID,
|
||||||
|
definition.Packet.DetailedConstructorData(obj).get
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose a contained detailed `ObjectCreateMessage` packet of an entity.
|
||||||
|
* @param obj the entity being converted into a packet
|
||||||
|
* @param parent information about the container for this entity
|
||||||
|
* @return an `ObjectCreateMessage` packet
|
||||||
|
*/
|
||||||
|
def detailed(obj: PlanetSideGameObject, parent: Option[ObjectCreateMessageParent]): PlanetSideGamePacket = {
|
||||||
|
parent match {
|
||||||
|
case Some(info) => detailed(obj, info)
|
||||||
|
case _ => detailed(obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Compose a contained detailed `ObjectCreateMessage` packet of an entity.
|
||||||
|
* @param obj the entity being converted into a packet
|
||||||
|
* @param parent information about the container for this entity
|
||||||
|
* @return an `ObjectCreateMessage` packet
|
||||||
|
*/
|
||||||
|
def detailed(obj: PlanetSideGameObject, parent: ObjectCreateMessageParent): PlanetSideGamePacket = {
|
||||||
|
val definition = obj.Definition
|
||||||
|
val packet = definition.Packet
|
||||||
|
if (packet.noDetailedForm(obj)) {
|
||||||
|
apply(obj, parent) //fall back
|
||||||
|
} else {
|
||||||
|
ObjectCreateDetailedMessage(
|
||||||
|
definition.ObjectId,
|
||||||
|
obj.GUID,
|
||||||
|
parent,
|
||||||
|
definition.Packet.DetailedConstructorData(obj).get
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,8 @@ sealed trait PacketConverter
|
||||||
* @tparam A the type of game object
|
* @tparam A the type of game object
|
||||||
*/
|
*/
|
||||||
abstract class ObjectCreateConverter[A <: PlanetSideGameObject] extends PacketConverter {
|
abstract class ObjectCreateConverter[A <: PlanetSideGameObject] extends PacketConverter {
|
||||||
|
/** some objects do not have a detailed constructor data form */
|
||||||
|
def noDetailedForm(obj: A): Boolean = DetailedConstructorData(obj).isFailure
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Take a game object and transform it into its equivalent data for an `0x17` packet.
|
* Take a game object and transform it into its equivalent data for an `0x17` packet.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.definition.converter
|
||||||
|
|
||||||
|
import net.psforever.objects.ballistics.Projectile
|
||||||
|
import net.psforever.packet.game.objectcreate._
|
||||||
|
|
||||||
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
||||||
|
class RadiationCloudConverter extends ObjectCreateConverter[Projectile]() {
|
||||||
|
override def ConstructorData(obj: Projectile): Try[RadiationCloudData] = {
|
||||||
|
Success(RadiationCloudData(PlacementData(obj.Position, obj.Orientation), obj.owner.Faction))
|
||||||
|
}
|
||||||
|
|
||||||
|
override def DetailedConstructorData(obj: Projectile): Try[RadiationCloudData] =
|
||||||
|
Failure(new Exception("RadiationCloudConverter should not be used to generate detailed RadiationCloudData (nothing should)"))
|
||||||
|
}
|
||||||
|
|
@ -3,11 +3,11 @@ package net.psforever.objects.definition.converter
|
||||||
|
|
||||||
import net.psforever.objects.Player
|
import net.psforever.objects.Player
|
||||||
import net.psforever.objects.serverobject.mount.Seat
|
import net.psforever.objects.serverobject.mount.Seat
|
||||||
import net.psforever.packet.game.objectcreate.{InventoryItemData, ObjectClass, PlayerData, VehicleData}
|
import net.psforever.packet.game.objectcreate._
|
||||||
|
|
||||||
object SeatConverter {
|
object SeatConverter {
|
||||||
def MakeSeat(player: Player, offset: Long): PlayerData = {
|
def MakeSeat(player: Player, offset: Long): PlayerData = {
|
||||||
VehicleData.PlayerData(
|
MountableInventory.PlayerData(
|
||||||
AvatarConverter.MakeAppearanceData(player),
|
AvatarConverter.MakeAppearanceData(player),
|
||||||
AvatarConverter.MakeCharacterData(player),
|
AvatarConverter.MakeCharacterData(player),
|
||||||
AvatarConverter.MakeInventoryData(player),
|
AvatarConverter.MakeInventoryData(player),
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private def MakeDriverSeat(obj: Vehicle): List[InventoryItemData.InventoryItem] = {
|
private def MakeDriverSeat(obj: Vehicle): List[InventoryItemData.InventoryItem] = {
|
||||||
val offset: Long = VehicleData.InitialStreamLengthToSeatEntries(obj.Velocity.nonEmpty, SpecificFormatModifier)
|
val offset: Long = MountableInventory.InitialStreamLengthToSeatEntries(obj.Velocity.nonEmpty, SpecificFormatModifier)
|
||||||
obj.Seats(0).occupant match {
|
obj.Seats(0).occupant match {
|
||||||
case Some(player) =>
|
case Some(player) =>
|
||||||
List(InventoryItemData(ObjectClass.avatar, player.GUID, 0, SeatConverter.MakeSeat(player, offset)))
|
List(InventoryItemData(ObjectClass.avatar, player.GUID, 0, SeatConverter.MakeSeat(player, offset)))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.equipment
|
||||||
|
|
||||||
|
import akka.actor.{Actor, Cancellable}
|
||||||
|
import net.psforever.objects.ballistics.VehicleSource
|
||||||
|
import net.psforever.objects.{GlobalDefinitions, Tool, Vehicle}
|
||||||
|
import net.psforever.objects.serverobject.CommonMessages
|
||||||
|
import net.psforever.objects.serverobject.damage.Damageable
|
||||||
|
import net.psforever.objects.vital.RepairFromArmorSiphon
|
||||||
|
import net.psforever.objects.vital.etc.{ArmorSiphonModifiers, ArmorSiphonReason}
|
||||||
|
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||||
|
import net.psforever.packet.game.QuantityUpdateMessage
|
||||||
|
import net.psforever.services.Service
|
||||||
|
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||||
|
import net.psforever.types.PlanetSideGUID
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
|
object ArmorSiphonBehavior {
|
||||||
|
sealed case class RepairedByArmorSiphon(cause: DamageInteraction, amount: Int)
|
||||||
|
|
||||||
|
sealed case class Recharge(guid: PlanetSideGUID)
|
||||||
|
|
||||||
|
trait Target {
|
||||||
|
_: Actor with Damageable =>
|
||||||
|
def SiphonableObject: Vehicle
|
||||||
|
|
||||||
|
val siphoningBehavior: Receive = {
|
||||||
|
case CommonMessages.Use(player, Some(item : Tool))
|
||||||
|
if GlobalDefinitions.isBattleFrameArmorSiphon(item.Definition) && player.Faction != DamageableObject.Faction =>
|
||||||
|
val obj = SiphonableObject
|
||||||
|
val zone = obj.Zone
|
||||||
|
val iguid = item.GUID
|
||||||
|
//see Damageable.takesDamage
|
||||||
|
zone.Vehicles.find { v =>
|
||||||
|
v.Weapons.values.exists { slot => slot.Equipment.nonEmpty && slot.Equipment.get.GUID == iguid}
|
||||||
|
} match {
|
||||||
|
case Some(v: Vehicle) if v.CanDamage =>
|
||||||
|
//remember: we are the vehicle being siphoned; we need the vehicle doing the siphoning
|
||||||
|
val before = item.Magazine
|
||||||
|
val after = item.Discharge()
|
||||||
|
if (before > after) {
|
||||||
|
v.Actor ! ArmorSiphonBehavior.Recharge(iguid)
|
||||||
|
PerformDamage(
|
||||||
|
obj,
|
||||||
|
DamageInteraction(
|
||||||
|
VehicleSource(obj),
|
||||||
|
ArmorSiphonReason(v, item, obj.DamageModel),
|
||||||
|
obj.Position
|
||||||
|
).calculate()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait SiphonOwner {
|
||||||
|
_: Actor =>
|
||||||
|
def SiphoningObject: Vehicle
|
||||||
|
|
||||||
|
private val siphonRecharge: mutable.HashMap[PlanetSideGUID, Cancellable] = mutable.HashMap[PlanetSideGUID, Cancellable]()
|
||||||
|
|
||||||
|
def repairPostStop(): Unit = {
|
||||||
|
siphonRecharge.keys.foreach { endSiphonRecharge }
|
||||||
|
}
|
||||||
|
|
||||||
|
val siphonRepairBehavior: Receive = {
|
||||||
|
case RepairedByArmorSiphon(cause, amount) =>
|
||||||
|
val obj = SiphoningObject
|
||||||
|
val before = obj.Health
|
||||||
|
cause.cause match {
|
||||||
|
case asr: ArmorSiphonReason
|
||||||
|
if before < obj.MaxHealth =>
|
||||||
|
val after = obj.Health += amount
|
||||||
|
if(before < after) {
|
||||||
|
obj.History(RepairFromArmorSiphon(asr.siphon.Definition, before - after))
|
||||||
|
val zone = obj.Zone
|
||||||
|
zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 0, after)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ArmorSiphonBehavior.Recharge(guid) =>
|
||||||
|
siphonRecharge.remove(guid) match {
|
||||||
|
case Some(timer) => timer.cancel()
|
||||||
|
case None => ;
|
||||||
|
}
|
||||||
|
val obj = SiphoningObject
|
||||||
|
obj.Weapons.values.find { slot => slot.Equipment.nonEmpty && slot.Equipment.get.GUID == guid } match {
|
||||||
|
case Some(siphonSlot) =>
|
||||||
|
val siphon = siphonSlot.Equipment.get.asInstanceOf[Tool]
|
||||||
|
val zone = obj.Zone
|
||||||
|
//update current charge level
|
||||||
|
zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
obj.Actor.toString,
|
||||||
|
VehicleAction.SendResponse(Service.defaultPlayerGUID, QuantityUpdateMessage(siphon.AmmoSlot.Box.GUID, siphon.Magazine))
|
||||||
|
)
|
||||||
|
siphonRecharge.put(guid, context.system.scheduler.scheduleWithFixedDelay(
|
||||||
|
initialDelay = 3000 milliseconds,
|
||||||
|
delay = 200 milliseconds,
|
||||||
|
self,
|
||||||
|
SiphonOwner.Recharge(guid)
|
||||||
|
))
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SiphonOwner.Recharge(guid) =>
|
||||||
|
val obj = SiphoningObject
|
||||||
|
val zone = obj.Zone
|
||||||
|
obj.Weapons.values.find { slot => slot.Equipment.nonEmpty && slot.Equipment.get.GUID == guid } match {
|
||||||
|
case Some(slot: EquipmentSlot) =>
|
||||||
|
val siphon = slot.Equipment.get.asInstanceOf[Tool]
|
||||||
|
val before = siphon.Magazine
|
||||||
|
val after = siphon.Magazine = before + 1
|
||||||
|
if (after > before) {
|
||||||
|
zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
obj.Actor.toString,
|
||||||
|
VehicleAction.SendResponse(Service.defaultPlayerGUID, QuantityUpdateMessage(siphon.AmmoSlot.Box.GUID, after))
|
||||||
|
)
|
||||||
|
if (after == siphon.MaxMagazine) {
|
||||||
|
endSiphonRecharge(guid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case _ =>
|
||||||
|
endSiphonRecharge(guid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def endSiphonRecharge(guid: PlanetSideGUID): Unit = {
|
||||||
|
siphonRecharge.remove(guid) match {
|
||||||
|
case Some(c) => c.cancel()
|
||||||
|
case None => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object SiphonOwner {
|
||||||
|
private case class Recharge(guid: PlanetSideGUID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case object ArmorSiphonRepairHost extends ArmorSiphonModifiers.Mod {
|
||||||
|
def calculate(damage: Int, data: DamageInteraction, cause: ArmorSiphonReason): Int = {
|
||||||
|
if (damage > 0) {
|
||||||
|
cause.hostVehicle.Actor ! ArmorSiphonBehavior.RepairedByArmorSiphon(data, damage)
|
||||||
|
}
|
||||||
|
damage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.equipment
|
||||||
|
|
||||||
|
import net.psforever.objects.definition.EquipmentDefinition
|
||||||
|
import enumeratum.values.{StringEnum, StringEnumEntry}
|
||||||
|
|
||||||
|
sealed abstract class Hand(val value: String) extends StringEnumEntry
|
||||||
|
|
||||||
|
object Handiness extends StringEnum[Hand] {
|
||||||
|
val values = findValues
|
||||||
|
|
||||||
|
case object Generic extends Hand(value = "Generic")
|
||||||
|
case object Left extends Hand(value = "Left")
|
||||||
|
case object Right extends Hand(value = "Right")
|
||||||
|
}
|
||||||
|
|
||||||
|
final case class EquipmentHandiness(
|
||||||
|
generic: EquipmentDefinition,
|
||||||
|
left: EquipmentDefinition,
|
||||||
|
right: EquipmentDefinition
|
||||||
|
) {
|
||||||
|
def transform(handiness: Hand): EquipmentDefinition = {
|
||||||
|
handiness match {
|
||||||
|
case Handiness.Generic => generic
|
||||||
|
case Handiness.Left => left
|
||||||
|
case Handiness.Right => right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def contains(findDef: EquipmentDefinition): Boolean = {
|
||||||
|
generic == findDef || left == findDef || right == findDef
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,7 @@ class FireModeDefinition extends DamageModifiers {
|
||||||
|
|
||||||
/** how many rounds are replenished each reload cycle */
|
/** how many rounds are replenished each reload cycle */
|
||||||
private var magazine: Int = 1
|
private var magazine: Int = 1
|
||||||
|
private var defaultMagazine: Option[Int] = None
|
||||||
|
|
||||||
/** how many rounds are replenished each reload cycle, per type of ammunition loaded
|
/** how many rounds are replenished each reload cycle, per type of ammunition loaded
|
||||||
* key - ammo type index, value - magazine capacity
|
* key - ammo type index, value - magazine capacity
|
||||||
|
|
@ -63,6 +64,15 @@ class FireModeDefinition extends DamageModifiers {
|
||||||
projectileTypeIndices += index
|
projectileTypeIndices += index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def DefaultMagazine: Int = defaultMagazine.getOrElse(magazine)
|
||||||
|
|
||||||
|
def DefaultMagazine_=(inMagazine: Int): Int = DefaultMagazine_=(Some(inMagazine))
|
||||||
|
|
||||||
|
def DefaultMagazine_=(inMagazine: Option[Int]): Int = {
|
||||||
|
defaultMagazine = inMagazine
|
||||||
|
DefaultMagazine
|
||||||
|
}
|
||||||
|
|
||||||
def Magazine: Int = magazine
|
def Magazine: Int = magazine
|
||||||
|
|
||||||
def Magazine_=(inMagazine: Int): Int = {
|
def Magazine_=(inMagazine: Int): Int = {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||||
import net.psforever.objects.vehicles.MountedWeapons
|
import net.psforever.objects.vehicles.MountedWeapons
|
||||||
import net.psforever.objects.vital.interaction.DamageResult
|
import net.psforever.objects.vital.interaction.DamageResult
|
||||||
import net.psforever.objects.vital.projectile.ProjectileReason
|
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||||
import net.psforever.objects.zones.ZoneAware
|
import net.psforever.objects.zones.{Zone, ZoneAware}
|
||||||
import net.psforever.types.Vector3
|
import net.psforever.types.Vector3
|
||||||
import net.psforever.services.Service
|
import net.psforever.services.Service
|
||||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||||
|
|
@ -254,7 +254,7 @@ trait JammableMountedWeapons extends JammableBehavior {
|
||||||
override def StartJammeredStatus(target: Any, dur: Int): Unit = {
|
override def StartJammeredStatus(target: Any, dur: Int): Unit = {
|
||||||
target match {
|
target match {
|
||||||
case obj: PlanetSideServerObject with MountedWeapons with JammableUnit if !obj.Jammed =>
|
case obj: PlanetSideServerObject with MountedWeapons with JammableUnit if !obj.Jammed =>
|
||||||
JammableMountedWeapons.JammeredStatus(obj, 1)
|
JammableMountedWeaponsJammeredStatus(obj, statusCode = 1)
|
||||||
super.StartJammeredStatus(target, dur)
|
super.StartJammeredStatus(target, dur)
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
|
@ -275,11 +275,15 @@ trait JammableMountedWeapons extends JammableBehavior {
|
||||||
override def CancelJammeredStatus(target: Any): Unit = {
|
override def CancelJammeredStatus(target: Any): Unit = {
|
||||||
target match {
|
target match {
|
||||||
case obj: PlanetSideServerObject with MountedWeapons with JammableUnit if obj.Jammed =>
|
case obj: PlanetSideServerObject with MountedWeapons with JammableUnit if obj.Jammed =>
|
||||||
JammableMountedWeapons.JammeredStatus(obj, 0)
|
JammableMountedWeaponsJammeredStatus(obj, statusCode = 0)
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
super.CancelJammeredStatus(target)
|
super.CancelJammeredStatus(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def JammableMountedWeaponsJammeredStatus(target: PlanetSideServerObject with MountedWeapons, statusCode: Int): Unit = {
|
||||||
|
JammableMountedWeapons.JammeredStatus(target, statusCode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object JammableMountedWeapons {
|
object JammableMountedWeapons {
|
||||||
|
|
@ -292,17 +296,20 @@ object JammableMountedWeapons {
|
||||||
* 1 for activation
|
* 1 for activation
|
||||||
*/
|
*/
|
||||||
def JammeredStatus(target: PlanetSideServerObject with MountedWeapons, statusCode: Int): Unit = {
|
def JammeredStatus(target: PlanetSideServerObject with MountedWeapons, statusCode: Int): Unit = {
|
||||||
val zone = target.Zone
|
val zone = target.Zone
|
||||||
val zoneId = zone.id
|
|
||||||
target.Weapons.values
|
target.Weapons.values
|
||||||
.map { _.Equipment }
|
.map { _.Equipment }
|
||||||
.collect {
|
.collect {
|
||||||
case Some(item: Tool) =>
|
case Some(item: Tool) =>
|
||||||
item.Jammed = statusCode == 1
|
JammedWeaponStatus(zone, item, statusCode)
|
||||||
zone.VehicleEvents ! VehicleServiceMessage(
|
|
||||||
zoneId,
|
|
||||||
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, item.GUID, 27, statusCode)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def JammedWeaponStatus(zone: Zone, target: Equipment with JammableUnit, statusCode: Int): Unit = {
|
||||||
|
target.Jammed = statusCode == 1
|
||||||
|
zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 27, statusCode)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2021 PSForever
|
// Copyright (c) 2021 PSForever
|
||||||
package net.psforever.objects.geometry
|
package net.psforever.objects.geometry
|
||||||
|
|
||||||
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
|
import net.psforever.objects.ballistics.{PlayerSource, Projectile, SourceEntry}
|
||||||
import net.psforever.objects.geometry.d3._
|
import net.psforever.objects.geometry.d3._
|
||||||
import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player}
|
import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player}
|
||||||
import net.psforever.types.{ExoSuitType, Vector3}
|
import net.psforever.types.{ExoSuitType, Vector3}
|
||||||
|
|
@ -80,6 +80,22 @@ object GeometryForm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The geometric representation is a sphere around the entity's centroid
|
||||||
|
* positioned following the axis of rotation (the entity's base).
|
||||||
|
* The specific entity should be a projectile, else the result is invalid.
|
||||||
|
* @param o the entity
|
||||||
|
* @return the representation
|
||||||
|
*/
|
||||||
|
def representProjectileBySphere()(o: Any): VolumetricGeometry = {
|
||||||
|
o match {
|
||||||
|
case p: Projectile =>
|
||||||
|
Sphere(p.Position, p.Definition.DamageRadius)
|
||||||
|
case _ =>
|
||||||
|
invalidPoint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The geometric representation is a cylinder around the entity's base.
|
* The geometric representation is a cylinder around the entity's base.
|
||||||
* @param radius half the distance across
|
* @param radius half the distance across
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ class NumberPoolHub(private val source: NumberSource) {
|
||||||
case Nil => ;
|
case Nil => ;
|
||||||
case collisions =>
|
case collisions =>
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
s"can not add pool $name - it contains the following redundant numbers: ${collisions.mkString(",")}"
|
s"can not add pool $name - it contains the following redundant numbers: ${collisions.sorted.mkString(",")}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
pool.foreach(i => bigpool += i.toLong -> name)
|
pool.foreach(i => bigpool += i.toLong -> name)
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,15 @@ trait Container {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the slot reported is not the slot requested, change the slot.
|
||||||
|
* @param slot the original slot index
|
||||||
|
* @return the modified slot index
|
||||||
|
*/
|
||||||
|
def SlotMapResolution(slot: Int): Int = {
|
||||||
|
slot
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a region of "searchable unit positions" considered as stowable,
|
* Given a region of "searchable unit positions" considered as stowable,
|
||||||
* determine if any previously stowed items are contained within that region.<br>
|
* determine if any previously stowed items are contained within that region.<br>
|
||||||
|
|
|
||||||
|
|
@ -22,20 +22,23 @@ object InventoryTile {
|
||||||
final val Tile11 = InventoryTile(1, 1) //occasional placeholder
|
final val Tile11 = InventoryTile(1, 1) //occasional placeholder
|
||||||
final val Tile22 = InventoryTile(2, 2) //grenades, boomer trigger
|
final val Tile22 = InventoryTile(2, 2) //grenades, boomer trigger
|
||||||
final val Tile23 = InventoryTile(2, 3) //canister ammo
|
final val Tile23 = InventoryTile(2, 3) //canister ammo
|
||||||
final val Tile42 = InventoryTile(4, 2) //medkit
|
|
||||||
final val Tile33 = InventoryTile(3, 3) //ammo box, pistols, ace
|
final val Tile33 = InventoryTile(3, 3) //ammo box, pistols, ace
|
||||||
|
final val Tile42 = InventoryTile(4, 2) //medkit
|
||||||
final val Tile44 = InventoryTile(4, 4) //large ammo box
|
final val Tile44 = InventoryTile(4, 4) //large ammo box
|
||||||
final val Tile55 = InventoryTile(5, 5) //bfr ammo box
|
final val Tile55 = InventoryTile(5, 5) //bfr ammo box
|
||||||
final val Tile66 = InventoryTile(6, 6) //infiltration suit inventory
|
final val Tile66 = InventoryTile(6, 6) //infiltration suit inventory
|
||||||
final val Tile63 = InventoryTile(6, 3) //rifles
|
final val Tile63 = InventoryTile(6, 3) //rifles
|
||||||
final val Tile93 = InventoryTile(9, 3) //long-body weapons
|
final val Tile93 = InventoryTile(9, 3) //long-body weapons
|
||||||
|
final val Tile84 = InventoryTile(8, 4) //bfr arm weapons
|
||||||
final val Tile96 = InventoryTile(9, 6) //standard exo-suit inventory
|
final val Tile96 = InventoryTile(9, 6) //standard exo-suit inventory
|
||||||
final val Tile99 = InventoryTile(9, 9) //agile exo-suit inventory
|
final val Tile99 = InventoryTile(9, 9) //agile exo-suit inventory
|
||||||
|
final val Tile1004 = InventoryTile(10, 4) //bfr gunner weapons
|
||||||
final val Tile1107 = InventoryTile(11, 7) //uncommon small trunk capacity - phantasm
|
final val Tile1107 = InventoryTile(11, 7) //uncommon small trunk capacity - phantasm
|
||||||
final val Tile1111 = InventoryTile(11, 11) //common small trunk capacity
|
final val Tile1111 = InventoryTile(11, 11) //common small trunk capacity
|
||||||
final val Tile1209 = InventoryTile(12, 9) //reinforced exo-suit inventory
|
final val Tile1209 = InventoryTile(12, 9) //reinforced exo-suit inventory
|
||||||
final val Tile1511 = InventoryTile(15, 11) //common medium trunk capacity
|
final val Tile1511 = InventoryTile(15, 11) //common medium trunk capacity
|
||||||
final val Tile1515 = InventoryTile(15, 15) //common large trunk capacity
|
final val Tile1515 = InventoryTile(15, 15) //common large trunk capacity
|
||||||
|
final val Tile1518 = InventoryTile(15, 18) //gunner bfr trunk capacity
|
||||||
final val Tile1611 = InventoryTile(16, 11) //uncommon medium trunk capacity - vulture
|
final val Tile1611 = InventoryTile(16, 11) //uncommon medium trunk capacity - vulture
|
||||||
final val Tile1612 = InventoryTile(16, 12) //MAX; uncommon medium trunk capacity - lodestar
|
final val Tile1612 = InventoryTile(16, 12) //MAX; uncommon medium trunk capacity - lodestar
|
||||||
final val Tile1816 = InventoryTile(18, 16) //uncommon massive trunk capacity - galaxy_gunship
|
final val Tile1816 = InventoryTile(18, 16) //uncommon massive trunk capacity - galaxy_gunship
|
||||||
|
|
|
||||||
|
|
@ -51,12 +51,13 @@ object Loadout {
|
||||||
* @return a `VehicleLoadout` object populated with appropriate information about the current state of the vehicle
|
* @return a `VehicleLoadout` object populated with appropriate information about the current state of the vehicle
|
||||||
*/
|
*/
|
||||||
def Create(vehicle: Vehicle, label: String): Loadout = {
|
def Create(vehicle: Vehicle, label: String): Loadout = {
|
||||||
|
val (_, entries: List[Loadout.SimplifiedEntry]) = vehicle.Weapons.collect {
|
||||||
|
case (index, slot: EquipmentSlot) if slot.Equipment.nonEmpty =>
|
||||||
|
(index, SimplifiedEntry(buildSimplification(slot.Equipment.get), index))
|
||||||
|
}.unzip
|
||||||
VehicleLoadout(
|
VehicleLoadout(
|
||||||
label,
|
label,
|
||||||
packageSimplifications(vehicle.Weapons.collect {
|
entries,
|
||||||
case (index, slot) if slot.Equipment.nonEmpty =>
|
|
||||||
InventoryItem(slot.Equipment.get, index)
|
|
||||||
}.toList),
|
|
||||||
packageSimplifications(vehicle.Trunk.Items),
|
packageSimplifications(vehicle.Trunk.Items),
|
||||||
vehicle.Definition
|
vehicle.Definition
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects.loadouts
|
package net.psforever.objects.loadouts
|
||||||
|
|
||||||
|
import net.psforever.objects.GlobalDefinitions
|
||||||
import net.psforever.objects.definition._
|
import net.psforever.objects.definition._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -27,3 +28,24 @@ final case class VehicleLoadout(
|
||||||
inventory: List[Loadout.SimplifiedEntry],
|
inventory: List[Loadout.SimplifiedEntry],
|
||||||
vehicle_definition: VehicleDefinition
|
vehicle_definition: VehicleDefinition
|
||||||
) extends EquipmentLoadout(label, visible_slots, inventory)
|
) extends EquipmentLoadout(label, visible_slots, inventory)
|
||||||
|
|
||||||
|
object VehicleLoadout {
|
||||||
|
/**
|
||||||
|
* The variant of the battleframe vehicle.
|
||||||
|
* Why these numbers map to the specific type of battleframe is a mystery.
|
||||||
|
* @see `FavoritesMessage`
|
||||||
|
* @param definition the vehicle's definition
|
||||||
|
* @return a number directly indicative of the type
|
||||||
|
*/
|
||||||
|
def DetermineBattleframeSubtype(definition: VehicleDefinition): Int = {
|
||||||
|
definition match {
|
||||||
|
case GlobalDefinitions.aphelion_flight => 1
|
||||||
|
case GlobalDefinitions.aphelion_gunner => 2
|
||||||
|
case GlobalDefinitions.colossus_flight => 4
|
||||||
|
case GlobalDefinitions.colossus_gunner => 5
|
||||||
|
case GlobalDefinitions.peregrine_flight => 7
|
||||||
|
case GlobalDefinitions.peregrine_gunner => 8
|
||||||
|
case _ => 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.serverobject
|
||||||
|
|
||||||
|
import akka.actor.Actor
|
||||||
|
import net.psforever.types.PlanetSideGUID
|
||||||
|
|
||||||
|
abstract class ServerObjectControl
|
||||||
|
extends Actor {
|
||||||
|
protected val log = org.log4s.getLogger(toString())
|
||||||
|
|
||||||
|
val attributeBehavior: Receive = {
|
||||||
|
case ServerObject.AttributeMsg(attribute, value, other) =>
|
||||||
|
parseAttribute(attribute, value, other)
|
||||||
|
|
||||||
|
case ServerObject.GenericObjectAction(guid, action, other) =>
|
||||||
|
parseObjectAction(guid, action, other)
|
||||||
|
|
||||||
|
case ServerObject.GenericAction(guid, action, other) =>
|
||||||
|
parseGenericAction(guid, action, other)
|
||||||
|
}
|
||||||
|
|
||||||
|
def parseAttribute(attribute: Int, value: Long, other: Option[Any]): Unit
|
||||||
|
|
||||||
|
def parseGenericAction(guid: PlanetSideGUID, action: Int, other: Option[Any]): Unit = { /*intentionally blank*/ }
|
||||||
|
|
||||||
|
def parseObjectAction(guid: PlanetSideGUID, action: Int, other: Option[Any]): Unit = { /*intentionally blank*/ }
|
||||||
|
}
|
||||||
|
|
||||||
|
object ServerObject {
|
||||||
|
final case class AttributeMsg(attribute: Int, value: Long, other: Option[Any] = None)
|
||||||
|
|
||||||
|
final case class GenericAction(guid: PlanetSideGUID, action: Int, other: Option[Any] = None)
|
||||||
|
|
||||||
|
final case class GenericObjectAction(guid: PlanetSideGUID, action: Int, other: Option[Any] = None)
|
||||||
|
|
||||||
|
final case class StateChangeDenied(original: Any, msg: String)
|
||||||
|
}
|
||||||
|
|
@ -4,11 +4,11 @@ package net.psforever.objects.serverobject.containable
|
||||||
import akka.actor.{Actor, ActorRef}
|
import akka.actor.{Actor, ActorRef}
|
||||||
import akka.pattern.{AskTimeoutException, ask}
|
import akka.pattern.{AskTimeoutException, ask}
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import net.psforever.objects.equipment.Equipment
|
import net.psforever.objects.equipment.{Equipment, EquipmentSize}
|
||||||
import net.psforever.objects.inventory.{Container, InventoryItem}
|
import net.psforever.objects.inventory.{Container, InventoryItem}
|
||||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
import net.psforever.objects.{BoomerTrigger, GlobalDefinitions, Player}
|
import net.psforever.objects._
|
||||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||||
|
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
|
@ -92,58 +92,7 @@ trait ContainableBehavior {
|
||||||
|
|
||||||
case msg @ Containable.MoveItem(destination, equipment, destSlot) =>
|
case msg @ Containable.MoveItem(destination, equipment, destSlot) =>
|
||||||
/* can be deferred */
|
/* can be deferred */
|
||||||
if (ContainableBehavior.TestPutItemInSlot(destination, equipment, destSlot).nonEmpty) { //test early, before we try to move the item
|
ContainableMoveItem(destination, equipment, destSlot, msg)
|
||||||
val source = ContainerObject
|
|
||||||
val item = equipment
|
|
||||||
val dest = destSlot
|
|
||||||
LocalRemoveItemFromSlot(item) match {
|
|
||||||
case Containable.ItemFromSlot(_, Some(_), slot @ Some(originalSlot)) =>
|
|
||||||
if (source eq destination) {
|
|
||||||
//when source and destination are the same, moving the item can be performed in one pass
|
|
||||||
LocalPutItemInSlot(item, dest) match {
|
|
||||||
case Containable.ItemPutInSlot(_, _, _, None) => ; //success
|
|
||||||
case Containable.ItemPutInSlot(_, _, _, Some(swapItem)) => //success, but with swap item
|
|
||||||
LocalPutItemInSlotOnlyOrAway(swapItem, slot) match {
|
|
||||||
case Containable.ItemPutInSlot(_, _, _, None) => ;
|
|
||||||
case _ =>
|
|
||||||
source.Zone.Ground.tell(
|
|
||||||
Zone.Ground.DropItem(swapItem, source.Position, Vector3.z(source.Orientation.z)),
|
|
||||||
source.Actor
|
|
||||||
) //drop it
|
|
||||||
}
|
|
||||||
case _: Containable.CanNotPutItemInSlot => //failure case ; try restore original item placement
|
|
||||||
LocalPutItemInSlot(item, originalSlot)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//destination sync
|
|
||||||
destination.Actor ! ContainableBehavior.Wait()
|
|
||||||
implicit val timeout = new Timeout(1000 milliseconds)
|
|
||||||
val moveItemOver = ask(destination.Actor, ContainableBehavior.MoveItemPutItemInSlot(item, dest))
|
|
||||||
moveItemOver.onComplete {
|
|
||||||
case Success(Containable.ItemPutInSlot(_, _, _, None)) => ; //successful
|
|
||||||
|
|
||||||
case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) => //successful, but with swap item
|
|
||||||
PutItBackOrDropIt(source, swapItem, slot, destination.Actor)
|
|
||||||
|
|
||||||
case Success(_: Containable.CanNotPutItemInSlot) => //failure case ; try restore original item placement
|
|
||||||
PutItBackOrDropIt(source, item, slot, source.Actor)
|
|
||||||
|
|
||||||
case Failure(_) => //failure case ; try restore original item placement
|
|
||||||
PutItBackOrDropIt(source, item, slot, source.Actor)
|
|
||||||
|
|
||||||
case _ => ; //TODO what?
|
|
||||||
}
|
|
||||||
//always do this
|
|
||||||
moveItemOver
|
|
||||||
.recover { case _: AskTimeoutException => destination.Actor ! ContainableBehavior.Resume() }
|
|
||||||
.onComplete { _ => destination.Actor ! ContainableBehavior.Resume() }
|
|
||||||
}
|
|
||||||
case _ => ;
|
|
||||||
//we could not find the item to be moved in the source location; trying to act on old data?
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MessageDeferredCallback(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
case ContainableBehavior.MoveItemPutItemInSlot(item, dest) =>
|
case ContainableBehavior.MoveItemPutItemInSlot(item, dest) =>
|
||||||
sender() ! LocalPutItemInSlot(item, dest)
|
sender() ! LocalPutItemInSlot(item, dest)
|
||||||
|
|
@ -188,6 +137,66 @@ trait ContainableBehavior {
|
||||||
|
|
||||||
/* Functions (item transfer) */
|
/* Functions (item transfer) */
|
||||||
|
|
||||||
|
protected def ContainableMoveItem(
|
||||||
|
destination: PlanetSideServerObject with Container,
|
||||||
|
equipment: Equipment,
|
||||||
|
destSlot: Int,
|
||||||
|
msg: Any
|
||||||
|
) : Unit = {
|
||||||
|
if (ContainableBehavior.TestPutItemInSlot(destination, equipment, destSlot).nonEmpty) { //test early, before we try to move the item
|
||||||
|
val source = ContainerObject
|
||||||
|
val item = equipment
|
||||||
|
val dest = destSlot
|
||||||
|
LocalRemoveItemFromSlot(item) match {
|
||||||
|
case Containable.ItemFromSlot(_, Some(_), slot @ Some(originalSlot)) =>
|
||||||
|
if (source eq destination) {
|
||||||
|
//when source and destination are the same, moving the item can be performed in one pass
|
||||||
|
LocalPutItemInSlot(item, dest) match {
|
||||||
|
case Containable.ItemPutInSlot(_, _, _, None) => ; //success
|
||||||
|
case Containable.ItemPutInSlot(_, _, _, Some(swapItem)) => //success, but with swap item
|
||||||
|
LocalPutItemInSlotOnlyOrAway(swapItem, slot) match {
|
||||||
|
case Containable.ItemPutInSlot(_, _, _, None) => ;
|
||||||
|
case _ =>
|
||||||
|
source.Zone.Ground.tell(
|
||||||
|
Zone.Ground.DropItem(swapItem, source.Position, Vector3.z(source.Orientation.z)),
|
||||||
|
source.Actor
|
||||||
|
) //drop it
|
||||||
|
}
|
||||||
|
case _: Containable.CanNotPutItemInSlot => //failure case ; try restore original item placement
|
||||||
|
LocalPutItemInSlot(item, originalSlot)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//destination sync
|
||||||
|
destination.Actor ! ContainableBehavior.Wait()
|
||||||
|
implicit val timeout = new Timeout(1000 milliseconds)
|
||||||
|
val moveItemOver = ask(destination.Actor, ContainableBehavior.MoveItemPutItemInSlot(item, dest))
|
||||||
|
moveItemOver.onComplete {
|
||||||
|
case Success(Containable.ItemPutInSlot(_, _, _, None)) => ; //successful
|
||||||
|
|
||||||
|
case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) => //successful, but with swap item
|
||||||
|
PutItBackOrDropIt(source, swapItem, slot, destination.Actor)
|
||||||
|
|
||||||
|
case Success(_: Containable.CanNotPutItemInSlot) => //failure case ; try restore original item placement
|
||||||
|
PutItBackOrDropIt(source, item, slot, source.Actor)
|
||||||
|
|
||||||
|
case Failure(_) => //failure case ; try restore original item placement
|
||||||
|
PutItBackOrDropIt(source, item, slot, source.Actor)
|
||||||
|
|
||||||
|
case _ => ; //TODO what?
|
||||||
|
}
|
||||||
|
//always do this
|
||||||
|
moveItemOver
|
||||||
|
.recover { case _: AskTimeoutException => destination.Actor ! ContainableBehavior.Resume() }
|
||||||
|
.onComplete { _ => destination.Actor ! ContainableBehavior.Resume() }
|
||||||
|
}
|
||||||
|
case _ => ;
|
||||||
|
//we could not find the item to be moved in the source location; trying to act on old data?
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MessageDeferredCallback(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private def LocalRemoveItemFromSlot(slot: Int): Any = {
|
private def LocalRemoveItemFromSlot(slot: Int): Any = {
|
||||||
val source = ContainerObject
|
val source = ContainerObject
|
||||||
val (outSlot, item) = ContainableBehavior.TryRemoveItemFromSlot(source, slot)
|
val (outSlot, item) = ContainableBehavior.TryRemoveItemFromSlot(source, slot)
|
||||||
|
|
@ -370,14 +379,15 @@ object ContainableBehavior {
|
||||||
item: Equipment
|
item: Equipment
|
||||||
): (Option[Int], Option[Equipment]) = {
|
): (Option[Int], Option[Equipment]) = {
|
||||||
source.Find(item) match {
|
source.Find(item) match {
|
||||||
case slot @ Some(index) =>
|
case slot @ Some(index)
|
||||||
|
if ContainableBehavior.PermitEquipmentExtract(source, item, index)=>
|
||||||
source.Slot(index).Equipment = None
|
source.Slot(index).Equipment = None
|
||||||
if (source.Slot(index).Equipment.isEmpty) {
|
if (source.Slot(index).Equipment.isEmpty) {
|
||||||
(slot, Some(item))
|
(slot, Some(item))
|
||||||
} else {
|
} else {
|
||||||
(None, None)
|
(None, None)
|
||||||
}
|
}
|
||||||
case None =>
|
case _ =>
|
||||||
(None, None)
|
(None, None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -399,14 +409,14 @@ object ContainableBehavior {
|
||||||
source: PlanetSideServerObject with Container,
|
source: PlanetSideServerObject with Container,
|
||||||
slot: Int
|
slot: Int
|
||||||
): (Option[Int], Option[Equipment]) = {
|
): (Option[Int], Option[Equipment]) = {
|
||||||
val (item, outSlot) = source.Slot(slot).Equipment match {
|
source.Slot(slot).Equipment match {
|
||||||
case Some(thing) => (Some(thing), source.Find(thing))
|
case Some(thing)
|
||||||
case None => (None, None)
|
if ContainableBehavior.PermitEquipmentExtract(source, thing, slot) =>
|
||||||
}
|
if ((source.Slot(slot).Equipment = None).isEmpty) {
|
||||||
source.Slot(slot).Equipment = None
|
(Some(slot), Some(thing))
|
||||||
item match {
|
} else {
|
||||||
case Some(_) if item.nonEmpty && source.Slot(slot).Equipment.isEmpty =>
|
(None, None)
|
||||||
(outSlot, item)
|
}
|
||||||
case _ =>
|
case _ =>
|
||||||
(None, None)
|
(None, None)
|
||||||
}
|
}
|
||||||
|
|
@ -431,7 +441,7 @@ object ContainableBehavior {
|
||||||
item: Equipment,
|
item: Equipment,
|
||||||
dest: Int
|
dest: Int
|
||||||
): Option[List[InventoryItem]] = {
|
): Option[List[InventoryItem]] = {
|
||||||
if (ContainableBehavior.PermitEquipmentStow(destination, item)) {
|
if (ContainableBehavior.PermitEquipmentStow(destination, item, dest)) {
|
||||||
val tile = item.Definition.Tile
|
val tile = item.Definition.Tile
|
||||||
val destinationCollisionTest = destination.Collisions(dest, tile.Width, tile.Height)
|
val destinationCollisionTest = destination.Collisions(dest, tile.Width, tile.Height)
|
||||||
destinationCollisionTest match {
|
destinationCollisionTest match {
|
||||||
|
|
@ -505,7 +515,7 @@ object ContainableBehavior {
|
||||||
def TryPutItemAway(destination: PlanetSideServerObject with Container, item: Equipment): Option[Int] = {
|
def TryPutItemAway(destination: PlanetSideServerObject with Container, item: Equipment): Option[Int] = {
|
||||||
destination.Fit(item) match {
|
destination.Fit(item) match {
|
||||||
case out @ Some(dest)
|
case out @ Some(dest)
|
||||||
if ContainableBehavior.PermitEquipmentStow(destination, item) && (destination.Slot(dest).Equipment = item)
|
if ContainableBehavior.PermitEquipmentStow(destination, item, dest) && (destination.Slot(dest).Equipment = item)
|
||||||
.contains(item) =>
|
.contains(item) =>
|
||||||
out
|
out
|
||||||
case _ =>
|
case _ =>
|
||||||
|
|
@ -572,22 +582,67 @@ object ContainableBehavior {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO convert PermitEquipmentExtract and PermitEquipmentStow into instance methods?
|
||||||
|
/**
|
||||||
|
* Apply incontestable, arbitrary limitations
|
||||||
|
* whereby certain items are denied removal from certain containers
|
||||||
|
* for vaguely documented but assuredly fantastic excuses on the part of the developer.
|
||||||
|
* @see `ContainableBehavior.PermitEquipmentStow`
|
||||||
|
* @param source the container
|
||||||
|
* @param equipment the item to be removed
|
||||||
|
* @param slot where the equipment can be found
|
||||||
|
* @return `true`, if the type of equipment object is allowed to be removed from the containing entity;
|
||||||
|
* `false`, otherwise
|
||||||
|
*/
|
||||||
|
def PermitEquipmentExtract(
|
||||||
|
source: PlanetSideServerObject with Container,
|
||||||
|
equipment: Equipment,
|
||||||
|
slot: Int
|
||||||
|
): Boolean = {
|
||||||
|
source match {
|
||||||
|
case v: Vehicle if v.VisibleSlots.contains(slot) =>
|
||||||
|
//can not remove equipment slot items if vehicle is jammed
|
||||||
|
//applies mostly to BFR's, but we do not need to filter
|
||||||
|
!v.Jammed
|
||||||
|
case _ =>
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply incontestable, arbitrary limitations
|
* Apply incontestable, arbitrary limitations
|
||||||
* whereby certain items are denied insertion into certain containers
|
* whereby certain items are denied insertion into certain containers
|
||||||
* for vaguely documented but assuredly fantastic excuses on the part of the developer.
|
* for vaguely documented but assuredly fantastic excuses on the part of the developer.
|
||||||
|
* @see `ContainableBehavior.PermitEquipmentExtract`
|
||||||
* @param destination the container
|
* @param destination the container
|
||||||
* @param equipment the item to be inserted
|
* @param equipment the item to be inserted
|
||||||
* @return `true`, if the object is allowed to contain the type of equipment object;
|
* @return `true`, if the object is allowed to contain the type of equipment object;
|
||||||
* `false`, otherwise
|
* `false`, otherwise
|
||||||
*/
|
*/
|
||||||
def PermitEquipmentStow(destination: PlanetSideServerObject with Container, equipment: Equipment): Boolean = {
|
def PermitEquipmentStow(
|
||||||
|
destination: PlanetSideServerObject with Container,
|
||||||
|
equipment: Equipment,
|
||||||
|
dest: Int
|
||||||
|
): Boolean = {
|
||||||
import net.psforever.objects.{BoomerTrigger, Player}
|
import net.psforever.objects.{BoomerTrigger, Player}
|
||||||
equipment match {
|
equipment match {
|
||||||
case _: BoomerTrigger =>
|
case _: BoomerTrigger =>
|
||||||
//a BoomerTrigger can only be stowed in a player's holsters or inventory
|
//a BoomerTrigger can only be stowed in a player's holsters or inventory
|
||||||
//this is only a requirement until they, and their Boomer explosive complement, are cleaned-up properly
|
//this is only a requirement until they, and their Boomer explosive complement, are cleaned-up properly
|
||||||
destination.isInstanceOf[Player]
|
destination.isInstanceOf[Player]
|
||||||
|
case weapon: Tool
|
||||||
|
if weapon.Size == EquipmentSize.BFRArmWeapon || weapon.Size == EquipmentSize.BFRGunnerWeapon =>
|
||||||
|
//Battleframe weaponry must be placed in an appropriate equipment mount spot, or held in the player's free hand
|
||||||
|
//if in the vehicle slots, then the vehicle must not be jammed
|
||||||
|
destination match {
|
||||||
|
case v: Vehicle
|
||||||
|
if GlobalDefinitions.isBattleFrameVehicle(v.Definition) =>
|
||||||
|
v.VisibleSlots.contains(dest) && !v.Jammed
|
||||||
|
case _: Player =>
|
||||||
|
dest == Player.FreeHandSlot
|
||||||
|
case _ =>
|
||||||
|
false
|
||||||
|
}
|
||||||
case _ =>
|
case _ =>
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//Copyright (c) 2020 PSForever
|
//Copyright (c) 2020 PSForever
|
||||||
package net.psforever.objects.serverobject.damage
|
package net.psforever.objects.serverobject.damage
|
||||||
|
|
||||||
import akka.actor.Actor
|
import akka.actor.{Actor, Cancellable}
|
||||||
import net.psforever.objects.{Vehicle, Vehicles}
|
import net.psforever.objects.{Vehicle, Vehicles}
|
||||||
import net.psforever.objects.equipment.JammableUnit
|
import net.psforever.objects.equipment.JammableUnit
|
||||||
import net.psforever.objects.serverobject.damage.Damageable.Target
|
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||||
|
|
@ -30,6 +30,8 @@ trait DamageableVehicle
|
||||||
|
|
||||||
/** whether or not the vehicle has been damaged directly, report that damage has occurred */
|
/** whether or not the vehicle has been damaged directly, report that damage has occurred */
|
||||||
protected var reportDamageToVehicle: Boolean = false
|
protected var reportDamageToVehicle: Boolean = false
|
||||||
|
/** when the vehicle is destroyed, its major explosion is delayed */
|
||||||
|
protected var queuedDestruction: Option[Cancellable] = None
|
||||||
|
|
||||||
def DamageableObject: Vehicle
|
def DamageableObject: Vehicle
|
||||||
def AggravatedObject : Vehicle = DamageableObject
|
def AggravatedObject : Vehicle = DamageableObject
|
||||||
|
|
@ -44,6 +46,7 @@ trait DamageableVehicle
|
||||||
|
|
||||||
case DamageableVehicle.Destruction(cause) =>
|
case DamageableVehicle.Destruction(cause) =>
|
||||||
//cargo vehicles are destroyed when carrier is destroyed
|
//cargo vehicles are destroyed when carrier is destroyed
|
||||||
|
//bfrs undergo a shiver spell before exploding
|
||||||
val obj = DamageableObject
|
val obj = DamageableObject
|
||||||
obj.Health = 0
|
obj.Health = 0
|
||||||
obj.History(cause)
|
obj.History(cause)
|
||||||
|
|
@ -59,24 +62,28 @@ trait DamageableVehicle
|
||||||
target: Damageable.Target,
|
target: Damageable.Target,
|
||||||
applyDamageTo: ResolutionCalculations.Output
|
applyDamageTo: ResolutionCalculations.Output
|
||||||
): Unit = {
|
): Unit = {
|
||||||
val obj = DamageableObject
|
queuedDestruction match {
|
||||||
val originalHealth = obj.Health
|
case Some(_) => ;
|
||||||
val originalShields = obj.Shields
|
case None =>
|
||||||
val cause = applyDamageTo(obj)
|
val obj = DamageableObject
|
||||||
val health = obj.Health
|
val originalHealth = obj.Health
|
||||||
val shields = obj.Shields
|
val originalShields = obj.Shields
|
||||||
val damageToHealth = originalHealth - health
|
val cause = applyDamageTo(obj)
|
||||||
val damageToShields = originalShields - shields
|
val health = obj.Health
|
||||||
if (WillAffectTarget(target, damageToHealth + damageToShields, cause)) {
|
val shields = obj.Shields
|
||||||
target.History(cause)
|
val damageToHealth = originalHealth - health
|
||||||
DamageLog(
|
val damageToShields = originalShields - shields
|
||||||
target,
|
if (WillAffectTarget(target, damageToHealth + damageToShields, cause)) {
|
||||||
s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields"
|
target.History(cause)
|
||||||
)
|
DamageLog(
|
||||||
HandleDamage(target, cause, (damageToHealth, damageToShields))
|
target,
|
||||||
} else {
|
s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields"
|
||||||
obj.Health = originalHealth
|
)
|
||||||
obj.Shields = originalShields
|
HandleDamage(target, cause, (damageToHealth, damageToShields))
|
||||||
|
} else {
|
||||||
|
obj.Health = originalHealth
|
||||||
|
obj.Shields = originalShields
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,7 +134,7 @@ trait DamageableVehicle
|
||||||
if (damageToShields > 0) {
|
if (damageToShields > 0) {
|
||||||
events ! VehicleServiceMessage(
|
events ! VehicleServiceMessage(
|
||||||
vehicleChannel,
|
vehicleChannel,
|
||||||
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 68, obj.Shields)
|
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, obj.Definition.shieldUiAttribute, obj.Shields)
|
||||||
)
|
)
|
||||||
announceConfrontation = true
|
announceConfrontation = true
|
||||||
}
|
}
|
||||||
|
|
@ -175,29 +182,60 @@ trait DamageableVehicle
|
||||||
* @param cause historical information about the damage
|
* @param cause historical information about the damage
|
||||||
*/
|
*/
|
||||||
override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = {
|
override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = {
|
||||||
super.DestructionAwareness(target, cause)
|
(queuedDestruction, DamageableObject.Definition.destructionDelay) match {
|
||||||
val obj = DamageableObject
|
case (None, Some(delay)) => //set a future explosion for later
|
||||||
val zone = target.Zone
|
destructionDelayed(delay, cause)
|
||||||
//aggravation cancel
|
case (Some(_), _) | (None, None) => //explode now
|
||||||
EndAllAggravation()
|
super.DestructionAwareness(target, cause)
|
||||||
//passengers die with us
|
val obj = DamageableObject
|
||||||
DamageableMountable.DestructionAwareness(obj, cause)
|
val zone = target.Zone
|
||||||
//things positioned around us can get hurt from us
|
//aggravation cancel
|
||||||
Zone.serverSideDamage(obj.Zone, target, Zone.explosionDamage(Some(cause)))
|
EndAllAggravation()
|
||||||
//special considerations for certain vehicles
|
//passengers die with us
|
||||||
Vehicles.BeforeUnloadVehicle(obj, zone)
|
DamageableMountable.DestructionAwareness(obj, cause)
|
||||||
//shields
|
Zone.serverSideDamage(obj.Zone, target, Zone.explosionDamage(Some(cause)))
|
||||||
if (obj.Shields > 0) {
|
//special considerations for certain vehicles
|
||||||
obj.Shields = 0
|
Vehicles.BeforeUnloadVehicle(obj, zone)
|
||||||
zone.VehicleEvents ! VehicleServiceMessage(
|
//shields
|
||||||
zone.id,
|
if (obj.Shields > 0) {
|
||||||
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, 0)
|
obj.Shields = 0
|
||||||
)
|
zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, obj.Definition.shieldUiAttribute, 0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
//clean up
|
||||||
|
target.Actor ! Vehicle.Deconstruct(Some(1 minute))
|
||||||
|
target.ClearHistory()
|
||||||
|
DamageableWeaponTurret.DestructionAwareness(obj, cause)
|
||||||
|
case _ => ;
|
||||||
}
|
}
|
||||||
//clean up
|
}
|
||||||
target.Actor ! Vehicle.Deconstruct(Some(1 minute))
|
|
||||||
target.ClearHistory()
|
def destructionDelayed(delay: Long, cause: DamageResult): Unit = {
|
||||||
DamageableWeaponTurret.DestructionAwareness(obj, cause)
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
val obj = DamageableObject
|
||||||
|
//health to 1, shields to 0
|
||||||
|
obj.Health = 1
|
||||||
|
obj.Shields = 0
|
||||||
|
val guid = obj.GUID
|
||||||
|
val guid0 = Service.defaultPlayerGUID
|
||||||
|
val zone = obj.Zone
|
||||||
|
val zoneid = zone.id
|
||||||
|
val events = zone.VehicleEvents
|
||||||
|
events ! VehicleServiceMessage(
|
||||||
|
zoneid,
|
||||||
|
VehicleAction.PlanetsideAttribute(guid0, guid, 0, 1)
|
||||||
|
)
|
||||||
|
events ! VehicleServiceMessage(
|
||||||
|
zoneid,
|
||||||
|
VehicleAction.PlanetsideAttribute(guid0, guid, obj.Definition.shieldUiAttribute, 0)
|
||||||
|
)
|
||||||
|
//passengers die with us
|
||||||
|
DamageableMountable.DestructionAwareness(DamageableObject, cause)
|
||||||
|
//come back to this death later
|
||||||
|
queuedDestruction = Some(context.system.scheduler.scheduleOnce(delay milliseconds, self, DamageableVehicle.Destruction(cause)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ package net.psforever.objects.serverobject.environment
|
||||||
import net.psforever.objects.GlobalDefinitions
|
import net.psforever.objects.GlobalDefinitions
|
||||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||||
import net.psforever.objects.zones._
|
import net.psforever.objects.zones._
|
||||||
import net.psforever.objects.zones.blockmap.BlockMapEntity
|
import net.psforever.objects.zones.blockmap.{BlockMapEntity, SectorPopulation}
|
||||||
|
|
||||||
|
case object EnvironmentInteraction extends ZoneInteractionType
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This game entity may infrequently test whether it may interact with game world environment.
|
* This game entity may infrequently test whether it may interact with game world environment.
|
||||||
|
|
@ -14,6 +16,10 @@ class InteractWithEnvironment()
|
||||||
private var interactingWithEnvironment: (PlanetSideServerObject, Boolean) => Any =
|
private var interactingWithEnvironment: (PlanetSideServerObject, Boolean) => Any =
|
||||||
InteractWithEnvironment.onStableEnvironment()
|
InteractWithEnvironment.onStableEnvironment()
|
||||||
|
|
||||||
|
def Type = EnvironmentInteraction
|
||||||
|
|
||||||
|
def range: Float = 0f
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The method by which zone interactions are tested or a current interaction maintained.
|
* The method by which zone interactions are tested or a current interaction maintained.
|
||||||
* Utilize a function literal that, when called, returns a function literal of the same type;
|
* Utilize a function literal that, when called, returns a function literal of the same type;
|
||||||
|
|
@ -24,8 +30,10 @@ class InteractWithEnvironment()
|
||||||
* @see `InteractsWithEnvironment.blockedFromInteracting`
|
* @see `InteractsWithEnvironment.blockedFromInteracting`
|
||||||
* @see `InteractsWithEnvironment.onStableEnvironment`
|
* @see `InteractsWithEnvironment.onStableEnvironment`
|
||||||
* @see `InteractsWithEnvironment.awaitOngoingInteraction`
|
* @see `InteractsWithEnvironment.awaitOngoingInteraction`
|
||||||
|
* @param sector the portion of the block map being tested
|
||||||
|
* @param target the fixed element in this test
|
||||||
*/
|
*/
|
||||||
def interaction(target: InteractsWithZone): Unit = {
|
def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = {
|
||||||
interactingWithEnvironment = interactingWithEnvironment(target, true)
|
interactingWithEnvironment = interactingWithEnvironment(target, true)
|
||||||
.asInstanceOf[(PlanetSideServerObject, Boolean) => Any]
|
.asInstanceOf[(PlanetSideServerObject, Boolean) => Any]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,5 +41,7 @@ case object SmallCargo extends MountRestriction[Vehicle] {
|
||||||
}
|
}
|
||||||
|
|
||||||
case object LargeCargo extends MountRestriction[Vehicle] {
|
case object LargeCargo extends MountRestriction[Vehicle] {
|
||||||
def test(target : Vehicle) : Boolean = !target.Definition.CanFly
|
def test(target: Vehicle): Boolean = {
|
||||||
|
GlobalDefinitions.isBattleFrameVehicle(target.Definition) || !target.Definition.CanFly
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,28 +18,16 @@ class VehicleSpawnPadDefinition(objectId: Int) extends AmenityDefinition(objectI
|
||||||
// However, it seems these values need to be reversed to turn CCW to CW rotation (e.g. +90 to -90)
|
// However, it seems these values need to be reversed to turn CCW to CW rotation (e.g. +90 to -90)
|
||||||
private var vehicle_creation_z_orient_offset = 0f
|
private var vehicle_creation_z_orient_offset = 0f
|
||||||
|
|
||||||
def VehicleCreationZOffset: Float = vehicle_creation_z_offset
|
def VehicleCreationZOffset: Float = vehicle_creation_z_offset
|
||||||
def VehicleCreationZOrientOffset: Float = vehicle_creation_z_orient_offset
|
def VehicleCreationZOrientOffset: Float = vehicle_creation_z_orient_offset
|
||||||
|
|
||||||
objectId match {
|
def VehicleCreationZOffset_=(offset: Float): Float = {
|
||||||
case 141 =>
|
vehicle_creation_z_offset = offset
|
||||||
Name = "bfr_door"
|
vehicle_creation_z_offset
|
||||||
vehicle_creation_z_offset = -4.5f
|
}
|
||||||
vehicle_creation_z_orient_offset = 90f
|
def VehicleCreationZOrientOffset_=(offset: Float): Float = {
|
||||||
case 261 =>
|
vehicle_creation_z_orient_offset = offset
|
||||||
Name = "dropship_pad_doors"
|
vehicle_creation_z_orient_offset
|
||||||
vehicle_creation_z_offset = 4.89507f
|
|
||||||
vehicle_creation_z_orient_offset = -90f
|
|
||||||
case 525 =>
|
|
||||||
Name = "mb_pad_creation"
|
|
||||||
vehicle_creation_z_offset = 2.52604f
|
|
||||||
case 615 => Name = "pad_create"
|
|
||||||
case 616 =>
|
|
||||||
Name = "pad_creation"
|
|
||||||
vehicle_creation_z_offset = 1.70982f
|
|
||||||
case 816 => Name = "spawnpoint_vehicle"
|
|
||||||
case 947 => Name = "vanu_vehicle_creation_pad"
|
|
||||||
case _ => throw new IllegalArgumentException("Not a valid object id with the type vehicle_creation_pad")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The region surrounding a vehicle spawn pad that is cleared of damageable targets prior to a vehicle being spawned.
|
/** The region surrounding a vehicle spawn pad that is cleared of damageable targets prior to a vehicle being spawned.
|
||||||
|
|
@ -161,17 +149,44 @@ object VehicleSpawnPadDefinition {
|
||||||
flightVehicle: Boolean
|
flightVehicle: Boolean
|
||||||
): (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = {
|
): (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = {
|
||||||
if (flightVehicle) {
|
if (flightVehicle) {
|
||||||
vanuKillBox(pad.Position, radius, aboveLimit * 2)
|
cylinderKillBox(pad.Position, radius, aboveLimit * 2)
|
||||||
} else {
|
} else {
|
||||||
vanuKillBox(pad.Position, radius * 1.2f, aboveLimit)
|
cylinderKillBox(pad.Position, radius * 1.2f, aboveLimit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that sets up the region around a battleframe vehicle spawn chamber's doors
|
||||||
|
* to be cleared of damageable targets upon spawning of a vehicle.
|
||||||
|
* All measurements are provided in terms of distance from the middle of the door.
|
||||||
|
* Internally, the pad is referred to as `bfr_door`;
|
||||||
|
* colloquially, the pad is referred to as a "BFR shed".
|
||||||
|
* @param radius the distance from the middle of the spawn pad
|
||||||
|
* @param aboveLimit how far above the spawn pad is to be cleared
|
||||||
|
* @param pad he vehicle spawn pad in question
|
||||||
|
* @param requiredButUnused required by the function prototype
|
||||||
|
* @return a function that describes a region ahead of the battleframe vehicle spawn shed
|
||||||
|
*/
|
||||||
|
def prepareBfrShedKillBox(
|
||||||
|
radius: Float,
|
||||||
|
aboveLimit: Float
|
||||||
|
)
|
||||||
|
(
|
||||||
|
pad: VehicleSpawnPad,
|
||||||
|
requiredButUnused: Boolean
|
||||||
|
): (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = {
|
||||||
|
cylinderKillBox(
|
||||||
|
Vector3(0,radius,0).Rz(pad.Orientation.z + pad.Definition.VehicleCreationZOrientOffset) + pad.Position,
|
||||||
|
radius,
|
||||||
|
aboveLimit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function that finalizes the detection for the region around a vehicle spawn pad
|
* A function that finalizes the detection for the region around a vehicle spawn pad
|
||||||
* to be cleared of damageable targets upon spawning of a vehicle.
|
* to be cleared of damageable targets upon spawning of a vehicle.
|
||||||
* All measurements are provided in terms of distance from the center of the pad.
|
* All measurements are provided in terms of distance from the center of the pad.
|
||||||
* These pads are only found in the cavern zones and are cylindrical in shape.
|
* These pads are cylindrical in shape.
|
||||||
* @param origin the center of the spawn pad
|
* @param origin the center of the spawn pad
|
||||||
* @param radius the distance from the middle of the spawn pad
|
* @param radius the distance from the middle of the spawn pad
|
||||||
* @param aboveLimit how far above the spawn pad is to be cleared
|
* @param aboveLimit how far above the spawn pad is to be cleared
|
||||||
|
|
@ -182,16 +197,16 @@ object VehicleSpawnPadDefinition {
|
||||||
* @return `true`, if the two entities are near enough to each other;
|
* @return `true`, if the two entities are near enough to each other;
|
||||||
* `false`, otherwise
|
* `false`, otherwise
|
||||||
*/
|
*/
|
||||||
def vanuKillBox(
|
def cylinderKillBox(
|
||||||
origin: Vector3,
|
origin: Vector3,
|
||||||
radius: Float,
|
radius: Float,
|
||||||
aboveLimit: Float
|
aboveLimit: Float
|
||||||
)
|
)
|
||||||
(
|
(
|
||||||
obj1: PlanetSideGameObject,
|
obj1: PlanetSideGameObject,
|
||||||
obj2: PlanetSideGameObject,
|
obj2: PlanetSideGameObject,
|
||||||
maxDistance: Float
|
maxDistance: Float
|
||||||
): Boolean = {
|
): Boolean = {
|
||||||
val dir: Vector3 = {
|
val dir: Vector3 = {
|
||||||
val g2 = obj2.Definition.Geometry(obj2)
|
val g2 = obj2.Definition.Geometry(obj2)
|
||||||
val cdir = Vector3.Unit(origin - g2.center.asVector3)
|
val cdir = Vector3.Unit(origin - g2.center.asVector3)
|
||||||
|
|
|
||||||
|
|
@ -173,12 +173,12 @@ object PainboxControl {
|
||||||
val min = Vector3(
|
val min = Vector3(
|
||||||
nearbyAmenities.minBy(_.Position.x).Position.x - 0.5f,
|
nearbyAmenities.minBy(_.Position.x).Position.x - 0.5f,
|
||||||
nearbyAmenities.minBy(_.Position.y).Position.y - 0.5f,
|
nearbyAmenities.minBy(_.Position.y).Position.y - 0.5f,
|
||||||
nearbyAmenities.minBy(_.Position.z).Position.z
|
nearbyAmenities.minBy(_.Position.z).Position.z - 0.5f
|
||||||
)
|
)
|
||||||
val max = Vector3(
|
val max = Vector3(
|
||||||
nearbyAmenities.maxBy(_.Position.x).Position.x + 0.5f,
|
nearbyAmenities.maxBy(_.Position.x).Position.x + 0.5f,
|
||||||
nearbyAmenities.maxBy(_.Position.y).Position.y + 0.5f,
|
nearbyAmenities.maxBy(_.Position.y).Position.y + 0.5f,
|
||||||
painbox.Position.z
|
painbox.Position.z + 0.5f
|
||||||
)
|
)
|
||||||
(min, max, Vector3.midpoint(min, max))
|
(min, max, Vector3.midpoint(min, max))
|
||||||
case _ =>
|
case _ =>
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,7 @@ trait AmenityAutoRepair
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
autoRepairTimer.cancel()
|
autoRepairTimer.cancel()
|
||||||
autoRepairQueueTask = Some(System.currentTimeMillis() + delay)
|
autoRepairQueueTask = Some(System.currentTimeMillis() + delay)
|
||||||
val modifiedDrain = drain * Config.app.game.amenityAutorepairDrainRate
|
val modifiedDrain = drain * 2 * Config.app.game.amenityAutorepairDrainRate //doubled intentionally
|
||||||
autoRepairTimer = if(AutoRepairObject.Owner == Building.NoBuilding) {
|
autoRepairTimer = if(AutoRepairObject.Owner == Building.NoBuilding) {
|
||||||
//without an owner, auto-repair freely
|
//without an owner, auto-repair freely
|
||||||
context.system.scheduler.scheduleOnce(
|
context.system.scheduler.scheduleOnce(
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,11 @@ import net.psforever.actors.zone.BuildingActor
|
||||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||||
import net.psforever.objects.serverobject.transfer.TransferBehavior
|
import net.psforever.objects.serverobject.transfer.TransferBehavior
|
||||||
import net.psforever.objects.serverobject.structures.Building
|
import net.psforever.objects.serverobject.structures.Building
|
||||||
import net.psforever.objects.{Ntu, NtuContainer, NtuStorageBehavior}
|
import net.psforever.objects.{GlobalDefinitions, Ntu, NtuContainer, NtuStorageBehavior}
|
||||||
import net.psforever.types.PlanetSideEmpire
|
import net.psforever.types.PlanetSideEmpire
|
||||||
import net.psforever.services.Service
|
import net.psforever.services.Service
|
||||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||||
import net.psforever.util.Config
|
|
||||||
|
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
@ -53,16 +52,32 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
|
||||||
.orElse(storageBehavior)
|
.orElse(storageBehavior)
|
||||||
.orElse {
|
.orElse {
|
||||||
case CommonMessages.Use(player, _) =>
|
case CommonMessages.Use(player, _) =>
|
||||||
if (resourceSilo.Faction == PlanetSideEmpire.NEUTRAL || player.Faction == resourceSilo.Faction) {
|
val siloFaction = resourceSilo.Faction
|
||||||
resourceSilo.Zone.Vehicles.find(v => v.PassengerInSeat(player).contains(0)) match {
|
val playerFaction = player.Faction
|
||||||
case Some(vehicle) =>
|
resourceSilo.Zone.Vehicles.find(v => v.PassengerInSeat(player).contains(0)) match {
|
||||||
context.system.scheduler.scheduleOnce(
|
case Some(vehicle) =>
|
||||||
delay = 1000 milliseconds,
|
(if (GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition)) {
|
||||||
vehicle.Actor,
|
//bfr's discharge into friendly silos and charge from enemy and neutral silos
|
||||||
TransferBehavior.Discharging(Ntu.Nanites)
|
if (siloFaction == playerFaction) {
|
||||||
)
|
Some(TransferBehavior.Discharging(Ntu.Nanites))
|
||||||
case _ =>
|
} else {
|
||||||
}
|
Some(TransferBehavior.Charging(Ntu.Nanites))
|
||||||
|
}
|
||||||
|
} else if(siloFaction == PlanetSideEmpire.NEUTRAL || siloFaction == playerFaction) {
|
||||||
|
//ants discharge into neutral and friendly silos
|
||||||
|
Some(TransferBehavior.Discharging(Ntu.Nanites))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}) match {
|
||||||
|
case Some(msg) =>
|
||||||
|
context.system.scheduler.scheduleOnce(
|
||||||
|
delay = 1000 milliseconds,
|
||||||
|
vehicle.Actor,
|
||||||
|
msg
|
||||||
|
)
|
||||||
|
case None => ;
|
||||||
|
}
|
||||||
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ResourceSilo.LowNtuWarning(enabled: Boolean) =>
|
case ResourceSilo.LowNtuWarning(enabled: Boolean) =>
|
||||||
|
|
@ -128,11 +143,11 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
|
||||||
*/
|
*/
|
||||||
def HandleNtuOffer(sender: ActorRef, src: NtuContainer): Unit = {
|
def HandleNtuOffer(sender: ActorRef, src: NtuContainer): Unit = {
|
||||||
sender ! (if (resourceSilo.NtuCapacitor < resourceSilo.MaxNtuCapacitor) {
|
sender ! (if (resourceSilo.NtuCapacitor < resourceSilo.MaxNtuCapacitor) {
|
||||||
Ntu.Request(0, resourceSilo.MaxNtuCapacitor - resourceSilo.NtuCapacitor)
|
Ntu.Request(0, resourceSilo.MaxNtuCapacitor - resourceSilo.NtuCapacitor)
|
||||||
} else {
|
} else {
|
||||||
StopNtuBehavior(sender)
|
StopNtuBehavior(sender)
|
||||||
Ntu.Request(0, 0)
|
Ntu.Request(0, 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -152,7 +167,7 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
|
||||||
*/
|
*/
|
||||||
def HandleNtuRequest(sender: ActorRef, min: Float, max: Float): Unit = {
|
def HandleNtuRequest(sender: ActorRef, min: Float, max: Float): Unit = {
|
||||||
val originalAmount = resourceSilo.NtuCapacitor
|
val originalAmount = resourceSilo.NtuCapacitor
|
||||||
UpdateChargeLevel(-(min * Config.app.game.amenityAutorepairDrainRate))
|
UpdateChargeLevel(-min)
|
||||||
sender ! Ntu.Grant(resourceSilo, originalAmount - resourceSilo.NtuCapacitor)
|
sender ! Ntu.Grant(resourceSilo, originalAmount - resourceSilo.NtuCapacitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,15 +190,30 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
|
||||||
* if negative or zero, disable the animation
|
* if negative or zero, disable the animation
|
||||||
*/
|
*/
|
||||||
def PanelAnimation(source: ActorRef, trigger: Float): Unit = {
|
def PanelAnimation(source: ActorRef, trigger: Float): Unit = {
|
||||||
val zone = resourceSilo.Zone
|
val currentlyHas = resourceSilo.NtuCapacitor
|
||||||
zone.VehicleEvents ! VehicleServiceMessage(
|
|
||||||
zone.id,
|
|
||||||
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, resourceSilo.GUID, 49, if (trigger > 0) 1 else 0)
|
|
||||||
) // panel glow & orb particles
|
|
||||||
// do not let the trigger charge go to waste, but also do not let the silo be filled
|
// do not let the trigger charge go to waste, but also do not let the silo be filled
|
||||||
// attempting to return it to the source may sabotage an ongoing transfer process
|
// attempting to return it to the source may sabotage an ongoing transfer process
|
||||||
val amount = math.min(resourceSilo.MaxNtuCapacitor - resourceSilo.NtuCapacitor, trigger)
|
val amount = (if (trigger > 0) {
|
||||||
UpdateChargeLevel(amount - amount*0.1f)
|
// panel glow & orb particles on
|
||||||
|
val zone = resourceSilo.Zone
|
||||||
|
zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, resourceSilo.GUID, 49, 1)
|
||||||
|
)
|
||||||
|
math.min(resourceSilo.MaxNtuCapacitor - currentlyHas, trigger)
|
||||||
|
} else if (trigger < 0) {
|
||||||
|
// no change to animation state
|
||||||
|
if (currentlyHas > -trigger) { trigger } else { -currentlyHas }
|
||||||
|
} else {
|
||||||
|
// panel glow & orb particles off
|
||||||
|
val zone = resourceSilo.Zone
|
||||||
|
zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, resourceSilo.GUID, 49, 0)
|
||||||
|
)
|
||||||
|
0
|
||||||
|
}) * 0.9f
|
||||||
|
UpdateChargeLevel(amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,10 @@ class WarpGate(name: String, building_guid: Int, map_id: Int, zone: Zone, buildi
|
||||||
|
|
||||||
def NtuCapacitor_=(value: Float): Float = NtuCapacitor
|
def NtuCapacitor_=(value: Float): Float = NtuCapacitor
|
||||||
|
|
||||||
|
def MaxNtuCapacitor : Float = Int.MaxValue
|
||||||
|
|
||||||
|
override def NtuSource: Option[NtuContainer] = Some(this)
|
||||||
|
|
||||||
override def hasLatticeBenefit(wantedBenefit: ObjectDefinition): Boolean = false
|
override def hasLatticeBenefit(wantedBenefit: ObjectDefinition): Boolean = false
|
||||||
|
|
||||||
override def latticeConnectedFacilityBenefits(): Set[ObjectDefinition] = Set.empty
|
override def latticeConnectedFacilityBenefits(): Set[ObjectDefinition] = Set.empty
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,61 @@ object EquipmentTerminalDefinition {
|
||||||
"flail_targeting_laser" -> MakeSimpleItem(flail_targeting_laser)
|
"flail_targeting_laser" -> MakeSimpleItem(flail_targeting_laser)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `Map` of operations for producing the `Tool` `Equipment` for battleframe arm weapons.
|
||||||
|
* key - an identification string sent by the client
|
||||||
|
* value - a curried function that builds the object
|
||||||
|
*/
|
||||||
|
val bfrArmWeapons : Map[String, () => Equipment] = Map(
|
||||||
|
"aphelion_armor_siphon" -> MakeTool(aphelion_armor_siphon),
|
||||||
|
"aphelion_laser" -> MakeTool(aphelion_laser),
|
||||||
|
"aphelion_ntu_siphon" -> MakeTool(aphelion_ntu_siphon),
|
||||||
|
"aphelion_ppa" -> MakeTool(aphelion_ppa),
|
||||||
|
"aphelion_starfire" -> MakeTool(aphelion_starfire),
|
||||||
|
"colossus_armor_siphon" -> MakeTool(colossus_armor_siphon),
|
||||||
|
"colossus_burster" -> MakeTool(colossus_burster),
|
||||||
|
"colossus_chaingun" -> MakeTool(colossus_chaingun),
|
||||||
|
"colossus_ntu_siphon" -> MakeTool(colossus_ntu_siphon),
|
||||||
|
"colossus_tank_cannon" -> MakeTool(colossus_tank_cannon),
|
||||||
|
"peregrine_armor_siphon" -> MakeTool(peregrine_armor_siphon),
|
||||||
|
"peregrine_dual_machine_gun" -> MakeTool(peregrine_dual_machine_gun),
|
||||||
|
"peregrine_mechhammer" -> MakeTool(peregrine_mechhammer),
|
||||||
|
"peregrine_ntu_siphon" -> MakeTool(peregrine_ntu_siphon),
|
||||||
|
"peregrine_sparrow" -> MakeTool(peregrine_sparrow)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `Map` of operations for producing the `Tool` `Equipment` for battleframe gunner weapons.
|
||||||
|
* key - an identification string sent by the client
|
||||||
|
* value - a curried function that builds the object
|
||||||
|
*/
|
||||||
|
val bfrGunnerWeapons : Map[String, () => Equipment] = Map(
|
||||||
|
"aphelion_immolation_cannon" -> MakeTool(aphelion_immolation_cannon),
|
||||||
|
"aphelion_plasma_rocket_pod" -> MakeTool(aphelion_plasma_rocket_pod),
|
||||||
|
"colossus_cluster_bomb_pod" -> MakeTool(colossus_cluster_bomb_pod),
|
||||||
|
"colossus_dual_100mm_cannons" -> MakeTool(colossus_dual_100mm_cannons),
|
||||||
|
"peregrine_dual_rocket_pods" -> MakeTool(peregrine_dual_rocket_pods),
|
||||||
|
"peregrine_particle_cannon" -> MakeTool(peregrine_particle_cannon)
|
||||||
|
)
|
||||||
|
|
||||||
|
val bfrAmmunition : Map[String, () => AmmoBox] = Map(
|
||||||
|
"aphelion_laser_ammo" -> MakeAmmoBox(aphelion_laser_ammo),
|
||||||
|
"aphelion_immolation_cannon_ammo" -> MakeAmmoBox(aphelion_immolation_cannon_ammo),
|
||||||
|
"aphelion_plasma_rocket_ammo" -> MakeAmmoBox(aphelion_plasma_rocket_ammo),
|
||||||
|
"aphelion_ppa_ammo" -> MakeAmmoBox(aphelion_ppa_ammo),
|
||||||
|
"aphelion_starfire_ammo" -> MakeAmmoBox(aphelion_starfire_ammo),
|
||||||
|
"colossus_100mm_cannon_ammo" -> MakeAmmoBox(colossus_100mm_cannon_ammo),
|
||||||
|
"colossus_burster_ammo" -> MakeAmmoBox(colossus_burster_ammo),
|
||||||
|
"colossus_cluster_bomb_ammo" -> MakeAmmoBox(colossus_cluster_bomb_ammo),
|
||||||
|
"colossus_chaingun_ammo" -> MakeAmmoBox(colossus_chaingun_ammo),
|
||||||
|
"colossus_tank_cannon_ammo" -> MakeAmmoBox(colossus_tank_cannon_ammo),
|
||||||
|
"peregrine_dual_machine_gun_ammo" -> MakeAmmoBox(peregrine_dual_machine_gun_ammo),
|
||||||
|
"peregrine_mechhammer_ammo" -> MakeAmmoBox(peregrine_mechhammer_ammo),
|
||||||
|
"peregrine_particle_cannon_ammo" -> MakeAmmoBox(peregrine_particle_cannon_ammo),
|
||||||
|
"peregrine_rocket_pod_ammo" -> MakeAmmoBox(peregrine_rocket_pod_ammo),
|
||||||
|
"peregrine_sparrow_ammo" -> MakeAmmoBox(peregrine_sparrow_ammo)
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A single-element `Map` of the one piece of `Equipment` specific to the Router.
|
* A single-element `Map` of the one piece of `Equipment` specific to the Router.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -334,9 +334,9 @@ object OrderTerminalDefinition {
|
||||||
* @see `Loadout`
|
* @see `Loadout`
|
||||||
* @see `VehicleLoadout`
|
* @see `VehicleLoadout`
|
||||||
*/
|
*/
|
||||||
final case class VehicleLoadoutPage() extends LoadoutTab {
|
final case class VehicleLoadoutPage(lineOffset: Int) extends LoadoutTab {
|
||||||
override def Buy(player: Player, msg: ItemTransactionMessage): Terminal.Exchange = {
|
override def Buy(player: Player, msg: ItemTransactionMessage): Terminal.Exchange = {
|
||||||
player.avatar.loadouts(msg.unk1 + 10) match {
|
player.avatar.loadouts(msg.unk1 + lineOffset) match {
|
||||||
case Some(loadout: VehicleLoadout) if !Exclude.contains(loadout.vehicle_definition) =>
|
case Some(loadout: VehicleLoadout) if !Exclude.contains(loadout.vehicle_definition) =>
|
||||||
val weapons = loadout.visible_slots
|
val weapons = loadout.visible_slots
|
||||||
.map(entry => {
|
.map(entry => {
|
||||||
|
|
@ -401,6 +401,43 @@ object OrderTerminalDefinition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The special page used by the `bfr_terminal` to select a vehicle to be spawned
|
||||||
|
* based on the player's previous loadouts for battleframe vehicles.
|
||||||
|
* Vehicle loadouts are defined by a superfluous redefinition of the vehicle's mounted weapons
|
||||||
|
* and equipment in the trunk.
|
||||||
|
* @see `Equipment`
|
||||||
|
* @see `Loadout`
|
||||||
|
* @see `Vehicle`
|
||||||
|
* @see `VehicleLoadout`
|
||||||
|
*/
|
||||||
|
final case class BattleframeSpawnLoadoutPage(vehicles: Map[String, () => Vehicle]) extends LoadoutTab {
|
||||||
|
override def Buy(player: Player, msg: ItemTransactionMessage): Terminal.Exchange = {
|
||||||
|
player.avatar.loadouts(msg.unk1 + 15) match {
|
||||||
|
case Some(loadout: VehicleLoadout) if !Exclude.contains(loadout.vehicle_definition) =>
|
||||||
|
vehicles.get(loadout.vehicle_definition.Name) match {
|
||||||
|
case Some(vehicle) =>
|
||||||
|
val weapons = loadout.visible_slots.map(entry => {
|
||||||
|
InventoryItem(EquipmentTerminalDefinition.BuildSimplifiedPattern(entry.item), entry.index)
|
||||||
|
})
|
||||||
|
val inventory = loadout.inventory.map(entry => {
|
||||||
|
InventoryItem(EquipmentTerminalDefinition.BuildSimplifiedPattern(entry.item), entry.index)
|
||||||
|
})
|
||||||
|
Terminal.BuyVehicle(vehicle(), weapons, inventory)
|
||||||
|
case None =>
|
||||||
|
Terminal.NoDeal()
|
||||||
|
}
|
||||||
|
|
||||||
|
case _ =>
|
||||||
|
Terminal.NoDeal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def Dispatch(sender: ActorRef, terminal: Terminal, msg: Terminal.TerminalMessage): Unit = {
|
||||||
|
sender ! msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assemble some logic for a provided object.
|
* Assemble some logic for a provided object.
|
||||||
* @param obj an `Amenity` object;
|
* @param obj an `Amenity` object;
|
||||||
|
|
|
||||||
|
|
@ -71,12 +71,12 @@ object VehicleTerminalDefinition {
|
||||||
* value - a curried function that builds the object
|
* value - a curried function that builds the object
|
||||||
*/
|
*/
|
||||||
val bfrVehicles: Map[String, () => Vehicle] = Map(
|
val bfrVehicles: Map[String, () => Vehicle] = Map(
|
||||||
// "colossus_gunner" -> (()=>Unit),
|
"colossus_gunner" -> MakeVehicle(colossus_gunner),
|
||||||
// "colossus_flight" -> (()=>Unit),
|
"colossus_flight" -> MakeVehicle(colossus_flight),
|
||||||
// "peregrine_gunner" -> (()=>Unit),
|
"peregrine_gunner" -> MakeVehicle(peregrine_gunner),
|
||||||
// "peregrine_flight" -> (()=>Unit),
|
"peregrine_flight" -> MakeVehicle(peregrine_flight),
|
||||||
// "aphelion_gunner" -> (()=>Unit),
|
"aphelion_gunner" -> MakeVehicle(aphelion_gunner),
|
||||||
// "aphelion_flight" -> (()=>Unit)
|
"aphelion_flight" -> MakeVehicle(aphelion_flight)
|
||||||
)
|
)
|
||||||
|
|
||||||
import net.psforever.objects.loadouts.{Loadout => _Loadout} //distinguish from Terminal.Loadout message
|
import net.psforever.objects.loadouts.{Loadout => _Loadout} //distinguish from Terminal.Loadout message
|
||||||
|
|
@ -88,15 +88,18 @@ object VehicleTerminalDefinition {
|
||||||
* value - a curried function that builds the object
|
* value - a curried function that builds the object
|
||||||
*/
|
*/
|
||||||
val trunk: Map[String, _Loadout] = {
|
val trunk: Map[String, _Loadout] = {
|
||||||
val ammo_12mm = ShorthandAmmoBox(bullet_12mm, bullet_12mm.Capacity)
|
val ammo_12mm = ShorthandAmmoBox(bullet_12mm, bullet_12mm.Capacity)
|
||||||
val ammo_15mm = ShorthandAmmoBox(bullet_15mm, bullet_15mm.Capacity)
|
val ammo_15mm = ShorthandAmmoBox(bullet_15mm, bullet_15mm.Capacity)
|
||||||
val ammo_25mm = ShorthandAmmoBox(bullet_25mm, bullet_25mm.Capacity)
|
val ammo_25mm = ShorthandAmmoBox(bullet_25mm, bullet_25mm.Capacity)
|
||||||
val ammo_35mm = ShorthandAmmoBox(bullet_35mm, bullet_35mm.Capacity)
|
val ammo_35mm = ShorthandAmmoBox(bullet_35mm, bullet_35mm.Capacity)
|
||||||
val ammo_20mm = ShorthandAmmoBox(bullet_20mm, bullet_20mm.Capacity)
|
val ammo_20mm = ShorthandAmmoBox(bullet_20mm, bullet_20mm.Capacity)
|
||||||
val ammo_75mm = ShorthandAmmoBox(bullet_75mm, bullet_75mm.Capacity)
|
val ammo_75mm = ShorthandAmmoBox(bullet_75mm, bullet_75mm.Capacity)
|
||||||
val ammo_mortar = ShorthandAmmoBox(heavy_grenade_mortar, heavy_grenade_mortar.Capacity)
|
val ammo_mortar = ShorthandAmmoBox(heavy_grenade_mortar, heavy_grenade_mortar.Capacity)
|
||||||
val ammo_flux = ShorthandAmmoBox(flux_cannon_thresher_battery, flux_cannon_thresher_battery.Capacity)
|
val ammo_flux = ShorthandAmmoBox(flux_cannon_thresher_battery, flux_cannon_thresher_battery.Capacity)
|
||||||
val ammo_bomb = ShorthandAmmoBox(liberator_bomb, liberator_bomb.Capacity)
|
val ammo_bomb = ShorthandAmmoBox(liberator_bomb, liberator_bomb.Capacity)
|
||||||
|
val ammo_ppa = ShorthandAmmoBox(aphelion_ppa_ammo, aphelion_ppa_ammo.Capacity)
|
||||||
|
val ammo_tcannon = ShorthandAmmoBox(colossus_tank_cannon_ammo, colossus_tank_cannon_ammo.Capacity)
|
||||||
|
val ammo_mgun = ShorthandAmmoBox(peregrine_dual_machine_gun_ammo, peregrine_dual_machine_gun_ammo.Capacity)
|
||||||
Map(
|
Map(
|
||||||
//"quadstealth" -> VehicleLoadout("default_quadstealth", List(), List(), quadstealth),
|
//"quadstealth" -> VehicleLoadout("default_quadstealth", List(), List(), quadstealth),
|
||||||
"quadassault" -> VehicleLoadout(
|
"quadassault" -> VehicleLoadout(
|
||||||
|
|
@ -513,9 +516,114 @@ object VehicleTerminalDefinition {
|
||||||
SimplifiedEntry(ammo_mortar, 186)
|
SimplifiedEntry(ammo_mortar, 186)
|
||||||
),
|
),
|
||||||
galaxy_gunship
|
galaxy_gunship
|
||||||
)
|
),
|
||||||
//"phantasm" -> VehicleLoadout("default_phantasm", List(), List(), phantasm),
|
//"phantasm" -> VehicleLoadout("default_phantasm", List(), List(), phantasm),
|
||||||
//"lodestar" -> VehicleLoadout("default_lodestar", List(), List(), lodestar),
|
//"lodestar" -> VehicleLoadout("default_lodestar", List(), List(), lodestar),
|
||||||
|
{
|
||||||
|
val ammo = ShorthandAmmoBox(aphelion_plasma_rocket_ammo, aphelion_plasma_rocket_ammo.Capacity)
|
||||||
|
"aphelion_gunner" -> VehicleLoadout(
|
||||||
|
"default_aphelion_gunner",
|
||||||
|
List(),
|
||||||
|
List(
|
||||||
|
SimplifiedEntry(ammo_ppa, 30),
|
||||||
|
SimplifiedEntry(ammo_ppa, 34),
|
||||||
|
SimplifiedEntry(ammo_ppa, 38),
|
||||||
|
SimplifiedEntry(ammo_ppa, 90),
|
||||||
|
SimplifiedEntry(ammo_ppa, 94),
|
||||||
|
SimplifiedEntry(ammo_ppa, 98),
|
||||||
|
SimplifiedEntry(ammo, 150),
|
||||||
|
SimplifiedEntry(ammo, 155),
|
||||||
|
SimplifiedEntry(ammo, 160),
|
||||||
|
SimplifiedEntry(ammo, 225),
|
||||||
|
SimplifiedEntry(ammo, 230),
|
||||||
|
SimplifiedEntry(ammo, 235)
|
||||||
|
),
|
||||||
|
aphelion_gunner
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"aphelion_flight" -> VehicleLoadout(
|
||||||
|
"default_aphelion_flight",
|
||||||
|
List(),
|
||||||
|
List(
|
||||||
|
SimplifiedEntry(ammo_ppa, 30),
|
||||||
|
SimplifiedEntry(ammo_ppa, 34),
|
||||||
|
SimplifiedEntry(ammo_ppa, 38),
|
||||||
|
SimplifiedEntry(ammo_ppa, 90),
|
||||||
|
SimplifiedEntry(ammo_ppa, 94),
|
||||||
|
SimplifiedEntry(ammo_ppa, 98)
|
||||||
|
),
|
||||||
|
aphelion_flight
|
||||||
|
),
|
||||||
|
{
|
||||||
|
val ammo = ShorthandAmmoBox(colossus_100mm_cannon_ammo, colossus_100mm_cannon_ammo.Capacity)
|
||||||
|
"colossus_gunner" -> VehicleLoadout(
|
||||||
|
"default_colossus_gunner",
|
||||||
|
List(),
|
||||||
|
List(
|
||||||
|
SimplifiedEntry(ammo_tcannon, 30),
|
||||||
|
SimplifiedEntry(ammo_tcannon, 34),
|
||||||
|
SimplifiedEntry(ammo_tcannon, 38),
|
||||||
|
SimplifiedEntry(ammo_tcannon, 90),
|
||||||
|
SimplifiedEntry(ammo_tcannon, 94),
|
||||||
|
SimplifiedEntry(ammo_tcannon, 98),
|
||||||
|
SimplifiedEntry(ammo, 150),
|
||||||
|
SimplifiedEntry(ammo, 155),
|
||||||
|
SimplifiedEntry(ammo, 160),
|
||||||
|
SimplifiedEntry(ammo, 225),
|
||||||
|
SimplifiedEntry(ammo, 230),
|
||||||
|
SimplifiedEntry(ammo, 235)
|
||||||
|
),
|
||||||
|
colossus_gunner
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"colossus_flight" -> VehicleLoadout(
|
||||||
|
"default_colossus_flight",
|
||||||
|
List(),
|
||||||
|
List(
|
||||||
|
SimplifiedEntry(ammo_tcannon, 30),
|
||||||
|
SimplifiedEntry(ammo_tcannon, 34),
|
||||||
|
SimplifiedEntry(ammo_tcannon, 38),
|
||||||
|
SimplifiedEntry(ammo_tcannon, 90),
|
||||||
|
SimplifiedEntry(ammo_tcannon, 94),
|
||||||
|
SimplifiedEntry(ammo_tcannon, 98)
|
||||||
|
),
|
||||||
|
colossus_flight
|
||||||
|
),
|
||||||
|
{
|
||||||
|
val ammo = ShorthandAmmoBox(peregrine_particle_cannon_ammo, peregrine_particle_cannon_ammo.Capacity)
|
||||||
|
"peregrine_gunner" -> VehicleLoadout(
|
||||||
|
"default_peregrine_gunner",
|
||||||
|
List(),
|
||||||
|
List(
|
||||||
|
SimplifiedEntry(ammo_mgun, 30),
|
||||||
|
SimplifiedEntry(ammo_mgun, 34),
|
||||||
|
SimplifiedEntry(ammo_mgun, 38),
|
||||||
|
SimplifiedEntry(ammo_mgun, 90),
|
||||||
|
SimplifiedEntry(ammo_mgun, 94),
|
||||||
|
SimplifiedEntry(ammo_mgun, 98),
|
||||||
|
SimplifiedEntry(ammo, 150),
|
||||||
|
SimplifiedEntry(ammo, 155),
|
||||||
|
SimplifiedEntry(ammo, 160),
|
||||||
|
SimplifiedEntry(ammo, 225),
|
||||||
|
SimplifiedEntry(ammo, 230),
|
||||||
|
SimplifiedEntry(ammo, 235)
|
||||||
|
),
|
||||||
|
peregrine_gunner
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"peregrine_flight" -> VehicleLoadout(
|
||||||
|
"default_peregrine_flight",
|
||||||
|
List(),
|
||||||
|
List(
|
||||||
|
SimplifiedEntry(ammo_mgun, 30),
|
||||||
|
SimplifiedEntry(ammo_mgun, 34),
|
||||||
|
SimplifiedEntry(ammo_mgun, 38),
|
||||||
|
SimplifiedEntry(ammo_mgun, 90),
|
||||||
|
SimplifiedEntry(ammo_mgun, 94),
|
||||||
|
SimplifiedEntry(ammo_mgun, 98)
|
||||||
|
),
|
||||||
|
peregrine_flight
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,18 +86,21 @@ object TransferBehavior {
|
||||||
Discharging
|
Discharging
|
||||||
= Value
|
= Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed trait Command
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message to cue a process of transferring into oneself.
|
* Message to cue a process of transferring into oneself.
|
||||||
*/
|
*/
|
||||||
final case class Charging(transferMaterial : Any)
|
final case class Charging(transferMaterial : Any) extends Command
|
||||||
/**
|
/**
|
||||||
* Message to cue a process of transferring from oneself.
|
* Message to cue a process of transferring from oneself.
|
||||||
*/
|
*/
|
||||||
final case class Discharging(transferMaterial : Any)
|
final case class Discharging(transferMaterial : Any) extends Command
|
||||||
/**
|
/**
|
||||||
* Message to cue a stopping the transfer process.
|
* Message to cue a stopping the transfer process.
|
||||||
*/
|
*/
|
||||||
final case class Stopping()
|
final case class Stopping() extends Command
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A default search function that does not actually search for anything or ever find anything.
|
* A default search function that does not actually search for anything or ever find anything.
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,17 @@
|
||||||
package net.psforever.objects.serverobject.transfer
|
package net.psforever.objects.serverobject.transfer
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
|
import net.psforever.objects.PlanetSideGameObject
|
||||||
import net.psforever.objects.entity.{Identifiable, WorldEntity}
|
import net.psforever.objects.entity.{Identifiable, WorldEntity}
|
||||||
|
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||||
import net.psforever.objects.zones.ZoneAware
|
import net.psforever.objects.zones.ZoneAware
|
||||||
|
|
||||||
trait TransferContainer extends Identifiable
|
trait TransferContainer
|
||||||
|
extends PlanetSideGameObject
|
||||||
|
with Identifiable
|
||||||
with ZoneAware
|
with ZoneAware
|
||||||
with WorldEntity {
|
with WorldEntity
|
||||||
|
with FactionAffinity {
|
||||||
def Actor : ActorRef
|
def Actor : ActorRef
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,25 +97,28 @@ class FacilityTurretControl(turret: FacilityTurret)
|
||||||
)
|
)
|
||||||
|
|
||||||
case FacilityTurret.RechargeAmmo() =>
|
case FacilityTurret.RechargeAmmo() =>
|
||||||
val weapon = turret.ControlledWeapon(1).get.asInstanceOf[net.psforever.objects.Tool]
|
turret.ControlledWeapon(wepNumber = 1).foreach {
|
||||||
// recharge when last shot fired 3s delay, +1, 200ms interval
|
case weapon: Tool =>
|
||||||
if (weapon.Magazine < weapon.MaxMagazine && System.nanoTime() - weapon.LastDischarge > 3000000000L) {
|
// recharge when last shot fired 3s delay, +1, 200ms interval
|
||||||
weapon.Magazine += 1
|
if (weapon.Magazine < weapon.MaxMagazine && System.nanoTime() - weapon.LastDischarge > 3000000000L) {
|
||||||
val seat = turret.Seat(0).get
|
weapon.Magazine += 1
|
||||||
seat.occupant match {
|
val seat = turret.Seat(0).get
|
||||||
case Some(player: Player) =>
|
seat.occupant match {
|
||||||
turret.Zone.LocalEvents ! LocalServiceMessage(
|
case Some(player : Player) =>
|
||||||
turret.Zone.id,
|
turret.Zone.LocalEvents ! LocalServiceMessage(
|
||||||
LocalAction.RechargeVehicleWeapon(player.GUID, turret.GUID, weapon.GUID)
|
turret.Zone.id,
|
||||||
)
|
LocalAction.RechargeVehicleWeapon(player.GUID, turret.GUID, weapon.GUID)
|
||||||
case _ => ;
|
)
|
||||||
}
|
case _ => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (weapon.Magazine == weapon.MaxMagazine && weaponAmmoRechargeTimer != Default.Cancellable) {
|
||||||
|
weaponAmmoRechargeTimer.cancel()
|
||||||
|
weaponAmmoRechargeTimer = Default.Cancellable
|
||||||
|
}
|
||||||
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (weapon.Magazine == weapon.MaxMagazine && weaponAmmoRechargeTimer != Default.Cancellable) {
|
|
||||||
weaponAmmoRechargeTimer.cancel()
|
|
||||||
weaponAmmoRechargeTimer = Default.Cancellable
|
|
||||||
}
|
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2020 PSForever
|
// Copyright (c) 2020 PSForever
|
||||||
package net.psforever.objects.vehicles
|
package net.psforever.objects.vehicles
|
||||||
|
|
||||||
import akka.actor.{ActorRef, Cancellable}
|
import akka.actor.ActorRef
|
||||||
import net.psforever.actors.commands.NtuCommand
|
import net.psforever.actors.commands.NtuCommand
|
||||||
import net.psforever.actors.zone.BuildingActor
|
import net.psforever.actors.zone.BuildingActor
|
||||||
import net.psforever.objects.serverobject.deploy.Deployment
|
import net.psforever.objects.serverobject.deploy.Deployment
|
||||||
|
|
@ -18,8 +18,10 @@ import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior {
|
trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior {
|
||||||
var ntuChargingTick: Cancellable = Default.Cancellable
|
|
||||||
var panelAnimationFunc: () => Unit = NoCharge
|
var panelAnimationFunc: () => Unit = NoCharge
|
||||||
|
var ntuChargingTick = Default.Cancellable
|
||||||
|
findChargeTargetFunc = Vehicles.FindANTChargingSource
|
||||||
|
findDischargeTargetFunc = Vehicles.FindANTDischargingTarget
|
||||||
|
|
||||||
def TransferMaterial = Ntu.Nanites
|
def TransferMaterial = Ntu.Nanites
|
||||||
|
|
||||||
|
|
@ -28,7 +30,8 @@ trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior {
|
||||||
def antBehavior: Receive = storageBehavior.orElse(transferBehavior)
|
def antBehavior: Receive = storageBehavior.orElse(transferBehavior)
|
||||||
|
|
||||||
def ActivatePanelsForChargingEvent(vehicle: NtuContainer): Unit = {
|
def ActivatePanelsForChargingEvent(vehicle: NtuContainer): Unit = {
|
||||||
val zone = vehicle.Zone
|
val obj = ChargeTransferObject
|
||||||
|
val zone = obj.Zone
|
||||||
zone.VehicleEvents ! VehicleServiceMessage(
|
zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
zone.id,
|
zone.id,
|
||||||
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vehicle.GUID, 52, 1L)
|
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vehicle.GUID, 52, 1L)
|
||||||
|
|
@ -37,7 +40,8 @@ trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior {
|
||||||
|
|
||||||
/** Charging */
|
/** Charging */
|
||||||
def StartNtuChargingEvent(vehicle: NtuContainer): Unit = {
|
def StartNtuChargingEvent(vehicle: NtuContainer): Unit = {
|
||||||
val zone = vehicle.Zone
|
val obj = ChargeTransferObject
|
||||||
|
val zone = obj.Zone
|
||||||
zone.VehicleEvents ! VehicleServiceMessage(
|
zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
zone.id,
|
zone.id,
|
||||||
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vehicle.GUID, 49, 1L)
|
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vehicle.GUID, 49, 1L)
|
||||||
|
|
@ -190,7 +194,6 @@ trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior {
|
||||||
} else {
|
} else {
|
||||||
scala.math.min(min, chargeable.NtuCapacitor)
|
scala.math.min(min, chargeable.NtuCapacitor)
|
||||||
}
|
}
|
||||||
// var chargeToDeposit = Math.min(Math.min(chargeable.NtuCapacitor, 100), max)
|
|
||||||
chargeable.NtuCapacitor -= chargeToDeposit
|
chargeable.NtuCapacitor -= chargeToDeposit
|
||||||
UpdateNtuUI(chargeable)
|
UpdateNtuUI(chargeable)
|
||||||
sender ! Ntu.Grant(chargeable, chargeToDeposit)
|
sender ! Ntu.Grant(chargeable, chargeToDeposit)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,327 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.vehicles
|
||||||
|
|
||||||
|
import akka.actor.ActorRef
|
||||||
|
import akka.actor.typed.scaladsl.adapter._
|
||||||
|
import net.psforever.actors.commands.NtuCommand
|
||||||
|
import net.psforever.actors.zone.BuildingActor
|
||||||
|
import net.psforever.objects.{NtuContainer, NtuContainerDefinition, _}
|
||||||
|
import net.psforever.objects.definition.ObjectDefinition
|
||||||
|
import net.psforever.objects.equipment.EquipmentSlot
|
||||||
|
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
|
||||||
|
import net.psforever.objects.serverobject.structures.WarpGate
|
||||||
|
import net.psforever.objects.serverobject.transfer.{TransferBehavior, TransferContainer}
|
||||||
|
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||||
|
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||||
|
|
||||||
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
|
trait BfrTransferBehavior
|
||||||
|
extends TransferBehavior
|
||||||
|
with NtuStorageBehavior {
|
||||||
|
var ntuProcessingRequest: Boolean = false
|
||||||
|
var ntuProcessingTick = Default.Cancellable
|
||||||
|
|
||||||
|
findChargeTargetFunc = Vehicles.FindBfrChargingSource
|
||||||
|
findDischargeTargetFunc = Vehicles.FindBfrDischargingTarget
|
||||||
|
|
||||||
|
def TransferMaterial = Ntu.Nanites
|
||||||
|
|
||||||
|
private var pairedSlotList: Option[List[(VehicleSubsystem, (Int, EquipmentSlot))]] = None
|
||||||
|
/**
|
||||||
|
* Return the paired arm weapon subsystems with arm weapon equipment mount and the slot number for that mount,
|
||||||
|
* connecting "left" to "left" and "right" to "right".
|
||||||
|
* Either return the existing connection or create that connection for the first time and retain it for future use.
|
||||||
|
* Works regardless of the type of battleframe unit.
|
||||||
|
* @return the arm weapon subsystems for each arm weapon mount and that mount's slot number
|
||||||
|
*/
|
||||||
|
def pairedArmSlotSubsystems(): List[(VehicleSubsystem, (Int, EquipmentSlot))] = {
|
||||||
|
pairedSlotList.getOrElse {
|
||||||
|
val obj = ChargeTransferObject
|
||||||
|
val pairs = obj.Subsystems()
|
||||||
|
.filter { sub =>
|
||||||
|
sub.sys.name.startsWith("BattleframeLeftArm") || sub.sys.name.startsWith("BattleframeRightArm")
|
||||||
|
}
|
||||||
|
.zip(
|
||||||
|
obj.Weapons.filter { case (a, _) =>
|
||||||
|
a == 1 || a == 2 || a == 3 //gunner -> 2,3; flight -> 1,2
|
||||||
|
}
|
||||||
|
)
|
||||||
|
pairedSlotList = Some(pairs)
|
||||||
|
pairs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private var pairedList: Option[List[(VehicleSubsystem, EquipmentSlot)]] = None
|
||||||
|
/**
|
||||||
|
* Return the paired arm weapon subsystems with arm weapon mount,
|
||||||
|
* connecting "left" to "left" and "right" to "right".
|
||||||
|
* Either return the existing connection or create that connection for the first time and retain it for future use.
|
||||||
|
* Works regardless of the type of battleframe unit.
|
||||||
|
* @return the arm weapon subsystems for each arm weapon mount
|
||||||
|
*/
|
||||||
|
def pairedArmSubsystems(): List[(VehicleSubsystem, EquipmentSlot)] = {
|
||||||
|
pairedList.getOrElse {
|
||||||
|
val pairs = pairedArmSlotSubsystems().map { case (a, (_, c)) => (a, c) }
|
||||||
|
pairedList = Some(pairs)
|
||||||
|
pairs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getNtuContainer(): Option[NtuContainer] = {
|
||||||
|
pairedArmSubsystems()
|
||||||
|
.find { case (sub, arm) =>
|
||||||
|
//find an active ntu siphon
|
||||||
|
arm.Equipment.nonEmpty &&
|
||||||
|
GlobalDefinitions.isBattleFrameNTUSiphon(arm.Equipment.get.Definition) &&
|
||||||
|
sub.Enabled
|
||||||
|
}
|
||||||
|
.map { d => d._2.Equipment.get } match {
|
||||||
|
case Some(equipment: Tool) =>
|
||||||
|
Some(new NtuSiphon(equipment, ChargeTransferObject.Definition))
|
||||||
|
case _ =>
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def ChargeTransferObject: Vehicle with NtuContainer
|
||||||
|
|
||||||
|
def bfrBehavior: Receive = storageBehavior
|
||||||
|
.orElse(transferBehavior)
|
||||||
|
.orElse {
|
||||||
|
case BfrTransferBehavior.NextProcessTick(event) =>
|
||||||
|
transferTarget match {
|
||||||
|
case Some(target)
|
||||||
|
if event == transferEvent && ntuProcessingRequest && event == TransferBehavior.Event.Charging =>
|
||||||
|
HandleChargingOps(target)
|
||||||
|
case Some(target)
|
||||||
|
if event == transferEvent && ntuProcessingRequest && event == TransferBehavior.Event.Discharging =>
|
||||||
|
HandleDischargingEvent(target)
|
||||||
|
case Some(target)
|
||||||
|
if event == transferEvent && !ntuProcessingRequest =>
|
||||||
|
TryStopChargingEvent(target)
|
||||||
|
case _ => ;
|
||||||
|
TryStopChargingEvent(ChargeTransferObject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def UpdateNtuUI(vehicle: Vehicle with NtuContainer): Unit = {
|
||||||
|
getNtuContainer() match {
|
||||||
|
case Some(siphon) =>
|
||||||
|
UpdateNtuUI(vehicle, siphon)
|
||||||
|
case None => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def UpdateNtuUI(vehicle: Vehicle with NtuContainer, siphon: NtuContainer): Unit = {
|
||||||
|
siphon match {
|
||||||
|
case equip: NtuSiphon =>
|
||||||
|
vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
vehicle.Actor.toString,
|
||||||
|
VehicleAction.InventoryState2(PlanetSideGUID(0), equip.storageGUID, siphon.GUID, siphon.NtuCapacitor.toInt)
|
||||||
|
)
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def HandleChargingEvent(target: TransferContainer): Boolean = {
|
||||||
|
if (transferEvent == TransferBehavior.Event.None) {
|
||||||
|
HandleChargingOps(target)
|
||||||
|
} else {
|
||||||
|
ntuProcessingRequest = true
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def HandleChargingOps(target: TransferContainer): Boolean = {
|
||||||
|
ntuProcessingRequest = false
|
||||||
|
getNtuContainer() match {
|
||||||
|
case Some(siphon: NtuSiphon)
|
||||||
|
if siphon.NtuCapacitor < siphon.MaxNtuCapacitor =>
|
||||||
|
//charging
|
||||||
|
transferTarget = Some(target)
|
||||||
|
transferEvent = TransferBehavior.Event.Charging
|
||||||
|
val max = siphon.NtuCapacitor
|
||||||
|
val fromMax = siphon.MaxNtuCapacitor - max
|
||||||
|
target match {
|
||||||
|
case _: WarpGate =>
|
||||||
|
//siphon.drain -> math.min(math.min(siphon.MaxNtuCapacitor / 75f, fromMax)
|
||||||
|
target.Actor ! BuildingActor.Ntu(NtuCommand.Request(math.min(siphon.drain.toFloat, fromMax), context.self))
|
||||||
|
case _: ResourceSilo =>
|
||||||
|
//siphon.drain -> scala.math.min(silo.MaxNtuCapacitor * 0.325f / max, fromMax)
|
||||||
|
target.Actor ! NtuCommand.Request(scala.math.min(0.5f * siphon.drain, fromMax), context.self)
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
ntuProcessingTick.cancel()
|
||||||
|
ntuProcessingTick = context.system.scheduler.scheduleOnce(
|
||||||
|
delay = 1250 milliseconds,
|
||||||
|
self,
|
||||||
|
BfrTransferBehavior.NextProcessTick(transferEvent)
|
||||||
|
)
|
||||||
|
true
|
||||||
|
case _ =>
|
||||||
|
TryStopChargingEvent(ChargeTransferObject)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def ReceiveAndDepositUntilFull(vehicle: Vehicle, amount: Float): Boolean = {
|
||||||
|
getNtuContainer() match {
|
||||||
|
case Some(siphon) =>
|
||||||
|
ReceiveAndDepositUntilFull(vehicle, siphon, amount)
|
||||||
|
case None =>
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def ReceiveAndDepositUntilFull(vehicle: Vehicle, obj: NtuContainer, amount: Float): Boolean = {
|
||||||
|
val isNotFull = (obj.NtuCapacitor += amount) < obj.MaxNtuCapacitor
|
||||||
|
UpdateNtuUI(vehicle, obj)
|
||||||
|
isNotFull
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Discharging */
|
||||||
|
def HandleDischargingEvent(target: TransferContainer): Boolean = {
|
||||||
|
if (transferEvent == TransferBehavior.Event.None) {
|
||||||
|
HandleDischargingOps(target)
|
||||||
|
} else {
|
||||||
|
ntuProcessingRequest = true
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def HandleDischargingOps(target: TransferContainer): Boolean = {
|
||||||
|
ntuProcessingRequest = false
|
||||||
|
val obj = ChargeTransferObject
|
||||||
|
getNtuContainer() match {
|
||||||
|
case Some(siphon)
|
||||||
|
if siphon.NtuCapacitor > 0 =>
|
||||||
|
transferTarget = Some(target)
|
||||||
|
transferEvent = TransferBehavior.Event.Discharging
|
||||||
|
target.Actor ! Ntu.Offer(obj)
|
||||||
|
ntuProcessingTick.cancel()
|
||||||
|
ntuProcessingTick = context.system.scheduler.scheduleOnce(
|
||||||
|
delay = 1250 milliseconds,
|
||||||
|
self,
|
||||||
|
BfrTransferBehavior.NextProcessTick(transferEvent)
|
||||||
|
)
|
||||||
|
true
|
||||||
|
case _ =>
|
||||||
|
TryStopChargingEvent(obj)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def WithdrawAndTransmit(vehicle: Vehicle, maxRequested: Float): Any = {
|
||||||
|
val chargeable = ChargeTransferObject
|
||||||
|
val chargeToDeposit = getNtuContainer() match {
|
||||||
|
case Some(siphon) =>
|
||||||
|
var chargeToDeposit = Math.min(Math.min(siphon.NtuCapacitor, 100), maxRequested)
|
||||||
|
siphon.NtuCapacitor -= chargeToDeposit
|
||||||
|
UpdateNtuUI(chargeable, siphon)
|
||||||
|
chargeToDeposit
|
||||||
|
case _ =>
|
||||||
|
0
|
||||||
|
}
|
||||||
|
Ntu.Grant(chargeable, chargeToDeposit)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stopping */
|
||||||
|
override def TryStopChargingEvent(container: TransferContainer): Unit = {
|
||||||
|
ntuProcessingTick.cancel()
|
||||||
|
ntuProcessingRequest = false
|
||||||
|
transferTarget match {
|
||||||
|
case Some(target: WarpGate) =>
|
||||||
|
target.Actor ! BuildingActor.Ntu(NtuCommand.Grant(null, 0))
|
||||||
|
case Some(target) =>
|
||||||
|
target.Actor ! NtuCommand.Grant(null, 0)
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
//cleanup
|
||||||
|
val obj = ChargeTransferObject
|
||||||
|
super.TryStopChargingEvent(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
def StopNtuBehavior(sender: ActorRef): Unit = TryStopChargingEvent(ChargeTransferObject)
|
||||||
|
|
||||||
|
def HandleNtuOffer(sender: ActorRef, src: NtuContainer): Unit = {}
|
||||||
|
|
||||||
|
def HandleNtuRequest(sender: ActorRef, min: Float, max: Float): Unit = {
|
||||||
|
val chargeable = ChargeTransferObject
|
||||||
|
getNtuContainer() match {
|
||||||
|
case Some(siphon) =>
|
||||||
|
if (transferEvent == TransferBehavior.Event.Discharging) {
|
||||||
|
val capacitor = siphon.NtuCapacitor
|
||||||
|
val bonus = System.currentTimeMillis()%2
|
||||||
|
val (chargeBase, chargeToDeposit): (Float, Float) = if (min == 0) {
|
||||||
|
transferTarget match {
|
||||||
|
case Some(silo: ResourceSilo) =>
|
||||||
|
// silos would charge from 0-30% in a full siphon's payload according to the wiki
|
||||||
|
val calcChargeBase = scala.math.min(scala.math.min(silo.MaxNtuCapacitor * 0.325f / siphon.MaxNtuCapacitor, capacitor), max)
|
||||||
|
(calcChargeBase, calcChargeBase + bonus)
|
||||||
|
case _ =>
|
||||||
|
(0f, 0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val charge = scala.math.min(min, capacitor)
|
||||||
|
(charge, charge + bonus)
|
||||||
|
}
|
||||||
|
siphon.NtuCapacitor -= chargeBase
|
||||||
|
UpdateNtuUI(chargeable, siphon)
|
||||||
|
sender ! Ntu.Grant(chargeable, chargeToDeposit)
|
||||||
|
} else {
|
||||||
|
TryStopChargingEvent(chargeable)
|
||||||
|
sender ! Ntu.Grant(chargeable, 0)
|
||||||
|
}
|
||||||
|
case None => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def HandleNtuGrant(sender: ActorRef, src: NtuContainer, amount: Float): Unit = {
|
||||||
|
val obj = ChargeTransferObject
|
||||||
|
if (transferEvent != TransferBehavior.Event.Charging || !ReceiveAndDepositUntilFull(obj, amount)) {
|
||||||
|
sender ! Ntu.Request(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object BfrTransferBehavior {
|
||||||
|
private case class NextProcessTick(eventType: TransferBehavior.Event.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
class NtuSiphon(
|
||||||
|
val equipment: Tool,
|
||||||
|
private val definition: ObjectDefinition with NtuContainerDefinition
|
||||||
|
) extends NtuContainer {
|
||||||
|
def Faction: PlanetSideEmpire.Value = equipment.Faction
|
||||||
|
|
||||||
|
def storageGUID: PlanetSideGUID = equipment.AmmoSlot.Box.GUID
|
||||||
|
|
||||||
|
def drain: Int = equipment.FireMode.RoundsPerShot
|
||||||
|
|
||||||
|
def NtuCapacitor: Float = equipment.Magazine.toFloat
|
||||||
|
|
||||||
|
def NtuCapacitor_=(value: Float): Float = equipment.Magazine_=(value.toInt).toFloat
|
||||||
|
|
||||||
|
def MaxNtuCapacitor: Float = equipment.MaxMagazine.toFloat
|
||||||
|
|
||||||
|
override def Definition: ObjectDefinition with NtuContainerDefinition = definition
|
||||||
|
|
||||||
|
def Actor: ActorRef = null
|
||||||
|
|
||||||
|
override def GUID : PlanetSideGUID = equipment.GUID
|
||||||
|
|
||||||
|
override def GUID_=(guid : PlanetSideGUID): PlanetSideGUID = equipment.GUID
|
||||||
|
|
||||||
|
override def Position: Vector3 = Vector3.Zero
|
||||||
|
|
||||||
|
override def Position_=(vec: Vector3): Vector3 = Vector3.Zero
|
||||||
|
|
||||||
|
override def Orientation: Vector3 = Vector3.Zero
|
||||||
|
|
||||||
|
override def Orientation_=(vec: Vector3): Vector3 = Vector3.Zero
|
||||||
|
|
||||||
|
override def Velocity: Option[Vector3] = None
|
||||||
|
|
||||||
|
override def Velocity_=(vec: Option[Vector3]): Option[Vector3] = None
|
||||||
|
}
|
||||||
|
|
@ -223,7 +223,7 @@ object CarrierBehavior {
|
||||||
)
|
)
|
||||||
zone.VehicleEvents ! VehicleServiceMessage(
|
zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
s"${cargo.Actor}",
|
s"${cargo.Actor}",
|
||||||
VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 68, cargo.Shields))
|
VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, cargo.Definition.shieldUiAttribute, cargo.Shields))
|
||||||
)
|
)
|
||||||
CargoMountBehaviorForAll(carrier, cargo, mountPoint)
|
CargoMountBehaviorForAll(carrier, cargo, mountPoint)
|
||||||
zone.actor ! ZoneActor.RemoveFromBlockMap(cargo)
|
zone.actor ! ZoneActor.RemoveFromBlockMap(cargo)
|
||||||
|
|
@ -470,7 +470,7 @@ object CarrierBehavior {
|
||||||
)
|
)
|
||||||
events ! VehicleServiceMessage(
|
events ! VehicleServiceMessage(
|
||||||
s"$cargoActor",
|
s"$cargoActor",
|
||||||
VehicleAction.SendResponse(GUID0, PlanetsideAttributeMessage(cargoGUID, 68, cargo.Shields))
|
VehicleAction.SendResponse(GUID0, PlanetsideAttributeMessage(cargoGUID, cargo.Definition.shieldUiAttribute, cargo.Shields))
|
||||||
)
|
)
|
||||||
zone.actor ! ZoneActor.AddToBlockMap(cargo, carrier.Position)
|
zone.actor ! ZoneActor.AddToBlockMap(cargo, carrier.Position)
|
||||||
if (carrier.isFlying) {
|
if (carrier.isFlying) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.vehicles
|
||||||
|
|
||||||
|
import net.psforever.objects.Vehicle
|
||||||
|
import net.psforever.objects.ballistics.{Projectile, ProjectileQuality, SourceEntry}
|
||||||
|
import net.psforever.objects.vital.Vitality
|
||||||
|
import net.psforever.objects.vital.base.{DamageResolution, DamageType}
|
||||||
|
import net.psforever.objects.vital.etc.RadiationReason
|
||||||
|
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||||
|
import net.psforever.objects.zones.blockmap.SectorPopulation
|
||||||
|
import net.psforever.objects.zones.{InteractsWithZone, Zone, ZoneInteraction, ZoneInteractionType}
|
||||||
|
import net.psforever.types.PlanetSideGUID
|
||||||
|
|
||||||
|
case object RadiationInVehicleInteraction extends ZoneInteractionType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This game entity may infrequently test whether it may interact with radiation cloud projectiles
|
||||||
|
* that may be emitted in the game environment for a limited amount of time.
|
||||||
|
* Since the entity in question is a vehicle, the occupants of the vehicle get tested their interaction.
|
||||||
|
*/
|
||||||
|
class InteractWithRadiationCloudsSeatedInVehicle(
|
||||||
|
private val obj: Vehicle,
|
||||||
|
val range: Float
|
||||||
|
) extends ZoneInteraction {
|
||||||
|
/**
|
||||||
|
* radiation clouds that, though detected, are skipped from affecting the target;
|
||||||
|
* in between interaction tests, a memory of the clouds that were tested last are retained and
|
||||||
|
* are excluded from being tested this next time;
|
||||||
|
* clouds that are detected a second time are cleared from the list and are available to be tested next time
|
||||||
|
*/
|
||||||
|
private var skipTargets: List[PlanetSideGUID] = List()
|
||||||
|
|
||||||
|
def Type = RadiationInVehicleInteraction
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drive into a radiation cloud and all the vehicle's occupants suffer the consequences.
|
||||||
|
* @param sector the portion of the block map being tested
|
||||||
|
* @param target the fixed element in this test
|
||||||
|
*/
|
||||||
|
override def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = {
|
||||||
|
val position = target.Position
|
||||||
|
//collect all projectiles in sector/range
|
||||||
|
val projectiles = sector
|
||||||
|
.projectileList
|
||||||
|
.filter { cloud =>
|
||||||
|
val definition = cloud.Definition
|
||||||
|
definition.radiation_cloud &&
|
||||||
|
definition.AllDamageTypes.contains(DamageType.Radiation) &&
|
||||||
|
{
|
||||||
|
val radius = definition.DamageRadius
|
||||||
|
Zone.distanceCheck(target, cloud, radius * radius)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.distinct
|
||||||
|
val notSkipped = projectiles.filterNot { t => skipTargets.contains(t.GUID) }
|
||||||
|
skipTargets = notSkipped.map { _.GUID }
|
||||||
|
if (notSkipped.nonEmpty) {
|
||||||
|
(
|
||||||
|
//isolate one of each type of projectile
|
||||||
|
notSkipped
|
||||||
|
.foldLeft(Nil: List[Projectile]) {
|
||||||
|
(acc, next) => if (acc.exists { _.profile == next.profile }) acc else next :: acc
|
||||||
|
},
|
||||||
|
obj.Seats
|
||||||
|
.values
|
||||||
|
.collect { case seat => seat.occupant }
|
||||||
|
.flatten
|
||||||
|
) match {
|
||||||
|
case (uniqueProjectiles, targets) if uniqueProjectiles.nonEmpty && targets.nonEmpty =>
|
||||||
|
val shielding = obj.Definition.RadiationShielding
|
||||||
|
targets.foreach { t =>
|
||||||
|
uniqueProjectiles.foreach { p =>
|
||||||
|
t.Actor ! Vitality.Damage(
|
||||||
|
DamageInteraction(
|
||||||
|
SourceEntry(t),
|
||||||
|
RadiationReason(
|
||||||
|
ProjectileQuality.modifiers(p, DamageResolution.Radiation, t, t.Position, None),
|
||||||
|
t.DamageModel,
|
||||||
|
shielding
|
||||||
|
),
|
||||||
|
position
|
||||||
|
).calculate()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj.CargoHolds
|
||||||
|
.values
|
||||||
|
.collect {
|
||||||
|
case hold if hold.isOccupied =>
|
||||||
|
val target = hold.occupant.get
|
||||||
|
target.interaction().find { _.Type == RadiationInVehicleInteraction } match {
|
||||||
|
case Some(func) => func.interaction(sector, target)
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any radiation clouds blocked from being tested should be cleared.
|
||||||
|
* All that can be done is blanking our retained previous effect targets.
|
||||||
|
* @param target the fixed element in this test
|
||||||
|
*/
|
||||||
|
def resetInteraction(target: InteractsWithZone): Unit = {
|
||||||
|
skipTargets = List()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,21 +15,26 @@ trait MountableWeapons
|
||||||
* @param seatNumber the mount number
|
* @param seatNumber the mount number
|
||||||
* @return a mounted weapon by index, or `None` if either the mount doesn't exist or there is no controlled weapon
|
* @return a mounted weapon by index, or `None` if either the mount doesn't exist or there is no controlled weapon
|
||||||
*/
|
*/
|
||||||
def WeaponControlledFromSeat(seatNumber: Int): Option[Equipment] = {
|
def WeaponControlledFromSeat(seatNumber: Int): Set[Equipment] = {
|
||||||
Definition
|
Definition
|
||||||
.asInstanceOf[MountableWeaponsDefinition]
|
.asInstanceOf[MountableWeaponsDefinition]
|
||||||
.controlledWeapons.get(seatNumber) match {
|
.controlledWeapons().get(seatNumber) match {
|
||||||
case Some(wepNumber) if seats.get(seatNumber).nonEmpty => controlledWeapon(wepNumber)
|
case Some(wepNumbers) if seats.get(seatNumber).nonEmpty => wepNumbers.flatMap { controlledWeapon }
|
||||||
case _ => None
|
case _ => Set.empty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def controlledWeapon(wepNumber: Int): Option[Equipment] = ControlledWeapon(wepNumber)
|
def controlledWeapon(wepNumber: Int): Set[Equipment] = ControlledWeapon(wepNumber)
|
||||||
|
|
||||||
def ControlledWeapon(wepNumber: Int): Option[Equipment] = {
|
def ControlledWeapon(wepNumber: Int): Set[Equipment] = {
|
||||||
weapons.get(wepNumber) match {
|
weapons.get(wepNumber) match {
|
||||||
case Some(slot) => slot.Equipment
|
case Some(slot) =>
|
||||||
case _ => None
|
slot.Equipment match {
|
||||||
|
case Some(weapon) => Set(weapon)
|
||||||
|
case None => Set.empty
|
||||||
|
}
|
||||||
|
case _ =>
|
||||||
|
Set.empty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,17 @@ import scala.collection.mutable
|
||||||
trait MountableWeaponsDefinition
|
trait MountableWeaponsDefinition
|
||||||
extends MountedWeaponsDefinition
|
extends MountedWeaponsDefinition
|
||||||
with MountableDefinition {
|
with MountableDefinition {
|
||||||
val controlledWeapons: mutable.HashMap[Int, Int] = mutable.HashMap[Int, Int]()
|
private val _controlledWeapons: mutable.HashMap[Int, Set[Int]] = mutable.HashMap[Int, Set[Int]]()
|
||||||
|
|
||||||
|
def controlledWeapons(): Map[Int, Set[Int]] = _controlledWeapons.toMap
|
||||||
|
|
||||||
|
def controlledWeapons(seat: Int, weapon: Int): Map[Int, Set[Int]] = {
|
||||||
|
_controlledWeapons.put(seat, Set(weapon))
|
||||||
|
_controlledWeapons.toMap
|
||||||
|
}
|
||||||
|
|
||||||
|
def controlledWeapons(seat: Int, weapons: Set[Int]): Map[Int, Set[Int]] = {
|
||||||
|
_controlledWeapons.put(seat, weapons)
|
||||||
|
_controlledWeapons.toMap
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1165
src/main/scala/net/psforever/objects/vehicles/VehicleSubsystem.scala
Normal file
1165
src/main/scala/net/psforever/objects/vehicles/VehicleSubsystem.scala
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -21,9 +21,6 @@ class AntControl(vehicle: Vehicle)
|
||||||
with AntTransferBehavior {
|
with AntTransferBehavior {
|
||||||
def ChargeTransferObject = vehicle
|
def ChargeTransferObject = vehicle
|
||||||
|
|
||||||
findChargeTargetFunc = Vehicles.FindANTChargingSource
|
|
||||||
findDischargeTargetFunc = Vehicles.FindANTDischargingTarget
|
|
||||||
|
|
||||||
override def commonEnabledBehavior: Receive = super.commonEnabledBehavior.orElse(antBehavior)
|
override def commonEnabledBehavior: Receive = super.commonEnabledBehavior.orElse(antBehavior)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,17 @@
|
||||||
package net.psforever.objects.vehicles.control
|
package net.psforever.objects.vehicles.control
|
||||||
|
|
||||||
import net.psforever.objects._
|
import net.psforever.objects._
|
||||||
|
import net.psforever.objects.equipment.{EffectTarget, TargetValidation}
|
||||||
import net.psforever.objects.serverobject.damage.Damageable.Target
|
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||||
|
import net.psforever.objects.vital.base.DamageType
|
||||||
import net.psforever.objects.vital.interaction.DamageResult
|
import net.psforever.objects.vital.interaction.DamageResult
|
||||||
|
import net.psforever.objects.vital.projectile.MaxDistanceCutoff
|
||||||
|
import net.psforever.objects.vital.prop.DamageWithPosition
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
import net.psforever.packet.game.{TriggerEffectMessage, TriggeredEffectLocation}
|
import net.psforever.packet.game.{TriggerEffectMessage, TriggeredEffectLocation}
|
||||||
import net.psforever.services.Service
|
import net.psforever.services.Service
|
||||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||||
|
import net.psforever.types.PlanetSideGUID
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A vehicle control agency exclusive to the armored personnel carrier (APC) ground transport vehicles.
|
* A vehicle control agency exclusive to the armored personnel carrier (APC) ground transport vehicles.
|
||||||
|
|
@ -20,97 +22,112 @@ import scala.concurrent.duration._
|
||||||
* @param vehicle the APC
|
* @param vehicle the APC
|
||||||
*/
|
*/
|
||||||
class ApcControl(vehicle: Vehicle)
|
class ApcControl(vehicle: Vehicle)
|
||||||
extends VehicleControl(vehicle) {
|
extends VehicleControl(vehicle)
|
||||||
protected var capacitor = Default.Cancellable
|
with VehicleCapacitance {
|
||||||
|
def CapacitanceObject: Vehicle = vehicle
|
||||||
startCapacitorTimer()
|
|
||||||
|
|
||||||
override def postStop() : Unit = {
|
override def postStop() : Unit = {
|
||||||
super.postStop()
|
super.postStop()
|
||||||
capacitor.cancel()
|
capacitancePostStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
override def commonEnabledBehavior : Receive =
|
override def commonEnabledBehavior : Receive = super.commonEnabledBehavior
|
||||||
super.commonEnabledBehavior
|
.orElse(capacitorBehavior)
|
||||||
.orElse {
|
.orElse {
|
||||||
case ApcControl.CapacitorCharge(amount) =>
|
case SpecialEmp.Burst() =>
|
||||||
if (vehicle.Capacitor < vehicle.Definition.MaxCapacitor) {
|
performEmpBurst()
|
||||||
val capacitance = vehicle.Capacitor += amount
|
|
||||||
vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
|
|
||||||
self.toString(),
|
|
||||||
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vehicle.GUID, 113, capacitance)
|
|
||||||
)
|
|
||||||
startCapacitorTimer()
|
|
||||||
} else {
|
|
||||||
capacitor = Default.Cancellable
|
|
||||||
}
|
|
||||||
|
|
||||||
case SpecialEmp.Burst() =>
|
case _ => ;
|
||||||
if (vehicle.Capacitor == vehicle.Definition.MaxCapacitor) { //only if the capacitor is full
|
}
|
||||||
val zone = vehicle.Zone
|
|
||||||
val events = zone.VehicleEvents
|
def performEmpBurst(): Unit = {
|
||||||
val pos = vehicle.Position
|
val obj = CapacitanceObject
|
||||||
val GUID0 = Service.defaultPlayerGUID
|
if (obj.Capacitor == obj.Definition.MaxCapacitor) { //only if the capacitor is full
|
||||||
val emp = vehicle.Definition.innateDamage.getOrElse { SpecialEmp.emp }
|
val zone = obj.Zone
|
||||||
val faction = vehicle.Faction
|
val events = zone.VehicleEvents
|
||||||
//drain the capacitor
|
val pos = obj.Position
|
||||||
vehicle.Capacitor = 0
|
val GUID0 = Service.defaultPlayerGUID
|
||||||
events ! VehicleServiceMessage(
|
val emp = ApcControl.apc_emp
|
||||||
self.toString(),
|
val faction = obj.Faction
|
||||||
VehicleAction.PlanetsideAttribute(GUID0, vehicle.GUID, 113, 0)
|
//drain the capacitor
|
||||||
)
|
capacitorCharge(-vehicle.Capacitor)
|
||||||
//cause the emp
|
//cause the emp
|
||||||
events ! VehicleServiceMessage(
|
events ! VehicleServiceMessage(
|
||||||
zone.id,
|
zone.id,
|
||||||
VehicleAction.SendResponse(
|
VehicleAction.SendResponse(
|
||||||
GUID0,
|
GUID0,
|
||||||
TriggerEffectMessage(
|
TriggerEffectMessage(
|
||||||
GUID0,
|
GUID0,
|
||||||
s"apc_explosion_emp_${faction.toString.toLowerCase}",
|
s"apc_explosion_emp_${faction.toString.toLowerCase}",
|
||||||
None,
|
None,
|
||||||
Some(TriggeredEffectLocation(pos, vehicle.Orientation))
|
Some(TriggeredEffectLocation(pos, obj.Orientation))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
//resolve what targets are affected by the emp
|
//resolve what targets are affected by the emp
|
||||||
Zone.serverSideDamage(
|
Zone.serverSideDamage(
|
||||||
zone,
|
zone,
|
||||||
vehicle,
|
obj,
|
||||||
emp,
|
emp,
|
||||||
SpecialEmp.createEmpInteraction(emp, pos),
|
SpecialEmp.createEmpInteraction(emp, pos),
|
||||||
ExplosiveDeployableControl.detectionForExplosiveSource(vehicle),
|
ExplosiveDeployableControl.detectionForExplosiveSource(obj),
|
||||||
Zone.findAllTargets
|
Zone.findAllTargets
|
||||||
)
|
)
|
||||||
//start charging again
|
//start charging again
|
||||||
startCapacitorTimer()
|
//startCapacitorTimer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
|
override def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
|
||||||
super.PrepareForDisabled(kickPassengers)
|
super.PrepareForDisabled(kickPassengers)
|
||||||
capacitor.cancel()
|
capacitanceStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = {
|
override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = {
|
||||||
super.DestructionAwareness(target, cause)
|
super.DestructionAwareness(target, cause)
|
||||||
capacitor.cancel()
|
capacitancePostStop()
|
||||||
vehicle.Capacitor = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO switch from magic numbers to definition numbers?
|
override def parseObjectAction(guid: PlanetSideGUID, action: Int, other: Option[Any]): Unit = {
|
||||||
private def startCapacitorTimer(): Unit = {
|
super.parseObjectAction(guid, action, other)
|
||||||
capacitor = context.system.scheduler.scheduleOnce(
|
if (action == 55) {
|
||||||
delay = 1000 millisecond,
|
performEmpBurst()
|
||||||
self,
|
}
|
||||||
ApcControl.CapacitorCharge(10)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ApcControl {
|
object ApcControl {
|
||||||
/**
|
final val apc_emp = new DamageWithPosition {
|
||||||
* Charge the vehicle's internal capacitor by the given amount during the schedulefd charge event.
|
CausesDamageType = DamageType.Splash
|
||||||
* @param amount how much energy in the charge
|
SympatheticExplosion = true
|
||||||
*/
|
Damage0 = 0
|
||||||
private case class CapacitorCharge(amount: Int)
|
DamageAtEdge = 1.0f
|
||||||
|
DamageRadius = 15f
|
||||||
|
AdditionalEffect = true
|
||||||
|
JammedEffectDuration += TargetValidation(
|
||||||
|
EffectTarget.Category.Player,
|
||||||
|
EffectTarget.Validation.Player
|
||||||
|
) -> 1000
|
||||||
|
JammedEffectDuration += TargetValidation(
|
||||||
|
EffectTarget.Category.Vehicle,
|
||||||
|
EffectTarget.Validation.AMS
|
||||||
|
) -> 5000
|
||||||
|
JammedEffectDuration += TargetValidation(
|
||||||
|
EffectTarget.Category.Deployable,
|
||||||
|
EffectTarget.Validation.MotionSensor
|
||||||
|
) -> 30000
|
||||||
|
JammedEffectDuration += TargetValidation(
|
||||||
|
EffectTarget.Category.Deployable,
|
||||||
|
EffectTarget.Validation.Spitfire
|
||||||
|
) -> 30000
|
||||||
|
JammedEffectDuration += TargetValidation(
|
||||||
|
EffectTarget.Category.Turret,
|
||||||
|
EffectTarget.Validation.Turret
|
||||||
|
) -> 30000
|
||||||
|
JammedEffectDuration += TargetValidation(
|
||||||
|
EffectTarget.Category.Vehicle,
|
||||||
|
EffectTarget.Validation.VehicleNotAMS
|
||||||
|
) -> 10000
|
||||||
|
Modifiers = MaxDistanceCutoff
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,631 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.vehicles.control
|
||||||
|
|
||||||
|
import akka.actor.Cancellable
|
||||||
|
import net.psforever.objects._
|
||||||
|
import net.psforever.objects.ballistics.VehicleSource
|
||||||
|
import net.psforever.objects.definition.{ToolDefinition, VehicleDefinition}
|
||||||
|
import net.psforever.objects.equipment._
|
||||||
|
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
|
||||||
|
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||||
|
import net.psforever.objects.serverobject.containable.ContainableBehavior
|
||||||
|
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||||
|
import net.psforever.objects.serverobject.transfer.TransferBehavior
|
||||||
|
import net.psforever.objects.vehicles._
|
||||||
|
import net.psforever.objects.vital.VehicleShieldCharge
|
||||||
|
import net.psforever.objects.vital.interaction.DamageResult
|
||||||
|
import net.psforever.objects.zones.Zone
|
||||||
|
import net.psforever.packet.game._
|
||||||
|
import net.psforever.services.Service
|
||||||
|
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||||
|
import net.psforever.types._
|
||||||
|
|
||||||
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A vehicle control agency exclusive to the battleframe robotics (BFR) combat vehicle system.
|
||||||
|
* @param vehicle the battleframe robotics unit
|
||||||
|
*/
|
||||||
|
class BfrControl(vehicle: Vehicle)
|
||||||
|
extends VehicleControl(vehicle)
|
||||||
|
with BfrTransferBehavior
|
||||||
|
with ArmorSiphonBehavior.SiphonOwner {
|
||||||
|
/** shield-auto charge */
|
||||||
|
var shieldCharge: Cancellable = Default.Cancellable
|
||||||
|
|
||||||
|
def SiphoningObject = vehicle
|
||||||
|
|
||||||
|
def ChargeTransferObject = vehicle
|
||||||
|
|
||||||
|
if (vehicle.Shields < vehicle.MaxShields) {
|
||||||
|
chargeShields(amount = 0) //start charging if starts as uncharged
|
||||||
|
}
|
||||||
|
|
||||||
|
override def postStop(): Unit = {
|
||||||
|
super.postStop()
|
||||||
|
shieldCharge.cancel()
|
||||||
|
repairPostStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
def explosionBehavior: Receive = {
|
||||||
|
case BfrControl.VehicleExplosion =>
|
||||||
|
val guid = vehicle.GUID
|
||||||
|
val guid0 = Service.defaultPlayerGUID
|
||||||
|
val zone = vehicle.Zone
|
||||||
|
val zoneid = zone.id
|
||||||
|
val events = zone.VehicleEvents
|
||||||
|
events ! VehicleServiceMessage(
|
||||||
|
zoneid,
|
||||||
|
VehicleAction.GenericObjectAction(guid0, guid, 46)
|
||||||
|
)
|
||||||
|
context.system.scheduler.scheduleOnce(delay = 500 milliseconds, self, BfrControl.VehicleExplosion)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def commonEnabledBehavior: Receive = super.commonEnabledBehavior
|
||||||
|
.orElse(siphonRepairBehavior)
|
||||||
|
.orElse(bfrBehavior)
|
||||||
|
.orElse(explosionBehavior)
|
||||||
|
.orElse {
|
||||||
|
case CommonMessages.Use(_, Some(item: Tool)) =>
|
||||||
|
if (GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition)) {
|
||||||
|
context.system.scheduler.scheduleOnce(
|
||||||
|
delay = 1000 milliseconds,
|
||||||
|
self,
|
||||||
|
TransferBehavior.Charging(Ntu.Nanites)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
case SpecialEmp.Burst() =>
|
||||||
|
performEmpBurst()
|
||||||
|
}
|
||||||
|
|
||||||
|
override def commonDisabledBehavior: Receive = super.commonDisabledBehavior.orElse(explosionBehavior)
|
||||||
|
|
||||||
|
override def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
|
||||||
|
super.PrepareForDisabled(kickPassengers)
|
||||||
|
if (vehicle.Health == 0) {
|
||||||
|
//shield off
|
||||||
|
disableShield()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def DamageAwareness(target: Target, cause: DamageResult, amount: Any) : Unit = {
|
||||||
|
super.DamageAwareness(target, cause, amount)
|
||||||
|
//manage shield display and charge
|
||||||
|
disableShieldIfDrained()
|
||||||
|
if (shieldCharge != Default.Cancellable && vehicle.Shields < vehicle.MaxShields) {
|
||||||
|
shieldCharge.cancel()
|
||||||
|
shieldCharge = context.system.scheduler.scheduleOnce(
|
||||||
|
delay = vehicle.Definition.ShieldDamageDelay milliseconds,
|
||||||
|
self,
|
||||||
|
Vehicle.ChargeShields(0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def destructionDelayed(delay: Long, cause: DamageResult): Unit = {
|
||||||
|
super.destructionDelayed(delay, cause)
|
||||||
|
shieldCharge.cancel()
|
||||||
|
shieldCharge = Default.Cancellable
|
||||||
|
//harmless boom boom's
|
||||||
|
context.system.scheduler.scheduleOnce(delay = 0 milliseconds, self, BfrControl.VehicleExplosion)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def DestructionAwareness(target: Target, cause: DamageResult): Unit = {
|
||||||
|
super.DestructionAwareness(target, cause)
|
||||||
|
shieldCharge.cancel()
|
||||||
|
shieldCharge = Default.Cancellable
|
||||||
|
disableShield()
|
||||||
|
}
|
||||||
|
|
||||||
|
override def RemoveItemFromSlotCallback(item: Equipment, slot: Int): Unit = {
|
||||||
|
BfrControl.dimorphics.find { _.contains(item.Definition) } match {
|
||||||
|
case Some(dimorph) if vehicle.VisibleSlots.contains(slot) => //revert to a generic variant
|
||||||
|
Tool.LoadDefinition(
|
||||||
|
item.asInstanceOf[Tool],
|
||||||
|
dimorph.transform(Handiness.Generic).asInstanceOf[ToolDefinition]
|
||||||
|
)
|
||||||
|
case _ => ; //no dimorphic entry; place as-is
|
||||||
|
}
|
||||||
|
val guid0 = PlanetSideGUID(0)
|
||||||
|
//if the weapon arm is disabled, enable it for later (makes life easy)
|
||||||
|
parseObjectAction(guid0, BfrControl.ArmState.Enabled, Some(slot))
|
||||||
|
//enable the other arm weapon regardless
|
||||||
|
parseObjectAction(guid0, BfrControl.ArmState.Enabled, Some(
|
||||||
|
//budget logic: the arm weapons are "next to each other" index-wise
|
||||||
|
if (vehicle.Weapons.keys.min == slot) { slot + 1 } else { slot - 1 }
|
||||||
|
))
|
||||||
|
super.RemoveItemFromSlotCallback(item, slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def PutItemInSlotCallback(item: Equipment, slot: Int): Unit = {
|
||||||
|
val definition = item.Definition
|
||||||
|
val handiness = BfrControl.dimorphics.find { _.contains(definition) } match {
|
||||||
|
case Some(dimorph) if vehicle.VisibleSlots.contains(slot) => //left-handed or right-handed variant
|
||||||
|
val handiness = bfrHandiness(slot)
|
||||||
|
Tool.LoadDefinition(
|
||||||
|
item.asInstanceOf[Tool],
|
||||||
|
dimorph.transform(handiness).asInstanceOf[ToolDefinition]
|
||||||
|
)
|
||||||
|
handiness
|
||||||
|
case Some(dimorph) => //revert to a generic variant
|
||||||
|
Tool.LoadDefinition(
|
||||||
|
item.asInstanceOf[Tool],
|
||||||
|
dimorph.transform(Handiness.Generic).asInstanceOf[ToolDefinition]
|
||||||
|
)
|
||||||
|
Handiness.Generic
|
||||||
|
case None => //no dimorphic entry; place as-is
|
||||||
|
Handiness.Generic
|
||||||
|
}
|
||||||
|
super.PutItemInSlotCallback(item, slot)
|
||||||
|
specialArmWeaponEquipManagement(item, slot, handiness)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def dismountCleanup(seatBeingDismounted: Int): Unit = {
|
||||||
|
super.dismountCleanup(seatBeingDismounted)
|
||||||
|
if (!vehicle.Seats.values.exists(_.isOccupied)) {
|
||||||
|
vehicle.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator) match {
|
||||||
|
case Some(subsys) =>
|
||||||
|
if (vehicle.Shields > 0) {
|
||||||
|
vehicleSubsystemMessages(
|
||||||
|
if (subsys.Enabled && !subsys.Enabled_=(state = false)) {
|
||||||
|
//turn off shield visually
|
||||||
|
subsys.changedMessages(vehicle)
|
||||||
|
} else if (subsys.Jammed || subsys.stateOfStatus(statusName = "Damaged").contains(false)) {
|
||||||
|
//hard coded: shield is "off" functionally, turn off static effect and turn off standard shield swirl
|
||||||
|
ComponentDamageMessage(vehicle.GUID, SubsystemComponent.ShieldGeneratorOffline, None) +:
|
||||||
|
BattleframeShieldGeneratorOffline.getMessage(SubsystemComponent.ShieldGeneratorOffline, vehicle, vehicle.GUID)
|
||||||
|
} else {
|
||||||
|
//shield is already off visually
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def mountCleanup(mount_point: Int, user: Player): Unit = {
|
||||||
|
super.mountCleanup(mount_point, user)
|
||||||
|
if (vehicle.Seats.values.exists(_.isOccupied)) {
|
||||||
|
vehicle.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator) match {
|
||||||
|
case Some(subsys)
|
||||||
|
if !subsys.Enabled && vehicle.Shields > 0 && subsys.Enabled_=(state = true) =>
|
||||||
|
//if the shield is damaged, it does not turn on until the damaged is cleared
|
||||||
|
vehicleSubsystemMessages(subsys.changedMessages(vehicle))
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def permitTerminalMessage(player: Player, msg: ItemTransactionMessage): Boolean = {
|
||||||
|
if (msg.transaction_type == TransactionType.Loadout) {
|
||||||
|
!vehicle.Jammed
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def handleTerminalMessageVehicleLoadout(
|
||||||
|
player: Player,
|
||||||
|
definition: VehicleDefinition,
|
||||||
|
weapons: List[InventoryItem],
|
||||||
|
inventory: List[InventoryItem]
|
||||||
|
): (
|
||||||
|
List[(Equipment, PlanetSideGUID)],
|
||||||
|
List[InventoryItem],
|
||||||
|
List[(Equipment, PlanetSideGUID)],
|
||||||
|
List[InventoryItem]
|
||||||
|
) = {
|
||||||
|
val vFaction = vehicle.Faction
|
||||||
|
val vWeapons = vehicle.Weapons
|
||||||
|
//remove old inventory
|
||||||
|
val oldInventory = vehicle.Inventory.Clear().map { case InventoryItem(obj, _) => (obj, obj.GUID) }
|
||||||
|
//"dropped" items are lost; if it doesn't go in the trunk, it vanishes into the nanite cloud
|
||||||
|
val (_, afterInventory) = inventory.partition(ContainableBehavior.DropPredicate(player))
|
||||||
|
val pairedArmSubsys = pairedArmSubsystems()
|
||||||
|
val (oldWeapons, newWeapons, finalInventory) = if (GlobalDefinitions.isBattleFrameVehicle(definition)) {
|
||||||
|
//vehicles are both battleframes; weapons must be swapped properly
|
||||||
|
if(vWeapons.size == 3 && GlobalDefinitions.isBattleFrameFlightVehicle(definition)) {
|
||||||
|
//battleframe is a gunner variant but loadout spec is for flight variant
|
||||||
|
// remap the hands, ignore the gunner weapon mount, and refit the trunk
|
||||||
|
val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory)
|
||||||
|
val afterWeapons = weapons
|
||||||
|
.map { item => item.start += 1; item }
|
||||||
|
(culledWeaponMounts(pairedArmSubsys.unzip._2), afterWeapons, stow)
|
||||||
|
} else if(vWeapons.size == 2 && GlobalDefinitions.isBattleFrameGunnerVehicle(definition)) {
|
||||||
|
//battleframe is a flight variant but loadout spec is for gunner variant
|
||||||
|
// remap the hands, shave the gunner mount from the spec, and refit the trunk
|
||||||
|
val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory)
|
||||||
|
val afterWeapons = weapons
|
||||||
|
.filterNot { _.obj.Size == EquipmentSize.BFRGunnerWeapon }
|
||||||
|
.map { item => item.start -= 1; item }
|
||||||
|
(culledWeaponMounts(vWeapons.values), afterWeapons, stow)
|
||||||
|
} else {
|
||||||
|
//same variant type of battleframe
|
||||||
|
// place as-is
|
||||||
|
(culledWeaponMounts(vWeapons.values), weapons, afterInventory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//vehicle loadout is not for this vehicle; do not transfer over weapon ammo
|
||||||
|
if (
|
||||||
|
vehicle.Definition.TrunkSize == definition.TrunkSize && vehicle.Definition.TrunkOffset == definition.TrunkOffset
|
||||||
|
) {
|
||||||
|
(Nil, Nil, afterInventory) //trunk is the same dimensions, however
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//accommodate as much of inventory as possible
|
||||||
|
val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory)
|
||||||
|
(Nil, Nil, stow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finalInventory.foreach {
|
||||||
|
_.obj.Faction = vFaction
|
||||||
|
}
|
||||||
|
(oldWeapons, newWeapons, oldInventory, finalInventory)
|
||||||
|
}
|
||||||
|
|
||||||
|
def culledWeaponMounts(values: Iterable[EquipmentSlot]): List[(Equipment, PlanetSideGUID)] = {
|
||||||
|
values.collect { case slot if slot.Equipment.nonEmpty =>
|
||||||
|
val obj = slot.Equipment.get
|
||||||
|
slot.Equipment = None
|
||||||
|
(obj, obj.GUID)
|
||||||
|
}.toList
|
||||||
|
}
|
||||||
|
|
||||||
|
def disableShieldIfDrained(): Unit = {
|
||||||
|
if (vehicle.Shields == 0) {
|
||||||
|
disableShield()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def disableShield(): Unit = {
|
||||||
|
val zone = vehicle.Zone
|
||||||
|
zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
s"${zone.id}",
|
||||||
|
VehicleAction.SendResponse(PlanetSideGUID(0), GenericObjectActionMessage(vehicle.GUID, 45))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def enableShieldIfNotDrained(): Unit = {
|
||||||
|
if (vehicle.Shields > 0) {
|
||||||
|
enableShield()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def enableShield(): Unit = {
|
||||||
|
val zone = vehicle.Zone
|
||||||
|
zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
s"${zone.id}",
|
||||||
|
VehicleAction.SendResponse(PlanetSideGUID(0), GenericObjectActionMessage(vehicle.GUID, 44))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def chargeShields(amount: Int): Unit = {
|
||||||
|
chargeShieldsOnly(amount)
|
||||||
|
shieldCharge(vehicle.Shields, vehicle.Definition, delay = 0) //continue charge?
|
||||||
|
}
|
||||||
|
|
||||||
|
def chargeShieldsOnly(amount: Int): Unit = {
|
||||||
|
val definition = vehicle.Definition
|
||||||
|
val before = vehicle.Shields
|
||||||
|
if (canChargeShields()) {
|
||||||
|
val chargeAmount = math.max(1, ((if (vehicle.DeploymentState == DriveState.Kneeling && vehicle.Seats(0).occupant.nonEmpty) {
|
||||||
|
definition.ShieldAutoRechargeSpecial
|
||||||
|
} else {
|
||||||
|
definition.ShieldAutoRecharge
|
||||||
|
}).getOrElse(amount) * vehicle.SubsystemStatusMultiplier(sys = "BattleframeShieldGenerator.RechargeRate")).toInt)
|
||||||
|
vehicle.Shields = before + chargeAmount
|
||||||
|
val after = vehicle.Shields
|
||||||
|
vehicle.History(VehicleShieldCharge(VehicleSource(vehicle), after - before))
|
||||||
|
showShieldCharge()
|
||||||
|
if (before == 0 && after > 0) {
|
||||||
|
enableShield()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def shieldCharge(delay: Long): Unit = {
|
||||||
|
shieldCharge(vehicle.Shields, vehicle.Definition, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
def shieldCharge(after:Int, definition: VehicleDefinition, delay: Long): Unit = {
|
||||||
|
shieldCharge.cancel()
|
||||||
|
if (after < definition.MaxShields && !vehicle.Jammed) {
|
||||||
|
shieldCharge = context.system.scheduler.scheduleOnce(
|
||||||
|
delay = definition.ShieldPeriodicDelay + delay milliseconds,
|
||||||
|
self,
|
||||||
|
Vehicle.ChargeShields(0)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
shieldCharge = Default.Cancellable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def showShieldCharge(): Unit = {
|
||||||
|
val vguid = vehicle.GUID
|
||||||
|
val zone = vehicle.Zone
|
||||||
|
val shields = vehicle.Shields
|
||||||
|
zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
s"${vehicle.Actor}",
|
||||||
|
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, vehicle.Definition.shieldUiAttribute, shields)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def StartJammeredStatus(target: Any, dur: Int): Unit = {
|
||||||
|
super.StartJammeredStatus(target, dur)
|
||||||
|
//cancels shield charge timer
|
||||||
|
shieldCharge(after = 0, vehicle.Definition, delay = 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def CancelJammeredStatus(target: Any): Unit = {
|
||||||
|
super.CancelJammeredStatus(target)
|
||||||
|
//restarts shield charge timer
|
||||||
|
shieldCharge(vehicle.Shields, vehicle.Definition, delay = 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def JammableMountedWeaponsJammeredStatus(target: PlanetSideServerObject with MountedWeapons, statusCode: Int): Unit = {
|
||||||
|
/** bfr weapons do not jam the same way normal vehicle weapons do */
|
||||||
|
}
|
||||||
|
|
||||||
|
override def parseObjectAction(guid: PlanetSideGUID, action: Int, other: Option[Any]): Unit = {
|
||||||
|
super.parseObjectAction(guid, action, other)
|
||||||
|
if (action == BfrControl.ArmState.Enabled || action == BfrControl.ArmState.Disabled) {
|
||||||
|
//disable or enable fire control for the left arm weapon or for the right arm weapon
|
||||||
|
((other match {
|
||||||
|
case Some(slot: Int) => (slot, bfrHandSubsystem(bfrHandiness(slot)))
|
||||||
|
case _ =>
|
||||||
|
vehicle.Weapons.find { case (_, slot) => slot.Equipment.nonEmpty && slot.Equipment.get.GUID == guid } match {
|
||||||
|
case Some((slot, _)) => (slot, bfrHandSubsystem(bfrHandiness(slot)))
|
||||||
|
case _ => (0, None)
|
||||||
|
}
|
||||||
|
}) match {
|
||||||
|
case out @ (_, Some(subsystem)) =>
|
||||||
|
if (action == BfrControl.ArmState.Enabled && !subsystem.Enabled) {
|
||||||
|
subsystem.Enabled = true
|
||||||
|
out
|
||||||
|
} else if (action == BfrControl.ArmState.Disabled && subsystem.Enabled) {
|
||||||
|
subsystem.Enabled = false
|
||||||
|
out
|
||||||
|
} else {
|
||||||
|
(0, None)
|
||||||
|
}
|
||||||
|
case _ =>
|
||||||
|
(0, None)
|
||||||
|
}) match {
|
||||||
|
case (slot, Some(_)) =>
|
||||||
|
specialArmWeaponActiveManagement(slot)
|
||||||
|
val guid0 = Service.defaultPlayerGUID
|
||||||
|
val doNotSendTo = other match {
|
||||||
|
case Some(pguid: PlanetSideGUID) => pguid
|
||||||
|
case _ => guid0
|
||||||
|
}
|
||||||
|
(if (guid == guid0) {
|
||||||
|
vehicle.Weapons(slot).Equipment match {
|
||||||
|
case Some(equip) => Some(equip.GUID)
|
||||||
|
case None => None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Some(guid)
|
||||||
|
}) match {
|
||||||
|
case Some(useThisGuid) =>
|
||||||
|
val zone = vehicle.Zone
|
||||||
|
zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
VehicleAction.GenericObjectAction(doNotSendTo, useThisGuid, action)
|
||||||
|
)
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def bfrHandiness(side: equipment.Hand): Int = {
|
||||||
|
if (side == Handiness.Left) 2
|
||||||
|
else if (side == Handiness.Right) 3
|
||||||
|
else throw new Exception("no hand associated with this slot")
|
||||||
|
}
|
||||||
|
|
||||||
|
def bfrHandiness(slot: Int): equipment.Hand = {
|
||||||
|
//for the benefit of BFR equipment slots interacting with MoveItemMessage
|
||||||
|
if (slot == 2) Handiness.Left
|
||||||
|
else if (slot == 3) Handiness.Right
|
||||||
|
else Handiness.Generic
|
||||||
|
}
|
||||||
|
|
||||||
|
def bfrHandSubsystem(side: equipment.Hand): Option[VehicleSubsystem] = {
|
||||||
|
//for the benefit of BFR equipment slots interacting with MoveItemMessage
|
||||||
|
side match {
|
||||||
|
case Handiness.Left => vehicle.Subsystems(VehicleSubsystemEntry.BattleframeLeftArm)
|
||||||
|
case Handiness.Right => vehicle.Subsystems(VehicleSubsystemEntry.BattleframeRightArm)
|
||||||
|
case _ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def specialArmWeaponEquipManagement(item: Equipment, slot: Int, handiness: equipment.Hand): Unit = {
|
||||||
|
if (item.Size == EquipmentSize.BFRArmWeapon && vehicle.VisibleSlots.contains(slot)) {
|
||||||
|
val weapons = vehicle.Weapons
|
||||||
|
//budget logic: the arm weapons are "next to each other" index-wise
|
||||||
|
val firstArmSlot = vehicle.Weapons.keys.min
|
||||||
|
val otherArmSlot = if (firstArmSlot == slot) {
|
||||||
|
slot + 1
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
slot - 1
|
||||||
|
}
|
||||||
|
val otherArmEquipment = weapons(otherArmSlot).Equipment
|
||||||
|
if ( {
|
||||||
|
val itemDef = item.Definition
|
||||||
|
GlobalDefinitions.isBattleFrameArmorSiphon(itemDef) || GlobalDefinitions.isBattleFrameNTUSiphon(itemDef)
|
||||||
|
} ||
|
||||||
|
(otherArmEquipment match {
|
||||||
|
case Some(thing) =>
|
||||||
|
//some equipment is attached to the other arm weapon mount
|
||||||
|
val otherDef = thing.Definition
|
||||||
|
GlobalDefinitions.isBattleFrameArmorSiphon(otherDef) || GlobalDefinitions.isBattleFrameNTUSiphon(otherDef)
|
||||||
|
case None =>
|
||||||
|
false
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
//installing a siphon; this siphon can safely be disabled
|
||||||
|
//alternately, installing normal equipment, but the other arm weapon is a siphon
|
||||||
|
parseObjectAction(PlanetSideGUID(0), BfrControl.ArmState.Enabled, Some(otherArmSlot)) //ensure enabled
|
||||||
|
parseObjectAction(item.GUID, BfrControl.ArmState.Disabled, Some(slot))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** since `specialArmWeaponActiveManagement` is called from `parseObjectAction`,
|
||||||
|
* and `parseObjectAction` gets called in `specialArmWeaponActiveManagement`,
|
||||||
|
* kill endless logic loops before they can happen */
|
||||||
|
var notSpecialManagingArmWeapon: Boolean = true
|
||||||
|
def specialArmWeaponActiveManagement(slotChanged: Int): Unit = {
|
||||||
|
if (notSpecialManagingArmWeapon) {
|
||||||
|
notSpecialManagingArmWeapon = false
|
||||||
|
val (thisArm, otherArm) = {
|
||||||
|
val pairedSystemsToSlots = pairedArmSlotSubsystems()
|
||||||
|
if (pairedSystemsToSlots.head._2._1 == slotChanged) {
|
||||||
|
(pairedSystemsToSlots.head, pairedSystemsToSlots(1))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
(pairedSystemsToSlots(1), pairedSystemsToSlots.head)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (thisArm._1.Enabled) {
|
||||||
|
//this arm weapon slot was enabled
|
||||||
|
if ({
|
||||||
|
val (thisArmExists, thisArmIsSiphon) = thisArm._2._2.Equipment match {
|
||||||
|
case Some(thing) =>
|
||||||
|
//some equipment is attached to the other arm weapon mount
|
||||||
|
val definition = thing.Definition
|
||||||
|
(
|
||||||
|
true,
|
||||||
|
GlobalDefinitions.isBattleFrameArmorSiphon(definition) || GlobalDefinitions.isBattleFrameNTUSiphon(definition)
|
||||||
|
)
|
||||||
|
case None =>
|
||||||
|
(false, false)
|
||||||
|
}
|
||||||
|
val (otherArmExists, otherArmIsSiphon) = otherArm._2._2.Equipment match {
|
||||||
|
case Some(thing) =>
|
||||||
|
//some equipment is attached to the other arm weapon mount
|
||||||
|
val definition = thing.Definition
|
||||||
|
(
|
||||||
|
true,
|
||||||
|
GlobalDefinitions.isBattleFrameArmorSiphon(definition) || GlobalDefinitions.isBattleFrameNTUSiphon(definition)
|
||||||
|
)
|
||||||
|
case None =>
|
||||||
|
(false, false)
|
||||||
|
}
|
||||||
|
thisArmExists && otherArmExists && (thisArmIsSiphon || otherArmIsSiphon)
|
||||||
|
}) {
|
||||||
|
//both arms weapons are installed and at least one of them is a siphon
|
||||||
|
parseObjectAction(PlanetSideGUID(0), BfrControl.ArmState.Disabled, Some(otherArm._2._1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//this arm weapon slot was disabled
|
||||||
|
thisArm._2._2.Equipment match {
|
||||||
|
case Some(item) =>
|
||||||
|
parseObjectAction(item.GUID, BfrControl.ArmState.Enabled, Some(otherArm._2._1)) //other arm must be enabled
|
||||||
|
case None =>
|
||||||
|
parseObjectAction(PlanetSideGUID(0), BfrControl.ArmState.Enabled, Some(thisArm._2._1)) //must stay enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notSpecialManagingArmWeapon = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def performEmpBurst(): Unit = {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val obj = ChargeTransferObject
|
||||||
|
val zone = obj.Zone
|
||||||
|
val events = zone.VehicleEvents
|
||||||
|
val GUID0 = Service.defaultPlayerGUID
|
||||||
|
getNtuContainer() match {
|
||||||
|
case Some(siphon : NtuSiphon)
|
||||||
|
if GlobalDefinitions.isBattleFrameNTUSiphon(siphon.equipment.Definition) &&
|
||||||
|
siphon.equipment.FireModeIndex == 1 &&
|
||||||
|
siphon.NtuCapacitor > 29 =>
|
||||||
|
val elapsedWait = now - siphon.equipment.lastDischarge
|
||||||
|
if (elapsedWait >= 30000) {
|
||||||
|
val pos = obj.Position
|
||||||
|
val emp = siphon.equipment.Projectile
|
||||||
|
val faction = obj.Faction
|
||||||
|
//need at least 30 ntu, so consume the charge
|
||||||
|
siphon.NtuCapacitor -= 30
|
||||||
|
UpdateNtuUI(obj, siphon)
|
||||||
|
//cause the emp
|
||||||
|
siphon.equipment.lastDischarge = now
|
||||||
|
//TODO this is the apc emp effect; is there an ntu siphon emp effect?
|
||||||
|
events ! VehicleServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
VehicleAction.SendResponse(
|
||||||
|
GUID0,
|
||||||
|
TriggerEffectMessage(
|
||||||
|
GUID0,
|
||||||
|
s"apc_explosion_emp_${faction.toString.toLowerCase}",
|
||||||
|
None,
|
||||||
|
Some(TriggeredEffectLocation(pos, obj.Orientation))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
//resolve what targets are affected by the emp
|
||||||
|
Zone.serverSideDamage(
|
||||||
|
zone,
|
||||||
|
obj,
|
||||||
|
emp,
|
||||||
|
SpecialEmp.createEmpInteraction(emp, pos),
|
||||||
|
ExplosiveDeployableControl.detectionForExplosiveSource(obj),
|
||||||
|
Zone.findAllTargets
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
//the siphon is not ready to dispatch another emp; chat message borrowed from kit use logic
|
||||||
|
//the client actually enforces a hard limit of 30s before it will react to use of the siphon emp mode
|
||||||
|
//it does not even dispatch the packet before that, making it rare if this precautionary message is seen
|
||||||
|
events ! VehicleServiceMessage(
|
||||||
|
obj.Seats(0).occupant.get.Name,
|
||||||
|
VehicleAction.SendResponse(
|
||||||
|
GUID0,
|
||||||
|
ChatMsg(ChatMessageType.UNK_225, wideContents = false, "", s"@TimeUntilNextUse^${30000 - elapsedWait}", None)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object BfrControl {
|
||||||
|
/** arm state values related to the `GenericObjectActionMessage` action codes */
|
||||||
|
object ArmState extends Enumeration {
|
||||||
|
final val Enabled = 38
|
||||||
|
final val Disabled = 39
|
||||||
|
}
|
||||||
|
|
||||||
|
private case object VehicleExplosion
|
||||||
|
|
||||||
|
val dimorphics: List[EquipmentHandiness] = {
|
||||||
|
import GlobalDefinitions._
|
||||||
|
List(
|
||||||
|
EquipmentHandiness(aphelion_armor_siphon, aphelion_armor_siphon_left, aphelion_armor_siphon_right),
|
||||||
|
EquipmentHandiness(aphelion_laser, aphelion_laser_left, aphelion_laser_right),
|
||||||
|
EquipmentHandiness(aphelion_ntu_siphon, aphelion_ntu_siphon_left, aphelion_ntu_siphon_right),
|
||||||
|
EquipmentHandiness(aphelion_ppa, aphelion_ppa_left, aphelion_ppa_right),
|
||||||
|
EquipmentHandiness(aphelion_starfire, aphelion_starfire_left, aphelion_starfire_right),
|
||||||
|
EquipmentHandiness(colossus_armor_siphon, colossus_armor_siphon_left, colossus_armor_siphon_right),
|
||||||
|
EquipmentHandiness(colossus_burster, colossus_burster_left, colossus_burster_right),
|
||||||
|
EquipmentHandiness(colossus_chaingun, colossus_chaingun_left, colossus_chaingun_right),
|
||||||
|
EquipmentHandiness(colossus_ntu_siphon, colossus_ntu_siphon_left, colossus_ntu_siphon_right),
|
||||||
|
EquipmentHandiness(colossus_tank_cannon, colossus_tank_cannon_left, colossus_tank_cannon_right),
|
||||||
|
EquipmentHandiness(peregrine_armor_siphon, peregrine_armor_siphon_left, peregrine_armor_siphon_right),
|
||||||
|
EquipmentHandiness(peregrine_dual_machine_gun, peregrine_dual_machine_gun_left, peregrine_dual_machine_gun_right),
|
||||||
|
EquipmentHandiness(peregrine_mechhammer, peregrine_mechhammer_left, peregrine_mechhammer_right),
|
||||||
|
EquipmentHandiness(peregrine_ntu_siphon, peregrine_ntu_siphon_left, peregrine_ntu_siphon_right),
|
||||||
|
EquipmentHandiness(peregrine_sparrow, peregrine_sparrow_left, peregrine_sparrow_right)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.vehicles.control
|
||||||
|
|
||||||
|
import net.psforever.objects.equipment.Handiness
|
||||||
|
import net.psforever.objects.{Vehicle, equipment}
|
||||||
|
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||||
|
import net.psforever.objects.vehicles.{VehicleSubsystem, VehicleSubsystemEntry}
|
||||||
|
import net.psforever.objects.vital.interaction.DamageResult
|
||||||
|
import net.psforever.types.Vector3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ...
|
||||||
|
*/
|
||||||
|
class BfrFlightControl(vehicle: Vehicle)
|
||||||
|
extends BfrControl(vehicle)
|
||||||
|
with VehicleCapacitance {
|
||||||
|
def CapacitanceObject: Vehicle = vehicle
|
||||||
|
|
||||||
|
var flying: Option[Boolean] = None
|
||||||
|
|
||||||
|
override def postStop() : Unit = {
|
||||||
|
super.postStop()
|
||||||
|
capacitancePostStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override def commonEnabledBehavior: Receive = super.commonEnabledBehavior
|
||||||
|
.orElse(capacitorBehavior)
|
||||||
|
.orElse {
|
||||||
|
case BfrFlight.Soaring(flightValue) =>
|
||||||
|
val localFlyingValue = flying
|
||||||
|
vehicle.Flying = Some(flightValue)
|
||||||
|
//capacitor drain
|
||||||
|
if (vehicle.Capacitor > 0) {
|
||||||
|
val definition = vehicle.Definition
|
||||||
|
val (_, cdrain) = if (flightValue == 0 || flightValue == -0) {
|
||||||
|
(0, vehicle.Capacitor)
|
||||||
|
} else {
|
||||||
|
val vdrain = if (flightValue > 0) definition.CapacitorDrain else 0
|
||||||
|
val hdrain = if ({
|
||||||
|
val vec = vehicle.Velocity.getOrElse(Vector3.Zero).xy
|
||||||
|
vec.x > 0.5f || vec.y > 0.5f
|
||||||
|
}) definition.CapacitorDrainSpecial else 0
|
||||||
|
(vdrain, vdrain + hdrain)
|
||||||
|
}
|
||||||
|
flying = Some(if (cdrain > 0) {
|
||||||
|
val modDrain = math.max(1, (cdrain * vehicle.SubsystemStatusMultiplier(sys = "BattleframeFlightPod.UseRate")).toInt)
|
||||||
|
if (super.capacitorOnlyCharge(-modDrain) || vehicle.Capacitor < vehicle.Definition.MaxCapacitor) {
|
||||||
|
startCapacitorTimer()
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
//shield drain
|
||||||
|
if (vehicle.Shields > 0) {
|
||||||
|
vehicle.Definition.ShieldDrain match {
|
||||||
|
case Some(drain) if localFlyingValue.isEmpty =>
|
||||||
|
//shields off
|
||||||
|
disableShield()
|
||||||
|
vehicle.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator).get.Enabled = false
|
||||||
|
vehicle.Shields -= drain
|
||||||
|
showShieldCharge()
|
||||||
|
case None if localFlyingValue.isEmpty =>
|
||||||
|
//shields off
|
||||||
|
disableShield()
|
||||||
|
vehicle.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator).get.Enabled = false
|
||||||
|
case Some(drain) =>
|
||||||
|
vehicle.Shields -= drain
|
||||||
|
showShieldCharge()
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (vehicle.Subsystems(VehicleSubsystemEntry.BattleframeFlightPod).get.Jammed) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
case BfrFlight.Landed =>
|
||||||
|
if (flying.nonEmpty) {
|
||||||
|
flying = None
|
||||||
|
vehicle.Flying = None
|
||||||
|
vehicle.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator).get.Enabled = true
|
||||||
|
if (vehicle.Shields > 0) {
|
||||||
|
enableShield()
|
||||||
|
}
|
||||||
|
shieldCharge(delay = 2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
|
||||||
|
override def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
|
||||||
|
super.PrepareForDisabled(kickPassengers)
|
||||||
|
capacitanceStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override def destructionDelayed(delay: Long, cause: DamageResult): Unit = {
|
||||||
|
super.destructionDelayed(delay, cause)
|
||||||
|
capacitanceStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override def DestructionAwareness(target: Target, cause: DamageResult): Unit = {
|
||||||
|
super.DestructionAwareness(target, cause)
|
||||||
|
capacitancePostStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override def chargeShieldsOnly(amount: Int): Unit = {
|
||||||
|
if (flying != null && (flying.isEmpty || flying.contains(false))) {
|
||||||
|
super.chargeShieldsOnly(amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override protected def capacitorOnlyCharge(amount: Int): Boolean = {
|
||||||
|
if (flying.isEmpty || flying.contains(false)) {
|
||||||
|
val mod = math.max(1, amount * vehicle.SubsystemStatusMultiplier(sys = "BattleframeFlightPod.RechargeRate").toInt)
|
||||||
|
super.capacitorOnlyCharge(mod)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def bfrHandiness(side: equipment.Hand): Int = {
|
||||||
|
if (side == Handiness.Left) 1
|
||||||
|
else if (side == Handiness.Right) 2
|
||||||
|
else throw new Exception("no hand associated with this slot; caller screwed up")
|
||||||
|
}
|
||||||
|
|
||||||
|
override def bfrHandiness(slot: Int): equipment.Hand = {
|
||||||
|
//for the benefit of BFR equipment slots interacting with MoveItemMessage
|
||||||
|
if (slot == 1) Handiness.Left
|
||||||
|
else if (slot == 2) Handiness.Right
|
||||||
|
else Handiness.Generic
|
||||||
|
}
|
||||||
|
|
||||||
|
override def bfrHandSubsystem(side: equipment.Hand): Option[VehicleSubsystem] = {
|
||||||
|
//for the benefit of BFR equipment slots interacting with MoveItemMessage
|
||||||
|
side match {
|
||||||
|
case Handiness.Left => vehicle.Subsystems(VehicleSubsystemEntry.BattleframeFlightLeftArm)
|
||||||
|
case Handiness.Right => vehicle.Subsystems(VehicleSubsystemEntry.BattleframeFlightRightArm)
|
||||||
|
case _ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object BfrFlight {
|
||||||
|
final case class Soaring(flyingValue: Int)
|
||||||
|
case object Landed
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.vehicles.control
|
||||||
|
|
||||||
|
import akka.actor.Actor
|
||||||
|
import net.psforever.objects._
|
||||||
|
import net.psforever.services.Service
|
||||||
|
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||||
|
|
||||||
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ...
|
||||||
|
*/
|
||||||
|
trait VehicleCapacitance {
|
||||||
|
_: Actor =>
|
||||||
|
def CapacitanceObject: Vehicle
|
||||||
|
|
||||||
|
protected var capacitor = Default.Cancellable
|
||||||
|
|
||||||
|
startCapacitorTimer()
|
||||||
|
|
||||||
|
def capacitanceStop(): Unit = {
|
||||||
|
capacitor.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
def capacitanceStopAndBlank(): Unit = {
|
||||||
|
capacitor.cancel()
|
||||||
|
capacitor = Default.Cancellable
|
||||||
|
}
|
||||||
|
|
||||||
|
def capacitancePostStop(): Unit = {
|
||||||
|
capacitanceStopAndBlank()
|
||||||
|
CapacitanceObject.Capacitor = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
def capacitorBehavior: Receive = {
|
||||||
|
case VehicleCapacitance.CapacitorCharge(amount) =>
|
||||||
|
capacitorCharge(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def capacitorCharge(amount: Int): Boolean = {
|
||||||
|
capacitorOnlyCharge(amount)
|
||||||
|
startCapacitorTimer()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def capacitorOnlyCharge(amount: Int): Boolean = {
|
||||||
|
val obj = CapacitanceObject
|
||||||
|
val capacitorBefore = obj.Capacitor
|
||||||
|
val capacitorAfter = obj.Capacitor += amount
|
||||||
|
if (capacitorBefore != capacitorAfter) {
|
||||||
|
showCapacitorCharge()
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def showCapacitorCharge(): Unit = {
|
||||||
|
val obj = CapacitanceObject
|
||||||
|
obj.Zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
self.toString(),
|
||||||
|
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 113, obj.Capacitor)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def startCapacitorTimer(): Unit = {
|
||||||
|
val obj = CapacitanceObject
|
||||||
|
if (obj.Capacitor < obj.Definition.MaxCapacitor) {
|
||||||
|
capacitor.cancel()
|
||||||
|
capacitor = context.system.scheduler.scheduleOnce(
|
||||||
|
delay = 1000 millisecond,
|
||||||
|
self,
|
||||||
|
VehicleCapacitance.CapacitorCharge(obj.Definition.CapacitorRecharge)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object VehicleCapacitance {
|
||||||
|
/**
|
||||||
|
* Charge the vehicle's internal capacitor by the given amount during the scheduled charge event.
|
||||||
|
* @param amount how much energy in the charge
|
||||||
|
*/
|
||||||
|
private case class CapacitorCharge(amount: Int)
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
// Copyright (c) 2017-2021 PSForever
|
// Copyright (c) 2017-2021 PSForever
|
||||||
package net.psforever.objects.vehicles.control
|
package net.psforever.objects.vehicles.control
|
||||||
|
|
||||||
import akka.actor.{Actor, Cancellable}
|
import akka.actor.Cancellable
|
||||||
import net.psforever.actors.zone.ZoneActor
|
import net.psforever.actors.zone.ZoneActor
|
||||||
import net.psforever.objects._
|
import net.psforever.objects._
|
||||||
import net.psforever.objects.ballistics.VehicleSource
|
import net.psforever.objects.ballistics.VehicleSource
|
||||||
|
import net.psforever.objects.definition.VehicleDefinition
|
||||||
|
import net.psforever.objects.definition.converter.OCM
|
||||||
import net.psforever.objects.entity.WorldEntity
|
import net.psforever.objects.entity.WorldEntity
|
||||||
import net.psforever.objects.equipment.{Equipment, EquipmentSlot, JammableMountedWeapons}
|
import net.psforever.objects.equipment.{ArmorSiphonBehavior, Equipment, EquipmentSlot, JammableMountedWeapons}
|
||||||
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||||
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
|
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
|
||||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject, ServerObjectControl}
|
||||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||||
import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior}
|
import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior}
|
||||||
import net.psforever.objects.serverobject.damage.{AggravatedBehavior, DamageableVehicle}
|
import net.psforever.objects.serverobject.damage.{AggravatedBehavior, DamageableVehicle}
|
||||||
|
|
@ -20,10 +22,11 @@ import net.psforever.objects.serverobject.repair.RepairableVehicle
|
||||||
import net.psforever.objects.serverobject.terminals.Terminal
|
import net.psforever.objects.serverobject.terminals.Terminal
|
||||||
import net.psforever.objects.vehicles._
|
import net.psforever.objects.vehicles._
|
||||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||||
import net.psforever.objects.vital.VehicleShieldCharge
|
import net.psforever.objects.vital.{DamagingActivity, VehicleShieldCharge, VitalsActivity}
|
||||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||||
import net.psforever.objects.vital.etc.SuicideReason
|
import net.psforever.objects.vital.etc.SuicideReason
|
||||||
import net.psforever.objects.zones._
|
import net.psforever.objects.zones._
|
||||||
|
import net.psforever.packet.PlanetSideGamePacket
|
||||||
import net.psforever.packet.game._
|
import net.psforever.packet.game._
|
||||||
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
|
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
|
||||||
import net.psforever.types._
|
import net.psforever.types._
|
||||||
|
|
@ -43,10 +46,11 @@ import scala.concurrent.duration._
|
||||||
* @param vehicle the `Vehicle` object being governed
|
* @param vehicle the `Vehicle` object being governed
|
||||||
*/
|
*/
|
||||||
class VehicleControl(vehicle: Vehicle)
|
class VehicleControl(vehicle: Vehicle)
|
||||||
extends Actor
|
extends ServerObjectControl
|
||||||
with FactionAffinityBehavior.Check
|
with FactionAffinityBehavior.Check
|
||||||
with MountableBehavior
|
with MountableBehavior
|
||||||
with DamageableVehicle
|
with DamageableVehicle
|
||||||
|
with ArmorSiphonBehavior.Target
|
||||||
with RepairableVehicle
|
with RepairableVehicle
|
||||||
with JammableMountedWeapons
|
with JammableMountedWeapons
|
||||||
with ContainableBehavior
|
with ContainableBehavior
|
||||||
|
|
@ -64,6 +68,8 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
|
|
||||||
def DamageableObject = vehicle
|
def DamageableObject = vehicle
|
||||||
|
|
||||||
|
def SiphonableObject = vehicle
|
||||||
|
|
||||||
def RepairableObject = vehicle
|
def RepairableObject = vehicle
|
||||||
|
|
||||||
def ContainerObject = vehicle
|
def ContainerObject = vehicle
|
||||||
|
|
@ -85,6 +91,8 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
var decayTimer : Cancellable = Default.Cancellable
|
var decayTimer : Cancellable = Default.Cancellable
|
||||||
/** becoming waterlogged, or drying out? */
|
/** becoming waterlogged, or drying out? */
|
||||||
var submergedCondition : Option[OxygenState] = None
|
var submergedCondition : Option[OxygenState] = None
|
||||||
|
/** ... */
|
||||||
|
var passengerRadiationCloudTimer: Cancellable = Default.Cancellable
|
||||||
|
|
||||||
def receive : Receive = Enabled
|
def receive : Receive = Enabled
|
||||||
|
|
||||||
|
|
@ -102,8 +110,10 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
}
|
}
|
||||||
|
|
||||||
def commonEnabledBehavior: Receive = checkBehavior
|
def commonEnabledBehavior: Receive = checkBehavior
|
||||||
|
.orElse(attributeBehavior)
|
||||||
.orElse(jammableBehavior)
|
.orElse(jammableBehavior)
|
||||||
.orElse(takesDamage)
|
.orElse(takesDamage)
|
||||||
|
.orElse(siphoningBehavior)
|
||||||
.orElse(canBeRepairedByNanoDispenser)
|
.orElse(canBeRepairedByNanoDispenser)
|
||||||
.orElse(containerBehavior)
|
.orElse(containerBehavior)
|
||||||
.orElse(environmentBehavior)
|
.orElse(environmentBehavior)
|
||||||
|
|
@ -124,23 +134,26 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
dismountCleanup(seat_num)
|
dismountCleanup(seat_num)
|
||||||
|
|
||||||
case Vehicle.ChargeShields(amount) =>
|
case Vehicle.ChargeShields(amount) =>
|
||||||
val now : Long = System.currentTimeMillis()
|
chargeShields(amount)
|
||||||
//make certain vehicles don't charge shields too quickly
|
|
||||||
if (
|
|
||||||
vehicle.Health > 0 && vehicle.Shields < vehicle.MaxShields &&
|
|
||||||
!vehicle.History.exists(VehicleControl.LastShieldChargeOrDamage(now))
|
|
||||||
) {
|
|
||||||
vehicle.History(VehicleShieldCharge(VehicleSource(vehicle), amount))
|
|
||||||
vehicle.Shields = vehicle.Shields + amount
|
|
||||||
vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
|
|
||||||
s"${vehicle.Actor}",
|
|
||||||
VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), vehicle.GUID, 68, vehicle.Shields)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
case Vehicle.UpdateZoneInteractionProgressUI(player) =>
|
case Vehicle.UpdateZoneInteractionProgressUI(player) =>
|
||||||
updateZoneInteractionProgressUI(player)
|
updateZoneInteractionProgressUI(player)
|
||||||
|
|
||||||
|
case Vehicle.UpdateSubsystemStates(toChannel, stateToResolve) =>
|
||||||
|
val events = vehicle.Zone.VehicleEvents
|
||||||
|
val guid0 = Service.defaultPlayerGUID
|
||||||
|
(stateToResolve match {
|
||||||
|
case Some(state) =>
|
||||||
|
vehicle.Subsystems().filter { _.Enabled == state } //only subsystems that are enabled or are disabled
|
||||||
|
case None =>
|
||||||
|
vehicle.Subsystems() //all subsystems
|
||||||
|
})
|
||||||
|
.flatMap { _.getMessage(vehicle) }
|
||||||
|
.foreach { pkt =>
|
||||||
|
events ! VehicleServiceMessage(toChannel, VehicleAction.SendResponse(guid0, pkt))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
case FactionAffinity.ConvertFactionAffinity(faction) =>
|
case FactionAffinity.ConvertFactionAffinity(faction) =>
|
||||||
val originalAffinity = vehicle.Faction
|
val originalAffinity = vehicle.Faction
|
||||||
if (originalAffinity != (vehicle.Faction = faction)) {
|
if (originalAffinity != (vehicle.Faction = faction)) {
|
||||||
|
|
@ -162,62 +175,29 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
}
|
}
|
||||||
|
|
||||||
case Terminal.TerminalMessage(player, msg, reply) =>
|
case Terminal.TerminalMessage(player, msg, reply) =>
|
||||||
reply match {
|
val zone = vehicle.Zone
|
||||||
case Terminal.VehicleLoadout(definition, weapons, inventory) =>
|
if (permitTerminalMessage(player, msg)) {
|
||||||
org.log4s
|
reply match {
|
||||||
.getLogger(vehicle.Definition.Name)
|
case Terminal.VehicleLoadout(definition, weapons, inventory) =>
|
||||||
.info(s"changing vehicle equipment loadout to ${player.Name}'s option #${msg.unk1 + 1}")
|
log.info(s"changing vehicle equipment loadout to ${player.Name}'s option #${msg.unk1 + 1}")
|
||||||
//remove old inventory
|
val (oldWeapons, newWeapons, oldInventory, finalInventory) =
|
||||||
val oldInventory = vehicle.Inventory.Clear().map { case InventoryItem(obj, _) => (obj, obj.GUID) }
|
handleTerminalMessageVehicleLoadout(player, definition, weapons, inventory)
|
||||||
//"dropped" items are lost; if it doesn't go in the trunk, it vanishes into the nanite cloud
|
zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
val (_, afterInventory) = inventory.partition(ContainableBehavior.DropPredicate(player))
|
zone.id,
|
||||||
val (oldWeapons, newWeapons, finalInventory) = if (vehicle.Definition == definition) {
|
VehicleAction.ChangeLoadout(vehicle.GUID, oldWeapons, newWeapons, oldInventory, finalInventory)
|
||||||
//vehicles are the same type
|
)
|
||||||
//TODO want to completely swap weapons, but holster icon vanishes temporarily after swap
|
zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
//TODO BFR arms must be swapped properly
|
player.Name,
|
||||||
// //remove old weapons
|
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, true)
|
||||||
// val oldWeapons = vehicle.Weapons.values.collect { case slot if slot.Equipment.nonEmpty =>
|
)
|
||||||
// val obj = slot.Equipment.get
|
|
||||||
// slot.Equipment = None
|
|
||||||
// (obj, obj.GUID)
|
|
||||||
// }.toList
|
|
||||||
// (oldWeapons, weapons, afterInventory)
|
|
||||||
//TODO for now, just refill ammo; assume weapons stay the same
|
|
||||||
vehicle.Weapons
|
|
||||||
.collect { case (_, slot: EquipmentSlot) if slot.Equipment.nonEmpty => slot.Equipment.get }
|
|
||||||
.collect {
|
|
||||||
case weapon: Tool =>
|
|
||||||
weapon.AmmoSlots.foreach { ammo => ammo.Box.Capacity = ammo.MaxMagazine() }
|
|
||||||
}
|
|
||||||
(Nil, Nil, afterInventory)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
//vehicle loadout is not for this vehicle
|
|
||||||
//do not transfer over weapon ammo
|
|
||||||
if (
|
|
||||||
vehicle.Definition.TrunkSize == definition.TrunkSize && vehicle.Definition.TrunkOffset == definition.TrunkOffset
|
|
||||||
) {
|
|
||||||
(Nil, Nil, afterInventory) //trunk is the same dimensions, however
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
//accommodate as much of inventory as possible
|
|
||||||
val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory)
|
|
||||||
(Nil, Nil, stow)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finalInventory.foreach {
|
|
||||||
_.obj.Faction = vehicle.Faction
|
|
||||||
}
|
|
||||||
player.Zone.VehicleEvents ! VehicleServiceMessage(
|
|
||||||
player.Zone.id,
|
|
||||||
VehicleAction.ChangeLoadout(vehicle.GUID, oldWeapons, newWeapons, oldInventory, finalInventory)
|
|
||||||
)
|
|
||||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
|
||||||
player.Name,
|
|
||||||
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, true)
|
|
||||||
)
|
|
||||||
|
|
||||||
case _ => ;
|
case _ => ;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
|
player.Name,
|
||||||
|
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, false)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
case VehicleControl.Disable() =>
|
case VehicleControl.Disable() =>
|
||||||
|
|
@ -248,6 +228,11 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
final def Enabled: Receive =
|
final def Enabled: Receive =
|
||||||
commonEnabledBehavior
|
commonEnabledBehavior
|
||||||
.orElse {
|
.orElse {
|
||||||
|
case VehicleControl.RadiationTick =>
|
||||||
|
vehicle.interaction().find { _.Type == RadiationInVehicleInteraction } match {
|
||||||
|
case Some(func) => func.interaction(vehicle.getInteractionSector(), vehicle)
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -323,8 +308,8 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
user.avatar.vehicle = None
|
user.avatar.vehicle = None
|
||||||
}
|
}
|
||||||
GainOwnership(user) //gain new ownership
|
GainOwnership(user) //gain new ownership
|
||||||
}
|
passengerRadiationCloudTimer.cancel()
|
||||||
else {
|
} else {
|
||||||
decaying = false
|
decaying = false
|
||||||
decayTimer.cancel()
|
decayTimer.cancel()
|
||||||
}
|
}
|
||||||
|
|
@ -347,6 +332,14 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
if (!obj.Seats(0).isOccupied) {
|
if (!obj.Seats(0).isOccupied) {
|
||||||
obj.Velocity = Some(Vector3.Zero)
|
obj.Velocity = Some(Vector3.Zero)
|
||||||
}
|
}
|
||||||
|
if (seatBeingDismounted == 0) {
|
||||||
|
passengerRadiationCloudTimer = context.system.scheduler.scheduleWithFixedDelay(
|
||||||
|
250.milliseconds,
|
||||||
|
250.milliseconds,
|
||||||
|
self,
|
||||||
|
VehicleControl.RadiationTick
|
||||||
|
)
|
||||||
|
}
|
||||||
if (!obj.Seats(seatBeingDismounted).isOccupied) { //seat was vacated
|
if (!obj.Seats(seatBeingDismounted).isOccupied) { //seat was vacated
|
||||||
//we were only owning the vehicle while we sat in its driver seat
|
//we were only owning the vehicle while we sat in its driver seat
|
||||||
val canBeOwned = obj.Definition.CanBeOwned
|
val canBeOwned = obj.Definition.CanBeOwned
|
||||||
|
|
@ -473,25 +466,19 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
}
|
}
|
||||||
|
|
||||||
def PutItemInSlotCallback(item: Equipment, slot: Int): Unit = {
|
def PutItemInSlotCallback(item: Equipment, slot: Int): Unit = {
|
||||||
val obj = ContainerObject
|
val obj = ContainerObject
|
||||||
val oguid = obj.GUID
|
val oguid = obj.GUID
|
||||||
val zone = obj.Zone
|
val zone = obj.Zone
|
||||||
val channel = self.toString
|
val channel = self.toString
|
||||||
val events = zone.VehicleEvents
|
val events = zone.VehicleEvents
|
||||||
val iguid = item.GUID
|
val iguid = item.GUID
|
||||||
val definition = item.Definition
|
|
||||||
item.Faction = obj.Faction
|
item.Faction = obj.Faction
|
||||||
events ! VehicleServiceMessage(
|
events ! VehicleServiceMessage(
|
||||||
//TODO when a new weapon, the equipment slot ui goes blank, but the weapon functions; remount vehicle to correct it
|
//TODO when a new weapon, the equipment slot ui goes blank, but the weapon functions; remount vehicle to correct it
|
||||||
if (obj.VisibleSlots.contains(slot)) zone.id else channel,
|
if (obj.VisibleSlots.contains(slot)) zone.id else channel,
|
||||||
VehicleAction.SendResponse(
|
VehicleAction.SendResponse(
|
||||||
Service.defaultPlayerGUID,
|
Service.defaultPlayerGUID,
|
||||||
ObjectCreateDetailedMessage(
|
OCM.detailed(item, ObjectCreateMessageParent(oguid, slot))
|
||||||
definition.ObjectId,
|
|
||||||
iguid,
|
|
||||||
ObjectCreateMessageParent(oguid, slot),
|
|
||||||
definition.Packet.DetailedConstructorData(item).get
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
item match {
|
item match {
|
||||||
|
|
@ -504,7 +491,7 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
weapon.AmmoSlots.map { slot => slot.Box }.foreach { box =>
|
weapon.AmmoSlots.map { slot => slot.Box }.foreach { box =>
|
||||||
events ! VehicleServiceMessage(
|
events ! VehicleServiceMessage(
|
||||||
channel,
|
channel,
|
||||||
VehicleAction.InventoryState2(Service.defaultPlayerGUID, iguid, weapon.GUID, box.Capacity)
|
VehicleAction.InventoryState2(Service.defaultPlayerGUID, box.GUID, iguid, box.Capacity)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
case _ => ;
|
case _ => ;
|
||||||
|
|
@ -521,6 +508,70 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def permitTerminalMessage(player: Player, msg: ItemTransactionMessage): Boolean = true
|
||||||
|
|
||||||
|
def handleTerminalMessageVehicleLoadout(
|
||||||
|
player: Player,
|
||||||
|
definition: VehicleDefinition,
|
||||||
|
weapons: List[InventoryItem],
|
||||||
|
inventory: List[InventoryItem]
|
||||||
|
): (
|
||||||
|
List[(Equipment, PlanetSideGUID)],
|
||||||
|
List[InventoryItem],
|
||||||
|
List[(Equipment, PlanetSideGUID)],
|
||||||
|
List[InventoryItem]
|
||||||
|
) = {
|
||||||
|
//remove old inventory
|
||||||
|
val oldInventory = vehicle.Inventory.Clear().map { case InventoryItem(obj, _) => (obj, obj.GUID) }
|
||||||
|
//"dropped" items are lost; if it doesn't go in the trunk, it vanishes into the nanite cloud
|
||||||
|
val (_, afterInventory) = inventory.partition(ContainableBehavior.DropPredicate(player))
|
||||||
|
val (oldWeapons, newWeapons, finalInventory) = if (vehicle.Definition == definition) {
|
||||||
|
//vehicles are the same type; just refill ammo, assuming weapons stay the same
|
||||||
|
vehicle.Weapons
|
||||||
|
.collect { case (_, slot: EquipmentSlot) if slot.Equipment.nonEmpty => slot.Equipment.get }
|
||||||
|
.collect {
|
||||||
|
case weapon: Tool =>
|
||||||
|
weapon.AmmoSlots.foreach { ammo => ammo.Box.Capacity = ammo.MaxMagazine() }
|
||||||
|
}
|
||||||
|
(Nil, Nil, afterInventory)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//vehicle loadout is not for this vehicle; do not transfer over weapon ammo
|
||||||
|
if (
|
||||||
|
vehicle.Definition.TrunkSize == definition.TrunkSize && vehicle.Definition.TrunkOffset == definition.TrunkOffset
|
||||||
|
) {
|
||||||
|
(Nil, Nil, afterInventory) //trunk is the same dimensions, however
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//accommodate as much of inventory as possible
|
||||||
|
val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory)
|
||||||
|
(Nil, Nil, stow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finalInventory.foreach {
|
||||||
|
_.obj.Faction = vehicle.Faction
|
||||||
|
}
|
||||||
|
(oldWeapons, newWeapons, oldInventory, finalInventory)
|
||||||
|
}
|
||||||
|
|
||||||
|
//make certain vehicles don't charge shields too quickly
|
||||||
|
def canChargeShields(): Boolean = {
|
||||||
|
val func: VitalsActivity => Boolean = VehicleControl.LastShieldChargeOrDamage(System.currentTimeMillis(), vehicle.Definition)
|
||||||
|
vehicle.Health > 0 && vehicle.Shields < vehicle.MaxShields &&
|
||||||
|
!vehicle.History.exists(func)
|
||||||
|
}
|
||||||
|
|
||||||
|
def chargeShields(amount: Int): Unit = {
|
||||||
|
if (canChargeShields()) {
|
||||||
|
vehicle.History(VehicleShieldCharge(VehicleSource(vehicle), amount))
|
||||||
|
vehicle.Shields = vehicle.Shields + amount
|
||||||
|
vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
s"${vehicle.Actor}",
|
||||||
|
VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), vehicle.GUID, vehicle.Definition.shieldUiAttribute, vehicle.Shields)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Water causes vehicles to become disabled if they dive off too far, too deep.
|
* Water causes vehicles to become disabled if they dive off too far, too deep.
|
||||||
* Flying vehicles do not display progress towards being waterlogged. They just disable outright.
|
* Flying vehicles do not display progress towards being waterlogged. They just disable outright.
|
||||||
|
|
@ -717,11 +768,96 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
case None => ;
|
case None => ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def parseAttribute(attribute: Int, value: Long, other: Option[Any]) : Unit = {
|
||||||
|
val vguid = vehicle.GUID
|
||||||
|
val (dname, dguid) = other match {
|
||||||
|
case Some(p: Player) => (p.Name, p.GUID)
|
||||||
|
case _ => (vehicle.OwnerName.getOrElse("The driver"), PlanetSideGUID(0))
|
||||||
|
}
|
||||||
|
val zone = vehicle.Zone
|
||||||
|
if (9 < attribute && attribute < 14) {
|
||||||
|
vehicle.PermissionGroup(attribute, value) match {
|
||||||
|
case Some(allow) =>
|
||||||
|
val group = AccessPermissionGroup(attribute - 10)
|
||||||
|
log.info(s"$dname changed ${vehicle.Definition.Name}'s access permission $group to $allow")
|
||||||
|
zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
VehicleAction.SeatPermissions(dguid, vguid, attribute, value)
|
||||||
|
)
|
||||||
|
//kick players who should not be seated in the vehicle due to permission changes
|
||||||
|
if (allow == VehicleLockState.Locked) { //TODO only important permission atm
|
||||||
|
vehicle.Seats.foreach {
|
||||||
|
case (seatIndex, seat) =>
|
||||||
|
seat.occupant match {
|
||||||
|
case Some(tplayer: Player) =>
|
||||||
|
if (vehicle.SeatPermissionGroup(seatIndex).contains(group) && !tplayer.Name.equals(dname)) { //can not kick self
|
||||||
|
seat.unmount(tplayer)
|
||||||
|
tplayer.VehicleSeated = None
|
||||||
|
zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
VehicleAction.KickPassenger(tplayer.GUID, 4, false, vguid)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case _ => ; // No player seated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vehicle.CargoHolds.foreach {
|
||||||
|
case (cargoIndex, hold) =>
|
||||||
|
hold.occupant match {
|
||||||
|
case Some(cargo) =>
|
||||||
|
if (vehicle.SeatPermissionGroup(cargoIndex).contains(group)) {
|
||||||
|
//todo: this probably doesn't work for passengers within the cargo vehicle
|
||||||
|
// Instruct client to start bail dismount procedure
|
||||||
|
self ! DismountVehicleCargoMsg(dguid, cargo.GUID, true, false, false)
|
||||||
|
}
|
||||||
|
case None => ; // No vehicle in cargo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case None => ;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warn(
|
||||||
|
s"parseAttributes: unsupported change on $vguid - $attribute, $dname"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def StartJammeredStatus(target: Any, dur: Int): Unit = {
|
||||||
|
super.StartJammeredStatus(target, dur)
|
||||||
|
val subsystems = vehicle.Subsystems()
|
||||||
|
if (!subsystems.exists { _.Jammed }) {
|
||||||
|
subsystems.foreach { _.jam() }
|
||||||
|
vehicleSubsystemMessages(subsystems.flatMap { _.changedMessages(vehicle) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def CancelJammeredStatus(target: Any): Unit = {
|
||||||
|
super.CancelJammeredStatus(target)
|
||||||
|
val subsystems = vehicle.Subsystems()
|
||||||
|
if (subsystems.exists { _.Jammed }) {
|
||||||
|
subsystems.foreach { _.unjam() }
|
||||||
|
vehicleSubsystemMessages(subsystems.flatMap { _.changedMessages(vehicle) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def vehicleSubsystemMessages(messages: List[PlanetSideGamePacket]): Unit = {
|
||||||
|
val zone = vehicle.Zone
|
||||||
|
val zoneid = zone.id
|
||||||
|
val events = zone.VehicleEvents
|
||||||
|
val guid0 = Service.defaultPlayerGUID
|
||||||
|
messages.foreach { pkt =>
|
||||||
|
events ! VehicleServiceMessage(
|
||||||
|
zoneid,
|
||||||
|
VehicleAction.SendResponse(guid0, pkt)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object VehicleControl {
|
object VehicleControl {
|
||||||
import net.psforever.objects.vital.{DamageFromProjectile, VehicleShieldCharge, VitalsActivity}
|
import net.psforever.objects.vital.{VehicleShieldCharge, VitalsActivity}
|
||||||
import scala.concurrent.duration._
|
|
||||||
|
|
||||||
private case class PrepareForDeletion()
|
private case class PrepareForDeletion()
|
||||||
|
|
||||||
|
|
@ -729,21 +865,22 @@ object VehicleControl {
|
||||||
|
|
||||||
private case class Deletion()
|
private case class Deletion()
|
||||||
|
|
||||||
|
private case object RadiationTick
|
||||||
|
|
||||||
final case class AssignOwnership(player: Option[Player])
|
final case class AssignOwnership(player: Option[Player])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a given activity entry would invalidate the act of charging vehicle shields this tick.
|
* Determine if a given activity entry would invalidate the act of charging vehicle shields this tick.
|
||||||
* @param now the current time (in nanoseconds)
|
* @param now the current time (in nanoseconds)
|
||||||
* @param act a `VitalsActivity` entry to test
|
* @param act a `VitalsActivity` entry to test
|
||||||
* @return `true`, if the vehicle took damage in the last five seconds or
|
* @return `true`, if the shield charge would be blocked;
|
||||||
* charged shields in the last second;
|
|
||||||
* `false`, otherwise
|
* `false`, otherwise
|
||||||
*/
|
*/
|
||||||
def LastShieldChargeOrDamage(now: Long)(act: VitalsActivity): Boolean = {
|
def LastShieldChargeOrDamage(now: Long, vdef: VehicleDefinition)(act: VitalsActivity): Boolean = {
|
||||||
act match {
|
act match {
|
||||||
case DamageFromProjectile(data) => now - data.interaction.hitTime < (5 seconds).toMillis //damage delays next charge by 5s
|
case dact: DamagingActivity => now - dact.time < vdef.ShieldDamageDelay //damage delays next charge
|
||||||
case vsc: VehicleShieldCharge => now - vsc.time < (1 seconds).toMillis //previous charge delays next by 1s
|
case vsc: VehicleShieldCharge => now - vsc.time < vdef.ShieldPeriodicDelay //previous charge delays next
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ object NoResistanceSelection extends ResistanceSelection {
|
||||||
def Splash: ResistanceSelection.Format = NoResistance.Calculate
|
def Splash: ResistanceSelection.Format = NoResistance.Calculate
|
||||||
def Lash: ResistanceSelection.Format = NoResistance.Calculate
|
def Lash: ResistanceSelection.Format = NoResistance.Calculate
|
||||||
def Aggravated: ResistanceSelection.Format = NoResistance.Calculate
|
def Aggravated: ResistanceSelection.Format = NoResistance.Calculate
|
||||||
|
def Radiation: ResistanceSelection.Format = ResistanceSelection.None
|
||||||
}
|
}
|
||||||
|
|
||||||
object StandardInfantryResistance extends ResistanceSelection {
|
object StandardInfantryResistance extends ResistanceSelection {
|
||||||
|
|
@ -81,6 +82,7 @@ object StandardInfantryResistance extends ResistanceSelection {
|
||||||
def Splash: ResistanceSelection.Format = InfantrySplashResistance.Calculate
|
def Splash: ResistanceSelection.Format = InfantrySplashResistance.Calculate
|
||||||
def Lash: ResistanceSelection.Format = InfantryLashResistance.Calculate
|
def Lash: ResistanceSelection.Format = InfantryLashResistance.Calculate
|
||||||
def Aggravated: ResistanceSelection.Format = InfantryAggravatedResistance.Calculate
|
def Aggravated: ResistanceSelection.Format = InfantryAggravatedResistance.Calculate
|
||||||
|
def Radiation: ResistanceSelection.Format = InfantrySplashResistance.Calculate
|
||||||
}
|
}
|
||||||
|
|
||||||
object StandardVehicleResistance extends ResistanceSelection {
|
object StandardVehicleResistance extends ResistanceSelection {
|
||||||
|
|
@ -88,6 +90,7 @@ object StandardVehicleResistance extends ResistanceSelection {
|
||||||
def Splash: ResistanceSelection.Format = VehicleSplashResistance.Calculate
|
def Splash: ResistanceSelection.Format = VehicleSplashResistance.Calculate
|
||||||
def Lash: ResistanceSelection.Format = VehicleLashResistance.Calculate
|
def Lash: ResistanceSelection.Format = VehicleLashResistance.Calculate
|
||||||
def Aggravated: ResistanceSelection.Format = VehicleAggravatedResistance.Calculate
|
def Aggravated: ResistanceSelection.Format = VehicleAggravatedResistance.Calculate
|
||||||
|
def Radiation: ResistanceSelection.Format = ResistanceSelection.None
|
||||||
}
|
}
|
||||||
|
|
||||||
object StandardAmenityResistance extends ResistanceSelection {
|
object StandardAmenityResistance extends ResistanceSelection {
|
||||||
|
|
@ -95,4 +98,5 @@ object StandardAmenityResistance extends ResistanceSelection {
|
||||||
def Splash: ResistanceSelection.Format = AmenityHitResistance.Calculate
|
def Splash: ResistanceSelection.Format = AmenityHitResistance.Calculate
|
||||||
def Lash: ResistanceSelection.Format = ResistanceSelection.None
|
def Lash: ResistanceSelection.Format = ResistanceSelection.None
|
||||||
def Aggravated: ResistanceSelection.Format = ResistanceSelection.None
|
def Aggravated: ResistanceSelection.Format = ResistanceSelection.None
|
||||||
|
def Radiation: ResistanceSelection.Format = ResistanceSelection.None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,12 @@ object VehicleResolutions
|
||||||
ResolutionCalculations.VehicleApplication
|
ResolutionCalculations.VehicleApplication
|
||||||
)
|
)
|
||||||
|
|
||||||
|
object BfrResolutions
|
||||||
|
extends DamageResistanceCalculations(
|
||||||
|
ResolutionCalculations.VehicleDamageAfterResist,
|
||||||
|
ResolutionCalculations.BfrApplication
|
||||||
|
)
|
||||||
|
|
||||||
object SimpleResolutions
|
object SimpleResolutions
|
||||||
extends DamageResistanceCalculations(
|
extends DamageResistanceCalculations(
|
||||||
ResolutionCalculations.VehicleDamageAfterResist,
|
ResolutionCalculations.VehicleDamageAfterResist,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
package net.psforever.objects.vital
|
package net.psforever.objects.vital
|
||||||
|
|
||||||
import net.psforever.objects.ballistics.{PlayerSource, VehicleSource}
|
import net.psforever.objects.ballistics.{PlayerSource, VehicleSource}
|
||||||
import net.psforever.objects.definition.{EquipmentDefinition, KitDefinition}
|
import net.psforever.objects.definition.{EquipmentDefinition, KitDefinition, ToolDefinition}
|
||||||
import net.psforever.objects.serverobject.terminals.TerminalDefinition
|
import net.psforever.objects.serverobject.terminals.TerminalDefinition
|
||||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||||
import net.psforever.objects.vital.etc.{ExplodingEntityReason, PainboxReason}
|
import net.psforever.objects.vital.etc.{ExplodingEntityReason, PainboxReason}
|
||||||
|
|
@ -15,7 +15,7 @@ trait VitalsActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
trait HealingActivity extends VitalsActivity {
|
trait HealingActivity extends VitalsActivity {
|
||||||
def time: Long = System.currentTimeMillis()
|
val time: Long = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
trait DamagingActivity extends VitalsActivity {
|
trait DamagingActivity extends VitalsActivity {
|
||||||
|
|
@ -53,6 +53,9 @@ final case class RepairFromEquipment(
|
||||||
final case class RepairFromTerm(term_def: TerminalDefinition, amount: Int)
|
final case class RepairFromTerm(term_def: TerminalDefinition, amount: Int)
|
||||||
extends HealingActivity
|
extends HealingActivity
|
||||||
|
|
||||||
|
final case class RepairFromArmorSiphon(siphon_def: ToolDefinition, amount: Int)
|
||||||
|
extends HealingActivity
|
||||||
|
|
||||||
final case class VehicleShieldCharge(amount: Int)
|
final case class VehicleShieldCharge(amount: Int)
|
||||||
extends HealingActivity //TODO facility
|
extends HealingActivity //TODO facility
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ object DamageResolution extends Enumeration {
|
||||||
Explosion, //area of effect damage caused by an internal mechanism; unrelated to Splash
|
Explosion, //area of effect damage caused by an internal mechanism; unrelated to Splash
|
||||||
Environmental, //died to environmental causes
|
Environmental, //died to environmental causes
|
||||||
Suicide, //i don't want to be the one the battles always choose
|
Suicide, //i don't want to be the one the battles always choose
|
||||||
Collision //went splat
|
Collision, //went splat
|
||||||
|
Radiation //it hurts to stand too close
|
||||||
= Value
|
= Value
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,5 +10,5 @@ object DamageType extends Enumeration(1) {
|
||||||
type Type = Value
|
type Type = Value
|
||||||
|
|
||||||
//"one" (numerical 1 in the ADB) corresponds to objects that explode
|
//"one" (numerical 1 in the ADB) corresponds to objects that explode
|
||||||
final val Direct, Splash, Lash, Radiation, Aggravated, One, None = Value
|
final val Direct, Splash, Lash, Radiation, Aggravated, One, Siphon, None = Value
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ object DamageCalculations {
|
||||||
|
|
||||||
def AgainstMaxSuit(profile : DamageProfile) : Int = profile.Damage3
|
def AgainstMaxSuit(profile : DamageProfile) : Int = profile.Damage3
|
||||||
|
|
||||||
def AgainstBFR(profile : DamageProfile) : Int = profile.Damage4
|
def AgainstBfr(profile : DamageProfile) : Int = profile.Damage4
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the damage value.
|
* Get the damage value.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.vital.etc
|
||||||
|
|
||||||
|
import net.psforever.objects.{GlobalDefinitions, Tool, Vehicle}
|
||||||
|
import net.psforever.objects.ballistics.SourceEntry
|
||||||
|
import net.psforever.objects.vital.base.{DamageModifiers, DamageReason, DamageResolution}
|
||||||
|
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||||
|
import net.psforever.objects.vital.prop.DamageWithPosition
|
||||||
|
import net.psforever.objects.vital.resolution.DamageResistanceModel
|
||||||
|
import net.psforever.types.Vector3
|
||||||
|
|
||||||
|
final case class ArmorSiphonReason(
|
||||||
|
hostVehicle: Vehicle,
|
||||||
|
siphon: Tool,
|
||||||
|
damageModel: DamageResistanceModel
|
||||||
|
) extends DamageReason {
|
||||||
|
assert(GlobalDefinitions.isBattleFrameArmorSiphon(siphon.Definition), "acting entity is not an armor siphon")
|
||||||
|
|
||||||
|
def source: DamageWithPosition = siphon.Projectile
|
||||||
|
|
||||||
|
def resolution: DamageResolution.Value = DamageResolution.Resolved
|
||||||
|
|
||||||
|
def same(test: DamageReason): Boolean = test match {
|
||||||
|
case asr: ArmorSiphonReason => (asr.hostVehicle eq hostVehicle) && (asr.siphon eq siphon)
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
|
||||||
|
def adversary: Option[SourceEntry] = None
|
||||||
|
|
||||||
|
override def attribution: Int = hostVehicle.Definition.ObjectId
|
||||||
|
}
|
||||||
|
|
||||||
|
object ArmorSiphonModifiers {
|
||||||
|
trait Mod extends DamageModifiers.Mod {
|
||||||
|
def calculate(damage: Int, data: DamageInteraction, cause: DamageReason): Int = {
|
||||||
|
cause match {
|
||||||
|
case o: ArmorSiphonReason => calculate(damage, data, o)
|
||||||
|
case _ => 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def calculate(damage: Int, data: DamageInteraction, cause: ArmorSiphonReason): Int
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case object ArmorSiphonMaxDistanceCutoff extends ArmorSiphonModifiers.Mod {
|
||||||
|
def calculate(damage: Int, data: DamageInteraction, cause: ArmorSiphonReason): Int = {
|
||||||
|
if (Vector3.DistanceSquared(data.target.Position, cause.hostVehicle.Position) < cause.source.DamageRadius * cause.source.DamageRadius) {
|
||||||
|
damage
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,7 +16,6 @@ import net.psforever.objects.zones.Zone
|
||||||
* A wrapper for a "damage source" in damage calculations
|
* A wrapper for a "damage source" in damage calculations
|
||||||
* that parameterizes information necessary to explain a server-driven explosion occurring.
|
* that parameterizes information necessary to explain a server-driven explosion occurring.
|
||||||
* Some game objects cause area-of-effect damage upon being destroyed.
|
* Some game objects cause area-of-effect damage upon being destroyed.
|
||||||
* @see `VitalityDefinition.explodes`
|
|
||||||
* @see `VitalityDefinition.innateDamage`
|
* @see `VitalityDefinition.innateDamage`
|
||||||
* @see `Zone.causesExplosion`
|
* @see `Zone.causesExplosion`
|
||||||
* @param entity what is accredited as the source of the explosive yield
|
* @param entity what is accredited as the source of the explosive yield
|
||||||
|
|
@ -61,7 +60,7 @@ object ExplodingEntityReason {
|
||||||
instigation: Option[DamageResult]
|
instigation: Option[DamageResult]
|
||||||
): ExplodingEntityReason = {
|
): ExplodingEntityReason = {
|
||||||
val definition = entity.Definition.asInstanceOf[ObjectDefinition with VitalityDefinition]
|
val definition = entity.Definition.asInstanceOf[ObjectDefinition with VitalityDefinition]
|
||||||
assert(definition.explodes && definition.innateDamage.nonEmpty, "causal entity does not explode")
|
assert(definition.innateDamage.nonEmpty, "causal entity does not explode")
|
||||||
ExplodingEntityReason(SourceEntry(entity), definition.innateDamage.get, damageModel, instigation)
|
ExplodingEntityReason(SourceEntry(entity), definition.innateDamage.get, damageModel, instigation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.vital.etc
|
||||||
|
|
||||||
|
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry, Projectile => ActualProjectile}
|
||||||
|
import net.psforever.objects.vital.base._
|
||||||
|
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||||
|
import net.psforever.objects.vital.projectile.{ProjectileDamageModifierFunctions, ProjectileReason}
|
||||||
|
import net.psforever.objects.vital.prop.DamageProperties
|
||||||
|
import net.psforever.objects.vital.resolution.DamageAndResistance
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper for a "damage source" in damage calculations
|
||||||
|
* that parameterizes information necessary to explain a radiation cloud.
|
||||||
|
* @param resolution how the damage is processed
|
||||||
|
* @param projectile the projectile that caused the damage
|
||||||
|
* @param damageModel the model to be utilized in these calculations;
|
||||||
|
* typically, but not always, defined by the target
|
||||||
|
* @param radiationShielding the amount of reduction to radiation damage that occurs due to external reasons;
|
||||||
|
* best utilized for protection extended to vehicle passengers
|
||||||
|
*/
|
||||||
|
final case class RadiationReason(
|
||||||
|
projectile: ActualProjectile,
|
||||||
|
damageModel: DamageAndResistance,
|
||||||
|
radiationShielding: Float
|
||||||
|
) extends DamageReason {
|
||||||
|
def resolution: DamageResolution.Value = DamageResolution.Radiation
|
||||||
|
|
||||||
|
def source: DamageProperties = projectile.profile
|
||||||
|
|
||||||
|
def same(test: DamageReason): Boolean = {
|
||||||
|
test match {
|
||||||
|
case o: RadiationReason => o.projectile.id == projectile.id //can only be another projectile with the same uid
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def adversary: Option[SourceEntry] = Some(projectile.owner)
|
||||||
|
|
||||||
|
override def unstructuredModifiers: List[DamageModifiers.Mod] = List(ShieldAgainstRadiation)
|
||||||
|
|
||||||
|
override def attribution: Int = projectile.attribute_to
|
||||||
|
}
|
||||||
|
|
||||||
|
object RadiationDamageModifiers {
|
||||||
|
trait Mod extends DamageModifiers.Mod {
|
||||||
|
def calculate(damage: Int, data: DamageInteraction, cause: DamageReason): Int = {
|
||||||
|
cause match {
|
||||||
|
case o: RadiationReason => calculate(damage, data, o)
|
||||||
|
case _ => damage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def calculate(damage: Int, data: DamageInteraction, cause: RadiationReason): Int
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the damge is caused by a projectile that emits a field that permeates vehicle armor,
|
||||||
|
* determine by how much the traversed armor's shielding reduces the damage.
|
||||||
|
* Infantry take damage, reduced only if one is equipped with a mechanized assault exo-suit.
|
||||||
|
*/
|
||||||
|
case object ShieldAgainstRadiation extends RadiationDamageModifiers.Mod {
|
||||||
|
def calculate(damage: Int, data: DamageInteraction, cause: RadiationReason): Int = {
|
||||||
|
if (data.resolution == DamageResolution.Radiation) {
|
||||||
|
data.target match {
|
||||||
|
case _: PlayerSource =>
|
||||||
|
damage - (damage * cause.radiationShielding).toInt
|
||||||
|
case _ =>
|
||||||
|
0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
damage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial application of aggravated damage against an infantry target
|
||||||
|
* due to interaction with a radiation field
|
||||||
|
* where the specific damage component is `Splash`.
|
||||||
|
*/
|
||||||
|
case object InfantryAggravatedRadiation extends RadiationDamageModifiers.Mod {
|
||||||
|
def calculate(damage: Int, data: DamageInteraction, cause: RadiationReason): Int = {
|
||||||
|
ProjectileDamageModifierFunctions.baseAggravatedFormula(
|
||||||
|
DamageResolution.Radiation,
|
||||||
|
DamageType.Splash
|
||||||
|
)(damage, data, ProjectileReason(cause.resolution, cause.projectile, cause.damageModel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ongoing application of aggravated damage ticks against an infantry target
|
||||||
|
* due to interaction with a radiation field
|
||||||
|
* where the specific damage component is `Splash`.
|
||||||
|
* This is called "burning" regardless of what the active aura effect actually is.
|
||||||
|
*/
|
||||||
|
case object InfantryAggravatedRadiationBurn extends RadiationDamageModifiers.Mod {
|
||||||
|
def calculate(damage: Int, data: DamageInteraction, cause: RadiationReason): Int = {
|
||||||
|
ProjectileDamageModifierFunctions.baseAggravatedBurnFormula(
|
||||||
|
DamageResolution.Radiation,
|
||||||
|
DamageType.Splash
|
||||||
|
)(damage, data, ProjectileReason(cause.resolution, cause.projectile, cause.damageModel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2020 PSForever
|
// Copyright (c) 2020 PSForever
|
||||||
package net.psforever.objects.vital.projectile
|
package net.psforever.objects.vital.projectile
|
||||||
|
|
||||||
import net.psforever.objects.ballistics.{ChargeDamage, PlayerSource, ProjectileQuality}
|
import net.psforever.objects.ballistics._
|
||||||
import net.psforever.objects.equipment.ChargeFireModeDefinition
|
import net.psforever.objects.equipment.ChargeFireModeDefinition
|
||||||
import net.psforever.objects.vital.base._
|
import net.psforever.objects.vital.base._
|
||||||
import net.psforever.objects.vital.damage.DamageModifierFunctions
|
import net.psforever.objects.vital.damage.DamageModifierFunctions
|
||||||
|
|
@ -329,6 +329,28 @@ case object FlailDistanceDamageBoost extends ProjectileDamageModifiers.Mod {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the damge is caused by a projectile that emits a field that permeates vehicle armor,
|
||||||
|
* determine by how much the traversed armor's shielding reduces the damage.
|
||||||
|
* Infantry take damage, reduced only if one is equipped with a mechanized assault exo-suit.
|
||||||
|
*/
|
||||||
|
case object ShieldAgainstRadiation extends ProjectileDamageModifiers.Mod {
|
||||||
|
def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = {
|
||||||
|
if (data.resolution == DamageResolution.Radiation) {
|
||||||
|
data.target match {
|
||||||
|
case p: PlayerSource if p.ExoSuit == ExoSuitType.MAX =>
|
||||||
|
damage - (damage * p.Modifiers.RadiationShielding).toInt
|
||||||
|
case _: PlayerSource =>
|
||||||
|
damage
|
||||||
|
case _ =>
|
||||||
|
0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
damage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Functions */
|
/* Functions */
|
||||||
object ProjectileDamageModifierFunctions {
|
object ProjectileDamageModifierFunctions {
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,8 @@ trait DamageProperties
|
||||||
/** use a specific modifier as a part of damage calculations */
|
/** use a specific modifier as a part of damage calculations */
|
||||||
private var useDamage1Subtract: Boolean = false
|
private var useDamage1Subtract: Boolean = false
|
||||||
/** some other entity confers damage;
|
/** some other entity confers damage;
|
||||||
* a set value should not `None` and not `0` but is preferred to be the damager's uid */
|
* a set value should be the damager's object uid
|
||||||
|
* usually corresponding to a projectile */
|
||||||
private var damageProxy: Option[Int] = None
|
private var damageProxy: Option[Int] = None
|
||||||
/** na;
|
/** na;
|
||||||
* currently used with jammer properties only */
|
* currently used with jammer properties only */
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,14 @@ trait ResistanceSelection {
|
||||||
def Splash: ResistanceSelection.Format
|
def Splash: ResistanceSelection.Format
|
||||||
def Lash: ResistanceSelection.Format
|
def Lash: ResistanceSelection.Format
|
||||||
def Aggravated: ResistanceSelection.Format
|
def Aggravated: ResistanceSelection.Format
|
||||||
|
def Radiation: ResistanceSelection.Format
|
||||||
|
|
||||||
def apply(data: DamageInteraction) : ResistanceSelection.Format = data.cause.source.CausesDamageType match {
|
def apply(data: DamageInteraction) : ResistanceSelection.Format = data.cause.source.CausesDamageType match {
|
||||||
case DamageType.Direct => Direct
|
case DamageType.Direct => Direct
|
||||||
case DamageType.Splash => Splash
|
case DamageType.Splash => Splash
|
||||||
case DamageType.Lash => Lash
|
case DamageType.Lash => Lash
|
||||||
case DamageType.Aggravated => Aggravated
|
case DamageType.Aggravated => Aggravated
|
||||||
|
case DamageType.Radiation => Splash
|
||||||
case _ => ResistanceSelection.None
|
case _ => ResistanceSelection.None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,6 +30,7 @@ trait ResistanceSelection {
|
||||||
case DamageType.Splash => Splash
|
case DamageType.Splash => Splash
|
||||||
case DamageType.Lash => Lash
|
case DamageType.Lash => Lash
|
||||||
case DamageType.Aggravated => Aggravated
|
case DamageType.Aggravated => Aggravated
|
||||||
|
case DamageType.Radiation => Splash
|
||||||
case _ => ResistanceSelection.None
|
case _ => ResistanceSelection.None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects.vital.resolution
|
package net.psforever.objects.vital.resolution
|
||||||
|
|
||||||
import net.psforever.objects.{PlanetSideGameObject, Player, TurretDeployable, Vehicle}
|
import net.psforever.objects._
|
||||||
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
|
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
|
||||||
import net.psforever.objects.ce.Deployable
|
import net.psforever.objects.ce.Deployable
|
||||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||||
import net.psforever.objects.serverobject.damage.Damageable
|
import net.psforever.objects.serverobject.damage.Damageable
|
||||||
|
import net.psforever.objects.vehicles.VehicleSubsystemEntry
|
||||||
import net.psforever.objects.vital.base.DamageResolution
|
import net.psforever.objects.vital.base.DamageResolution
|
||||||
import net.psforever.objects.vital.{DamagingActivity, Vitality, VitalsHistory}
|
import net.psforever.objects.vital.{DamagingActivity, Vitality, VitalsHistory}
|
||||||
import net.psforever.objects.vital.damage.DamageCalculations
|
import net.psforever.objects.vital.damage.DamageCalculations
|
||||||
|
|
@ -227,20 +228,36 @@ object ResolutionCalculations {
|
||||||
val targetBefore = SourceEntry(target)
|
val targetBefore = SourceEntry(target)
|
||||||
target match {
|
target match {
|
||||||
case vehicle: Vehicle if CanDamage(vehicle, damage, data) =>
|
case vehicle: Vehicle if CanDamage(vehicle, damage, data) =>
|
||||||
val shields = vehicle.Shields
|
vehicleDamageAfterShieldTest(
|
||||||
if (shields > damage) {
|
vehicle,
|
||||||
vehicle.Shields = shields - damage
|
damage,
|
||||||
} else if (shields > 0) {
|
{ vehicle.Shields == 0 || data.cause.source.DamageToVehicleOnly }
|
||||||
vehicle.Health = vehicle.Health - (damage - shields)
|
)
|
||||||
vehicle.Shields = 0
|
|
||||||
} else {
|
|
||||||
vehicle.Health = vehicle.Health - damage
|
|
||||||
}
|
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
DamageResult(targetBefore, SourceEntry(target), data)
|
DamageResult(targetBefore, SourceEntry(target), data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def vehicleDamageAfterShieldTest(
|
||||||
|
vehicle: Vehicle,
|
||||||
|
damage: Int,
|
||||||
|
ignoreShieldsDamage: Boolean
|
||||||
|
): Unit = {
|
||||||
|
val shields = vehicle.Shields
|
||||||
|
if (ignoreShieldsDamage) {
|
||||||
|
vehicle.Health = vehicle.Health - damage
|
||||||
|
} else {
|
||||||
|
if (shields > damage) {
|
||||||
|
vehicle.Shields = shields - damage
|
||||||
|
} else if (shields > 0) {
|
||||||
|
vehicle.Health = vehicle.Health - (damage - shields)
|
||||||
|
vehicle.Shields = 0
|
||||||
|
} else {
|
||||||
|
vehicle.Health = vehicle.Health - damage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def SimpleApplication(damage: Int, data: DamageInteraction)(target: PlanetSideGameObject with FactionAffinity): DamageResult = {
|
def SimpleApplication(damage: Int, data: DamageInteraction)(target: PlanetSideGameObject with FactionAffinity): DamageResult = {
|
||||||
val targetBefore = SourceEntry(target)
|
val targetBefore = SourceEntry(target)
|
||||||
target match {
|
target match {
|
||||||
|
|
@ -326,6 +343,31 @@ object ResolutionCalculations {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def BfrApplication(damage: Int, data: DamageInteraction)(target: PlanetSideGameObject with FactionAffinity): DamageResult = {
|
||||||
|
val targetBefore = SourceEntry(target)
|
||||||
|
target match {
|
||||||
|
case obj: Vehicle
|
||||||
|
if CanDamage(obj, damage, data) && GlobalDefinitions.isBattleFrameVehicle(obj.Definition) =>
|
||||||
|
vehicleDamageAfterShieldTest(
|
||||||
|
obj,
|
||||||
|
damage,
|
||||||
|
{
|
||||||
|
data.cause.source.DamageToBattleframeOnly ||
|
||||||
|
data.cause.source.DamageToVehicleOnly ||
|
||||||
|
!obj.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator).get.Enabled ||
|
||||||
|
obj.Shields == 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
DamageResult(targetBefore, SourceEntry(target), data)
|
||||||
|
|
||||||
|
case _: Vehicle =>
|
||||||
|
VehicleApplication(damage, data)(target)
|
||||||
|
|
||||||
|
case _ =>
|
||||||
|
DamageResult(targetBefore, SourceEntry(target), data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private def noDoubleLash(target: PlanetSideGameObject with VitalsHistory, data: DamageInteraction): Boolean = {
|
private def noDoubleLash(target: PlanetSideGameObject with VitalsHistory, data: DamageInteraction): Boolean = {
|
||||||
data.cause match {
|
data.cause match {
|
||||||
case reason: ProjectileReason if reason.resolution == DamageResolution.Lash =>
|
case reason: ProjectileReason if reason.resolution == DamageResolution.Lash =>
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,14 @@
|
||||||
package net.psforever.objects.zones
|
package net.psforever.objects.zones
|
||||||
|
|
||||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||||
|
import net.psforever.objects.zones.blockmap.SectorPopulation
|
||||||
|
|
||||||
trait InteractsWithZone
|
trait InteractsWithZone
|
||||||
extends PlanetSideServerObject {
|
extends PlanetSideServerObject {
|
||||||
/** interactions for this particular entity is allowed */
|
/** interactions for this particular entity is allowed */
|
||||||
private var _allowInteraction: Boolean = true
|
private var _allowInteraction: Boolean = true
|
||||||
|
/** maximum interaction range used to generate the commonly tested sector */
|
||||||
|
private var interactionRange: Float = 0.1f
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the interactive permissions of this entity change.
|
* If the interactive permissions of this entity change.
|
||||||
|
|
@ -24,7 +27,7 @@ trait InteractsWithZone
|
||||||
_allowInteraction = permit
|
_allowInteraction = permit
|
||||||
if (before != permit) {
|
if (before != permit) {
|
||||||
if (permit) {
|
if (permit) {
|
||||||
interactions.foreach { _.interaction(target = this) }
|
doInteractions()
|
||||||
} else {
|
} else {
|
||||||
interactions.foreach ( _.resetInteraction(target = this) )
|
interactions.foreach ( _.resetInteraction(target = this) )
|
||||||
}
|
}
|
||||||
|
|
@ -36,14 +39,26 @@ trait InteractsWithZone
|
||||||
|
|
||||||
def interaction(func: ZoneInteraction): List[ZoneInteraction] = {
|
def interaction(func: ZoneInteraction): List[ZoneInteraction] = {
|
||||||
interactions = interactions :+ func
|
interactions = interactions :+ func
|
||||||
|
if (func.range > interactionRange) {
|
||||||
|
interactionRange = func.range
|
||||||
|
}
|
||||||
interactions
|
interactions
|
||||||
}
|
}
|
||||||
|
|
||||||
def interaction(): List[ZoneInteraction] = interactions
|
def interaction(): List[ZoneInteraction] = interactions
|
||||||
|
|
||||||
|
def getInteractionSector(): SectorPopulation = {
|
||||||
|
this.Zone.blockMap.sector(this.Position, interactionRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
def doInteractions(): Unit = {
|
||||||
|
val sector = getInteractionSector()
|
||||||
|
interactions.foreach { _.interaction(sector, target = this) }
|
||||||
|
}
|
||||||
|
|
||||||
def zoneInteractions(): Unit = {
|
def zoneInteractions(): Unit = {
|
||||||
if (_allowInteraction) {
|
if (_allowInteraction) {
|
||||||
interactions.foreach { _.interaction(target = this) }
|
doInteractions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,18 +67,31 @@ trait InteractsWithZone
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait ZoneInteractionType
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The basic behavior of an entity in a zone.
|
* The basic behavior of an entity in a zone.
|
||||||
* @see `InteractsWithZone`
|
* @see `InteractsWithZone`
|
||||||
* @see `Zone`
|
* @see `Zone`
|
||||||
*/
|
*/
|
||||||
trait ZoneInteraction {
|
trait ZoneInteraction {
|
||||||
|
/**
|
||||||
|
* A categorical descriptor for this interaction.
|
||||||
|
*/
|
||||||
|
def Type: ZoneInteractionType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The anticipated (radial?) distance across which this interaction affects the zone's blockmap.
|
||||||
|
*/
|
||||||
|
def range: Float
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The method by which zone interactions are tested.
|
* The method by which zone interactions are tested.
|
||||||
* How a target tests this interaction with elements of the target's zone.
|
* How a target tests this interaction with elements of the target's zone.
|
||||||
|
* @param sector the portion of the block map being tested
|
||||||
* @param target the fixed element in this test
|
* @param target the fixed element in this test
|
||||||
*/
|
*/
|
||||||
def interaction(target: InteractsWithZone): Unit
|
def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Suspend any current interaction procedures.
|
* Suspend any current interaction procedures.
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package net.psforever.objects.zones
|
||||||
|
|
||||||
import akka.actor.{ActorContext, ActorRef, Props}
|
import akka.actor.{ActorContext, ActorRef, Props}
|
||||||
import net.psforever.objects.{PlanetSideGameObject, _}
|
import net.psforever.objects.{PlanetSideGameObject, _}
|
||||||
import net.psforever.objects.ballistics.SourceEntry
|
import net.psforever.objects.ballistics.{Projectile, SourceEntry}
|
||||||
import net.psforever.objects.ce.Deployable
|
import net.psforever.objects.ce.Deployable
|
||||||
import net.psforever.objects.equipment.Equipment
|
import net.psforever.objects.equipment.Equipment
|
||||||
import net.psforever.objects.guid.{NumberPoolHub, UniqueNumberOps, UniqueNumberSetup}
|
import net.psforever.objects.guid.{NumberPoolHub, UniqueNumberOps, UniqueNumberSetup}
|
||||||
|
|
@ -12,10 +12,10 @@ import net.psforever.objects.guid.source.MaxNumberSource
|
||||||
import net.psforever.objects.inventory.Container
|
import net.psforever.objects.inventory.Container
|
||||||
import net.psforever.objects.serverobject.painbox.{Painbox, PainboxDefinition}
|
import net.psforever.objects.serverobject.painbox.{Painbox, PainboxDefinition}
|
||||||
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
|
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
|
||||||
import net.psforever.objects.serverobject.structures.{Amenity, AmenityOwner, Building, StructureType, WarpGate}
|
import net.psforever.objects.serverobject.structures._
|
||||||
import net.psforever.objects.serverobject.turret.FacilityTurret
|
import net.psforever.objects.serverobject.turret.FacilityTurret
|
||||||
import net.psforever.objects.serverobject.zipline.ZipLinePath
|
import net.psforever.objects.serverobject.zipline.ZipLinePath
|
||||||
import net.psforever.types.{DriveState, PlanetSideEmpire, PlanetSideGUID, SpawnGroup, Vector3}
|
import net.psforever.types._
|
||||||
import org.log4s.Logger
|
import org.log4s.Logger
|
||||||
import net.psforever.services.avatar.AvatarService
|
import net.psforever.services.avatar.AvatarService
|
||||||
import net.psforever.services.local.LocalService
|
import net.psforever.services.local.LocalService
|
||||||
|
|
@ -124,6 +124,9 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
||||||
*/
|
*/
|
||||||
private val corpses: ListBuffer[Player] = ListBuffer[Player]()
|
private val corpses: ListBuffer[Player] = ListBuffer[Player]()
|
||||||
|
|
||||||
|
private var projectiles: ActorRef = Default.Actor
|
||||||
|
private val projectileList: ListBuffer[Projectile] = ListBuffer[Projectile]()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
private var population: ActorRef = Default.Actor
|
private var population: ActorRef = Default.Actor
|
||||||
|
|
@ -189,6 +192,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
||||||
context.actorOf(Props(classOf[UniqueNumberSys], this, this.guid), s"zone-$id-uns")
|
context.actorOf(Props(classOf[UniqueNumberSys], this, this.guid), s"zone-$id-uns")
|
||||||
ground = context.actorOf(Props(classOf[ZoneGroundActor], this, equipmentOnGround), s"zone-$id-ground")
|
ground = context.actorOf(Props(classOf[ZoneGroundActor], this, equipmentOnGround), s"zone-$id-ground")
|
||||||
deployables = context.actorOf(Props(classOf[ZoneDeployableActor], this, constructions), s"zone-$id-deployables")
|
deployables = context.actorOf(Props(classOf[ZoneDeployableActor], this, constructions), s"zone-$id-deployables")
|
||||||
|
projectiles = context.actorOf(Props(classOf[ZoneProjectileActor], this, projectileList), s"zone-$id-projectiles")
|
||||||
transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"zone-$id-vehicles")
|
transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"zone-$id-vehicles")
|
||||||
population = context.actorOf(Props(classOf[ZonePopulationActor], this, players, corpses), s"zone-$id-players")
|
population = context.actorOf(Props(classOf[ZonePopulationActor], this, players, corpses), s"zone-$id-players")
|
||||||
projector = context.actorOf(
|
projector = context.actorOf(
|
||||||
|
|
@ -540,6 +544,10 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
||||||
|
|
||||||
def Deployables: ActorRef = deployables
|
def Deployables: ActorRef = deployables
|
||||||
|
|
||||||
|
def Projectile: ActorRef = projectiles
|
||||||
|
|
||||||
|
def Projectiles: List[Projectile] = projectileList.toList
|
||||||
|
|
||||||
def Transport: ActorRef = transport
|
def Transport: ActorRef = transport
|
||||||
|
|
||||||
def Population: ActorRef = population
|
def Population: ActorRef = population
|
||||||
|
|
@ -1061,11 +1069,11 @@ object Zone {
|
||||||
* and a token that qualifies the current location of the object in the zone is returned.
|
* and a token that qualifies the current location of the object in the zone is returned.
|
||||||
* The following groups of objects are searched:
|
* The following groups of objects are searched:
|
||||||
* the inventories of all players and all corpses,
|
* the inventories of all players and all corpses,
|
||||||
* all vehicles trunks,
|
* all vehicles weapon mounts and trunks,
|
||||||
* the lockers of all players and corpses;
|
* the lockers of all players and corpses;
|
||||||
* and, if still not found, the ground is scoured too.
|
* and, if still not found, the ground is scoured too.
|
||||||
* @see `ItemLocation`<br>
|
* @see `ItemLocation`
|
||||||
* `LockerContainer`
|
* @see `LockerContainer`
|
||||||
* @param equipment the target object
|
* @param equipment the target object
|
||||||
* @param guid that target object's globally unique identifier
|
* @param guid that target object's globally unique identifier
|
||||||
* @param continent the zone whose objects to search
|
* @param continent the zone whose objects to search
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,224 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.zones
|
||||||
|
|
||||||
|
import akka.actor.{Actor, Cancellable}
|
||||||
|
import net.psforever.objects.ballistics.Projectile
|
||||||
|
import net.psforever.objects.guid.{GUIDTask, StraightforwardTask, TaskBundle, TaskWorkflow}
|
||||||
|
import net.psforever.services.Service
|
||||||
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
|
import net.psforever.types.PlanetSideGUID
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
import scala.concurrent.Future
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize management of the list of some `Projectile`s maintained by a zone.
|
||||||
|
* @param zone the zone being represented
|
||||||
|
* @param projectileList the zone's projectile list
|
||||||
|
*/
|
||||||
|
class ZoneProjectileActor(
|
||||||
|
zone: Zone,
|
||||||
|
projectileList: mutable.ListBuffer[Projectile]
|
||||||
|
) extends Actor {
|
||||||
|
/** a series of timers matched against projectile unique identifiers,
|
||||||
|
* marking the maximum lifespan of the projectile */
|
||||||
|
val projectileLifespan: mutable.HashMap[PlanetSideGUID, Cancellable] = new mutable.HashMap[PlanetSideGUID, Cancellable]
|
||||||
|
|
||||||
|
override def postStop() : Unit = {
|
||||||
|
projectileLifespan.values.foreach { _.cancel() }
|
||||||
|
projectileList.iterator.filter(_.HasGUID).foreach { p => cleanUpRemoteProjectile(p.GUID, p) }
|
||||||
|
projectileList.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
def receive: Receive = {
|
||||||
|
case ZoneProjectile.Add(filterGuid, projectile) =>
|
||||||
|
if (projectile.Definition.ExistsOnRemoteClients) {
|
||||||
|
if (projectile.HasGUID) {
|
||||||
|
cleanUpRemoteProjectile(projectile.GUID, projectile)
|
||||||
|
TaskWorkflow.execute(reregisterProjectile(filterGuid, projectile))
|
||||||
|
} else {
|
||||||
|
TaskWorkflow.execute(registerProjectile(filterGuid, projectile))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case ZoneProjectile.Remove(guid) =>
|
||||||
|
projectileList.find(_.GUID == guid) match {
|
||||||
|
case Some(projectile) =>
|
||||||
|
cleanUpRemoteProjectile(guid, projectile)
|
||||||
|
TaskWorkflow.execute(unregisterProjectile(projectile))
|
||||||
|
case _ =>
|
||||||
|
projectileLifespan.remove(guid)
|
||||||
|
//if we can't find this projectile by guid, remove any projectiles that are unregistered
|
||||||
|
val (in, out) = projectileList.filter(_.HasGUID).partition { p => zone.GUID(p.GUID).nonEmpty }
|
||||||
|
projectileList.clear()
|
||||||
|
projectileList.addAll(in)
|
||||||
|
out.foreach { p =>
|
||||||
|
cleanUpRemoteProjectile(p.GUID, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct tasking that adds a completed but unregistered projectile into the scene.
|
||||||
|
* After the projectile is registered to the curent zone's global unique identifier system,
|
||||||
|
* all connected clients save for the one that registered it will be informed about the projectile's "creation."
|
||||||
|
* @param obj the projectile to be registered
|
||||||
|
* @return a `TaskBundle` message
|
||||||
|
*/
|
||||||
|
private def registerProjectile(filterGuid: PlanetSideGUID, obj: Projectile): TaskBundle = {
|
||||||
|
TaskBundle(
|
||||||
|
new StraightforwardTask() {
|
||||||
|
private val filter = filterGuid
|
||||||
|
private val globalProjectile = obj
|
||||||
|
private val func: (PlanetSideGUID, PlanetSideGUID, Projectile) => Unit = loadedRemoteProjectile
|
||||||
|
|
||||||
|
override def description(): String = s"register a ${globalProjectile.profile.Name}"
|
||||||
|
|
||||||
|
def action(): Future[Any] = {
|
||||||
|
func(filter, globalProjectile.GUID, globalProjectile)
|
||||||
|
Future(true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
List(GUIDTask.registerObject(zone.GUID, obj))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct tasking that removes a formerly complete and currently registered projectile from the scene.
|
||||||
|
* After the projectile is unregistered from the curent zone's global unique identifier system,
|
||||||
|
* all connected clients save for the one that registered it will be informed about the projectile's "destruction."
|
||||||
|
* @param obj the projectile to be unregistered
|
||||||
|
* @return a `TaskBundle` message
|
||||||
|
*/
|
||||||
|
private def unregisterProjectile(obj: Projectile): TaskBundle = GUIDTask.unregisterObject(zone.GUID, obj)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the projectile object is unregistered, register it.
|
||||||
|
* If the projectile object is already registered, unregister it and then register it again.
|
||||||
|
* @see `registerProjectile(Projectile)`
|
||||||
|
* @see `unregisterProjectile(Projectile)`
|
||||||
|
* @param obj the projectile to be registered (a second time?)
|
||||||
|
* @return a `TaskBundle` message
|
||||||
|
*/
|
||||||
|
def reregisterProjectile(filterGuid: PlanetSideGUID, obj: Projectile): TaskBundle = {
|
||||||
|
val reg = registerProjectile(filterGuid, obj)
|
||||||
|
if (obj.HasGUID) {
|
||||||
|
TaskBundle(
|
||||||
|
reg.mainTask,
|
||||||
|
TaskBundle(
|
||||||
|
reg.subTasks(0).mainTask,
|
||||||
|
unregisterProjectile(obj)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
reg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For a given registered remote projectile,
|
||||||
|
* perform all the actions necessary to properly integrate it into the management system.<br>
|
||||||
|
* <br>
|
||||||
|
* Those actions involve:<br>
|
||||||
|
* - determine whether or not the default filter needs to be applied,<br>
|
||||||
|
* - add the projectile to the zone managing list,<br>
|
||||||
|
* - if the projectile is a radiation cloud, add it to the zone blockmap<br>
|
||||||
|
* - set up the internal disposal timer, and<br>
|
||||||
|
* - dispatch a message to introduce the projectile to the game world.
|
||||||
|
* @param filterGuid a unique identifier filtering messages from a certain recipient
|
||||||
|
* @param projectileGuid the projectile unique identifier that was assigned by the zone's unique number system
|
||||||
|
* @param projectile the projectile being included
|
||||||
|
*/
|
||||||
|
def loadedRemoteProjectile(
|
||||||
|
filterGuid: PlanetSideGUID,
|
||||||
|
projectileGuid: PlanetSideGUID,
|
||||||
|
projectile: Projectile
|
||||||
|
): Unit = {
|
||||||
|
val definition = projectile.Definition
|
||||||
|
projectileList.addOne(projectile)
|
||||||
|
val (clarifiedFilterGuid, duration) = if (definition.radiation_cloud) {
|
||||||
|
zone.blockMap.addTo(projectile)
|
||||||
|
(Service.defaultPlayerGUID, projectile.profile.Lifespan seconds)
|
||||||
|
} else {
|
||||||
|
//remote projectiles that are not radiation clouds have lifespans controlled by the controller (user)
|
||||||
|
//if the controller fails, the projectile has a bit more than its normal lifespan before automatic clean up
|
||||||
|
(filterGuid, projectile.profile.Lifespan * 1.5f seconds)
|
||||||
|
}
|
||||||
|
projectileLifespan.put(
|
||||||
|
projectileGuid,
|
||||||
|
context.system.scheduler.scheduleOnce(duration, self, ZoneProjectile.Remove(projectileGuid))
|
||||||
|
)
|
||||||
|
zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
AvatarAction.LoadProjectile(
|
||||||
|
clarifiedFilterGuid,
|
||||||
|
definition.ObjectId,
|
||||||
|
projectileGuid,
|
||||||
|
definition.Packet.ConstructorData(projectile).get
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For a given registered remote projectile, perform all the actions necessary to properly dispose of it.
|
||||||
|
* The projectile doesn't have to be registered at the moment,
|
||||||
|
* but you do need to know it's (previous) globally unique identifier.<br>
|
||||||
|
* <br>
|
||||||
|
* Those actions involve:<br>
|
||||||
|
* - remove and cancel the internal disposal timer,<br>
|
||||||
|
* - if the projectile is a radiation cloud, remove it from the zone blockmap<br>
|
||||||
|
* - remove the projectile from the zone managing list, and<br>
|
||||||
|
* - dispatch messages to eliminate the projectile from the game world.
|
||||||
|
* @param projectile_guid the globally unique identifier of the projectile
|
||||||
|
* @param projectile the projectile
|
||||||
|
*/
|
||||||
|
def cleanUpRemoteProjectile(projectile_guid: PlanetSideGUID, projectile: Projectile): Unit = {
|
||||||
|
projectileLifespan.remove(projectile_guid) match {
|
||||||
|
case Some(c) => c.cancel()
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
projectileList.remove(projectileList.indexOf(projectile))
|
||||||
|
if (projectile.Definition.radiation_cloud) {
|
||||||
|
zone.blockMap.removeFrom(projectile)
|
||||||
|
zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
AvatarAction.ObjectDelete(PlanetSideGUID(0), projectile_guid, 2)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
AvatarAction.ProjectileExplodes(PlanetSideGUID(0), projectile_guid, projectile)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ZoneProjectile {
|
||||||
|
/**
|
||||||
|
* Start monitoring the projectile.
|
||||||
|
* @param filterGuid a unique identifier filtering messages from a certain recipient
|
||||||
|
* @param projectile the projectile being included
|
||||||
|
*/
|
||||||
|
final case class Add(filterGuid: PlanetSideGUID, projectile: Projectile)
|
||||||
|
|
||||||
|
object Add {
|
||||||
|
/**
|
||||||
|
* Overloaded constructor for `Add` which onyl requires the projectile
|
||||||
|
* and defaults the filtering.
|
||||||
|
* @param projectile the projectile being included
|
||||||
|
* @return an `Add` message
|
||||||
|
*/
|
||||||
|
def apply(projectile: Projectile): Add = Add(PlanetSideGUID(0), projectile)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the projectile from being monitored.
|
||||||
|
* @param guid the projectile assigned global unique identifier;
|
||||||
|
* not the same as the client local unique identifier (40100 to 40125)
|
||||||
|
*/
|
||||||
|
final case class Remove(guid: PlanetSideGUID)
|
||||||
|
}
|
||||||
|
|
@ -77,7 +77,7 @@ class BlockMap(fullMapWidth: Int, fullMapHeight: Int, desiredSpanSize: Int) {
|
||||||
* @return a conglomerate sector which lists all of the entities in the discovered sector(s)
|
* @return a conglomerate sector which lists all of the entities in the discovered sector(s)
|
||||||
*/
|
*/
|
||||||
def sector(p: Vector3, range: Float): SectorPopulation = {
|
def sector(p: Vector3, range: Float): SectorPopulation = {
|
||||||
BlockMap.quickToSectorGroup( BlockMap.findSectorIndices(blockMap = this, p, range).map { blocks } )
|
BlockMap.quickToSectorGroup(range, BlockMap.findSectorIndices(blockMap = this, p, range).map { blocks } )
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -129,7 +129,7 @@ class BlockMap(fullMapWidth: Int, fullMapHeight: Int, desiredSpanSize: Int) {
|
||||||
val toSectors = to.toSet.map { blocks }
|
val toSectors = to.toSet.map { blocks }
|
||||||
toSectors.foreach { block => block.addTo(target) }
|
toSectors.foreach { block => block.addTo(target) }
|
||||||
target.blockMapEntry = Some(BlockMapEntry(toPosition, range, to.toSet))
|
target.blockMapEntry = Some(BlockMapEntry(toPosition, range, to.toSet))
|
||||||
BlockMap.quickToSectorGroup(toSectors)
|
BlockMap.quickToSectorGroup(range, toSectors)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -201,7 +201,7 @@ class BlockMap(fullMapWidth: Int, fullMapHeight: Int, desiredSpanSize: Int) {
|
||||||
target.blockMapEntry = None
|
target.blockMapEntry = None
|
||||||
val from = entry.sectors.map { blocks }
|
val from = entry.sectors.map { blocks }
|
||||||
from.foreach { block => block.removeFrom(target) }
|
from.foreach { block => block.removeFrom(target) }
|
||||||
BlockMap.quickToSectorGroup(from)
|
BlockMap.quickToSectorGroup(range, from)
|
||||||
case None =>
|
case None =>
|
||||||
SectorGroup(Nil)
|
SectorGroup(Nil)
|
||||||
}
|
}
|
||||||
|
|
@ -262,7 +262,7 @@ class BlockMap(fullMapWidth: Int, fullMapHeight: Int, desiredSpanSize: Int) {
|
||||||
to.diff(from).foreach { index => blocks(index).addTo(target) }
|
to.diff(from).foreach { index => blocks(index).addTo(target) }
|
||||||
from.diff(to).foreach { index => blocks(index).removeFrom(target) }
|
from.diff(to).foreach { index => blocks(index).removeFrom(target) }
|
||||||
target.blockMapEntry = Some(BlockMapEntry(toPosition, range, to))
|
target.blockMapEntry = Some(BlockMapEntry(toPosition, range, to))
|
||||||
BlockMap.quickToSectorGroup(to.map { blocks })
|
BlockMap.quickToSectorGroup(range, to.map { blocks })
|
||||||
case None =>
|
case None =>
|
||||||
SectorGroup(Nil)
|
SectorGroup(Nil)
|
||||||
}
|
}
|
||||||
|
|
@ -394,4 +394,19 @@ object BlockMap {
|
||||||
SectorGroup(to)
|
SectorGroup(to)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If only one sector, just return that sector.
|
||||||
|
* If a group of sectors, organize them into a single referential sector.
|
||||||
|
* @param range a custom range value
|
||||||
|
* @param to all allocated sectors
|
||||||
|
* @return a conglomerate sector which lists all of the entities in the allocated sector(s)
|
||||||
|
*/
|
||||||
|
def quickToSectorGroup(range: Float, to: Iterable[Sector]): SectorPopulation = {
|
||||||
|
if (to.size == 1) {
|
||||||
|
SectorGroup(range, to.head)
|
||||||
|
} else {
|
||||||
|
SectorGroup(range, to)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) 2021 PSForever
|
// Copyright (c) 2021 PSForever
|
||||||
package net.psforever.objects.zones.blockmap
|
package net.psforever.objects.zones.blockmap
|
||||||
|
|
||||||
|
import net.psforever.objects.ballistics.Projectile
|
||||||
import net.psforever.objects.ce.Deployable
|
import net.psforever.objects.ce.Deployable
|
||||||
import net.psforever.objects.equipment.Equipment
|
import net.psforever.objects.equipment.Equipment
|
||||||
import net.psforever.objects.serverobject.environment.PieceOfEnvironment
|
import net.psforever.objects.serverobject.environment.PieceOfEnvironment
|
||||||
|
|
@ -13,6 +14,8 @@ import scala.collection.mutable.ListBuffer
|
||||||
* The collections of entities in a sector conglomerate.
|
* The collections of entities in a sector conglomerate.
|
||||||
*/
|
*/
|
||||||
trait SectorPopulation {
|
trait SectorPopulation {
|
||||||
|
def range: Float
|
||||||
|
|
||||||
def livePlayerList: List[Player]
|
def livePlayerList: List[Player]
|
||||||
|
|
||||||
def corpseList: List[Player]
|
def corpseList: List[Player]
|
||||||
|
|
@ -29,6 +32,8 @@ trait SectorPopulation {
|
||||||
|
|
||||||
def environmentList: List[PieceOfEnvironment]
|
def environmentList: List[PieceOfEnvironment]
|
||||||
|
|
||||||
|
def projectileList: List[Projectile]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A count of all the entities in all the lists.
|
* A count of all the entities in all the lists.
|
||||||
*/
|
*/
|
||||||
|
|
@ -40,7 +45,8 @@ trait SectorPopulation {
|
||||||
deployableList.size +
|
deployableList.size +
|
||||||
buildingList.size +
|
buildingList.size +
|
||||||
amenityList.size +
|
amenityList.size +
|
||||||
environmentList.size
|
environmentList.size +
|
||||||
|
projectileList.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,6 +149,12 @@ class Sector(val longitude: Int, val latitude: Int, val span: Int)
|
||||||
(a: PieceOfEnvironment, b: PieceOfEnvironment) => a eq b
|
(a: PieceOfEnvironment, b: PieceOfEnvironment) => a eq b
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val projectiles: SectorListOf[Projectile] = new SectorListOf[Projectile](
|
||||||
|
(a: Projectile, b: Projectile) => a.id == b.id
|
||||||
|
)
|
||||||
|
|
||||||
|
def range: Float = span.toFloat
|
||||||
|
|
||||||
def livePlayerList : List[Player] = livePlayers.list
|
def livePlayerList : List[Player] = livePlayers.list
|
||||||
|
|
||||||
def corpseList: List[Player] = corpses.list
|
def corpseList: List[Player] = corpses.list
|
||||||
|
|
@ -159,6 +171,8 @@ class Sector(val longitude: Int, val latitude: Int, val span: Int)
|
||||||
|
|
||||||
def environmentList: List[PieceOfEnvironment] = environment.list
|
def environmentList: List[PieceOfEnvironment] = environment.list
|
||||||
|
|
||||||
|
def projectileList: List[Projectile] = projectiles.list
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appropriate an entity added to this blockmap bucket
|
* Appropriate an entity added to this blockmap bucket
|
||||||
* inot a list of objects that are like itself.
|
* inot a list of objects that are like itself.
|
||||||
|
|
@ -189,6 +203,8 @@ class Sector(val longitude: Int, val latitude: Int, val span: Int)
|
||||||
amenities.list.size < amenities.addTo(a).size
|
amenities.list.size < amenities.addTo(a).size
|
||||||
case e: PieceOfEnvironment =>
|
case e: PieceOfEnvironment =>
|
||||||
environment.list.size < environment.addTo(e).size
|
environment.list.size < environment.addTo(e).size
|
||||||
|
case p: Projectile =>
|
||||||
|
projectiles.list.size < projectiles.addTo(p).size
|
||||||
case _ =>
|
case _ =>
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
@ -211,6 +227,8 @@ class Sector(val longitude: Int, val latitude: Int, val span: Int)
|
||||||
equipmentOnGround.list.size > equipmentOnGround.removeFrom(e).size
|
equipmentOnGround.list.size > equipmentOnGround.removeFrom(e).size
|
||||||
case d: Deployable =>
|
case d: Deployable =>
|
||||||
deployables.list.size > deployables.removeFrom(d).size
|
deployables.list.size > deployables.removeFrom(d).size
|
||||||
|
case p: Projectile =>
|
||||||
|
projectiles.list.size > projectiles.removeFrom(p).size
|
||||||
case _ =>
|
case _ =>
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
@ -230,6 +248,7 @@ class Sector(val longitude: Int, val latitude: Int, val span: Int)
|
||||||
* @param environmentList fields that represent the game world environment
|
* @param environmentList fields that represent the game world environment
|
||||||
*/
|
*/
|
||||||
class SectorGroup(
|
class SectorGroup(
|
||||||
|
val range: Float,
|
||||||
val livePlayerList: List[Player],
|
val livePlayerList: List[Player],
|
||||||
val corpseList: List[Player],
|
val corpseList: List[Player],
|
||||||
val vehicleList: List[Vehicle],
|
val vehicleList: List[Vehicle],
|
||||||
|
|
@ -237,7 +256,8 @@ class SectorGroup(
|
||||||
val deployableList: List[Deployable],
|
val deployableList: List[Deployable],
|
||||||
val buildingList: List[Building],
|
val buildingList: List[Building],
|
||||||
val amenityList: List[Amenity],
|
val amenityList: List[Amenity],
|
||||||
val environmentList: List[PieceOfEnvironment]
|
val environmentList: List[PieceOfEnvironment],
|
||||||
|
val projectileList: List[Projectile]
|
||||||
)
|
)
|
||||||
extends SectorPopulation
|
extends SectorPopulation
|
||||||
|
|
||||||
|
|
@ -250,6 +270,7 @@ object SectorGroup {
|
||||||
*/
|
*/
|
||||||
def apply(sector: Sector): SectorGroup = {
|
def apply(sector: Sector): SectorGroup = {
|
||||||
new SectorGroup(
|
new SectorGroup(
|
||||||
|
sector.range,
|
||||||
sector.livePlayerList,
|
sector.livePlayerList,
|
||||||
sector.corpseList,
|
sector.corpseList,
|
||||||
sector.vehicleList,
|
sector.vehicleList,
|
||||||
|
|
@ -257,7 +278,30 @@ object SectorGroup {
|
||||||
sector.deployableList,
|
sector.deployableList,
|
||||||
sector.buildingList,
|
sector.buildingList,
|
||||||
sector.amenityList,
|
sector.amenityList,
|
||||||
sector.environmentList
|
sector.environmentList,
|
||||||
|
sector.projectileList
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded constructor that takes a single sector
|
||||||
|
* and transfers the lists of entities into a single conglomeration of the sector populations.
|
||||||
|
* @param range a custom range value
|
||||||
|
* @param sector the sector to be counted
|
||||||
|
* @return a `SectorGroup` object
|
||||||
|
*/
|
||||||
|
def apply(range: Float, sector: Sector): SectorGroup = {
|
||||||
|
new SectorGroup(
|
||||||
|
range,
|
||||||
|
sector.livePlayerList,
|
||||||
|
sector.corpseList,
|
||||||
|
sector.vehicleList,
|
||||||
|
sector.equipmentOnGroundList,
|
||||||
|
sector.deployableList,
|
||||||
|
sector.buildingList,
|
||||||
|
sector.amenityList,
|
||||||
|
sector.environmentList,
|
||||||
|
sector.projectileList
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -268,15 +312,52 @@ object SectorGroup {
|
||||||
* @return a `SectorGroup` object
|
* @return a `SectorGroup` object
|
||||||
*/
|
*/
|
||||||
def apply(sectors: Iterable[Sector]): SectorGroup = {
|
def apply(sectors: Iterable[Sector]): SectorGroup = {
|
||||||
new SectorGroup(
|
if (sectors.isEmpty) {
|
||||||
sectors.flatMap { _.livePlayerList }.toList.distinct,
|
SectorGroup(range = 0, sectors = Nil)
|
||||||
sectors.flatMap { _.corpseList }.toList.distinct,
|
} else if (sectors.size == 1) {
|
||||||
sectors.flatMap { _.vehicleList }.toList.distinct,
|
SectorGroup(sectors.head.range, sectors)
|
||||||
sectors.flatMap { _.equipmentOnGroundList }.toList.distinct,
|
} else {
|
||||||
sectors.flatMap { _.deployableList }.toList.distinct,
|
SectorGroup(sectors.maxBy { _.range }.range, sectors)
|
||||||
sectors.flatMap { _.buildingList }.toList.distinct,
|
}
|
||||||
sectors.flatMap { _.amenityList }.toList.distinct,
|
}
|
||||||
sectors.flatMap { _.environmentList }.toList.distinct
|
|
||||||
)
|
/**
|
||||||
|
* Overloaded constructor that takes a group of sectors
|
||||||
|
* and condenses all of the lists of entities into a single conglomeration of the sector populations.
|
||||||
|
* @param range a custom range value
|
||||||
|
* @param sectors the series of sectors to be counted
|
||||||
|
* @return a `SectorGroup` object
|
||||||
|
*/
|
||||||
|
def apply(range: Float, sectors: Iterable[Sector]): SectorGroup = {
|
||||||
|
if (sectors.isEmpty) {
|
||||||
|
new SectorGroup(range, Nil, Nil, Nil, Nil, Nil, Nil, Nil, Nil, Nil)
|
||||||
|
} else if (sectors.size == 1) {
|
||||||
|
val sector = sectors.head
|
||||||
|
new SectorGroup(
|
||||||
|
range,
|
||||||
|
sector.livePlayerList,
|
||||||
|
sector.corpseList,
|
||||||
|
sector.vehicleList,
|
||||||
|
sector.equipmentOnGroundList,
|
||||||
|
sector.deployableList,
|
||||||
|
sector.buildingList,
|
||||||
|
sector.amenityList,
|
||||||
|
sector.environmentList,
|
||||||
|
sector.projectileList
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
new SectorGroup(
|
||||||
|
range,
|
||||||
|
sectors.flatMap { _.livePlayerList }.toList.distinct,
|
||||||
|
sectors.flatMap { _.corpseList }.toList.distinct,
|
||||||
|
sectors.flatMap { _.vehicleList }.toList.distinct,
|
||||||
|
sectors.flatMap { _.equipmentOnGroundList }.toList.distinct,
|
||||||
|
sectors.flatMap { _.deployableList }.toList.distinct,
|
||||||
|
sectors.flatMap { _.buildingList }.toList.distinct,
|
||||||
|
sectors.flatMap { _.amenityList }.toList.distinct,
|
||||||
|
sectors.flatMap { _.environmentList }.toList.distinct,
|
||||||
|
sectors.flatMap { _.projectileList }.toList.distinct
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -335,7 +335,7 @@ object GamePacketOpcode extends Enumeration {
|
||||||
case 0x19 => game.ObjectDeleteMessage.decode
|
case 0x19 => game.ObjectDeleteMessage.decode
|
||||||
case 0x1a => game.PingMsg.decode
|
case 0x1a => game.PingMsg.decode
|
||||||
case 0x1b => game.VehicleStateMessage.decode
|
case 0x1b => game.VehicleStateMessage.decode
|
||||||
case 0x1c => noDecoder(FrameVehicleStateMessage)
|
case 0x1c => game.FrameVehicleStateMessage.decode
|
||||||
case 0x1d => game.GenericObjectStateMsg.decode
|
case 0x1d => game.GenericObjectStateMsg.decode
|
||||||
case 0x1e => game.ChildObjectStateMessage.decode
|
case 0x1e => game.ChildObjectStateMessage.decode
|
||||||
case 0x1f => game.ActionResultMessage.decode
|
case 0x1f => game.ActionResultMessage.decode
|
||||||
|
|
@ -547,14 +547,14 @@ object GamePacketOpcode extends Enumeration {
|
||||||
case 0xcc => noDecoder(ClockCalibrationMessage)
|
case 0xcc => noDecoder(ClockCalibrationMessage)
|
||||||
case 0xcd => game.DensityLevelUpdateMessage.decode
|
case 0xcd => game.DensityLevelUpdateMessage.decode
|
||||||
case 0xce => noDecoder(ActOfGodMessage)
|
case 0xce => noDecoder(ActOfGodMessage)
|
||||||
case 0xcf => noDecoder(AvatarAwardMessage)
|
case 0xcf => game.AvatarAwardMessage.decode
|
||||||
|
|
||||||
// OPCODES 0xd0-df
|
// OPCODES 0xd0-df
|
||||||
case 0xd0 => noDecoder(UnknownMessage208)
|
case 0xd0 => noDecoder(UnknownMessage208)
|
||||||
case 0xd1 => game.DisplayedAwardMessage.decode
|
case 0xd1 => game.DisplayedAwardMessage.decode
|
||||||
case 0xd2 => game.RespawnAMSInfoMessage.decode
|
case 0xd2 => game.RespawnAMSInfoMessage.decode
|
||||||
case 0xd3 => noDecoder(ComponentDamageMessage)
|
case 0xd3 => game.ComponentDamageMessage.decode
|
||||||
case 0xd4 => noDecoder(GenericObjectActionAtPositionMessage)
|
case 0xd4 => game.GenericObjectActionAtPositionMessage.decode
|
||||||
case 0xd5 => game.PropertyOverrideMessage.decode
|
case 0xd5 => game.PropertyOverrideMessage.decode
|
||||||
case 0xd6 => noDecoder(WarpgateLinkOverrideMessage)
|
case 0xd6 => noDecoder(WarpgateLinkOverrideMessage)
|
||||||
case 0xd7 => noDecoder(EmpireBenefitsMessage)
|
case 0xd7 => noDecoder(EmpireBenefitsMessage)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.packet.game
|
||||||
|
|
||||||
|
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
|
||||||
|
import scodec.Codec
|
||||||
|
import scodec.codecs._
|
||||||
|
import shapeless.{::, HNil}
|
||||||
|
|
||||||
|
import scala.annotation.switch
|
||||||
|
|
||||||
|
abstract class AwardOption(val code: Int) {
|
||||||
|
def unk1: Long
|
||||||
|
def unk2: Long
|
||||||
|
}
|
||||||
|
|
||||||
|
final case class AwardOptionZero(unk1: Long, unk2: Long) extends AwardOption(code = 0)
|
||||||
|
|
||||||
|
final case class AwardOptionOne(unk1: Long) extends AwardOption(code = 1) {
|
||||||
|
def unk2: Long = 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
final case class AwardOptionTwo(unk1: Long) extends AwardOption(code = 3) {
|
||||||
|
def unk2: Long = 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* na
|
||||||
|
* @param unk1 na
|
||||||
|
* @param unk2 na
|
||||||
|
* @param unk3 na
|
||||||
|
*/
|
||||||
|
final case class AvatarAwardMessage(
|
||||||
|
unk1: Long,
|
||||||
|
unk2: AwardOption,
|
||||||
|
unk3: Int
|
||||||
|
)
|
||||||
|
extends PlanetSideGamePacket {
|
||||||
|
type Packet = AvatarAwardMessage
|
||||||
|
def opcode = GamePacketOpcode.AvatarAwardMessage
|
||||||
|
def encode = AvatarAwardMessage.encode(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
object AvatarAwardMessage extends Marshallable[AvatarAwardMessage] {
|
||||||
|
private val codec_one: Codec[AwardOptionOne] = {
|
||||||
|
uint32L.hlist
|
||||||
|
}.xmap[AwardOptionOne](
|
||||||
|
{
|
||||||
|
case a :: HNil => AwardOptionOne(a)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case AwardOptionOne(a) => a :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
private val codec_two: Codec[AwardOptionTwo] = {
|
||||||
|
uint32L.hlist
|
||||||
|
}.xmap[AwardOptionTwo](
|
||||||
|
{
|
||||||
|
case a :: HNil => AwardOptionTwo(a)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case AwardOptionTwo(a) => a :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
private val codec_zero: Codec[AwardOptionZero] = {
|
||||||
|
uint32L :: uint32L
|
||||||
|
}.xmap[AwardOptionZero](
|
||||||
|
{
|
||||||
|
case a :: b :: HNil => AwardOptionZero(a, b)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case AwardOptionZero(a, b) => a :: b :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
private def selectAwardOption(code: Int): Codec[AwardOption] = {
|
||||||
|
((code: @switch) match {
|
||||||
|
case 2 | 3 => codec_two
|
||||||
|
case 1 => codec_one
|
||||||
|
case 0 => codec_zero
|
||||||
|
}).asInstanceOf[Codec[AwardOption]]
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val codec: Codec[AvatarAwardMessage] = (
|
||||||
|
("unk1" | uint32L) ::
|
||||||
|
(uint2 >>:~ { code =>
|
||||||
|
("unk2" | selectAwardOption(code)) ::
|
||||||
|
("unk3" | uint8L)
|
||||||
|
})
|
||||||
|
).xmap[AvatarAwardMessage](
|
||||||
|
{
|
||||||
|
case unk1 :: _ :: unk2 :: unk3 :: HNil =>
|
||||||
|
AvatarAwardMessage(unk1, unk2, unk3)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case AvatarAwardMessage(unk1, unk2, unk3) =>
|
||||||
|
unk1 :: unk2.code :: unk2 :: unk3 :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.packet.game
|
||||||
|
|
||||||
|
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||||
|
import net.psforever.types.{PlanetSideGUID, SubsystemComponent}
|
||||||
|
import scodec.codecs._
|
||||||
|
import scodec.Codec
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The status of the component's changing condition,
|
||||||
|
* including the level of alert the player experiences when the change occurs.
|
||||||
|
* @param alarm_level the klaxon sound effect associated with this damage
|
||||||
|
* @param damage the amount of damage (encoded ...)
|
||||||
|
* @param unk na;
|
||||||
|
* usually, `true`;
|
||||||
|
* known `false` states during shield generator offline and destruction conditions
|
||||||
|
*/
|
||||||
|
final case class ComponentDamageField(alarm_level: Long, damage: Long, unk: Boolean)
|
||||||
|
|
||||||
|
object ComponentDamageField {
|
||||||
|
def apply(alarmLevel: Long, dam: Long): ComponentDamageField = ComponentDamageField(alarmLevel, dam, unk = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vehicles have aspects that are neither registered -
|
||||||
|
* do not necessarily represented unique entities of the vehicle -
|
||||||
|
* and are not statistical behaviors derived from the same level as the game files -
|
||||||
|
* modify vehicle stats but are not vehicle stats themselves.
|
||||||
|
* When these "components of the vehicle" are affected, however,
|
||||||
|
* such as when the vehicle has been jammed or when it has sustained damage,
|
||||||
|
* changes to the handling of the vehicle will occur through the said statistical mechanics.
|
||||||
|
* @see `VehicleSubsystem`
|
||||||
|
* @see `VehicleSubsystemEntity`
|
||||||
|
* @param guid the entity that owns this component, usually a vehicle
|
||||||
|
* @param component the subsystem, or part of the subsystem, being affected
|
||||||
|
* @param status specific about the component damage;
|
||||||
|
* `None`, when damage issues are cleared
|
||||||
|
*/
|
||||||
|
final case class ComponentDamageMessage(
|
||||||
|
guid: PlanetSideGUID,
|
||||||
|
component: SubsystemComponent,
|
||||||
|
status: Option[ComponentDamageField]
|
||||||
|
) extends PlanetSideGamePacket {
|
||||||
|
type Packet = ComponentDamageMessage
|
||||||
|
def opcode = GamePacketOpcode.ComponentDamageMessage
|
||||||
|
def encode = ComponentDamageMessage.encode(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
object ComponentDamageMessage extends Marshallable[ComponentDamageMessage] {
|
||||||
|
/**
|
||||||
|
* Overloaded constructor where the component's current state is be cleared.
|
||||||
|
* @param guid the entity that owns this component, usually a vehicle
|
||||||
|
* @param component the subsystem, or part of the subsystem, being affected
|
||||||
|
* @return a `ComponentDamageMessage` packet
|
||||||
|
*/
|
||||||
|
def apply(guid: PlanetSideGUID, component: SubsystemComponent): ComponentDamageMessage =
|
||||||
|
ComponentDamageMessage(guid, component, None)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded constructor where the component's current state is always defined.
|
||||||
|
* @param guid the entity that owns this component, usually a vehicle
|
||||||
|
* @param component the subsystem, or part of the subsystem, being affected
|
||||||
|
* @param status specific about the component damage
|
||||||
|
* @return a `ComponentDamageMessage` packet
|
||||||
|
*/
|
||||||
|
def apply(guid: PlanetSideGUID, component: SubsystemComponent, status: ComponentDamageField): ComponentDamageMessage =
|
||||||
|
ComponentDamageMessage(guid, component, Some(status))
|
||||||
|
|
||||||
|
private val subsystemComponentCodec = PacketHelpers.createLongIntEnumCodec(SubsystemComponent, uint32L)
|
||||||
|
|
||||||
|
private val componentDamageFieldCodec: Codec[ComponentDamageField] = (
|
||||||
|
("unk1" | uint32L) ::
|
||||||
|
("unk2" | uint32L) ::
|
||||||
|
("unk3" | bool)
|
||||||
|
).as[ComponentDamageField]
|
||||||
|
|
||||||
|
implicit val codec: Codec[ComponentDamageMessage] = (
|
||||||
|
("guid" | PlanetSideGUID.codec) ::
|
||||||
|
("component" | subsystemComponentCodec) ::
|
||||||
|
("status" | optional(bool, componentDamageFieldCodec))
|
||||||
|
).as[ComponentDamageMessage]
|
||||||
|
}
|
||||||
|
|
@ -37,14 +37,16 @@ import shapeless.{::, HNil}
|
||||||
* @param player_guid the player
|
* @param player_guid the player
|
||||||
* @param line the zero-indexed line number of this entry in its list
|
* @param line the zero-indexed line number of this entry in its list
|
||||||
* @param label the identifier for this entry
|
* @param label the identifier for this entry
|
||||||
* @param armor the type of exo-suit, if an Infantry loadout
|
* @param armor_type the type of exo-suit, if an Infantry loadout;
|
||||||
|
* the type of battleframe, if a Battleframe loadout;
|
||||||
|
* `None`, if just a Vehicle loadout
|
||||||
*/
|
*/
|
||||||
final case class FavoritesMessage(
|
final case class FavoritesMessage(
|
||||||
list: LoadoutType.Value,
|
list: LoadoutType.Value,
|
||||||
player_guid: PlanetSideGUID,
|
player_guid: PlanetSideGUID,
|
||||||
line: Int,
|
line: Int,
|
||||||
label: String,
|
label: String,
|
||||||
armor: Option[Int]
|
armor_type: Option[Int]
|
||||||
) extends PlanetSideGamePacket {
|
) extends PlanetSideGamePacket {
|
||||||
type Packet = FavoritesMessage
|
type Packet = FavoritesMessage
|
||||||
def opcode = GamePacketOpcode.FavoritesMessage
|
def opcode = GamePacketOpcode.FavoritesMessage
|
||||||
|
|
@ -52,9 +54,8 @@ final case class FavoritesMessage(
|
||||||
}
|
}
|
||||||
|
|
||||||
object FavoritesMessage extends Marshallable[FavoritesMessage] {
|
object FavoritesMessage extends Marshallable[FavoritesMessage] {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overloaded constructor, for infantry loadouts specifically.
|
* Overloaded constructor.
|
||||||
* @param list the destination list
|
* @param list the destination list
|
||||||
* @param player_guid the player
|
* @param player_guid the player
|
||||||
* @param line the zero-indexed line number of this entry in its list
|
* @param line the zero-indexed line number of this entry in its list
|
||||||
|
|
@ -83,11 +84,66 @@ object FavoritesMessage extends Marshallable[FavoritesMessage] {
|
||||||
def apply(list: LoadoutType.Value, player_guid: PlanetSideGUID, line: Int, label: String): FavoritesMessage = {
|
def apply(list: LoadoutType.Value, player_guid: PlanetSideGUID, line: Int, label: String): FavoritesMessage = {
|
||||||
FavoritesMessage(list, player_guid, line, label, None)
|
FavoritesMessage(list, player_guid, line, label, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded constructor for infantry loadouts.
|
||||||
|
* @param player_guid the player
|
||||||
|
* @param line the zero-indexed line number of this entry in its list
|
||||||
|
* @param label the identifier for this entry
|
||||||
|
* @param armor the type of exo-suit
|
||||||
|
* @return a `FavoritesMessage` object
|
||||||
|
*/
|
||||||
|
def Infantry(
|
||||||
|
player_guid: PlanetSideGUID,
|
||||||
|
line: Int,
|
||||||
|
label: String,
|
||||||
|
armor: Int
|
||||||
|
): FavoritesMessage = {
|
||||||
|
FavoritesMessage(LoadoutType.Infantry, player_guid, line, label, Some(armor))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded constructor for vehicle loadouts.
|
||||||
|
* @param player_guid the player
|
||||||
|
* @param line the zero-indexed line number of this entry in its list
|
||||||
|
* @param label the identifier for this entry
|
||||||
|
* @return a `FavoritesMessage` object
|
||||||
|
*/
|
||||||
|
def Vehicle(
|
||||||
|
player_guid: PlanetSideGUID,
|
||||||
|
line: Int,
|
||||||
|
label: String
|
||||||
|
): FavoritesMessage = {
|
||||||
|
FavoritesMessage(LoadoutType.Vehicle, player_guid, line, label, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded constructor for battleframe loadouts.
|
||||||
|
* @param player_guid the player
|
||||||
|
* @param line the zero-indexed line number of this entry in its list
|
||||||
|
* @param label the identifier for this entry
|
||||||
|
* @param subtype the type of battleframe unit
|
||||||
|
* @return a `FavoritesMessage` object
|
||||||
|
*/
|
||||||
|
def Battleframe(
|
||||||
|
player_guid: PlanetSideGUID,
|
||||||
|
line: Int,
|
||||||
|
label: String,
|
||||||
|
subtype: Int
|
||||||
|
): FavoritesMessage = {
|
||||||
|
FavoritesMessage(LoadoutType.Battleframe, player_guid, line, label, Some(subtype))
|
||||||
|
}
|
||||||
|
|
||||||
implicit val codec: Codec[FavoritesMessage] = (("list" | LoadoutType.codec) >>:~ { value =>
|
implicit val codec: Codec[FavoritesMessage] = (("list" | LoadoutType.codec) >>:~ { value =>
|
||||||
("player_guid" | PlanetSideGUID.codec) ::
|
("player_guid" | PlanetSideGUID.codec) ::
|
||||||
("line" | uint4L) ::
|
("line" | uint4L) ::
|
||||||
("label" | PacketHelpers.encodedWideStringAligned(2)) ::
|
("label" | PacketHelpers.encodedWideStringAligned(adjustment = 2)) ::
|
||||||
conditional(value == LoadoutType.Infantry, "armor" | uintL(3))
|
("armor_type" | conditional(value != LoadoutType.Vehicle,
|
||||||
|
{
|
||||||
|
if (value == LoadoutType.Infantry) uint(bits = 3)
|
||||||
|
else uint4
|
||||||
|
}
|
||||||
|
))
|
||||||
}).xmap[FavoritesMessage](
|
}).xmap[FavoritesMessage](
|
||||||
{
|
{
|
||||||
case lst :: guid :: ln :: str :: arm :: HNil =>
|
case lst :: guid :: ln :: str :: arm :: HNil =>
|
||||||
|
|
@ -95,8 +151,7 @@ object FavoritesMessage extends Marshallable[FavoritesMessage] {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case FavoritesMessage(lst, guid, ln, str, arm) =>
|
case FavoritesMessage(lst, guid, ln, str, arm) =>
|
||||||
val armset: Option[Int] = if (lst == LoadoutType.Infantry && arm.isEmpty) { Some(0) }
|
val armset = if (lst != LoadoutType.Vehicle && arm.isEmpty) { Some(0) } else { arm }
|
||||||
else { arm }
|
|
||||||
lst :: guid :: ln :: str :: armset :: HNil
|
lst :: guid :: ln :: str :: armset :: HNil
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.packet.game
|
||||||
|
|
||||||
|
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
|
||||||
|
import net.psforever.types.{Angular, PlanetSideGUID, Vector3}
|
||||||
|
import scodec.Codec
|
||||||
|
import scodec.codecs._
|
||||||
|
|
||||||
|
//TODO write more thorough comments later.
|
||||||
|
/**
|
||||||
|
* Dispatched to report and update the operational condition of a given battle frame robotics vehicle.
|
||||||
|
* @param vehicle_guid the battleframe robotic unit
|
||||||
|
* @param unk1 na
|
||||||
|
* @param pos the xyz-coordinate location in the world
|
||||||
|
* @param orient the orientation of the vehicle
|
||||||
|
* @param vel optional movement data
|
||||||
|
* @param unk2 na
|
||||||
|
* @param unk3 na
|
||||||
|
* @param unk4 na
|
||||||
|
* @param is_crouched the battleframe unit is crouched
|
||||||
|
* @param is_airborne the battleframe unit is either flying or falling (after flying)
|
||||||
|
* @param ascending_flight is the battleframe unit ascending;
|
||||||
|
* normally reports `ascending_flight` before properly reporting as `is_airborne`;
|
||||||
|
* continues to report `ascending_flight` until begins falling
|
||||||
|
* @param flight_time_remaining a measure of how much longer the battleframe unit, if it can fly, can fly;
|
||||||
|
* reported as a 0-10 value, counting down from 10 when airborne and provided vertical thrust
|
||||||
|
* @param unk9 na
|
||||||
|
* @param unkA na
|
||||||
|
* @see `PlacementData`
|
||||||
|
*/
|
||||||
|
final case class FrameVehicleStateMessage(
|
||||||
|
vehicle_guid: PlanetSideGUID,
|
||||||
|
unk1: Int,
|
||||||
|
pos: Vector3,
|
||||||
|
orient: Vector3,
|
||||||
|
vel: Option[Vector3],
|
||||||
|
unk2: Boolean,
|
||||||
|
unk3: Int,
|
||||||
|
unk4: Int,
|
||||||
|
is_crouched: Boolean,
|
||||||
|
is_airborne: Boolean,
|
||||||
|
ascending_flight: Boolean,
|
||||||
|
flight_time_remaining: Int,
|
||||||
|
unk9: Long,
|
||||||
|
unkA: Long
|
||||||
|
) extends PlanetSideGamePacket {
|
||||||
|
type Packet = FrameVehicleStateMessage
|
||||||
|
def opcode = GamePacketOpcode.FrameVehicleStateMessage
|
||||||
|
def encode = FrameVehicleStateMessage.encode(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
object FrameVehicleStateMessage extends Marshallable[FrameVehicleStateMessage] {
|
||||||
|
/**
|
||||||
|
* Calculate common orientation from little-endian bit data.
|
||||||
|
* @see `Angular.codec_roll`
|
||||||
|
* @see `Angular.codec_pitch`
|
||||||
|
* @see `Angular.codec_yaw`
|
||||||
|
*/
|
||||||
|
val codec_orient : Codec[Vector3] = (
|
||||||
|
("roll" | Angular.codec_roll(bits = 10)) ::
|
||||||
|
("pitch" | Angular.codec_pitch(bits = 10)) ::
|
||||||
|
("yaw" | Angular.codec_yaw(bits = 10, North = 90f))
|
||||||
|
).as[Vector3]
|
||||||
|
|
||||||
|
implicit val codec : Codec[FrameVehicleStateMessage] = (
|
||||||
|
("vehicle_guid" | PlanetSideGUID.codec) ::
|
||||||
|
("unk1" | uint(bits = 3)) ::
|
||||||
|
("pos" | Vector3.codec_pos) ::
|
||||||
|
("orient" | codec_orient) ::
|
||||||
|
optional(bool, target = "vel" | Vector3.codec_vel) ::
|
||||||
|
("unk2" | bool) ::
|
||||||
|
("unk3" | uint2) ::
|
||||||
|
("unk4" | uint2) ::
|
||||||
|
("is_crouched" | bool) ::
|
||||||
|
("is_airborne" | bool) ::
|
||||||
|
("ascending_flight" | bool) ::
|
||||||
|
("flight_time_remaining" | uint4) ::
|
||||||
|
("unk9" | uint32) ::
|
||||||
|
("unkA" | uint32)
|
||||||
|
).as[FrameVehicleStateMessage]
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue