mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-19 18:14:44 +00:00
Experience for KDA [Prep-work] (#1024)
* extensive modifications to source entry shorthand * moving 11 files changes 55 other files * added score card classes; upgraded packet classes * I decided to import over everything * proliferation of in-game activity messages, especially for spawning activity; removed defaults for activities; fixed (most?) building tests * upkeep on the LLU's managing service, as well as the facility hack management service, in response to a potential bug * a facility that changes faction affiliation while it is the destination of an LLU delivery will cancel that LLU delivery * fixed crash due to boomer trigger overriding position of ace, without the ace being properly cleaned up on the client of the bomber; fixed issue with the boomer trigger going missing * flipped the first two FDU deployable settings so they match the correct fire modes; corrected a stack overflow situation with the sourcing entities * action, but no response * condensed parameters on avatar class * as always, fixing tests * quickly, loose ends tied
This commit is contained in:
parent
40cf783f18
commit
779054fef9
|
|
@ -46,6 +46,7 @@ lazy val psforeverSettings = Seq(
|
|||
"com.typesafe.akka" %% "akka-stream" % "2.6.17",
|
||||
"com.typesafe.akka" %% "akka-testkit" % "2.6.17" % "test",
|
||||
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.17",
|
||||
"com.typesafe.akka" %% "akka-actor-testkit-typed" % "2.6.17" % "test",
|
||||
"com.typesafe.akka" %% "akka-slf4j" % "2.6.17",
|
||||
"com.typesafe.akka" %% "akka-cluster-typed" % "2.6.17",
|
||||
"com.typesafe.akka" %% "akka-coordination" % "2.6.17",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import akka.testkit.TestProbe
|
|||
import base.FreedContextActorTest
|
||||
import net.psforever.actors.zone.BuildingActor
|
||||
import net.psforever.objects.avatar.Avatar
|
||||
import net.psforever.objects.ballistics.{Projectile, SourceEntry}
|
||||
import net.psforever.objects.ballistics.Projectile
|
||||
import net.psforever.objects.guid.NumberPoolHub
|
||||
import net.psforever.objects.guid.source.MaxNumberSource
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
|
|
@ -14,6 +14,7 @@ import net.psforever.objects.serverobject.deploy.Deployment
|
|||
import net.psforever.objects.serverobject.resourcesilo.{ResourceSilo, ResourceSiloControl}
|
||||
import net.psforever.objects.serverobject.structures.{AutoRepairStats, Building, StructureType}
|
||||
import net.psforever.objects.serverobject.terminals.{OrderTerminalDefinition, Terminal, TerminalControl}
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.base.DamageResolution
|
||||
import net.psforever.objects.vital.damage.DamageProfile
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@ import base.FreedContextActorTest
|
|||
import net.psforever.actors.commands.NtuCommand
|
||||
import net.psforever.actors.zone.BuildingActor
|
||||
import net.psforever.objects.avatar.Avatar
|
||||
import net.psforever.objects.ballistics.{Projectile, SourceEntry}
|
||||
import net.psforever.objects.ballistics.Projectile
|
||||
import net.psforever.objects.guid.NumberPoolHub
|
||||
import net.psforever.objects.guid.source.MaxNumberSource
|
||||
import net.psforever.objects.serverobject.structures.{AutoRepairStats, Building, StructureType}
|
||||
import net.psforever.objects.serverobject.terminals.{OrderTerminalDefinition, Terminal, TerminalControl}
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.base.DamageResolution
|
||||
import net.psforever.objects.vital.damage.DamageProfile
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ class MiddlewareActor(
|
|||
send(ServerStart(nonce, serverNonce), None, None)
|
||||
cryptoSetup()
|
||||
|
||||
case (Unknown30(nonce), _) =>
|
||||
case (Unknown30(_), _) =>
|
||||
/*
|
||||
Unknown30 is used to reuse an existing crypto session when switching from login to world
|
||||
When not handling it, it appears that the client will fall back to using ClientStart
|
||||
|
|
|
|||
|
|
@ -5,8 +5,15 @@ import java.util.concurrent.atomic.AtomicInteger
|
|||
import akka.actor.Cancellable
|
||||
import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer}
|
||||
import akka.actor.typed.{ActorRef, Behavior, PostStop, SupervisorStrategy}
|
||||
import net.psforever.objects.vital.{DamagingActivity, HealingActivity}
|
||||
import net.psforever.objects.avatar.{ProgressDecoration, SpecialCarry}
|
||||
import net.psforever.objects.avatar.scoring.{Death, EquipmentStat, KDAStat, Kill}
|
||||
import net.psforever.objects.sourcing.SourceWithHealthEntry
|
||||
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||
import net.psforever.objects.vital.{DamagingActivity, HealingActivity, SpawningActivity, Vitality}
|
||||
import net.psforever.packet.game.objectcreate.BasicCharacterData
|
||||
import net.psforever.types.ExperienceType
|
||||
import org.joda.time.{LocalDateTime, Seconds}
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.{ExecutionContextExecutor, Future, Promise}
|
||||
import scala.util.{Failure, Success}
|
||||
|
|
@ -32,8 +39,8 @@ import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
|
|||
import net.psforever.objects.inventory.InventoryItem
|
||||
import net.psforever.objects.loadouts.{InfantryLoadout, Loadout, VehicleLoadout}
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.ballistics.PlayerSource
|
||||
import net.psforever.objects.locker.LockerContainer
|
||||
import net.psforever.objects.sourcing.PlayerSource
|
||||
import net.psforever.objects.vital.HealFromImplant
|
||||
import net.psforever.packet.game.objectcreate.{ObjectClass, RibbonBars}
|
||||
import net.psforever.packet.game.{Friend => GameFriend, _}
|
||||
|
|
@ -188,7 +195,7 @@ object AvatarActor {
|
|||
final case class SuspendStaminaRegeneration(duration: FiniteDuration) extends Command
|
||||
|
||||
/** Award battle experience points */
|
||||
final case class AwardBep(bep: Long) extends Command
|
||||
final case class AwardBep(bep: Long, modifier: ExperienceType) extends Command
|
||||
|
||||
/** Set total battle experience points */
|
||||
final case class SetBep(bep: Long) extends Command
|
||||
|
|
@ -214,6 +221,8 @@ object AvatarActor {
|
|||
|
||||
final case class RemoveShortcut(slot: Int) extends Command
|
||||
|
||||
final case class UpdateToolDischarge(stat: EquipmentStat) extends Command
|
||||
|
||||
final case class AvatarResponse(avatar: Avatar)
|
||||
|
||||
final case class AvatarLoginResponse(avatar: Avatar)
|
||||
|
|
@ -618,22 +627,28 @@ object AvatarActor {
|
|||
*/
|
||||
def finalSavePlayerData(player: Player): Future[Int] = {
|
||||
val health = (
|
||||
player.History.find(_.isInstanceOf[DamagingActivity]),
|
||||
player.History.find(_.isInstanceOf[HealingActivity])
|
||||
player.History.findLast(_.isInstanceOf[DamagingActivity]),
|
||||
player.History.collect { case h: HealingActivity => h }
|
||||
) match {
|
||||
case (Some(damage), Some(heal)) =>
|
||||
case (Some(damage: DamagingActivity), heals) if heals.nonEmpty =>
|
||||
val health = damage.data.targetAfter.asInstanceOf[PlayerSource].health
|
||||
//between damage and potential healing, which came last?
|
||||
if (damage.time < heal.time) {
|
||||
heal.asInstanceOf[HealingActivity].amount % player.MaxHealth
|
||||
if (damage.time < heals.last.time) {
|
||||
health + heals.map { _.amount }.sum
|
||||
} else {
|
||||
damage.asInstanceOf[DamagingActivity].data.targetAfter.asInstanceOf[PlayerSource].health
|
||||
health
|
||||
}
|
||||
case (Some(damage: DamagingActivity), _) =>
|
||||
damage.data.targetAfter.asInstanceOf[PlayerSource].health
|
||||
case (None, heals) if heals.nonEmpty =>
|
||||
player.History.headOption match {
|
||||
case Some(es: SpawningActivity) =>
|
||||
es.src.asInstanceOf[SourceWithHealthEntry].health + heals.map { _.amount }.sum
|
||||
case _ =>
|
||||
player.Health
|
||||
}
|
||||
case (Some(damage), None) =>
|
||||
damage.asInstanceOf[DamagingActivity].data.targetAfter.asInstanceOf[PlayerSource].health
|
||||
case (None, Some(heal)) =>
|
||||
heal.asInstanceOf[HealingActivity].amount % player.MaxHealth
|
||||
case _ =>
|
||||
player.MaxHealth
|
||||
player.Health
|
||||
}
|
||||
savePlayerData(player, health)
|
||||
}
|
||||
|
|
@ -772,6 +787,21 @@ object AvatarActor {
|
|||
}
|
||||
out.future
|
||||
}
|
||||
|
||||
def toAvatar(avatar: persistence.Avatar): Avatar =
|
||||
Avatar(
|
||||
avatar.id,
|
||||
BasicCharacterData(
|
||||
avatar.name,
|
||||
PlanetSideEmpire(avatar.factionId),
|
||||
CharacterSex.valuesToEntriesMap(avatar.genderId),
|
||||
avatar.headId,
|
||||
CharacterVoice(avatar.voiceId)
|
||||
),
|
||||
avatar.bep,
|
||||
avatar.cep,
|
||||
decoration = ProgressDecoration(cosmetics = avatar.cosmetics.map(c => Cosmetic.valuesFromObjectCreateValue(c)))
|
||||
)
|
||||
}
|
||||
|
||||
class AvatarActor(
|
||||
|
|
@ -937,7 +967,7 @@ class AvatarActor(
|
|||
case Success(characters) =>
|
||||
characters.headOption match {
|
||||
case Some(character) =>
|
||||
avatar = character.toAvatar
|
||||
avatar = AvatarActor.toAvatar(character)
|
||||
replyTo ! AvatarResponse(avatar)
|
||||
case None =>
|
||||
log.error(s"selected character $charId not found")
|
||||
|
|
@ -1497,7 +1527,7 @@ class AvatarActor(
|
|||
val events = zone.AvatarEvents
|
||||
val guid = player.GUID
|
||||
val newHealth = player.Health = originalHealth + 1
|
||||
player.History(HealFromImplant(PlayerSource(player), 1, implantType))
|
||||
player.LogActivity(HealFromImplant(implantType, 1))
|
||||
events ! AvatarServiceMessage(
|
||||
zone.id,
|
||||
AvatarAction.PlanetsideAttributeToAll(guid, 0, newHealth)
|
||||
|
|
@ -1595,67 +1625,24 @@ class AvatarActor(
|
|||
initializeImplants()
|
||||
Behaviors.same
|
||||
|
||||
case AwardBep(bep) =>
|
||||
context.self ! SetBep(avatar.bep + bep)
|
||||
case UpdateToolDischarge(stats) =>
|
||||
updateToolDischarge(stats)
|
||||
Behaviors.same
|
||||
|
||||
case AwardBep(bep, modifier) =>
|
||||
setBep(avatar.bep + bep, modifier)
|
||||
Behaviors.same
|
||||
|
||||
case SetBep(bep) =>
|
||||
import ctx._
|
||||
val result = for {
|
||||
_ <-
|
||||
if (BattleRank.withExperience(bep).value < BattleRank.BR24.value) setCosmetics(Set())
|
||||
else Future.successful(())
|
||||
r <- ctx.run(query[persistence.Avatar].filter(_.id == lift(avatar.id)).update(_.bep -> lift(bep)))
|
||||
} yield r
|
||||
result.onComplete {
|
||||
case Success(_) =>
|
||||
sessionActor ! SessionActor.SendResponse(BattleExperienceMessage(session.get.player.GUID, bep, 0))
|
||||
session.get.zone.AvatarEvents ! AvatarServiceMessage(
|
||||
session.get.zone.id,
|
||||
AvatarAction.PlanetsideAttributeToAll(session.get.player.GUID, 17, bep)
|
||||
)
|
||||
// when the level is reduced, take away any implants over the implant slot limit
|
||||
val implants = avatar.implants.zipWithIndex.map {
|
||||
case (implant, index) =>
|
||||
if (index >= BattleRank.withExperience(bep).implantSlots && implant.isDefined) {
|
||||
ctx.run(
|
||||
query[persistence.Implant]
|
||||
.filter(_.name == lift(implant.get.definition.Name))
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.delete
|
||||
)
|
||||
.onComplete {
|
||||
case Success(_) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
AvatarImplantMessage(session.get.player.GUID, ImplantAction.Remove, index, 0)
|
||||
)
|
||||
case Failure(exception) => log.error(exception)("db failure")
|
||||
}
|
||||
None
|
||||
} else {
|
||||
implant
|
||||
}
|
||||
}
|
||||
avatar = avatar.copy(bep = bep, implants = implants)
|
||||
case Failure(exception) => log.error(exception)("db failure")
|
||||
}
|
||||
setBep(bep, ExperienceType.Normal)
|
||||
Behaviors.same
|
||||
|
||||
case AwardCep(cep) =>
|
||||
context.self ! SetCep(avatar.cep + cep)
|
||||
setCep(avatar.cep + cep)
|
||||
Behaviors.same
|
||||
|
||||
case SetCep(cep) =>
|
||||
import ctx._
|
||||
ctx.run(query[persistence.Avatar].filter(_.id == lift(avatar.id)).update(_.cep -> lift(cep))).onComplete {
|
||||
case Success(_) =>
|
||||
avatar = avatar.copy(cep = cep)
|
||||
session.get.zone.AvatarEvents ! AvatarServiceMessage(
|
||||
session.get.zone.id,
|
||||
AvatarAction.PlanetsideAttributeToAll(session.get.player.GUID, 18, cep)
|
||||
)
|
||||
case Failure(exception) => log.error(exception)("db failure")
|
||||
}
|
||||
setCep(cep)
|
||||
Behaviors.same
|
||||
|
||||
case SetCosmetics(cosmetics) =>
|
||||
|
|
@ -1690,8 +1677,8 @@ class AvatarActor(
|
|||
//short-circuit if the shortcut already exists at the given location
|
||||
val isMacroShortcut = shortcut.isInstanceOf[Shortcut.Macro]
|
||||
val isDifferentShortcut = !(targetShortcut match {
|
||||
case Some(target) => AvatarShortcut.equals(shortcut, target)
|
||||
case _ => false
|
||||
case Some(target: AvatarShortcut) => AvatarShortcut.equals(shortcut, target)
|
||||
case _ => false
|
||||
})
|
||||
if (isDifferentShortcut) {
|
||||
if (!isMacroShortcut && avatar.shortcuts.flatten.exists {
|
||||
|
|
@ -1701,7 +1688,7 @@ class AvatarActor(
|
|||
if (shortcut.isInstanceOf[Shortcut.Implant]) {
|
||||
//duplicate implant
|
||||
targetShortcut match {
|
||||
case Some(existingShortcut) =>
|
||||
case Some(existingShortcut: AvatarShortcut) =>
|
||||
//redraw redundant shortcut slot with existing shortcut
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
CreateShortcutMessage(session.get.player.GUID, slot + 1, Some(AvatarShortcut.convert(existingShortcut)))
|
||||
|
|
@ -2116,7 +2103,7 @@ class AvatarActor(
|
|||
|
||||
avatars.filter(!_.deleted) foreach { a =>
|
||||
val secondsSinceLastLogin = Seconds.secondsBetween(a.lastLogin, LocalDateTime.now()).getSeconds
|
||||
val avatar = a.toAvatar
|
||||
val avatar = AvatarActor.toAvatar(a)
|
||||
val player = new Player(avatar)
|
||||
|
||||
player.ExoSuit = ExoSuitType.Reinforced
|
||||
|
|
@ -2356,11 +2343,12 @@ class AvatarActor(
|
|||
}
|
||||
|
||||
def refreshLoadouts(loadouts: Iterable[(Option[Loadout], Int)]): Unit = {
|
||||
val guid = session.get.player.GUID
|
||||
loadouts
|
||||
.map {
|
||||
case (Some(loadout: InfantryLoadout), index) =>
|
||||
FavoritesMessage.Infantry(
|
||||
session.get.player.GUID,
|
||||
guid,
|
||||
index,
|
||||
loadout.label,
|
||||
InfantryLoadout.DetermineSubtypeB(loadout.exosuit, loadout.subtype)
|
||||
|
|
@ -2368,14 +2356,14 @@ class AvatarActor(
|
|||
case (Some(loadout: VehicleLoadout), index)
|
||||
if GlobalDefinitions.isBattleFrameVehicle(loadout.vehicle_definition) =>
|
||||
FavoritesMessage.Battleframe(
|
||||
session.get.player.GUID,
|
||||
guid,
|
||||
index - 15,
|
||||
loadout.label,
|
||||
VehicleLoadout.DetermineBattleframeSubtype(loadout.vehicle_definition)
|
||||
)
|
||||
case (Some(loadout: VehicleLoadout), index) =>
|
||||
FavoritesMessage.Vehicle(
|
||||
session.get.player.GUID,
|
||||
guid,
|
||||
index - 10,
|
||||
loadout.label
|
||||
)
|
||||
|
|
@ -2389,7 +2377,7 @@ class AvatarActor(
|
|||
}
|
||||
FavoritesMessage(
|
||||
mtype,
|
||||
session.get.player.GUID,
|
||||
guid,
|
||||
lineNo,
|
||||
"",
|
||||
0
|
||||
|
|
@ -2812,4 +2800,110 @@ class AvatarActor(
|
|||
sessionActor ! SessionActor.UpdateIgnoredPlayers(FriendsResponse(MemberAction.RemoveIgnoredPlayer, GameFriend(name)))
|
||||
sessionActor ! SessionActor.CharSaved
|
||||
}
|
||||
|
||||
def setBep(bep: Long, modifier: ExperienceType): Unit = {
|
||||
import ctx._
|
||||
val result = for {
|
||||
_ <-
|
||||
if (BattleRank.withExperience(bep).value < BattleRank.BR24.value) setCosmetics(Set())
|
||||
else Future.successful(())
|
||||
r <- ctx.run(query[persistence.Avatar].filter(_.id == lift(avatar.id)).update(_.bep -> lift(bep)))
|
||||
} yield r
|
||||
result.onComplete {
|
||||
case Success(_) =>
|
||||
val sess = session.get
|
||||
val zone = sess.zone
|
||||
val pguid = sess.player.GUID
|
||||
val localModifier = modifier
|
||||
sessionActor ! SessionActor.SendResponse(BattleExperienceMessage(pguid, bep, localModifier))
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zone.id,
|
||||
AvatarAction.PlanetsideAttributeToAll(pguid, 17, bep)
|
||||
)
|
||||
// when the level is reduced, take away any implants over the implant slot limit
|
||||
val implants = avatar.implants.zipWithIndex.map {
|
||||
case (implant, index) =>
|
||||
if (index >= BattleRank.withExperience(bep).implantSlots && implant.isDefined) {
|
||||
ctx.run(
|
||||
query[persistence.Implant]
|
||||
.filter(_.name == lift(implant.get.definition.Name))
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.delete
|
||||
)
|
||||
.onComplete {
|
||||
case Success(_) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
AvatarImplantMessage(pguid, ImplantAction.Remove, index, 0)
|
||||
)
|
||||
case Failure(exception) =>
|
||||
log.error(exception)("db failure")
|
||||
}
|
||||
None
|
||||
} else {
|
||||
implant
|
||||
}
|
||||
}
|
||||
avatar = avatar.copy(bep = bep, implants = implants)
|
||||
case Failure(exception) =>
|
||||
log.error(exception)("db failure")
|
||||
}
|
||||
}
|
||||
|
||||
def setCep(cep: Long): Unit = {
|
||||
import ctx._
|
||||
ctx.run(query[persistence.Avatar].filter(_.id == lift(avatar.id)).update(_.cep -> lift(cep))).onComplete {
|
||||
case Success(_) =>
|
||||
val sess = session.get
|
||||
val zone = sess.zone
|
||||
avatar = avatar.copy(cep = cep)
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zone.id,
|
||||
AvatarAction.PlanetsideAttributeToAll(sess.player.GUID, 18, cep)
|
||||
)
|
||||
case Failure(exception) =>
|
||||
log.error(exception)("db failure")
|
||||
}
|
||||
}
|
||||
|
||||
def updateKillsDeathsAssists(kdaStat: KDAStat): Unit = {
|
||||
avatar.scorecard.rate(kdaStat)
|
||||
val exp = kdaStat.experienceEarned
|
||||
val _session = session.get
|
||||
val zone = _session.zone
|
||||
val player = _session.player
|
||||
kdaStat match {
|
||||
case kill: Kill =>
|
||||
val _ = PlayerSource(player)
|
||||
(kill.info.interaction.cause match {
|
||||
case pr: ProjectileReason => pr.projectile.mounted_in.map { a => zone.GUID(a._1) }
|
||||
case _ => None
|
||||
}) match {
|
||||
case Some(Some(_: Vitality)) =>
|
||||
//zone.actor ! ZoneActor.RewardOurSupporters(playerSource, obj.History, kill, exp)
|
||||
case _ => ;
|
||||
}
|
||||
//zone.actor ! ZoneActor.RewardOurSupporters(playerSource, player.History, kill, exp)
|
||||
case _: Death =>
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
AvatarStatisticsMessage(DeathStatistic(avatar.scorecard.Lives.size))
|
||||
)
|
||||
)
|
||||
}
|
||||
if (exp > 0L) {
|
||||
val gameOpts = Config.app.game
|
||||
val (msg, modifier): (ExperienceType, Float) = if (player.Carrying.contains(SpecialCarry.RabbitBall)) {
|
||||
(ExperienceType.RabbitBall, 1.25f)
|
||||
} else {
|
||||
(ExperienceType.Normal, 1f)
|
||||
}
|
||||
setBep(avatar.bep + (exp * modifier * gameOpts.bepRate).toLong, msg)
|
||||
}
|
||||
}
|
||||
|
||||
def updateToolDischarge(stats: EquipmentStat): Unit = {
|
||||
avatar.scorecard.rate(stats)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import net.psforever.objects.definition.ImplantDefinition
|
|||
import net.psforever.packet.game.{CreateShortcutMessage, Shortcut}
|
||||
import net.psforever.packet.game.objectcreate.DrawnSlot
|
||||
import net.psforever.types.ChatMessageType.{CMT_GMOPEN, UNK_227}
|
||||
import net.psforever.types.ImplantType
|
||||
import net.psforever.types.{ExperienceType, ImplantType}
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.ExecutionContextExecutor
|
||||
|
|
@ -820,7 +820,7 @@ class ChatActor(
|
|||
|
||||
case (CMT_ADDBATTLEEXPERIENCE, _, contents) if gmCommandAllowed =>
|
||||
contents.toIntOption match {
|
||||
case Some(bep) => avatarActor ! AvatarActor.AwardBep(bep)
|
||||
case Some(bep) => avatarActor ! AvatarActor.AwardBep(bep, ExperienceType.Normal)
|
||||
case None =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
message.copy(messageType = UNK_229, contents = "@CMT_ADDBATTLEEXPERIENCE_usage")
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ class SessionAvatarHandlers(
|
|||
chatActor: typed.ActorRef[ChatActor.Command],
|
||||
implicit val context: ActorContext
|
||||
) extends CommonSessionInterfacingFunctionality {
|
||||
private[support] var lastSeenStreamMessage: Array[Long] = Array.fill[Long](65535)(elem=0L)
|
||||
|
||||
/**
|
||||
* na
|
||||
*
|
||||
|
|
@ -117,7 +119,7 @@ class SessionAvatarHandlers(
|
|||
sendResponse(sessionData.destroyDisplayMessage(killer, victim, method, unk))
|
||||
// TODO Temporary thing that should go somewhere else and use proper xp values
|
||||
if (killer.CharId == avatar.id && killer.Faction != victim.Faction) {
|
||||
avatarActor ! AvatarActor.AwardBep((1000 * Config.app.game.bepRate).toLong)
|
||||
avatarActor ! AvatarActor.AwardBep((1000 * Config.app.game.bepRate).toLong, ExperienceType.Normal)
|
||||
avatarActor ! AvatarActor.AwardCep((100 * Config.app.game.cepRate).toLong)
|
||||
}
|
||||
|
||||
|
|
@ -287,7 +289,7 @@ class SessionAvatarHandlers(
|
|||
val r2 = 2 + r.nextInt(4000).toFloat
|
||||
(Vector3(r2, r2, r1), 0L, 0f)
|
||||
} else {
|
||||
val before = player.lastSeenStreamMessage(guid.guid)
|
||||
val before = lastSeenStreamMessage(guid.guid)
|
||||
val dist = Vector3.DistanceSquared(player.Position, pos)
|
||||
(pos, now - before, dist)
|
||||
}
|
||||
|
|
@ -307,7 +309,7 @@ class SessionAvatarHandlers(
|
|||
is_cloaking
|
||||
)
|
||||
)
|
||||
player.lastSeenStreamMessage(guid.guid) = now
|
||||
lastSeenStreamMessage(guid.guid) = now
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package net.psforever.actors.session.support
|
|||
|
||||
import akka.actor.typed.scaladsl.adapter._
|
||||
import akka.actor.{ActorContext, ActorRef, Cancellable, OneForOneStrategy, SupervisorStrategy, typed}
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
|
|
@ -940,18 +942,22 @@ class SessionData(
|
|||
|
||||
def handleFacilityBenefitShieldChargeRequest(pkt: FacilityBenefitShieldChargeRequestMessage): Unit = {
|
||||
val FacilityBenefitShieldChargeRequestMessage(_) = pkt
|
||||
continent.GUID(player.VehicleSeated) match {
|
||||
case Some(obj) if obj.Destroyed => () //vehicle will try to charge even if destroyed
|
||||
case Some(obj: Vehicle) =>
|
||||
obj.Actor ! Vehicle.ChargeShields(15)
|
||||
case Some(_: TurretDeployable) => () //TODO the turret will charge a shield in some circumstances
|
||||
case None if player.VehicleSeated.nonEmpty =>
|
||||
log.error(
|
||||
s"FacilityBenefitShieldChargeRequest: ${player.Name} is seated in a vehicle that can not be found in zone ${continent.id}"
|
||||
)
|
||||
case _ =>
|
||||
log.warn(s"FacilityBenefitShieldChargeRequest: ${player.Name} is seated in an imaginary vehicle")
|
||||
}
|
||||
val vehicleGuid = player.VehicleSeated
|
||||
continent
|
||||
.GUID(vehicleGuid)
|
||||
.foreach {
|
||||
case obj: Vehicle if !obj.Destroyed => //vehicle will try to charge even if destroyed
|
||||
obj.Actor ! CommonMessages.ChargeShields(
|
||||
15,
|
||||
Some(continent.blockMap.sector(obj).buildingList.maxBy(_.Definition.SOIRadius))
|
||||
)
|
||||
case _ if vehicleGuid.nonEmpty =>
|
||||
log.warn(
|
||||
s"FacilityBenefitShieldChargeRequest: ${player.Name} can not find vehicle ${vehicleGuid.get.guid} in zone ${continent.id}"
|
||||
)
|
||||
case _ =>
|
||||
log.warn(s"FacilityBenefitShieldChargeRequest: ${player.Name} is not seated in a vehicle")
|
||||
}
|
||||
}
|
||||
|
||||
def handleBattleplan(pkt: BattleplanMessage): Unit = {
|
||||
|
|
@ -2284,7 +2290,7 @@ class SessionData(
|
|||
* @param tplayer the player to be killed
|
||||
*/
|
||||
def suicide(tplayer: Player): Unit = {
|
||||
tplayer.History(PlayerSuicide(PlayerSource(tplayer)))
|
||||
tplayer.LogActivity(PlayerSuicide(PlayerSource(tplayer)))
|
||||
tplayer.Actor ! Player.Die()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -128,20 +128,17 @@ class SessionLocalHandlers(
|
|||
llu.Definition.Packet.ConstructorData(llu).get
|
||||
)
|
||||
)
|
||||
|
||||
sendResponse(TriggerSoundMessage(TriggeredSound.LLUMaterialize, llu.Position, unk = 20, 0.8000001f))
|
||||
|
||||
case LocalResponse.LluDespawned(llu) =>
|
||||
sendResponse(TriggerSoundMessage(TriggeredSound.LLUDeconstruct, llu.Position, unk = 20, 0.8000001f))
|
||||
sendResponse(ObjectDeleteMessage(llu.GUID, 0))
|
||||
case LocalResponse.LluDespawned(lluGuid, position) =>
|
||||
sendResponse(TriggerSoundMessage(TriggeredSound.LLUDeconstruct, position, unk = 20, 0.8000001f))
|
||||
sendResponse(ObjectDeleteMessage(lluGuid, 0))
|
||||
// If the player was holding the LLU, remove it from their tracked special item slot
|
||||
sessionData.specialItemSlotGuid match {
|
||||
case Some(guid) =>
|
||||
if (guid == llu.GUID) {
|
||||
sessionData.specialItemSlotGuid = None
|
||||
player.Carrying = None
|
||||
}
|
||||
case _ => ;
|
||||
case Some(guid) if guid == lluGuid =>
|
||||
sessionData.specialItemSlotGuid = None
|
||||
player.Carrying = None
|
||||
case _ => ()
|
||||
}
|
||||
|
||||
case LocalResponse.ObjectDelete(object_guid, unk) =>
|
||||
|
|
|
|||
|
|
@ -132,6 +132,11 @@ class SessionVehicleHandlers(
|
|||
sendResponse(ObjectAttachMessage(vehicle_guid, guid, seat))
|
||||
}
|
||||
|
||||
case VehicleResponse.ObjectDelete(itemGuid) =>
|
||||
if (tplayer_guid != guid) {
|
||||
sendResponse(ObjectDeleteMessage(itemGuid, 0))
|
||||
}
|
||||
|
||||
case VehicleResponse.Ownership(vehicleGuid) =>
|
||||
if (tplayer_guid == guid) { // Only the player that owns this vehicle needs the ownership packet
|
||||
avatarActor ! AvatarActor.SetVehicle(Some(vehicleGuid))
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
package net.psforever.actors.session.support
|
||||
|
||||
import akka.actor.{ActorContext, typed}
|
||||
import net.psforever.objects.avatar.scoring.EquipmentStat
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
|
|
@ -9,7 +11,7 @@ import scala.concurrent.duration._
|
|||
//
|
||||
import net.psforever.actors.session.{AvatarActor, ChatActor, SessionActor}
|
||||
import net.psforever.login.WorldSession.{CountAmmunition, CountGrenades, FindAmmoBoxThatUses, FindEquipmentStock, FindToolThatUses, PutEquipmentInInventoryOrDrop, PutNewEquipmentInInventoryOrDrop, RemoveOldEquipmentFromInventory}
|
||||
import net.psforever.objects.ballistics.{PlayerSource, Projectile, ProjectileQuality, SourceEntry}
|
||||
import net.psforever.objects.ballistics.{Projectile, ProjectileQuality}
|
||||
import net.psforever.objects.entity.SimpleWorldEntity
|
||||
import net.psforever.objects.equipment.{ChargeFireModeDefinition, Equipment, EquipmentSize, FireModeSwitch}
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
|
||||
|
|
@ -24,7 +26,8 @@ import net.psforever.objects.vital.interaction.DamageInteraction
|
|||
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||
import net.psforever.objects.zones.{Zone, ZoneProjectile}
|
||||
import net.psforever.objects._
|
||||
import net.psforever.packet.game.{AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, ReloadMessage, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponLazeTargetPositionMessage, _}
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
import net.psforever.types.{ExoSuitType, PlanetSideGUID, Vector3}
|
||||
|
|
@ -40,6 +43,7 @@ private[support] class WeaponAndProjectileOperations(
|
|||
var prefire: mutable.Set[PlanetSideGUID] = mutable.Set.empty //if WeaponFireMessage precedes ChangeFireStateMessage_Start
|
||||
private[support] var shootingStart: mutable.HashMap[PlanetSideGUID, Long] = mutable.HashMap[PlanetSideGUID, Long]()
|
||||
private[support] var shootingStop: mutable.HashMap[PlanetSideGUID, Long] = mutable.HashMap[PlanetSideGUID, Long]()
|
||||
private var ongoingShotsFired: Int = 0
|
||||
private[support] var shotsWhileDead: Int = 0
|
||||
private val projectiles: Array[Option[Projectile]] =
|
||||
Array.fill[Option[Projectile]](Projectile.rangeUID - Projectile.baseUID)(None)
|
||||
|
|
@ -112,6 +116,7 @@ private[support] class WeaponAndProjectileOperations(
|
|||
prefire -= item_guid
|
||||
shooting += item_guid
|
||||
shootingStart += item_guid -> System.currentTimeMillis()
|
||||
ongoingShotsFired = 0
|
||||
//special case - suppress the decimator's alternate fire mode, by projectile
|
||||
if (tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile) {
|
||||
continent.AvatarEvents ! AvatarServiceMessage(
|
||||
|
|
@ -140,6 +145,7 @@ private[support] class WeaponAndProjectileOperations(
|
|||
prefire -= item_guid
|
||||
shooting += item_guid
|
||||
shootingStart += item_guid -> System.currentTimeMillis()
|
||||
ongoingShotsFired = 0
|
||||
continent.AvatarEvents ! AvatarServiceMessage(
|
||||
continent.id,
|
||||
AvatarAction.ChangeFireState_Start(player.GUID, item_guid)
|
||||
|
|
@ -168,8 +174,10 @@ private[support] class WeaponAndProjectileOperations(
|
|||
continent.id,
|
||||
AvatarAction.ChangeFireState_Start(pguid, item_guid)
|
||||
)
|
||||
ongoingShotsFired = ongoingShotsFired + tool.Discharge()
|
||||
shootingStart += item_guid -> (System.currentTimeMillis() - 1L)
|
||||
}
|
||||
avatarActor ! AvatarActor.UpdateToolDischarge(EquipmentStat(tool.Definition.ObjectId,ongoingShotsFired,0,0))
|
||||
tool.FireMode match {
|
||||
case _: ChargeFireModeDefinition =>
|
||||
sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, tool.Magazine))
|
||||
|
|
@ -410,6 +418,7 @@ private[support] class WeaponAndProjectileOperations(
|
|||
) =>
|
||||
ResolveProjectileInteraction(proj, DamageResolution.Hit, target, hitPos) match {
|
||||
case Some(resprojectile) =>
|
||||
avatarActor ! AvatarActor.UpdateToolDischarge(EquipmentStat(resprojectile.cause.attribution,0,1,0))
|
||||
sessionData.handleDealingDamage(target, resprojectile)
|
||||
case None => ;
|
||||
}
|
||||
|
|
@ -447,8 +456,9 @@ private[support] class WeaponAndProjectileOperations(
|
|||
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
|
||||
CheckForHitPositionDiscrepancy(projectile_guid, target.Position, target)
|
||||
ResolveProjectileInteraction(projectile, resolution1, target, target.Position) match {
|
||||
case Some(_projectile) =>
|
||||
sessionData.handleDealingDamage(target, _projectile)
|
||||
case Some(resprojectile) =>
|
||||
avatarActor ! AvatarActor.UpdateToolDischarge(EquipmentStat(resprojectile.cause.attribution,0,1,0))
|
||||
sessionData.handleDealingDamage(target, resprojectile)
|
||||
case None => ;
|
||||
}
|
||||
case _ => ;
|
||||
|
|
@ -459,8 +469,9 @@ private[support] class WeaponAndProjectileOperations(
|
|||
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
|
||||
CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target)
|
||||
ResolveProjectileInteraction(projectile, resolution2, target, explosion_pos) match {
|
||||
case Some(_projectile) =>
|
||||
sessionData.handleDealingDamage(target, _projectile)
|
||||
case Some(resprojectile) =>
|
||||
avatarActor ! AvatarActor.UpdateToolDischarge(EquipmentStat(resprojectile.cause.attribution,0,1,0))
|
||||
sessionData.handleDealingDamage(target, resprojectile)
|
||||
case None => ;
|
||||
}
|
||||
case _ => ;
|
||||
|
|
@ -499,8 +510,9 @@ private[support] class WeaponAndProjectileOperations(
|
|||
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
|
||||
CheckForHitPositionDiscrepancy(projectile_guid, hit_pos, target)
|
||||
ResolveProjectileInteraction(projectile_guid, DamageResolution.Lash, target, hit_pos) match {
|
||||
case Some(projectile) =>
|
||||
sessionData.handleDealingDamage(target, projectile)
|
||||
case Some(resprojectile) =>
|
||||
avatarActor ! AvatarActor.UpdateToolDischarge(EquipmentStat(resprojectile.cause.attribution,0,1,0))
|
||||
sessionData.handleDealingDamage(target, resprojectile)
|
||||
case None => ;
|
||||
}
|
||||
case _ => ;
|
||||
|
|
@ -554,17 +566,29 @@ private[support] class WeaponAndProjectileOperations(
|
|||
val distanceToOwner = Vector3.DistanceSquared(shotOrigin, player.Position)
|
||||
if (distanceToOwner <= acceptableDistanceToOwner) {
|
||||
val projectile_info = tool.Projectile
|
||||
val projectile =
|
||||
Projectile(
|
||||
projectile_info,
|
||||
tool.Definition,
|
||||
tool.FireMode,
|
||||
PlayerSource(player),
|
||||
attribution,
|
||||
shotOrigin,
|
||||
angle,
|
||||
shotVelocity
|
||||
)
|
||||
val wguid = weaponGUID.guid
|
||||
val mountedIn = (continent.turretToWeapon
|
||||
.find { case (guid, _) => guid == wguid } match {
|
||||
case Some((_, turretGuid)) => Some((
|
||||
turretGuid,
|
||||
continent.GUID(turretGuid).collect { case o: PlanetSideGameObject with FactionAffinity => SourceEntry(o) }
|
||||
))
|
||||
case _ => None
|
||||
}) match {
|
||||
case Some((guid, Some(entity))) => Some((guid, entity))
|
||||
case _ => None
|
||||
}
|
||||
val projectile = new Projectile(
|
||||
projectile_info,
|
||||
tool.Definition,
|
||||
tool.FireMode,
|
||||
mountedIn,
|
||||
PlayerSource(player),
|
||||
attribution,
|
||||
shotOrigin,
|
||||
angle,
|
||||
shotVelocity
|
||||
)
|
||||
val initialQuality = tool.FireMode match {
|
||||
case mode: ChargeFireModeDefinition =>
|
||||
ProjectileQuality.Modified(
|
||||
|
|
@ -641,7 +665,7 @@ private[support] class WeaponAndProjectileOperations(
|
|||
}
|
||||
avatarActor ! AvatarActor.SuspendStaminaRegeneration(3.seconds)
|
||||
prefire += weaponGUID
|
||||
tool.Discharge()
|
||||
ongoingShotsFired = ongoingShotsFired + tool.Discharge()
|
||||
}
|
||||
(o, Some(tool))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ import akka.actor.typed.scaladsl.adapter._
|
|||
import akka.actor.{ActorContext, ActorRef, Cancellable, typed}
|
||||
import akka.pattern.ask
|
||||
import akka.util.Timeout
|
||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, VehicleSource}
|
||||
import net.psforever.objects.vital.{InGameHistory, ReconstructionActivity, SpawningActivity}
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
|
@ -37,11 +41,12 @@ import net.psforever.objects.serverobject.turret.FacilityTurret
|
|||
import net.psforever.objects.vehicles._
|
||||
import net.psforever.objects.zones.{Zone, ZoneHotSpotProjector, Zoning}
|
||||
import net.psforever.objects._
|
||||
import net.psforever.packet.game.objectcreate.ObjectClass
|
||||
import net.psforever.packet.game.objectcreate.{DroppedItemData, ObjectCreateMessageParent, PlacementData}
|
||||
import net.psforever.packet.game.{BeginZoningMessage, DroppodLaunchRequestMessage, ReleaseAvatarRequestMessage, SpawnRequestMessage, WarpgateRequest}
|
||||
import net.psforever.packet.game.{AvatarAwardMessage, AvatarSearchCriteriaMessage, AvatarStatisticsMessage, AwardCompletion, BindPlayerMessage, BindStatus, CargoMountPointStatusMessage, ChangeShortcutBankMessage, ChatChannel, CreateShortcutMessage, DroppodFreefallingMessage, LoadMapMessage, ObjectCreateDetailedMessage, ObjectDeleteMessage, PlanetsideStringAttributeMessage, PlayerStateShiftMessage, SetChatFilterMessage, SetCurrentAvatarMessage, ShiftState, Statistics}
|
||||
import net.psforever.packet.game.{AvatarAwardMessage, AvatarSearchCriteriaMessage, AvatarStatisticsMessage, AwardCompletion, BindPlayerMessage, BindStatus, CargoMountPointStatusMessage, ChangeShortcutBankMessage, ChatChannel, CreateShortcutMessage, DroppodFreefallingMessage, LoadMapMessage, ObjectCreateDetailedMessage, ObjectDeleteMessage, PlanetsideStringAttributeMessage, PlayerStateShiftMessage, SetChatFilterMessage, SetCurrentAvatarMessage, ShiftState}
|
||||
import net.psforever.packet.game.{AvatarDeadStateMessage, BroadcastWarpgateUpdateMessage, ChatMsg, ContinentalLockUpdateMessage, DeadState, DensityLevelUpdateMessage, DeployRequestMessage, DeployableInfo, DeployableObjectsInfoMessage, DeploymentAction, DisconnectMessage, DroppodError, DroppodLaunchResponseMessage, FriendsResponse, GenericObjectActionMessage, GenericObjectStateMsg, HotSpotUpdateMessage, ObjectAttachMessage, ObjectCreateMessage, PlanetsideAttributeEnum, PlanetsideAttributeMessage, PropertyOverrideMessage, ReplicationStreamMessage, SetEmpireMessage, TimeOfDayMessage, TriggerEffectMessage, ZoneForcedCavernConnectionsMessage, ZoneInfoMessage, ZoneLockInfoMessage, ZonePopulationUpdateMessage, HotSpotInfo => PacketHotSpotInfo}
|
||||
import net.psforever.packet.game.{BeginZoningMessage, DroppodLaunchRequestMessage, ReleaseAvatarRequestMessage, SpawnRequestMessage, WarpgateRequest}
|
||||
import net.psforever.packet.game.DeathStatistic
|
||||
import net.psforever.packet.game.objectcreate.{DroppedItemData, ObjectCreateMessageParent, PlacementData}
|
||||
import net.psforever.packet.game.objectcreate.ObjectClass
|
||||
import net.psforever.packet.{PlanetSideGamePacket, game}
|
||||
import net.psforever.persistence.Savedplayer
|
||||
import net.psforever.services.RemoverActor
|
||||
|
|
@ -828,6 +833,10 @@ class ZoningOperations(
|
|||
spawn.LoadZonePhysicalSpawnPoint(zoneId, position, Vector3.Zero, 0 seconds, None)
|
||||
case _ => // not seated as the driver, in which case we can't move
|
||||
}
|
||||
case _ if !player.isAlive =>
|
||||
Player.Respawn(player)
|
||||
player.Health = 1
|
||||
spawn.LoadZonePhysicalSpawnPoint(continent.id, position, Vector3.z(player.Orientation.z), 0.seconds, None)
|
||||
case None =>
|
||||
spawn.deadState = DeadState.Release // cancel movement updates
|
||||
player.Position = position
|
||||
|
|
@ -1162,7 +1171,7 @@ class ZoningOperations(
|
|||
*/
|
||||
def LoadZoneAsPlayer(targetPlayer: Player, zoneId: String): Unit = {
|
||||
log.debug(s"LoadZoneAsPlayer: ${targetPlayer.avatar.name} loading into $zoneId")
|
||||
if (!zoneReload && zoneId == continent.id) {
|
||||
if (!zoneReload && zoneId.equals(continent.id)) {
|
||||
if (player.isBackpack) { // important! test the actor-wide player ref, not the parameter
|
||||
// respawning from unregistered player
|
||||
TaskWorkflow.execute(sessionData.registerAvatar(targetPlayer))
|
||||
|
|
@ -1436,6 +1445,10 @@ class ZoningOperations(
|
|||
Deployables.Disown(continent, avatar, context.self)
|
||||
spawn.drawDeloyableIcon = spawn.RedrawDeployableIcons //important for when SetCurrentAvatar initializes the UI next zone
|
||||
sessionData.squad.squadSetup = sessionData.squad.ZoneChangeSquadSetup
|
||||
val lastSeen = sessionData.avatarResponse.lastSeenStreamMessage
|
||||
lastSeen.indices.foreach { index =>
|
||||
lastSeen(index) = 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2542,17 +2555,26 @@ class ZoningOperations(
|
|||
nextSpawnPoint = physSpawnPoint
|
||||
shiftPosition = Some(pos)
|
||||
shiftOrientation = Some(ori)
|
||||
val toZoneNumber = if (continent.id.equals(zoneId)) {
|
||||
continent.Number
|
||||
} else {
|
||||
Zones.zones.find { _.id.equals(zoneId) }.orElse(Some(Zone.Nowhere)).get.Number
|
||||
}
|
||||
val toSpawnPoint = physSpawnPoint.collect { case o: PlanetSideGameObject with FactionAffinity => SourceEntry(o) }
|
||||
respawnTimer = context.system.scheduler.scheduleOnce(respawnTime) {
|
||||
if (player.isBackpack) { // if the player is dead, he is handled as dead infantry, even if he died in a vehicle
|
||||
// new player is spawning
|
||||
val newPlayer = RespawnClone(player)
|
||||
newPlayer.Position = pos
|
||||
newPlayer.Orientation = ori
|
||||
newPlayer.LogActivity(SpawningActivity(PlayerSource(newPlayer), toZoneNumber, toSpawnPoint))
|
||||
LoadZoneAsPlayer(newPlayer, zoneId)
|
||||
} else {
|
||||
avatarActor ! AvatarActor.DeactivateActiveImplants()
|
||||
interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match {
|
||||
case Some(vehicle: Vehicle) => // driver or passenger in vehicle using a warp gate, or a droppod
|
||||
InGameHistory.SpawnReconstructionActivity(vehicle, toZoneNumber, toSpawnPoint)
|
||||
InGameHistory.SpawnReconstructionActivity(player, toZoneNumber, toSpawnPoint)
|
||||
LoadZoneInVehicle(vehicle, pos, ori, zoneId)
|
||||
|
||||
case _ if player.HasGUID => // player is deconstructing self or instant action
|
||||
|
|
@ -2564,11 +2586,13 @@ class ZoningOperations(
|
|||
)
|
||||
player.Position = pos
|
||||
player.Orientation = ori
|
||||
InGameHistory.SpawnReconstructionActivity(player, toZoneNumber, toSpawnPoint)
|
||||
LoadZoneAsPlayer(player, zoneId)
|
||||
|
||||
case _ => //player is logging in
|
||||
player.Position = pos
|
||||
player.Orientation = ori
|
||||
InGameHistory.SpawnReconstructionActivity(player, toZoneNumber, toSpawnPoint)
|
||||
LoadZoneAsPlayer(player, zoneId)
|
||||
}
|
||||
}
|
||||
|
|
@ -2698,7 +2722,6 @@ class ZoningOperations(
|
|||
}
|
||||
|
||||
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 82, 0))
|
||||
//TODO if Medkit does not have shortcut, add to a free slot or write over slot 64
|
||||
avatar.shortcuts
|
||||
.zipWithIndex
|
||||
.collect { case (Some(shortcut), index) =>
|
||||
|
|
@ -2739,7 +2762,7 @@ class ZoningOperations(
|
|||
}
|
||||
(0 to 30).foreach(_ => {
|
||||
//TODO 30 for a new character only?
|
||||
sendResponse(AvatarStatisticsMessage(2, Statistics(0L)))
|
||||
sendResponse(AvatarStatisticsMessage(DeathStatistic(0L)))
|
||||
})
|
||||
if (tplayer.ExoSuit == ExoSuitType.MAX) {
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 7, tplayer.Capacitor.toLong))
|
||||
|
|
@ -2787,13 +2810,13 @@ class ZoningOperations(
|
|||
)
|
||||
)
|
||||
case (Some(vehicle), Some(0)) =>
|
||||
//driver; summon any passengers and cargo vehicles left behind on previous continent
|
||||
//driver of vehicle
|
||||
if (vehicle.Jammed) {
|
||||
//TODO something better than just canceling?
|
||||
vehicle.Actor ! JammableUnit.ClearJammeredStatus()
|
||||
vehicle.Actor ! JammableUnit.ClearJammeredSound()
|
||||
}
|
||||
//positive shield strength
|
||||
// positive shield strength
|
||||
if (vehicle.Definition.MaxShields > 0) {
|
||||
sendResponse(PlanetsideAttributeMessage(vehicle.GUID, vehicle.Definition.shieldUiAttribute, vehicle.Shields))
|
||||
}
|
||||
|
|
@ -2805,6 +2828,11 @@ class ZoningOperations(
|
|||
if (vehicle.Definition.MaxCapacitor > 0) {
|
||||
sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 113, vehicle.Capacitor))
|
||||
}
|
||||
// vehicle entering zone
|
||||
if (vehicle.History.headOption.exists { _.isInstanceOf[SpawningActivity] }) {
|
||||
vehicle.LogActivity(ReconstructionActivity(VehicleSource(vehicle), continent.Number, None))
|
||||
}
|
||||
// summon any passengers and cargo vehicles left behind on previous continent
|
||||
LoadZoneTransferPassengerMessages(
|
||||
guid,
|
||||
continent.id,
|
||||
|
|
@ -2825,12 +2853,31 @@ class ZoningOperations(
|
|||
} else if (originalDeadState == DeadState.Dead || player.Health == 0) {
|
||||
//killed during spawn setup or possibly a relog into a corpse (by accident?)
|
||||
player.Actor ! Player.Die()
|
||||
} else {
|
||||
AvatarActor.savePlayerData(player)
|
||||
sessionData.displayCharSavedMsgThenRenewTimer(
|
||||
Config.app.game.savedMsg.short.fixed,
|
||||
Config.app.game.savedMsg.short.variable
|
||||
)
|
||||
//player
|
||||
val effortBy = nextSpawnPoint
|
||||
.collect { case sp: SpawnTube => (sp, continent.GUID(sp.Owner.GUID)) }
|
||||
.collect {
|
||||
case (_, Some(v: Vehicle)) => continent.GUID(v.Owner)
|
||||
case (sp, Some(_: Building)) => Some(sp)
|
||||
}
|
||||
.collect { case Some(thing: PlanetSideGameObject with FactionAffinity) => Some(SourceEntry(thing)) }
|
||||
.flatten
|
||||
player.LogActivity({
|
||||
if (player.History.headOption.exists { _.isInstanceOf[SpawningActivity] }) {
|
||||
ReconstructionActivity(PlayerSource(player), continent.Number, effortBy)
|
||||
} else {
|
||||
SpawningActivity(PlayerSource(player), continent.Number, effortBy)
|
||||
}
|
||||
})
|
||||
//ride
|
||||
|
||||
}
|
||||
AvatarActor.savePlayerData(player)
|
||||
sessionData.displayCharSavedMsgThenRenewTimer(
|
||||
Config.app.game.savedMsg.short.fixed,
|
||||
Config.app.game.savedMsg.short.variable
|
||||
)
|
||||
upstreamMessageCount = 0
|
||||
setAvatar = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -188,6 +188,10 @@ class BuildingActor(
|
|||
|
||||
def setup(details: BuildingControlDetails): Behavior[Command] = {
|
||||
Behaviors.receiveMessage {
|
||||
case ReceptionistListing(InterstellarClusterService.InterstellarClusterServiceKey.Listing(listings))
|
||||
if listings.isEmpty =>
|
||||
Behaviors.same
|
||||
|
||||
case ReceptionistListing(InterstellarClusterService.InterstellarClusterServiceKey.Listing(listings)) =>
|
||||
switchToBehavior(details.copy(interstellarCluster = listings.head))
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package net.psforever.actors.zone
|
|||
|
||||
import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy}
|
||||
import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors}
|
||||
import net.psforever.objects.ballistics.SourceEntry
|
||||
import net.psforever.objects.ce.Deployable
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.serverobject.structures.{StructureType, WarpGate}
|
||||
|
|
@ -11,12 +10,13 @@ import net.psforever.objects.zones.blockmap.{BlockMapEntity, SectorGroup}
|
|||
import net.psforever.objects.{ConstructionItem, Player, Vehicle}
|
||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import akka.actor.typed.scaladsl.adapter._
|
||||
import net.psforever.actors.zone.building.MajorFacilityLogic
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.util.Database._
|
||||
import net.psforever.persistence
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.util.{Failure, Success}
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ object ZoneActor {
|
|||
.supervise[Command] {
|
||||
Behaviors.setup(context => new ZoneActor(context, zone))
|
||||
}
|
||||
.onFailure[Exception](SupervisorStrategy.restart)
|
||||
.onFailure[Exception](SupervisorStrategy.resume)
|
||||
|
||||
sealed trait Command
|
||||
|
||||
|
|
@ -73,13 +73,13 @@ object ZoneActor {
|
|||
}
|
||||
|
||||
class ZoneActor(context: ActorContext[ZoneActor.Command], zone: Zone)
|
||||
extends AbstractBehavior[ZoneActor.Command](context) {
|
||||
extends AbstractBehavior[ZoneActor.Command](context) {
|
||||
|
||||
import ZoneActor._
|
||||
import ctx._
|
||||
|
||||
private[this] val log = org.log4s.getLogger
|
||||
val players: ListBuffer[Player] = ListBuffer()
|
||||
val players: mutable.ListBuffer[Player] = mutable.ListBuffer()
|
||||
|
||||
zone.actor = context.self
|
||||
zone.init(context.toClassic)
|
||||
|
|
@ -102,7 +102,7 @@ class ZoneActor(context: ActorContext[ZoneActor.Command], zone: Zone)
|
|||
case Failure(e) => log.error(e.getMessage)
|
||||
}
|
||||
|
||||
override def onMessage(msg: Command): Behavior[Command] = {
|
||||
def onMessage(msg: Command): Behavior[Command] = {
|
||||
msg match {
|
||||
case GetZone(replyTo) =>
|
||||
replyTo ! ZoneResponse(zone)
|
||||
|
|
|
|||
|
|
@ -38,17 +38,21 @@ case object CavernFacilityLogic
|
|||
def amenityStateChange(details: BuildingWrapper, entity: Amenity, data: Option[Any]): Behavior[Command] = {
|
||||
entity match {
|
||||
case terminal: CaptureTerminal =>
|
||||
val building = details.building
|
||||
// Notify amenities that listen for CC hack state changes, e.g. wall turrets to dismount seated players
|
||||
details.building.Amenities.filter(x => x.isInstanceOf[CaptureTerminalAware]).foreach(amenity => {
|
||||
data match {
|
||||
case Some(isResecured: Boolean) => amenity.Actor ! CaptureTerminalAwareBehavior.TerminalStatusChanged(terminal, isResecured)
|
||||
case _ => log(details).warn("CaptureTerminal AmenityStateChange was received with no attached data.")
|
||||
}
|
||||
})
|
||||
data match {
|
||||
case Some(isResecured: Boolean) =>
|
||||
//pass hack information to amenities
|
||||
building.Amenities.filter(x => x.isInstanceOf[CaptureTerminalAware]).foreach(amenity => {
|
||||
amenity.Actor ! CaptureTerminalAwareBehavior.TerminalStatusChanged(terminal, isResecured)
|
||||
})
|
||||
case _ =>
|
||||
log(details).warn("CaptureTerminal AmenityStateChange was received with no attached data.")
|
||||
}
|
||||
// When a CC is hacked (or resecured) all currently hacked amenities for the base should return to their default unhacked state
|
||||
details.building.HackableAmenities.foreach(amenity => {
|
||||
building.HackableAmenities.foreach(amenity => {
|
||||
if (amenity.HackedBy.isDefined) {
|
||||
details.building.Zone.LocalEvents ! LocalServiceMessage(amenity.Zone.id,LocalAction.ClearTemporaryHack(PlanetSideGUID(0), amenity))
|
||||
building.Zone.LocalEvents ! LocalServiceMessage(amenity.Zone.id,LocalAction.ClearTemporaryHack(PlanetSideGUID(0), amenity))
|
||||
}
|
||||
})
|
||||
// No map update needed - will be sent by `HackCaptureActor` when required
|
||||
|
|
|
|||
|
|
@ -38,17 +38,21 @@ case object FacilityLogic
|
|||
def amenityStateChange(details: BuildingWrapper, entity: Amenity, data: Option[Any]): Behavior[Command] = {
|
||||
entity match {
|
||||
case terminal: CaptureTerminal =>
|
||||
val building = details.building
|
||||
// Notify amenities that listen for CC hack state changes, e.g. wall turrets to dismount seated players
|
||||
details.building.Amenities.filter(x => x.isInstanceOf[CaptureTerminalAware]).foreach(amenity => {
|
||||
data match {
|
||||
case Some(isResecured: Boolean) => amenity.Actor ! CaptureTerminalAwareBehavior.TerminalStatusChanged(terminal, isResecured)
|
||||
case _ => log(details).warn("CaptureTerminal AmenityStateChange was received with no attached data.")
|
||||
}
|
||||
})
|
||||
data match {
|
||||
case Some(isResecured: Boolean) =>
|
||||
//pass hack information to amenities
|
||||
building.Amenities.filter(x => x.isInstanceOf[CaptureTerminalAware]).foreach(amenity => {
|
||||
amenity.Actor ! CaptureTerminalAwareBehavior.TerminalStatusChanged(terminal, isResecured)
|
||||
})
|
||||
case _ =>
|
||||
log(details).warn("CaptureTerminal AmenityStateChange was received with no attached data.")
|
||||
}
|
||||
// When a CC is hacked (or resecured) all currently hacked amenities for the base should return to their default unhacked state
|
||||
details.building.HackableAmenities.foreach(amenity => {
|
||||
building.HackableAmenities.foreach(amenity => {
|
||||
if (amenity.HackedBy.isDefined) {
|
||||
details.building.Zone.LocalEvents ! LocalServiceMessage(amenity.Zone.id,LocalAction.ClearTemporaryHack(PlanetSideGUID(0), amenity))
|
||||
building.Zone.LocalEvents ! LocalServiceMessage(amenity.Zone.id, LocalAction.ClearTemporaryHack(PlanetSideGUID(0), amenity))
|
||||
}
|
||||
})
|
||||
// No map update needed - will be sent by `HackCaptureActor` when required
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import net.psforever.actors.zone.{BuildingActor, BuildingControlDetails, ZoneAct
|
|||
import net.psforever.objects.serverobject.generator.{Generator, GeneratorControl}
|
||||
import net.psforever.objects.serverobject.structures.{Amenity, Building}
|
||||
import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalAware, CaptureTerminalAwareBehavior}
|
||||
import net.psforever.objects.sourcing.PlayerSource
|
||||
import net.psforever.services.{InterstellarClusterService, Service}
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage}
|
||||
|
|
@ -164,17 +165,21 @@ case object MajorFacilityLogic
|
|||
details.building.Zone.actor ! ZoneActor.ZoneMapUpdate()
|
||||
}
|
||||
case terminal: CaptureTerminal =>
|
||||
val building = details.building
|
||||
// Notify amenities that listen for CC hack state changes, e.g. wall turrets to dismount seated players
|
||||
details.building.Amenities.filter(x => x.isInstanceOf[CaptureTerminalAware]).foreach(amenity => {
|
||||
data match {
|
||||
case Some(isResecured: Boolean) => amenity.Actor ! CaptureTerminalAwareBehavior.TerminalStatusChanged(terminal, isResecured)
|
||||
case _ => log(details).warn("CaptureTerminal AmenityStateChange was received with no attached data.")
|
||||
}
|
||||
})
|
||||
data match {
|
||||
case Some(isResecured: Boolean) =>
|
||||
//pass hack information to amenities
|
||||
building.Amenities.filter(x => x.isInstanceOf[CaptureTerminalAware]).foreach(amenity => {
|
||||
amenity.Actor ! CaptureTerminalAwareBehavior.TerminalStatusChanged(terminal, isResecured)
|
||||
})
|
||||
case _ =>
|
||||
log(details).warn("CaptureTerminal AmenityStateChange was received with no attached data.")
|
||||
}
|
||||
// When a CC is hacked (or resecured) all currently hacked amenities for the base should return to their default unhacked state
|
||||
details.building.HackableAmenities.foreach(amenity => {
|
||||
building.HackableAmenities.foreach(amenity => {
|
||||
if (amenity.HackedBy.isDefined) {
|
||||
details.building.Zone.LocalEvents ! LocalServiceMessage(amenity.Zone.id,LocalAction.ClearTemporaryHack(PlanetSideGUID(0), amenity))
|
||||
building.Zone.LocalEvents ! LocalServiceMessage(amenity.Zone.id,LocalAction.ClearTemporaryHack(PlanetSideGUID(0), amenity))
|
||||
}
|
||||
})
|
||||
// No map update needed - will be sent by `HackCaptureActor` when required
|
||||
|
|
@ -330,6 +335,14 @@ case object MajorFacilityLogic
|
|||
|
||||
def alertToFactionChange(details: BuildingWrapper, building: Building): Behavior[Command] = {
|
||||
alignForceDomeStatus(details)
|
||||
val bldg = details.building
|
||||
//the presence of the flag means that we are involved in an ongoing llu hack
|
||||
(bldg.GetFlag, bldg.CaptureTerminal) match {
|
||||
case (Some(flag), Some(terminal)) if (flag.Target eq building) && flag.Faction != building.Faction =>
|
||||
//our hack destination may have been compromised and the hack needs to be cancelled
|
||||
bldg.Zone.LocalEvents ! LocalServiceMessage("", LocalAction.ResecureCaptureTerminal(terminal, PlayerSource.Nobody))
|
||||
case _ => ()
|
||||
}
|
||||
Behaviors.same
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ import net.psforever.objects.guid._
|
|||
import net.psforever.objects.inventory.{Container, InventoryItem}
|
||||
import net.psforever.objects.locker.LockerContainer
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.containable.Containable
|
||||
import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior}
|
||||
import net.psforever.objects.serverobject.terminals.Terminal
|
||||
import net.psforever.objects.sourcing.AmenitySource
|
||||
import net.psforever.objects.vital.TerminalUsedActivity
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.types.{ExoSuitType, PlanetSideGUID, TransactionType, Vector3}
|
||||
import net.psforever.services.Service
|
||||
|
|
@ -31,7 +34,8 @@ object WorldSession {
|
|||
* @return 1 for `true`; 0 for `false`
|
||||
*/
|
||||
implicit def boolToInt(b: Boolean): Int = if (b) 1 else 0
|
||||
private implicit val timeout = new Timeout(5000 milliseconds)
|
||||
|
||||
private implicit val timeout: Timeout = new Timeout(5000 milliseconds)
|
||||
|
||||
/**
|
||||
* Use this for placing equipment that has already been registered into a container,
|
||||
|
|
@ -185,7 +189,6 @@ object WorldSession {
|
|||
val localZone = obj.Zone
|
||||
TaskBundle(
|
||||
new StraightforwardTask() {
|
||||
private val localContainer = obj
|
||||
private val localItem = item
|
||||
private val localSlot = slot
|
||||
private val localFunc: (Equipment, Int) => Future[Any] = PutEquipmentInInventorySlot(obj)
|
||||
|
|
@ -556,10 +559,12 @@ object WorldSession {
|
|||
val tile = item.Definition.Tile
|
||||
destination.Inventory.CheckCollisionsVar(dest, tile.Width, tile.Height)
|
||||
} match {
|
||||
case Success(Nil) =>
|
||||
case Success(Nil)
|
||||
if ContainableBehavior.PermitEquipmentStow(destination, item, dest) =>
|
||||
//no swap item
|
||||
(true, None)
|
||||
case Success(List(swapEntry: InventoryItem)) =>
|
||||
case Success(List(swapEntry: InventoryItem))
|
||||
if ContainableBehavior.PermitEquipmentStow(destination, item, dest) =>
|
||||
//the swap item is to be registered to the source's zone
|
||||
(true, Some(swapEntry.obj.GUID))
|
||||
case _ =>
|
||||
|
|
@ -568,13 +573,13 @@ object WorldSession {
|
|||
}
|
||||
if (performSwap) {
|
||||
def moveItemTaskFunc(toSlot: Int): Task = new StraightforwardTask() {
|
||||
val localGUID = swapItemGUID //the swap item's original GUID, if any swap item
|
||||
val localChannel = toChannel
|
||||
val localSource = source
|
||||
val localDestination = destination
|
||||
val localItem = item
|
||||
val localDestSlot = dest
|
||||
val localSrcSlot = toSlot
|
||||
val localGUID: Option[PlanetSideGUID] = swapItemGUID //the swap item's original GUID, if any swap item
|
||||
val localChannel: String = toChannel
|
||||
val localSource: PlanetSideServerObject with Container = source
|
||||
val localDestination: PlanetSideServerObject with Container = destination
|
||||
val localItem: Equipment = item
|
||||
val localDestSlot: Int = dest
|
||||
val localSrcSlot: Int = toSlot
|
||||
val localMoveOnComplete: Try[Any] => Unit = {
|
||||
case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) =>
|
||||
//swapItem is not registered right now, we can not drop the item without re-registering it
|
||||
|
|
@ -673,13 +678,13 @@ object WorldSession {
|
|||
}
|
||||
if (performSwap) {
|
||||
def moveItemTaskFunc(toSlot: Int): Task = new StraightforwardTask() {
|
||||
val localGUID = swapItemGUID //the swap item's original GUID, if any swap item
|
||||
val localChannel = toChannel
|
||||
val localSource = source
|
||||
val localDestination = destination
|
||||
val localItem = item
|
||||
val localDestSlot = dest
|
||||
val localSrcSlot = toSlot
|
||||
val localGUID: Option[PlanetSideGUID] = swapItemGUID //the swap item's original GUID, if any swap item
|
||||
val localChannel: String = toChannel
|
||||
val localSource: PlanetSideServerObject with Container = source
|
||||
val localDestination: PlanetSideServerObject with Container = destination
|
||||
val localItem: Equipment = item
|
||||
val localDestSlot: Int = dest
|
||||
val localSrcSlot: Int = toSlot
|
||||
val localMoveOnComplete: Try[Any] => Unit = {
|
||||
case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) =>
|
||||
//swapItem is not registered right now, we can not drop the item without re-registering it
|
||||
|
|
@ -936,6 +941,11 @@ object WorldSession {
|
|||
def TerminalResult(guid: PlanetSideGUID, player: Player, transaction: TransactionType.Value)(
|
||||
result: Boolean
|
||||
): Unit = {
|
||||
if (result) {
|
||||
player.Zone.GUID(guid).collect {
|
||||
case term: Terminal => player.LogActivity(TerminalUsedActivity(AmenitySource(term), transaction))
|
||||
}
|
||||
}
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.TerminalOrderResult(guid, transaction, result)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
package net.psforever.objects
|
||||
|
||||
import akka.actor.{ActorContext, Props}
|
||||
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.ce.{Deployable, DeployedItem}
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.vital.etc.TriggerUsedReason
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
import net.psforever.objects.zones.Zone
|
||||
|
|
@ -35,7 +35,7 @@ class BoomerDeployable(cdef: ExplosiveDeployableDefinition)
|
|||
}
|
||||
|
||||
class BoomerDeployableDefinition(private val objectId: Int) extends ExplosiveDeployableDefinition(objectId) {
|
||||
override def Initialize(obj: Deployable, context: ActorContext) = {
|
||||
override def Initialize(obj: Deployable, context: ActorContext): Unit = {
|
||||
obj.Actor =
|
||||
context.actorOf(Props(classOf[BoomerDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj))
|
||||
}
|
||||
|
|
@ -97,7 +97,7 @@ class BoomerDeployableControl(mine: BoomerDeployable)
|
|||
}
|
||||
zone.AvatarEvents! AvatarServiceMessage(
|
||||
zone.id,
|
||||
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, trigger.GUID)
|
||||
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, guid)
|
||||
)
|
||||
TaskWorkflow.execute(GUIDTask.unregisterObject(zone.GUID, trigger))
|
||||
case None => ;
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ private class DefinitionWrappedInVitality(definition: ObjectDefinition)
|
|||
case p: ProjectileDefinition => p
|
||||
case _ => GlobalDefinitions.no_projectile
|
||||
}
|
||||
|
||||
Name = { definition.Name }
|
||||
DefaultHealth = 1 //just cuz
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
package net.psforever.objects
|
||||
|
||||
import akka.actor.{Actor, ActorContext, ActorRef, Props}
|
||||
import net.psforever.objects.ballistics.{DeployableSource, PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.ce._
|
||||
import net.psforever.objects.definition.{DeployableDefinition, ExoSuitDefinition}
|
||||
import net.psforever.objects.definition.converter.SmallDeployableConverter
|
||||
|
|
@ -12,13 +11,14 @@ import net.psforever.objects.serverobject.affinity.FactionAffinity
|
|||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity}
|
||||
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||
import net.psforever.objects.sourcing.{DeployableSource, PlayerSource, SourceEntry, UniquePlayer}
|
||||
import net.psforever.objects.vital.etc.TrippedMineReason
|
||||
import net.psforever.objects.vital.resolution.ResolutionCalculations.Output
|
||||
import net.psforever.objects.vital.{SimpleResolutions, Vitality}
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.types.{ExoSuitType, Vector3}
|
||||
import net.psforever.types.{CharacterSex, ExoSuitType, Vector3}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
|
|
@ -127,7 +127,7 @@ object ExplosiveDeployableControl {
|
|||
* @param damage na
|
||||
*/
|
||||
def DamageResolution(target: ExplosiveDeployable, cause: DamageResult, damage: Int): Unit = {
|
||||
target.History(cause)
|
||||
target.LogActivity(cause)
|
||||
if (cause.interaction.cause.source.SympatheticExplosion) {
|
||||
explodes(target, cause)
|
||||
DestructionAwareness(target, cause)
|
||||
|
|
@ -324,32 +324,32 @@ object MineDeployableControl {
|
|||
val deployableSource = DeployableSource(mine)
|
||||
val blame = mine.OwnerName match {
|
||||
case Some(name) =>
|
||||
val(charId, exosuit, seated): (Long, ExoSuitType.Value, Boolean) = mine.Zone
|
||||
val(charId, exosuit, seatedIn): (Long, ExoSuitType.Value, Option[(SourceEntry, Int)]) = mine.Zone
|
||||
.LivePlayers
|
||||
.find { _.Name.equals(name) } match {
|
||||
case Some(player) =>
|
||||
//if the owner is alive in the same zone as the mine, use data from their body to create the source
|
||||
(player.CharId, player.ExoSuit, player.VehicleSeated.nonEmpty)
|
||||
(player.CharId, player.ExoSuit, PlayerSource.mountableAndSeat(player))
|
||||
case None =>
|
||||
//if the owner is as dead as a corpse or is not in the same zone as the mine, use defaults
|
||||
(0L, ExoSuitType.Standard, false)
|
||||
(0L, ExoSuitType.Standard, None)
|
||||
}
|
||||
val faction = mine.Faction
|
||||
PlayerSource(
|
||||
name,
|
||||
charId,
|
||||
GlobalDefinitions.avatar,
|
||||
mine.Faction,
|
||||
exosuit,
|
||||
seated,
|
||||
100,
|
||||
100,
|
||||
seatedIn,
|
||||
health = 100,
|
||||
armor = 0,
|
||||
mine.Position,
|
||||
Vector3.Zero,
|
||||
None,
|
||||
crouching = false,
|
||||
jumping = false,
|
||||
ExoSuitDefinition.Select(exosuit, faction)
|
||||
ExoSuitDefinition.Select(exosuit, faction),
|
||||
bep = 0,
|
||||
kills = Nil,
|
||||
UniquePlayer(charId, name, CharacterSex.Male, mine.Faction)
|
||||
)
|
||||
case None =>
|
||||
//credit where credit is due
|
||||
|
|
|
|||
|
|
@ -5647,10 +5647,10 @@ object GlobalDefinitions {
|
|||
advanced_ace.Name = "advanced_ace"
|
||||
advanced_ace.Size = EquipmentSize.Rifle
|
||||
advanced_ace.Modes += new ConstructionFireMode {
|
||||
Item(DeployedItem.portable_manned_turret, Set(Certification.AssaultEngineering))
|
||||
Item(DeployedItem.tank_traps, Set(Certification.FortificationEngineering))
|
||||
}
|
||||
advanced_ace.Modes += new ConstructionFireMode {
|
||||
Item(DeployedItem.tank_traps, Set(Certification.FortificationEngineering))
|
||||
Item(DeployedItem.portable_manned_turret, Set(Certification.AssaultEngineering))
|
||||
}
|
||||
advanced_ace.Modes += new ConstructionFireMode {
|
||||
Item(DeployedItem.deployable_shield_generator, Set(Certification.AssaultEngineering))
|
||||
|
|
@ -9841,14 +9841,17 @@ object GlobalDefinitions {
|
|||
capture_terminal.Name = "capture_terminal"
|
||||
capture_terminal.Damageable = false
|
||||
capture_terminal.Repairable = false
|
||||
capture_terminal.FacilityHackTime = 15.minutes
|
||||
|
||||
secondary_capture.Name = "secondary_capture"
|
||||
secondary_capture.Damageable = false
|
||||
secondary_capture.Repairable = false
|
||||
secondary_capture.FacilityHackTime = 1.nanosecond
|
||||
|
||||
vanu_control_console.Name = "vanu_control_console"
|
||||
vanu_control_console.Damageable = false
|
||||
vanu_control_console.Repairable = false
|
||||
vanu_control_console.FacilityHackTime = 10.minutes
|
||||
|
||||
lodestar_repair_terminal.Name = "lodestar_repair_terminal"
|
||||
lodestar_repair_terminal.Interval = 1000
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ package net.psforever.objects
|
|||
import net.psforever.types.PlanetSideGUID
|
||||
|
||||
trait OwnableByPlayer {
|
||||
private var owner: Option[PlanetSideGUID] = None
|
||||
private var ownerName: Option[String] = None
|
||||
private var owner: Option[PlanetSideGUID] = None
|
||||
private var ownerName: Option[String] = None
|
||||
private var originalOwnerName: Option[String] = None
|
||||
|
||||
def Owner: Option[PlanetSideGUID] = owner
|
||||
|
||||
|
|
@ -33,24 +34,27 @@ trait OwnableByPlayer {
|
|||
owner match {
|
||||
case Some(_) =>
|
||||
ownerName = owner
|
||||
originalOwnerName = originalOwnerName.orElse(owner)
|
||||
case None =>
|
||||
ownerName = None
|
||||
}
|
||||
OwnerName
|
||||
}
|
||||
|
||||
def OriginalOwnerName: Option[String] = originalOwnerName
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param player na
|
||||
* @return na
|
||||
*/
|
||||
* na
|
||||
* @param player na
|
||||
* @return na
|
||||
*/
|
||||
def AssignOwnership(player: Player): OwnableByPlayer = AssignOwnership(Some(player))
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param playerOpt na
|
||||
* @return na
|
||||
*/
|
||||
* na
|
||||
* @param playerOpt na
|
||||
* @return na
|
||||
*/
|
||||
def AssignOwnership(playerOpt: Option[Player]): OwnableByPlayer = {
|
||||
playerOpt match {
|
||||
case Some(player) =>
|
||||
|
|
|
|||
|
|
@ -72,7 +72,6 @@ class Player(var avatar: Avatar)
|
|||
var spectator: Boolean = false
|
||||
var silenced: Boolean = false
|
||||
var death_by: Int = 0
|
||||
var lastSeenStreamMessage: Array[Long] = Array.fill[Long](65535)(0L)
|
||||
var lastShotSeq_time: Int = -1
|
||||
|
||||
/** From PlanetsideAttributeMessage */
|
||||
|
|
|
|||
|
|
@ -60,12 +60,14 @@ object Players {
|
|||
* @param medic the name of the player doing the reviving
|
||||
* @param item the tool being used to revive the target player
|
||||
*/
|
||||
def FinishRevivingPlayer(target: Player, medic: String, item: Tool)(): Unit = {
|
||||
def FinishRevivingPlayer(target: Player, medic: Player, item: Tool)(): Unit = {
|
||||
val name = target.Name
|
||||
log.info(s"$medic had revived $name")
|
||||
val medicName = medic.Name
|
||||
log.info(s"$medicName had revived $name")
|
||||
//target.History(PlayerRespawn(PlayerSource(target), target.Zone, target.Position, Some(PlayerSource(medic))))
|
||||
val magazine = item.Discharge(Some(25))
|
||||
target.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
medic,
|
||||
medicName,
|
||||
AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine)
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ class ShieldGeneratorControl(gen: ShieldGeneratorDeployable)
|
|||
val damageToShields = originalShields - shields
|
||||
val damage = damageToHealth + damageToShields
|
||||
if (WillAffectTarget(target, damage, cause)) {
|
||||
target.History(cause)
|
||||
target.LogActivity(cause)
|
||||
DamageLog(
|
||||
target,
|
||||
s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import net.psforever.objects.ballistics.SourceEntry
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.equipment.{EffectTarget, TargetValidation}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.{Vitality, VitalityDefinition}
|
||||
import net.psforever.objects.vital.base.DamageType
|
||||
import net.psforever.objects.vital.etc.EmpReason
|
||||
|
|
|
|||
|
|
@ -590,13 +590,6 @@ object Vehicle {
|
|||
*/
|
||||
final case class Reactivate()
|
||||
|
||||
/**
|
||||
* A request has been made to charge this vehicle's shields.
|
||||
* @see `FacilityBenefitShieldChargeRequestMessage`
|
||||
* @param amount the number of points to charge
|
||||
*/
|
||||
final case class ChargeShields(amount: Int)
|
||||
|
||||
/**
|
||||
* Following a successful shield charge tick, display the results of the update.
|
||||
* @see `FacilityBenefitShieldChargeRequestMessage`
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@
|
|||
package net.psforever.objects.avatar
|
||||
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.objects.avatar.scoring.ScoreCard
|
||||
import net.psforever.objects.definition.{AvatarDefinition, BasicDefinition}
|
||||
import net.psforever.objects.equipment.{EquipmentSize, EquipmentSlot}
|
||||
import net.psforever.objects.inventory.LocallyRegisteredInventory
|
||||
import net.psforever.objects.loadouts.{Loadout, SquadLoadout}
|
||||
import net.psforever.objects.locker.{LockerContainer, LockerEquipment}
|
||||
import net.psforever.objects.{GlobalDefinitions, OffhandEquipmentSlot}
|
||||
import net.psforever.packet.game.objectcreate.RibbonBars
|
||||
import net.psforever.packet.game.objectcreate.{BasicCharacterData, RibbonBars}
|
||||
import net.psforever.types._
|
||||
import org.joda.time.{Duration, LocalDateTime, Seconds}
|
||||
|
||||
|
|
@ -74,6 +75,10 @@ object Avatar {
|
|||
GlobalDefinitions.super_staminakit -> 5.minutes // Temporary - Default value is 20 minutes
|
||||
)
|
||||
|
||||
def apply(charId: Int, name: String, faction: PlanetSideEmpire.Value, sex: CharacterSex, head: Int, voice: CharacterVoice.Value): Avatar = {
|
||||
Avatar(charId, BasicCharacterData(name, faction, sex, head, voice))
|
||||
}
|
||||
|
||||
def makeLocker(): LockerContainer = {
|
||||
new LockerContainer({
|
||||
val inv = new LocallyRegisteredInventory(numbers = 40150 until 40450) // TODO var bad
|
||||
|
|
@ -113,11 +118,7 @@ case class MemberLists(
|
|||
case class Avatar(
|
||||
/** unique identifier corresponding to a database table row index */
|
||||
id: Int,
|
||||
name: String,
|
||||
faction: PlanetSideEmpire.Value,
|
||||
sex: CharacterSex,
|
||||
head: Int,
|
||||
voice: CharacterVoice.Value,
|
||||
basic: BasicCharacterData,
|
||||
bep: Long = 0,
|
||||
cep: Long = 0,
|
||||
stamina: Int = 100,
|
||||
|
|
@ -132,7 +133,8 @@ case class Avatar(
|
|||
decoration: ProgressDecoration = ProgressDecoration(),
|
||||
loadouts: Loadouts = Loadouts(),
|
||||
cooldowns: Cooldowns = Cooldowns(),
|
||||
people: MemberLists = MemberLists()
|
||||
people: MemberLists = MemberLists(),
|
||||
scorecard: ScoreCard = new ScoreCard()
|
||||
) {
|
||||
assert(bep >= 0)
|
||||
assert(cep >= 0)
|
||||
|
|
@ -140,6 +142,16 @@ case class Avatar(
|
|||
val br: BattleRank = BattleRank.withExperience(bep)
|
||||
val cr: CommandRank = CommandRank.withExperience(cep)
|
||||
|
||||
def name: String = basic.name
|
||||
|
||||
def faction: PlanetSideEmpire.Value = basic.faction
|
||||
|
||||
def sex: CharacterSex = basic.sex
|
||||
|
||||
def head: Int = basic.head
|
||||
|
||||
def voice: CharacterVoice.Value = basic.voice
|
||||
|
||||
private def cooldown(
|
||||
times: Map[String, LocalDateTime],
|
||||
cooldowns: Map[BasicDefinition, FiniteDuration],
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@ package net.psforever.objects.avatar
|
|||
import akka.actor.{Actor, ActorRef, Props, typed}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.login.WorldSession.{DropEquipmentFromInventory, HoldNewEquipmentUp, PutNewEquipmentInInventoryOrDrop, RemoveOldEquipmentFromInventory}
|
||||
import net.psforever.objects.{Player, _}
|
||||
import net.psforever.objects.ballistics.PlayerSource
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.ce.Deployable
|
||||
import net.psforever.objects.definition.DeployAnimation
|
||||
import net.psforever.objects.definition.converter.OCM
|
||||
|
|
@ -33,6 +32,7 @@ import net.psforever.objects.locker.LockerContainerControl
|
|||
import net.psforever.objects.serverobject.environment._
|
||||
import net.psforever.objects.serverobject.repair.Repairable
|
||||
import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad
|
||||
import net.psforever.objects.sourcing.PlayerSource
|
||||
import net.psforever.objects.vital.collision.CollisionReason
|
||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||
import net.psforever.objects.vital.etc.{PainboxReason, SuicideReason}
|
||||
|
|
@ -50,21 +50,21 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
with AggravatedBehavior
|
||||
with AuraEffectBehavior
|
||||
with RespondsToZoneEnvironment {
|
||||
def JammableObject = player
|
||||
def JammableObject: Player = player
|
||||
|
||||
def DamageableObject = player
|
||||
def DamageableObject: Player = player
|
||||
|
||||
def ContainerObject = player
|
||||
def ContainerObject: Player = player
|
||||
|
||||
def AggravatedObject = player
|
||||
def AggravatedObject: Player = player
|
||||
|
||||
def AuraTargetObject = player
|
||||
def AuraTargetObject: Player = player
|
||||
ApplicableEffect(Aura.Plasma)
|
||||
ApplicableEffect(Aura.Napalm)
|
||||
ApplicableEffect(Aura.Comet)
|
||||
ApplicableEffect(Aura.Fire)
|
||||
|
||||
def InteractiveObject = player
|
||||
def InteractiveObject: Player = player
|
||||
SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
|
||||
SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
|
||||
SetInteraction(EnvironmentAttribute.Death, doInteractingWithDeath)
|
||||
|
|
@ -105,8 +105,18 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
case Player.Die(Some(reason)) =>
|
||||
if (player.isAlive) {
|
||||
//primary death
|
||||
PerformDamage(player, reason.calculate())
|
||||
suicide()
|
||||
val health = player.Health
|
||||
val psource = PlayerSource(player)
|
||||
player.Health = 0
|
||||
HandleDamage(
|
||||
player,
|
||||
DamageResult(psource, psource.copy(health = 0), reason),
|
||||
health,
|
||||
damageToArmor = 0,
|
||||
damageToStamina = 0,
|
||||
damageToCapacitor = 0
|
||||
)
|
||||
damageLog.info(s"${player.Name}-infantry: dead by explicit reason - ${reason.cause.resolution}")
|
||||
}
|
||||
|
||||
case Player.Die(None) =>
|
||||
|
|
@ -138,12 +148,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
)
|
||||
)
|
||||
events ! AvatarServiceMessage(zone.id, AvatarAction.PlanetsideAttributeToAll(guid, 0, newHealth))
|
||||
player.History(
|
||||
player.LogActivity(
|
||||
HealFromEquipment(
|
||||
PlayerSource(player),
|
||||
PlayerSource(user),
|
||||
newHealth - originalHealth,
|
||||
GlobalDefinitions.medicalapplicator
|
||||
GlobalDefinitions.medicalapplicator,
|
||||
newHealth - originalHealth
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -172,7 +181,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
) {
|
||||
sender() ! CommonMessages.Progress(
|
||||
4,
|
||||
Players.FinishRevivingPlayer(player, user.Name, item),
|
||||
Players.FinishRevivingPlayer(player, user, item),
|
||||
Players.RevivingTickAction(player, user, item)
|
||||
)
|
||||
}
|
||||
|
|
@ -202,12 +211,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
)
|
||||
)
|
||||
events ! AvatarServiceMessage(zone.id, AvatarAction.PlanetsideAttributeToAll(guid, 4, player.Armor))
|
||||
player.History(
|
||||
player.LogActivity(
|
||||
RepairFromEquipment(
|
||||
PlayerSource(player),
|
||||
PlayerSource(user),
|
||||
newArmor - originalArmor,
|
||||
GlobalDefinitions.bank
|
||||
GlobalDefinitions.bank,
|
||||
newArmor - originalArmor
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -242,7 +250,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
if (player.Health == player.MaxHealth) {
|
||||
(None, 0, 0, "@HealComplete")
|
||||
} else {
|
||||
player.History(HealFromKit(PlayerSource(player), 25, kdef))
|
||||
player.LogActivity(HealFromKit(kdef, 25))
|
||||
player.Health = player.Health + 25
|
||||
(Some(index), 0, player.Health, "")
|
||||
}
|
||||
|
|
@ -250,7 +258,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
if (player.Health == player.MaxHealth) {
|
||||
(None, 0, 0, "@HealComplete")
|
||||
} else {
|
||||
player.History(HealFromKit(PlayerSource(player), 100, kdef))
|
||||
player.LogActivity(HealFromKit(kdef, 100))
|
||||
player.Health = player.Health + 100
|
||||
(Some(index), 0, player.Health, "")
|
||||
}
|
||||
|
|
@ -258,7 +266,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
if (player.Armor == player.MaxArmor) {
|
||||
(None, 0, 0, "Armor at maximum - No repairing required.")
|
||||
} else {
|
||||
player.History(RepairFromKit(PlayerSource(player), 200, kdef))
|
||||
player.LogActivity(RepairFromKit(kdef, 200))
|
||||
player.Armor = player.Armor + 200
|
||||
(Some(index), 4, player.Armor, "")
|
||||
}
|
||||
|
|
@ -426,13 +434,14 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
val originalArmor = player.Armor
|
||||
player.ExoSuit = nextSuit
|
||||
val toMaxArmor = player.MaxArmor
|
||||
val toArmor =
|
||||
val toArmor = {
|
||||
if (originalSuit != nextSuit || originalSubtype != nextSubtype || originalArmor > toMaxArmor) {
|
||||
player.History(HealFromExoSuitChange(PlayerSource(player), nextSuit))
|
||||
player.LogActivity(RepairFromExoSuitChange(nextSuit, toMaxArmor - player.Armor))
|
||||
player.Armor = toMaxArmor
|
||||
} else {
|
||||
player.Armor = originalArmor
|
||||
}
|
||||
}
|
||||
//ensure arm is down, even if it needs to go back up
|
||||
if (player.DrawnSlot != Player.HandsDownSlot) {
|
||||
player.DrawnSlot = Player.HandsDownSlot
|
||||
|
|
@ -488,9 +497,9 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
)
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, true)
|
||||
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, result=true)
|
||||
)
|
||||
case _ => assert(false, msg.toString)
|
||||
case _ => assert(assertion=false, msg.toString)
|
||||
}
|
||||
|
||||
case Zone.Ground.ItemOnGround(item, _, _) =>
|
||||
|
|
@ -528,8 +537,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
val trigger = new BoomerTrigger
|
||||
trigger.Companion = obj.GUID
|
||||
obj.Trigger = trigger
|
||||
//TODO sufficiently delete the tool
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.ObjectDelete(player.GUID, tool.GUID))
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, tool.GUID))
|
||||
TaskWorkflow.execute(GUIDTask.unregisterEquipment(zone.GUID, tool))
|
||||
player.Find(tool) match {
|
||||
case Some(index) if player.VisibleSlots.contains(index) =>
|
||||
|
|
@ -593,19 +601,18 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
val originalSubtype = Loadout.DetermineSubtype(player)
|
||||
val requestToChangeArmor = originalSuit != exosuit || originalSubtype != subtype
|
||||
val allowedToChangeArmor = Players.CertificationToUseExoSuit(player, exosuit, subtype) &&
|
||||
(if (exosuit == ExoSuitType.MAX) {
|
||||
val weapon = GlobalDefinitions.MAXArms(subtype, player.Faction)
|
||||
player.avatar.purchaseCooldown(weapon) match {
|
||||
case Some(_) =>
|
||||
false
|
||||
case None =>
|
||||
avatarActor ! AvatarActor.UpdatePurchaseTime(weapon)
|
||||
true
|
||||
}
|
||||
}
|
||||
else {
|
||||
true
|
||||
})
|
||||
(if (exosuit == ExoSuitType.MAX) {
|
||||
val weapon = GlobalDefinitions.MAXArms(subtype, player.Faction)
|
||||
player.avatar.purchaseCooldown(weapon) match {
|
||||
case Some(_) =>
|
||||
false
|
||||
case None =>
|
||||
avatarActor ! AvatarActor.UpdatePurchaseTime(weapon)
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
})
|
||||
if (requestToChangeArmor && allowedToChangeArmor) {
|
||||
log.info(s"${player.Name} wants to change to a different exo-suit - $exosuit")
|
||||
val beforeHolsters = Players.clearHolsters(player.Holsters().iterator)
|
||||
|
|
@ -614,13 +621,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
val originalArmor = player.Armor
|
||||
player.ExoSuit = exosuit //changes the value of MaxArmor to reflect the new exo-suit
|
||||
val toMaxArmor = player.MaxArmor
|
||||
val toArmor = if (originalSuit != exosuit || originalSubtype != subtype || originalArmor > toMaxArmor) {
|
||||
player.History(HealFromExoSuitChange(PlayerSource(player), exosuit))
|
||||
player.Armor = toMaxArmor
|
||||
}
|
||||
else {
|
||||
player.Armor = originalArmor
|
||||
val toArmor = toMaxArmor
|
||||
if (originalArmor != toMaxArmor) {
|
||||
player.LogActivity(RepairFromExoSuitChange(exosuit, toMaxArmor - originalArmor))
|
||||
}
|
||||
player.Armor = toMaxArmor
|
||||
//ensure arm is down, even if it needs to go back up
|
||||
if (player.DrawnSlot != Player.HandsDownSlot) {
|
||||
player.DrawnSlot = Player.HandsDownSlot
|
||||
|
|
@ -804,7 +809,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
})) {
|
||||
//activate second wind
|
||||
player.Health += 25
|
||||
player.History(HealFromImplant(PlayerSource(player), 25, ImplantType.SecondWind))
|
||||
player.LogActivity(HealFromImplant(ImplantType.SecondWind, 25))
|
||||
avatarActor ! AvatarActor.ResetImplant(ImplantType.SecondWind)
|
||||
avatarActor ! AvatarActor.RestoreStamina(25)
|
||||
}
|
||||
|
|
@ -844,7 +849,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
cause.interaction.cause.source.Aggravated.nonEmpty
|
||||
}
|
||||
//log historical event (always)
|
||||
target.History(cause)
|
||||
target.LogActivity(cause)
|
||||
//stat changes
|
||||
if (damageToCapacitor > 0) {
|
||||
events ! AvatarServiceMessage(
|
||||
|
|
@ -959,7 +964,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
avatarActor ! AvatarActor.DeinitializeImplants()
|
||||
|
||||
//log historical event
|
||||
target.History(cause)
|
||||
target.LogActivity(cause)
|
||||
//log message
|
||||
cause.adversarial match {
|
||||
case Some(a) =>
|
||||
|
|
@ -1015,7 +1020,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
nameChannel,
|
||||
AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, pos, target.Faction, true)
|
||||
AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, pos, target.Faction, unk5=true)
|
||||
)
|
||||
)
|
||||
//TODO other methods of death?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2023 PSForever
|
||||
package net.psforever.objects.avatar.scoring
|
||||
|
||||
final case class EquipmentStat(objectId: Int, shotsFired: Int, shotsLanded: Int, kills: Int)
|
||||
|
||||
object EquipmentStat {
|
||||
def apply(objectId: Int): EquipmentStat = EquipmentStat(objectId, 0, 1, 0)
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) 2023 PSForever
|
||||
package net.psforever.objects.avatar.scoring
|
||||
|
||||
import net.psforever.objects.sourcing.PlayerSource
|
||||
import net.psforever.objects.vital.interaction.DamageResult
|
||||
import org.joda.time.LocalDateTime
|
||||
|
||||
trait KDAStat {
|
||||
def experienceEarned: Long
|
||||
val time: LocalDateTime = LocalDateTime.now()
|
||||
}
|
||||
|
||||
final case class Kill(victim: PlayerSource, info: DamageResult, experienceEarned: Long) extends KDAStat
|
||||
|
||||
final case class Assist(victim: PlayerSource, weapons: Seq[Int], damageInflictedPercentage: Float, experienceEarned: Long) extends KDAStat
|
||||
|
||||
final case class Death(assailant: Seq[PlayerSource], timeAlive: Long, bep: Long) extends KDAStat {
|
||||
def experienceEarned: Long = 0
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) 2023 PSForever
|
||||
package net.psforever.objects.avatar.scoring
|
||||
|
||||
final case class Life(
|
||||
kills: Seq[Kill],
|
||||
assists: Seq[Assist],
|
||||
death: Option[Death],
|
||||
equipmentStats: Seq[EquipmentStat]
|
||||
)
|
||||
|
||||
object Life {
|
||||
def apply(): Life = Life(Nil, Nil, None, Nil)
|
||||
|
||||
def bep(life: Life): Long = {
|
||||
life.kills.foldLeft(0L)(_ + _.experienceEarned) + life.assists.foldLeft(0L)(_ + _.experienceEarned)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
// Copyright (c) 2023 PSForever
|
||||
package net.psforever.objects.avatar.scoring
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, VehicleSource}
|
||||
import net.psforever.types.{PlanetSideEmpire, StatisticalCategory}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.mutable
|
||||
|
||||
class ScoreCard() {
|
||||
private var curr: Life = Life()
|
||||
private var lives: Seq[Life] = Seq()
|
||||
private val killStatistics: mutable.HashMap[Int, Statistic] = mutable.HashMap[Int, Statistic]()
|
||||
private val assistStatistics: mutable.HashMap[Int, Statistic] = mutable.HashMap[Int, Statistic]()
|
||||
|
||||
def CurrentLife: Life = curr
|
||||
|
||||
def Lives: Seq[Life] = lives
|
||||
|
||||
def KillStatistics: Map[Int, Statistic] = killStatistics.toMap
|
||||
|
||||
def AssistStatistics: Map[Int, Statistic] = assistStatistics.toMap
|
||||
|
||||
def rate(msg: Any): Unit = {
|
||||
msg match {
|
||||
case e: EquipmentStat =>
|
||||
curr = ScoreCard.updateEquipmentStat(curr, e)
|
||||
case k: Kill =>
|
||||
curr = curr.copy(kills = k +: curr.kills)
|
||||
curr = ScoreCard.updateEquipmentStat(curr, EquipmentStat(k.info.interaction.cause.attribution, 0, 0, 1))
|
||||
ScoreCard.updateStatisticsFor(killStatistics, k.info.interaction.cause.attribution, k.victim.Faction)
|
||||
case a: Assist =>
|
||||
curr = curr.copy(assists = a +: curr.assists)
|
||||
val faction = a.victim.Faction
|
||||
a.weapons.foreach { wid =>
|
||||
ScoreCard.updateStatisticsFor(assistStatistics, wid, faction)
|
||||
}
|
||||
case d: Death =>
|
||||
val expired = curr
|
||||
curr = Life()
|
||||
lives = expired.copy(death = Some(d)) +: lives
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ScoreCard {
|
||||
private def updateEquipmentStat(curr: Life, entry: EquipmentStat): Life = {
|
||||
updateEquipmentStat(curr, entry, entry.objectId, entry.kills)
|
||||
}
|
||||
|
||||
private def updateEquipmentStat(
|
||||
curr: Life,
|
||||
entry: EquipmentStat,
|
||||
objectId: Int,
|
||||
killCount: Int
|
||||
): Life = {
|
||||
curr.equipmentStats.indexWhere { a => a.objectId == objectId } match {
|
||||
case -1 =>
|
||||
curr.copy(equipmentStats = entry +: curr.equipmentStats)
|
||||
case index =>
|
||||
val stats = curr.equipmentStats
|
||||
val old = stats(index)
|
||||
curr.copy(
|
||||
equipmentStats = (stats.take(index) :+ old.copy(
|
||||
shotsFired = old.shotsFired + entry.shotsFired,
|
||||
shotsLanded = old.shotsLanded + entry.shotsLanded,
|
||||
kills = old.kills + killCount
|
||||
)) ++ stats.drop(index+1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@tailrec
|
||||
private def updateStatisticsFor(
|
||||
statisticMap: mutable.HashMap[Int, Statistic],
|
||||
objectId: Int,
|
||||
victimFaction: PlanetSideEmpire.Value
|
||||
): Statistic = {
|
||||
statisticMap.get(objectId) match {
|
||||
case Some(fields) =>
|
||||
val outEntry = victimFaction match {
|
||||
case PlanetSideEmpire.TR => fields.copy(tr_b = fields.tr_b + 1)
|
||||
case PlanetSideEmpire.NC => fields.copy(nc_b = fields.nc_b + 1)
|
||||
case PlanetSideEmpire.VS => fields.copy(vs_b = fields.vs_b + 1)
|
||||
case PlanetSideEmpire.NEUTRAL => fields.copy(ps_b = fields.ps_b + 1)
|
||||
}
|
||||
outEntry
|
||||
case _ =>
|
||||
val out = Statistic(0, 0, 0, 0, 0, 0, 0, 0)
|
||||
statisticMap.put(objectId, out)
|
||||
updateStatisticsFor(statisticMap, objectId, victimFaction)
|
||||
}
|
||||
}
|
||||
|
||||
def weaponObjectIdMap(objectId: Int): Int = {
|
||||
objectId match {
|
||||
//aphelion
|
||||
case 81 | 82 => 80
|
||||
case 90 | 92 => 88
|
||||
case 94 | 95 => 93
|
||||
case 102 | 104 => 100
|
||||
case 107 | 109 => 105
|
||||
//colossus
|
||||
case 183 | 184 => 182
|
||||
case 187 | 189 => 185
|
||||
case 192 | 194 => 190
|
||||
case 202 | 203 => 201
|
||||
case 206 | 208 => 204
|
||||
//cycler
|
||||
case 234 | 235 | 236 => 233
|
||||
//peregrine
|
||||
case 634 | 635 => 633
|
||||
case 638 | 640 => 636
|
||||
case 646 | 648 => 644
|
||||
case 650 | 651 => 649
|
||||
case 660 | 662 => 658
|
||||
//eh
|
||||
case _ => objectId
|
||||
}
|
||||
}
|
||||
|
||||
def rewardKillGetCategories(victim: SourceEntry): Seq[StatisticalCategory] = {
|
||||
victim match {
|
||||
case p: PlayerSource =>
|
||||
p.seatedIn match {
|
||||
case Some((v: VehicleSource, seat: Int)) =>
|
||||
val seatCategory = if (seat == 0) {
|
||||
StatisticalCategory.DriverKilled
|
||||
} else {
|
||||
v.Definition.controlledWeapons().get(seat) match {
|
||||
case Some(_) => StatisticalCategory.GunnerKilled
|
||||
case None => StatisticalCategory.PassengerKilled
|
||||
}
|
||||
}
|
||||
if (GlobalDefinitions.isFlightVehicle(v.Definition)) {
|
||||
Seq(StatisticalCategory.Dogfighter, seatCategory)
|
||||
} else {
|
||||
Seq(seatCategory)
|
||||
}
|
||||
case _ =>
|
||||
Seq(StatisticalCategory.Destroyed)
|
||||
}
|
||||
case _ =>
|
||||
Seq(StatisticalCategory.Destroyed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (c) 2023 PSForever
|
||||
package net.psforever.objects.avatar.scoring
|
||||
|
||||
final case class Statistic(tr_a: Int, tr_b: Int, nc_a: Int, nc_b: Int, vs_a: Int, vs_b: Int, ps_a: Int, ps_b: Int)
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.ballistics
|
||||
|
||||
import net.psforever.objects.ce.Deployable
|
||||
import net.psforever.objects.definition.{DeployableDefinition, ObjectDefinition}
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfile
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
|
||||
final case class DeployableSource(
|
||||
obj_def: ObjectDefinition with DeployableDefinition,
|
||||
faction: PlanetSideEmpire.Value,
|
||||
health: Int,
|
||||
shields: Int,
|
||||
owner: SourceEntry,
|
||||
position: Vector3,
|
||||
orientation: Vector3
|
||||
) extends SourceEntry {
|
||||
override def Name = obj_def.Descriptor
|
||||
override def Faction = faction
|
||||
def Definition: ObjectDefinition with DeployableDefinition = obj_def
|
||||
def Health = health
|
||||
def Shields = shields
|
||||
def OwnerName = owner.Name
|
||||
def Position = position
|
||||
def Orientation = orientation
|
||||
def Velocity = None
|
||||
def Modifiers = obj_def.asInstanceOf[ResistanceProfile]
|
||||
}
|
||||
|
||||
object DeployableSource {
|
||||
def apply(obj: Deployable): DeployableSource = {
|
||||
val ownerName = obj.OwnerName
|
||||
val ownerSource = (obj.Zone.LivePlayers ++ obj.Zone.Corpses)
|
||||
.find { p => ownerName.contains(p.Name) }
|
||||
match {
|
||||
case Some(p) => SourceEntry(p)
|
||||
case _ => SourceEntry.None
|
||||
}
|
||||
DeployableSource(
|
||||
obj.Definition,
|
||||
obj.Faction,
|
||||
obj.Health,
|
||||
obj.Shields,
|
||||
ownerSource,
|
||||
obj.Position,
|
||||
obj.Orientation
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
package net.psforever.objects.ballistics
|
||||
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.base.DamageResolution
|
||||
import net.psforever.objects.vital.etc.RadiationReason
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.ballistics
|
||||
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.vital.VitalityDefinition
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
|
||||
final case class ObjectSource(
|
||||
obj: PlanetSideGameObject,
|
||||
faction: PlanetSideEmpire.Value,
|
||||
position: Vector3,
|
||||
orientation: Vector3,
|
||||
velocity: Option[Vector3]
|
||||
) extends SourceEntry {
|
||||
private val definition = obj.Definition match {
|
||||
case vital : VitalityDefinition => vital
|
||||
case genericDefinition => NonvitalDefinition(genericDefinition)
|
||||
}
|
||||
private val modifiers = definition match {
|
||||
case nonvital : NonvitalDefinition => nonvital
|
||||
case _ => ObjectSource.FixedResistances
|
||||
}
|
||||
override def Name = SourceEntry.NameFormat(obj.Definition.Name)
|
||||
override def Faction = faction
|
||||
def Definition = definition
|
||||
def Position = position
|
||||
def Orientation = orientation
|
||||
def Velocity = velocity
|
||||
def Modifiers = modifiers
|
||||
}
|
||||
|
||||
object ObjectSource {
|
||||
final val FixedResistances = new ResistanceProfileMutators() { }
|
||||
|
||||
def apply(obj: PlanetSideGameObject): ObjectSource = {
|
||||
ObjectSource(
|
||||
obj,
|
||||
obj match {
|
||||
case aligned: FactionAffinity => aligned.Faction
|
||||
case _ => PlanetSideEmpire.NEUTRAL
|
||||
},
|
||||
obj.Position,
|
||||
obj.Orientation,
|
||||
obj.Velocity
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper for a definition that does not represent a `Vitality` object.
|
||||
* @param definition the original definition
|
||||
*/
|
||||
class NonvitalDefinition(private val definition : ObjectDefinition)
|
||||
extends ObjectDefinition(definition.ObjectId)
|
||||
with ResistanceProfileMutators
|
||||
with VitalityDefinition {
|
||||
Name = { definition.Name }
|
||||
Packet = { definition.Packet }
|
||||
|
||||
def canEqual(a: Any) : Boolean = a.isInstanceOf[definition.type]
|
||||
|
||||
override def equals(that: Any): Boolean = definition.equals(that)
|
||||
|
||||
override def hashCode: Int = definition.hashCode
|
||||
}
|
||||
|
||||
object NonvitalDefinition {
|
||||
//single point of contact for all wrapped definitions
|
||||
private val storage: scala.collection.mutable.LongMap[NonvitalDefinition] =
|
||||
new scala.collection.mutable.LongMap[NonvitalDefinition]()
|
||||
|
||||
def apply(definition : ObjectDefinition) : NonvitalDefinition = {
|
||||
storage.get(definition.ObjectId) match {
|
||||
case Some(existing) =>
|
||||
existing
|
||||
case None =>
|
||||
val out = new NonvitalDefinition(definition)
|
||||
storage += definition.ObjectId.toLong -> out
|
||||
out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.ballistics
|
||||
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition}
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfile
|
||||
import net.psforever.types.{ExoSuitType, PlanetSideEmpire, Vector3}
|
||||
|
||||
final case class PlayerSource(
|
||||
name: String,
|
||||
char_id: Long,
|
||||
obj_def: AvatarDefinition,
|
||||
faction: PlanetSideEmpire.Value,
|
||||
exosuit: ExoSuitType.Value,
|
||||
seated: Boolean,
|
||||
health: Int,
|
||||
armor: Int,
|
||||
position: Vector3,
|
||||
orientation: Vector3,
|
||||
velocity: Option[Vector3],
|
||||
crouching: Boolean,
|
||||
jumping: Boolean,
|
||||
modifiers: ResistanceProfile
|
||||
) extends SourceEntry {
|
||||
override def Name = name
|
||||
override def Faction = faction
|
||||
override def CharId = char_id
|
||||
def Definition = obj_def
|
||||
def ExoSuit = exosuit
|
||||
def Seated = seated
|
||||
def Health = health
|
||||
def Armor = armor
|
||||
def Position = position
|
||||
def Orientation = orientation
|
||||
def Velocity = velocity
|
||||
def Modifiers = modifiers
|
||||
}
|
||||
|
||||
object PlayerSource {
|
||||
def apply(tplayer: Player): PlayerSource = {
|
||||
PlayerSource(
|
||||
tplayer.Name,
|
||||
tplayer.CharId,
|
||||
tplayer.Definition,
|
||||
tplayer.Faction,
|
||||
tplayer.ExoSuit,
|
||||
tplayer.VehicleSeated.nonEmpty,
|
||||
tplayer.Health,
|
||||
tplayer.Armor,
|
||||
tplayer.Position,
|
||||
tplayer.Orientation,
|
||||
tplayer.Velocity,
|
||||
tplayer.Crouching,
|
||||
tplayer.Jumping,
|
||||
ExoSuitDefinition.Select(tplayer.ExoSuit, tplayer.Faction)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.ballistics
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
//
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.definition.{ProjectileDefinition, ToolDefinition}
|
||||
import net.psforever.objects.entity.SimpleWorldEntity
|
||||
|
|
@ -13,42 +15,44 @@ import net.psforever.objects.zones.blockmap.BlockMapEntity
|
|||
import net.psforever.types.Vector3
|
||||
|
||||
/**
|
||||
* A summation of weapon discharge.
|
||||
* @see `ProjectileDefinition`
|
||||
* @see `ToolDefinition`
|
||||
* @see `FireModeDefinition`
|
||||
* @see `SourceEntry`
|
||||
* @see `PlayerSource`
|
||||
* @param profile an explanation of the damage that can be performed by this discharge
|
||||
* @param tool_def the weapon that caused this discharge
|
||||
* @param fire_mode the current fire mode of the tool used
|
||||
* @param owner the agency that caused the weapon to produce this projectile;
|
||||
* most often a player (`PlayerSource`)
|
||||
* @param attribute_to an object ID that refers to the method of death that would be reported;
|
||||
* usually the same as `tool_def.ObjectId`;
|
||||
* if not, then it is a type of vehicle (and owner should have a positive `seated` field)
|
||||
* @param shot_origin where the projectile started
|
||||
* @param shot_angle in which direction the projectile was aimed when it was discharged
|
||||
* @param shot_velocity the initial velocity coordinates of the projectile according to its world directions
|
||||
* @param quality na
|
||||
* @param id an exclusive identifier for this projectile;
|
||||
* normally generated internally, but can be manually set (for modifying a continuous projectile reference)
|
||||
* @param fire_time when the weapon discharged was recorded;
|
||||
* defaults to `System.currentTimeMillis()`
|
||||
*/
|
||||
* A summation of weapon discharge.
|
||||
* @see `ProjectileDefinition`
|
||||
* @see `ToolDefinition`
|
||||
* @see `FireModeDefinition`
|
||||
* @see `SourceEntry`
|
||||
* @see `PlayerSource`
|
||||
* @param profile an explanation of the damage that can be performed by this discharge
|
||||
* @param tool_def the weapon that caused this discharge
|
||||
* @param fire_mode the current fire mode of the tool used
|
||||
* @param mounted_in na
|
||||
* @param owner the agency that caused the weapon to produce this projectile;
|
||||
* most often a player (`PlayerSource`)
|
||||
* @param attribute_to an object ID that refers to the method of death that would be reported;
|
||||
* usually the same as `tool_def.ObjectId`;
|
||||
* if not, then it is a type of vehicle (and owner should have a positive `seated` field)
|
||||
* @param shot_origin where the projectile started
|
||||
* @param shot_angle in which direction the projectile was aimed when it was discharged
|
||||
* @param shot_velocity the initial velocity coordinates of the projectile according to its world directions
|
||||
* @param quality na
|
||||
* @param id an exclusive identifier for this projectile;
|
||||
* normally generated internally, but can be manually set (for modifying a continuous projectile reference)
|
||||
* @param fire_time when the weapon discharged was recorded;
|
||||
* defaults to `System.currentTimeMillis()`
|
||||
*/
|
||||
final case class Projectile(
|
||||
profile: ProjectileDefinition,
|
||||
tool_def: ToolDefinition,
|
||||
fire_mode: FireModeDefinition,
|
||||
owner: SourceEntry,
|
||||
attribute_to: Int,
|
||||
shot_origin: Vector3,
|
||||
shot_angle: Vector3,
|
||||
shot_velocity: Option[Vector3],
|
||||
quality: ProjectileQuality = ProjectileQuality.Normal,
|
||||
id: Long = Projectile.idGenerator.getAndIncrement(),
|
||||
fire_time: Long = System.currentTimeMillis()
|
||||
) extends PlanetSideGameObject
|
||||
profile: ProjectileDefinition,
|
||||
tool_def: ToolDefinition,
|
||||
fire_mode: FireModeDefinition,
|
||||
mounted_in: Option[(Int, SourceEntry)],
|
||||
owner: SourceEntry,
|
||||
attribute_to: Int,
|
||||
shot_origin: Vector3,
|
||||
shot_angle: Vector3,
|
||||
shot_velocity: Option[Vector3],
|
||||
quality: ProjectileQuality = ProjectileQuality.Normal,
|
||||
id: Long = Projectile.idGenerator.getAndIncrement(),
|
||||
fire_time: Long = System.currentTimeMillis()
|
||||
) extends PlanetSideGameObject
|
||||
with BlockMapEntity {
|
||||
Position = shot_origin
|
||||
Orientation = shot_angle
|
||||
|
|
@ -66,18 +70,19 @@ final case class Projectile(
|
|||
private var resolved: DamageResolution.Value = DamageResolution.Unresolved
|
||||
|
||||
/**
|
||||
* Create a copy of this projectile with all the same information
|
||||
* save for the quality.
|
||||
* Used mainly for aggravated damage.
|
||||
* It is important to note that the new projectile shares the (otherwise) exclusive id of the original.
|
||||
* @param value the new quality
|
||||
* @return a new `Projectile` entity
|
||||
*/
|
||||
* Create a copy of this projectile with all the same information
|
||||
* save for the quality.
|
||||
* Used mainly for aggravated damage.
|
||||
* It is important to note that the new projectile shares the (otherwise) exclusive id of the original.
|
||||
* @param value the new quality
|
||||
* @return a new `Projectile` entity
|
||||
*/
|
||||
def quality(value: ProjectileQuality): Projectile = {
|
||||
val projectile = Projectile(
|
||||
val projectile = new Projectile(
|
||||
profile,
|
||||
tool_def,
|
||||
fire_mode,
|
||||
mounted_in,
|
||||
owner,
|
||||
attribute_to,
|
||||
shot_origin,
|
||||
|
|
@ -93,8 +98,8 @@ final case class Projectile(
|
|||
}
|
||||
|
||||
/**
|
||||
* Mark the projectile as being "encountered" or "managed" at least once.
|
||||
*/
|
||||
* Mark the projectile as being "encountered" or "managed" at least once.
|
||||
*/
|
||||
def Resolve(): Unit = {
|
||||
resolved = DamageResolution.Resolved
|
||||
}
|
||||
|
|
@ -107,7 +112,7 @@ final case class Projectile(
|
|||
|
||||
def isMiss: Boolean = resolved == DamageResolution.Missed
|
||||
|
||||
def Definition = profile
|
||||
def Definition: ProjectileDefinition = profile
|
||||
}
|
||||
|
||||
object Projectile {
|
||||
|
|
@ -116,22 +121,22 @@ object Projectile {
|
|||
final val baseUID: Int = 40100
|
||||
|
||||
/** all clients progress through 40100 to 40124 normally, skipping only for long-lived projectiles
|
||||
* 40125 to 40149 are being reserved as a guard against undetected overflow
|
||||
*/
|
||||
* 40125 to 40149 are being reserved as a guard against undetected overflow
|
||||
*/
|
||||
final val rangeUID: Int = 40150
|
||||
|
||||
private val idGenerator: AtomicLong = new AtomicLong
|
||||
|
||||
/**
|
||||
* Overloaded constructor for an `Unresolved` projectile.
|
||||
* @param profile an explanation of the damage that can be performed by this discharge
|
||||
* @param tool_def the weapon that caused this discharge
|
||||
* @param fire_mode the current fire mode of the tool used
|
||||
* @param owner the agency that caused the weapon to produce this projectile
|
||||
* @param shot_origin where the projectile started
|
||||
* @param shot_angle in which direction the projectile was aimed when it was discharged
|
||||
* @return the `Projectile` object
|
||||
*/
|
||||
* Overloaded constructor for an `Unresolved` projectile.
|
||||
* @param profile an explanation of the damage that can be performed by this discharge
|
||||
* @param tool_def the weapon that caused this discharge
|
||||
* @param fire_mode the current fire mode of the tool used
|
||||
* @param owner the agency that caused the weapon to produce this projectile
|
||||
* @param shot_origin where the projectile started
|
||||
* @param shot_angle in which direction the projectile was aimed when it was discharged
|
||||
* @return the `Projectile` object
|
||||
*/
|
||||
def apply(
|
||||
profile: ProjectileDefinition,
|
||||
tool_def: ToolDefinition,
|
||||
|
|
@ -140,20 +145,20 @@ object Projectile {
|
|||
shot_origin: Vector3,
|
||||
shot_angle: Vector3
|
||||
): Projectile = {
|
||||
Projectile(profile, tool_def, fire_mode, SourceEntry(owner), tool_def.ObjectId, shot_origin, shot_angle, None)
|
||||
Projectile(profile, tool_def, fire_mode, None, SourceEntry(owner), tool_def.ObjectId, shot_origin, shot_angle, None)
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloaded constructor for an `Unresolved` projectile.
|
||||
* @param profile an explanation of the damage that can be performed by this discharge
|
||||
* @param tool_def the weapon that caused this discharge
|
||||
* @param fire_mode the current fire mode of the tool used
|
||||
* @param owner the agency that caused the weapon to produce this projectile
|
||||
* @param attribute_to an object ID that refers to the method of death that would be reported
|
||||
* @param shot_origin where the projectile started
|
||||
* @param shot_angle in which direction the projectile was aimed when it was discharged
|
||||
* @return the `Projectile` object
|
||||
*/
|
||||
* Overloaded constructor for an `Unresolved` projectile.
|
||||
* @param profile an explanation of the damage that can be performed by this discharge
|
||||
* @param tool_def the weapon that caused this discharge
|
||||
* @param fire_mode the current fire mode of the tool used
|
||||
* @param owner the agency that caused the weapon to produce this projectile
|
||||
* @param attribute_to an object ID that refers to the method of death that would be reported
|
||||
* @param shot_origin where the projectile started
|
||||
* @param shot_angle in which direction the projectile was aimed when it was discharged
|
||||
* @return the `Projectile` object
|
||||
*/
|
||||
def apply(
|
||||
profile: ProjectileDefinition,
|
||||
tool_def: ToolDefinition,
|
||||
|
|
@ -163,7 +168,7 @@ object Projectile {
|
|||
shot_origin: Vector3,
|
||||
shot_angle: Vector3
|
||||
): Projectile = {
|
||||
Projectile(profile, tool_def, fire_mode, SourceEntry(owner), attribute_to, shot_origin, shot_angle, None)
|
||||
Projectile(profile, tool_def, fire_mode, None, SourceEntry(owner), attribute_to, shot_origin, shot_angle, None)
|
||||
}
|
||||
|
||||
def apply(
|
||||
|
|
@ -175,6 +180,6 @@ object Projectile {
|
|||
shot_origin: Vector3,
|
||||
shot_angle: Vector3
|
||||
): Projectile = {
|
||||
Projectile(profile, tool_def, fire_mode, owner, attribute_to, shot_origin, shot_angle, None)
|
||||
Projectile(profile, tool_def, fire_mode, None, owner, attribute_to, shot_origin, shot_angle, None)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.ballistics
|
||||
|
||||
import net.psforever.objects.ce.Deployable
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle}
|
||||
import net.psforever.objects.entity.WorldEntity
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.vital.VitalityDefinition
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfile
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
|
||||
trait SourceEntry extends WorldEntity {
|
||||
def Name: String = ""
|
||||
def Definition: ObjectDefinition with VitalityDefinition
|
||||
def CharId: Long = 0L
|
||||
def Faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
|
||||
def Position_=(pos: Vector3) = Position
|
||||
def Orientation_=(pos: Vector3) = Position
|
||||
def Velocity_=(pos: Option[Vector3]) = Velocity
|
||||
def Modifiers: ResistanceProfile
|
||||
}
|
||||
|
||||
object SourceEntry {
|
||||
final val None = new SourceEntry() {
|
||||
def Definition = null
|
||||
def Position = Vector3.Zero
|
||||
def Orientation = Vector3.Zero
|
||||
def Velocity = Some(Vector3.Zero)
|
||||
def Modifiers = null
|
||||
}
|
||||
|
||||
def apply(target: PlanetSideGameObject with FactionAffinity): SourceEntry = {
|
||||
target match {
|
||||
case obj: Player => PlayerSource(obj)
|
||||
case obj: Vehicle => VehicleSource(obj)
|
||||
case obj: Deployable => DeployableSource(obj)
|
||||
case _ => ObjectSource(target)
|
||||
}
|
||||
}
|
||||
|
||||
def NameFormat(name: String): String = {
|
||||
name
|
||||
.replace("_", " ")
|
||||
.split(" ")
|
||||
.map(_.capitalize)
|
||||
.mkString(" ")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.ballistics
|
||||
|
||||
import net.psforever.objects.Vehicle
|
||||
import net.psforever.objects.definition.VehicleDefinition
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfile
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
|
||||
final case class VehicleSource(
|
||||
obj_def: VehicleDefinition,
|
||||
faction: PlanetSideEmpire.Value,
|
||||
health: Int,
|
||||
shields: Int,
|
||||
position: Vector3,
|
||||
orientation: Vector3,
|
||||
velocity: Option[Vector3],
|
||||
occupants: List[SourceEntry],
|
||||
modifiers: ResistanceProfile
|
||||
) extends SourceEntry {
|
||||
override def Name = SourceEntry.NameFormat(obj_def.Name)
|
||||
override def Faction = faction
|
||||
def Definition: VehicleDefinition = obj_def
|
||||
def Health = health
|
||||
def Shields = shields
|
||||
def Position = position
|
||||
def Orientation = orientation
|
||||
def Velocity = velocity
|
||||
def Modifiers = modifiers
|
||||
}
|
||||
|
||||
object VehicleSource {
|
||||
def apply(obj: Vehicle): VehicleSource = {
|
||||
VehicleSource(
|
||||
obj.Definition,
|
||||
obj.Faction,
|
||||
obj.Health,
|
||||
obj.Shields,
|
||||
obj.Position,
|
||||
obj.Orientation,
|
||||
obj.Velocity,
|
||||
obj.Seats.values.map { seat =>
|
||||
seat.occupant match {
|
||||
case Some(p) => PlayerSource(p)
|
||||
case _ => SourceEntry.None
|
||||
}
|
||||
}.toList,
|
||||
obj.Definition.asInstanceOf[ResistanceProfile]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ object AvatarConverter {
|
|||
def MakeAppearanceData(obj: Player): Int => CharacterAppearanceData = {
|
||||
val alt_model_flag: Boolean = obj.isBackpack
|
||||
val aa: Int => CharacterAppearanceA = CharacterAppearanceA(
|
||||
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, obj.Voice),
|
||||
obj.avatar.basic,
|
||||
CommonFieldData(
|
||||
obj.Faction,
|
||||
bops = false,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package net.psforever.objects.definition.converter
|
|||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.serverobject.llu.CaptureFlag
|
||||
import net.psforever.objects.serverobject.structures.Building
|
||||
import net.psforever.objects.sourcing.PlayerSource
|
||||
import net.psforever.packet.game.objectcreate.{CaptureFlagData, PlacementData}
|
||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||
|
||||
|
|
@ -13,7 +14,7 @@ class CaptureFlagConverter extends ObjectCreateConverter[CaptureFlag]() {
|
|||
override def ConstructorData(obj : CaptureFlag) : Try[CaptureFlagData] = {
|
||||
val hackInfo = obj.Owner.asInstanceOf[Building].CaptureTerminal.get.HackedBy match {
|
||||
case Some(hackInfo) => hackInfo
|
||||
case _ => Hackable.HackInfo("", PlanetSideGUID(0), PlanetSideEmpire.NEUTRAL, Vector3.Zero, 0L, 0L)
|
||||
case _ => Hackable.HackInfo(PlayerSource("", PlanetSideEmpire.NEUTRAL, Vector3.Zero), PlanetSideGUID(0), 0L, 0L)
|
||||
}
|
||||
|
||||
val millisecondsRemaining = TimeUnit.MILLISECONDS.convert(math.max(0, hackInfo.hackStartTime + hackInfo.hackDuration - System.nanoTime), TimeUnit.NANOSECONDS)
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class CharacterSelectConverter extends AvatarConverter {
|
|||
*/
|
||||
private def MakeAppearanceData(obj: Player): Int => CharacterAppearanceData = {
|
||||
val aa: Int => CharacterAppearanceA = CharacterAppearanceA(
|
||||
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, CharacterVoice.Mute),
|
||||
obj.avatar.basic,
|
||||
CommonFieldData(
|
||||
obj.Faction,
|
||||
bops = false,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class CorpseConverter extends AvatarConverter {
|
|||
*/
|
||||
private def MakeAppearanceData(obj: Player): Int => CharacterAppearanceData = {
|
||||
val aa: Int => CharacterAppearanceA = CharacterAppearanceA(
|
||||
BasicCharacterData(obj.Name, obj.Faction, CharacterSex.Male, 0, CharacterVoice.Mute),
|
||||
obj.avatar.basic,
|
||||
CommonFieldData(
|
||||
obj.Faction,
|
||||
bops = false,
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
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.sourcing.VehicleSource
|
||||
import net.psforever.objects.vital.RepairFromArmorSiphon
|
||||
import net.psforever.objects.vital.etc.{ArmorSiphonModifiers, ArmorSiphonReason}
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
|
|
@ -76,7 +76,7 @@ object ArmorSiphonBehavior {
|
|||
if before < obj.MaxHealth =>
|
||||
val after = obj.Health += amount
|
||||
if(before < after) {
|
||||
obj.History(RepairFromArmorSiphon(asr.siphon.Definition, before - after))
|
||||
obj.LogActivity(RepairFromArmorSiphon(asr.siphon.Definition, VehicleSource(obj), before - after))
|
||||
val zone = obj.Zone
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zone.id,
|
||||
|
|
|
|||
|
|
@ -39,22 +39,38 @@ object EffectTarget {
|
|||
false
|
||||
}
|
||||
|
||||
/**
|
||||
* To repair at this silo, the vehicle:
|
||||
* can not be a flight vehicle,
|
||||
* must have some health already, but does not have all its health,
|
||||
* and can not have taken damage in the last five seconds.
|
||||
*/
|
||||
def RepairSilo(target: PlanetSideGameObject): Boolean =
|
||||
target match {
|
||||
case v: Vehicle =>
|
||||
!GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0 && v.Health < v.MaxHealth && !v.History.takeWhile(System.currentTimeMillis() - _.time <= 5000L).exists(_.isInstanceOf[DamagingActivity])
|
||||
case _ =>
|
||||
false
|
||||
case v: Vehicle => !GlobalDefinitions.isFlightVehicle(v.Definition) && CommonRepairConditions(v)
|
||||
case _ => false
|
||||
}
|
||||
|
||||
/**
|
||||
* To repair at this landing pad, the vehicle:
|
||||
* be a flight vehicle,
|
||||
* must have some health already, but does not have all its health,
|
||||
* and can not have taken damage in the last five seconds.
|
||||
*/
|
||||
def PadLanding(target: PlanetSideGameObject): Boolean =
|
||||
target match {
|
||||
case v: Vehicle =>
|
||||
GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0 && v.Health < v.MaxHealth && !v.History.takeWhile(System.currentTimeMillis() - _.time <= 5000L).exists(_.isInstanceOf[DamagingActivity])
|
||||
case _ =>
|
||||
false
|
||||
case v: Vehicle => GlobalDefinitions.isFlightVehicle(v.Definition) && CommonRepairConditions(v)
|
||||
case _ => false
|
||||
}
|
||||
|
||||
private def CommonRepairConditions(v: Vehicle): Boolean = {
|
||||
v.Health > 0 && v.Health < v.MaxHealth &&
|
||||
v.History.findLast { entry => entry.isInstanceOf[DamagingActivity] }.exists {
|
||||
case entry if System.currentTimeMillis() - entry.time < 5000L => true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
def Player(target: PlanetSideGameObject): Boolean =
|
||||
target match {
|
||||
case p: Player =>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package net.psforever.objects.geometry
|
||||
|
||||
import net.psforever.objects.ballistics.{PlayerSource, Projectile, SourceEntry}
|
||||
import net.psforever.objects.ballistics.Projectile
|
||||
import net.psforever.objects.geometry.d3._
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player}
|
||||
import net.psforever.types.{ExoSuitType, Vector3}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.serverobject
|
||||
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.{PlanetSideGameObject, Player}
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
|
||||
//temporary location for these messages
|
||||
|
|
@ -23,4 +23,15 @@ object CommonMessages {
|
|||
final case class Progress(delta: Float, completionAction: () => Unit, tickAction: Float => Boolean) {
|
||||
assert(delta > 0, s"progress activity change value must be positive number - $delta")
|
||||
}
|
||||
|
||||
/**
|
||||
* A request has been made to charge this entity's shields.
|
||||
* @see `FacilityBenefitShieldChargeRequestMessage`
|
||||
* @param amount the number of points to charge
|
||||
* @param motivator the element that caused the shield to charge;
|
||||
* allowed to be `None`;
|
||||
* most often, a `Building`;
|
||||
* if the vehicle instigated its own charge (battleframe robotics), specify that
|
||||
*/
|
||||
final case class ChargeShields(amount: Int, motivator: Option[PlanetSideGameObject])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ package net.psforever.objects.serverobject.damage
|
|||
import akka.actor.{Actor, Cancellable}
|
||||
import net.psforever.objects.ballistics._
|
||||
import net.psforever.objects.serverobject.aura.Aura
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.base._
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ trait DamageableAmenity extends DamageableEntity {
|
|||
override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = {
|
||||
super.DestructionAwareness(target, cause)
|
||||
DamageableAmenity.DestructionAwareness(target, cause)
|
||||
target.ClearHistory()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ trait DamageableEntity extends Damageable {
|
|||
val health = target.Health
|
||||
val damage = originalHealth - health
|
||||
if (WillAffectTarget(target, damage, cause)) {
|
||||
target.History(cause)
|
||||
target.LogActivity(cause)
|
||||
DamageLog(target, s"BEFORE=$originalHealth, AFTER=$health, CHANGE=$damage")
|
||||
HandleDamage(target, cause, damage)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
package net.psforever.objects.serverobject.damage
|
||||
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
import net.psforever.packet.game.DamageWithPositionMessage
|
||||
import net.psforever.services.Service
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ trait DamageableVehicle
|
|||
//bfrs undergo a shiver spell before exploding
|
||||
val obj = DamageableObject
|
||||
obj.Health = 0
|
||||
obj.History(cause)
|
||||
obj.LogActivity(cause)
|
||||
DestructionAwareness(obj, cause)
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ trait DamageableVehicle
|
|||
val damageToHealth = originalHealth - health
|
||||
val damageToShields = originalShields - shields
|
||||
if (WillAffectTarget(target, damageToHealth + damageToShields, cause)) {
|
||||
target.History(cause)
|
||||
target.LogActivity(cause)
|
||||
DamageLog(
|
||||
target,
|
||||
s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields"
|
||||
|
|
@ -130,7 +130,7 @@ trait DamageableVehicle
|
|||
|
||||
if (obj.MountedIn.nonEmpty) {
|
||||
//log historical event
|
||||
target.History(cause)
|
||||
target.LogActivity(cause)
|
||||
}
|
||||
//damage
|
||||
if (Damageable.CanDamageOrJammer(target, totalDamage, cause.interaction)) {
|
||||
|
|
@ -214,7 +214,6 @@ trait DamageableVehicle
|
|||
}
|
||||
//clean up
|
||||
target.Actor ! Vehicle.Deconstruct(Some(1 minute))
|
||||
target.ClearHistory()
|
||||
DamageableWeaponTurret.DestructionAwareness(obj, cause)
|
||||
case _ => ;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ trait DamageableWeaponTurret
|
|||
}
|
||||
|
||||
//log historical event
|
||||
target.History(cause)
|
||||
target.LogActivity(cause)
|
||||
//damage
|
||||
if (Damageable.CanDamageOrJammer(target, damageToHealth, cause.interaction)) {
|
||||
//jammering
|
||||
|
|
|
|||
|
|
@ -30,10 +30,10 @@ class GeneratorControl(gen: Generator)
|
|||
with DamageableEntity
|
||||
with RepairableEntity
|
||||
with AmenityAutoRepair {
|
||||
def FactionObject = gen
|
||||
def DamageableObject = gen
|
||||
def RepairableObject = gen
|
||||
def AutoRepairObject = gen
|
||||
def FactionObject: Generator = gen
|
||||
def DamageableObject: Generator = gen
|
||||
def RepairableObject: Generator = gen
|
||||
def AutoRepairObject: Generator = gen
|
||||
/** flagged to explode after some time */
|
||||
var imminentExplosion: Boolean = false
|
||||
/** explode when this timer completes */
|
||||
|
|
@ -123,9 +123,9 @@ class GeneratorControl(gen: Generator)
|
|||
imminentExplosion = false
|
||||
//hate on everything nearby
|
||||
Zone.serverSideDamage(gen.Zone, gen, Zone.explosionDamage(gen.LastDamage), explosionFunc)
|
||||
gen.ClearHistory()
|
||||
|
||||
case GeneratorControl.Restored() =>
|
||||
gen.ClearHistory()
|
||||
GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Online))
|
||||
|
||||
case _ => ;
|
||||
|
|
@ -153,7 +153,6 @@ class GeneratorControl(gen: Generator)
|
|||
imminentExplosion = false
|
||||
gen.Condition = PlanetSideGeneratorState.Destroyed
|
||||
GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Destroyed))
|
||||
gen.ClearHistory()
|
||||
|
||||
case _ =>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.hackable
|
|||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.hackable.Hackable.HackInfo
|
||||
import net.psforever.objects.sourcing.PlayerSource
|
||||
import net.psforever.packet.game.TriggeredSound
|
||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||
|
||||
|
|
@ -24,14 +25,14 @@ trait Hackable {
|
|||
def HackedBy_=(agent: Option[Player]): Option[HackInfo] = {
|
||||
(hackedBy, agent) match {
|
||||
case (None, Some(actor)) =>
|
||||
hackedBy = Some(HackInfo(actor.Name, actor.GUID, actor.Faction, actor.Position, System.nanoTime, 0L))
|
||||
hackedBy = Some(HackInfo(PlayerSource(actor), actor.GUID, System.nanoTime, 0L))
|
||||
case (Some(info), Some(actor)) =>
|
||||
if (actor.Faction == this.Faction) {
|
||||
//hack cleared
|
||||
hackedBy = None
|
||||
} else if (actor.Faction != info.hackerFaction) {
|
||||
//override the hack state with a new hack state if the new user has different faction affiliation
|
||||
hackedBy = Some(HackInfo(actor.Name, actor.GUID, actor.Faction, actor.Position, System.nanoTime, 0L))
|
||||
hackedBy = Some(HackInfo(PlayerSource(actor), actor.GUID, System.nanoTime, 0L))
|
||||
}
|
||||
case (_, None) =>
|
||||
hackedBy = None
|
||||
|
|
@ -67,30 +68,19 @@ trait Hackable {
|
|||
hackDuration = arr
|
||||
arr
|
||||
}
|
||||
|
||||
// private var hackable : Option[Boolean] = None
|
||||
// def Hackable : Boolean = hackable.getOrElse(Definition.Hackable)
|
||||
//
|
||||
// def Hackable_=(state : Boolean) : Boolean = Hackable_=(Some(state))
|
||||
//
|
||||
// def Hackable_=(state : Option[Boolean]) : Boolean = {
|
||||
// hackable = state
|
||||
// Hackable
|
||||
// }
|
||||
//
|
||||
// def Definition : HackableDefinition
|
||||
}
|
||||
|
||||
object Hackable {
|
||||
final case class HackInfo(
|
||||
hackerName: String,
|
||||
hackerGUID: PlanetSideGUID,
|
||||
hackerFaction: PlanetSideEmpire.Value,
|
||||
hackerPos: Vector3,
|
||||
hackStartTime: Long,
|
||||
hackDuration: Long
|
||||
) {
|
||||
def Duration(time: Long): HackInfo =
|
||||
HackInfo(hackerName, hackerGUID, hackerFaction, hackerPos, hackStartTime, time)
|
||||
player: PlayerSource,
|
||||
hackerGUID: PlanetSideGUID,
|
||||
hackStartTime: Long,
|
||||
hackDuration: Long
|
||||
) {
|
||||
def hackerName: String = player.Name
|
||||
def hackerFaction: PlanetSideEmpire.Value = player.Faction
|
||||
def hackerPos: Vector3 = player.Position
|
||||
|
||||
def Duration(time: Long): HackInfo = HackInfo(player, hackerGUID, hackStartTime, time)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package net.psforever.objects.serverobject.llu
|
||||
|
||||
import net.psforever.objects.serverobject.structures.{Amenity, AmenityOwner, Building}
|
||||
|
|
@ -5,9 +6,28 @@ import net.psforever.objects.{GlobalDefinitions, Player}
|
|||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
|
||||
/**
|
||||
* This object represents the LLU that gets spawned at a LLU socket when a LLU control console is hacked
|
||||
*/
|
||||
class CaptureFlag(tDef: CaptureFlagDefinition) extends Amenity {
|
||||
* Represent a special entity that is carried by the player in certain circumstances.
|
||||
* The entity is not a piece of `Equipment` so it does not go into the holsters,
|
||||
* doe not into the player's inventory,
|
||||
* and is not carried in or manipulated by the player's hands.
|
||||
* The different game elements it simulates are:
|
||||
* a facility's lattice logic unit (LLU),
|
||||
* the cavern modules,
|
||||
* and the rabbit ball (special game mode).<br>
|
||||
* <br>
|
||||
* For the lattice logic unit, when a facility is set to generate an LLU upon hack,
|
||||
* and an adjacent facility on the lattice provides an accommodating faction connection,
|
||||
* the unit gets spawned at the LLU socket within the hacked facility.
|
||||
* The LLU socket actually doesn't do anything but keep track of the spawned flag and provide a location.
|
||||
* It associates with the faction of the hacker and, carried by other players of the same faction only,
|
||||
* must be brought to the control console of a designated facility that is owned by the faction of the hacking empire.
|
||||
* If the hack is cancelled through a resecure, the LLU despawns.
|
||||
* If the facility is counter-hacked, the active LLU despawns and a new LLU is spawned in the socket.
|
||||
* Other empires can not interact with the LLU while it is dropped on the ground and
|
||||
* vehicles will be warned and then deconstructed if they linges too long near a dropped LLU.
|
||||
* The LLU can not be submerged in water or it will despawn and the hack will cancel.
|
||||
*/
|
||||
class CaptureFlag(private val tDef: CaptureFlagDefinition) extends Amenity {
|
||||
def Definition : CaptureFlagDefinition = tDef
|
||||
|
||||
private var target: Building = Building.NoBuilding
|
||||
|
|
@ -15,28 +35,35 @@ class CaptureFlag(tDef: CaptureFlagDefinition) extends Amenity {
|
|||
private var carrier: Option[Player] = None
|
||||
|
||||
def Target: Building = target
|
||||
def Target_=(new_target: Building): Building = {
|
||||
target = new_target
|
||||
def Target_=(newTarget: Building): Building = {
|
||||
target = newTarget
|
||||
target
|
||||
}
|
||||
|
||||
// Since a LLU belongs to a base, but needs to be picked up by the enemy faction we need to be able to override the faction that owns the LLU to the hacker faction
|
||||
/**
|
||||
* Since a LLU belongs to a base,
|
||||
* but needs to be picked up by the enemy faction,
|
||||
* override the faction that owns the LLU to display the hacker faction.
|
||||
*/
|
||||
override def Faction: PlanetSideEmpire.Value = faction
|
||||
override def Faction_=(new_faction: PlanetSideEmpire.Value): PlanetSideEmpire.Value = {
|
||||
faction = new_faction
|
||||
override def Faction_=(newFaction: PlanetSideEmpire.Value): PlanetSideEmpire.Value = {
|
||||
faction = newFaction
|
||||
faction
|
||||
}
|
||||
|
||||
// When the flag is carried by a player, the position returned should be that of the carrier not the flag
|
||||
/**
|
||||
* When the flag is carried by a player, the position returned should be that of the carrier not the flag.
|
||||
* @return the position of the carrier, if there is a player carrying the flag, or the flag itself
|
||||
*/
|
||||
override def Position: Vector3 = if (Carrier.nonEmpty) {
|
||||
carrier.get.Position
|
||||
} else {
|
||||
Entity.Position
|
||||
super.Position
|
||||
}
|
||||
|
||||
def Carrier: Option[Player] = carrier
|
||||
def Carrier_=(new_carrier: Option[Player]) : Option[Player] = {
|
||||
carrier = new_carrier
|
||||
def Carrier_=(newCarrier: Option[Player]) : Option[Player] = {
|
||||
carrier = newCarrier
|
||||
carrier
|
||||
}
|
||||
}
|
||||
|
|
@ -53,7 +80,6 @@ object CaptureFlag {
|
|||
obj.Target = target
|
||||
obj.Owner = owner
|
||||
obj.Faction = faction
|
||||
|
||||
obj
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import net.psforever.types.Vector3
|
|||
*/
|
||||
class CaptureFlagSocket(tDef: CaptureFlagSocketDefinition)
|
||||
extends Amenity {
|
||||
private var lastFlag: Option[CaptureFlag] = None
|
||||
private var spawnedCaptureFlag: Option[CaptureFlag] = None
|
||||
|
||||
def captureFlag: Option[CaptureFlag] = spawnedCaptureFlag
|
||||
|
|
@ -20,10 +21,19 @@ class CaptureFlagSocket(tDef: CaptureFlagSocketDefinition)
|
|||
def captureFlag_=(flag: CaptureFlag): Option[CaptureFlag] = captureFlag_=(Some(flag))
|
||||
|
||||
def captureFlag_=(flag: Option[CaptureFlag]): Option[CaptureFlag] = {
|
||||
lastFlag = flag.orElse(lastFlag)
|
||||
spawnedCaptureFlag = flag
|
||||
captureFlag
|
||||
}
|
||||
|
||||
def previousFlag: Option[CaptureFlag] = lastFlag
|
||||
|
||||
def clearOldFlagData(): Unit = {
|
||||
if (spawnedCaptureFlag.isEmpty) {
|
||||
lastFlag = None
|
||||
}
|
||||
}
|
||||
|
||||
def Definition : CaptureFlagSocketDefinition = tDef
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ package net.psforever.objects.serverobject.pad.process
|
|||
|
||||
import akka.actor.Props
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.ballistics.SourceEntry
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.etc.{ExplodingEntityReason, VehicleSpawnReason}
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
import net.psforever.objects.vital.prop.DamageProperties
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.zones.Zone
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
package net.psforever.objects.serverobject.painbox
|
||||
|
||||
import akka.actor.Cancellable
|
||||
import net.psforever.objects.ballistics.SourceEntry
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl}
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.etc.PainboxReason
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
import net.psforever.objects.{Default, GlobalDefinitions, Player}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
|
@ -58,7 +59,7 @@ class PainboxControl(painbox: Painbox) extends PoweredAmenityControl {
|
|||
}
|
||||
|
||||
var commonBehavior: Receive = {
|
||||
case "startup" =>
|
||||
case Service.Startup() =>
|
||||
if (!disabled && domain.midpoint == Vector3.Zero) {
|
||||
initialStartup()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import net.psforever.actors.zone.BuildingActor
|
|||
import net.psforever.objects.{Default, NtuContainer, NtuStorageBehavior}
|
||||
import net.psforever.objects.serverobject.damage.Damageable
|
||||
import net.psforever.objects.serverobject.structures.{Amenity, AutoRepairStats, Building}
|
||||
import net.psforever.objects.vital.RepairFromAmenityAutoRepair
|
||||
import net.psforever.util.Config
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
|
@ -96,6 +97,7 @@ trait AmenityAutoRepair
|
|||
wholeRepairAmount + wholeOverflow
|
||||
}
|
||||
PerformRepairs(obj, finalRepairAmount)
|
||||
obj.LogActivity(RepairFromAmenityAutoRepair(finalRepairAmount))
|
||||
val currentTime = System.currentTimeMillis()
|
||||
val taskTime = currentTime - autoRepairQueueTask.getOrElse(currentTime)
|
||||
autoRepairQueueTask = Some(0L)
|
||||
|
|
@ -148,7 +150,8 @@ trait AmenityAutoRepair
|
|||
* or if the current process has stalled.
|
||||
*/
|
||||
private def startAutoRepairIfStopped(): Unit = {
|
||||
if(autoRepairQueueTask.isEmpty || stallDetection(stallTime = 15000L)) {
|
||||
val stallTime: Long = 15000L
|
||||
if(autoRepairQueueTask.isEmpty || stallDetection(stallTime)) {
|
||||
trySetupAutoRepairInitial()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package net.psforever.objects.serverobject.repair
|
|||
|
||||
import net.psforever.objects.Tool
|
||||
import net.psforever.objects.serverobject.structures.Amenity
|
||||
import net.psforever.objects.sourcing.{SourceEntry, SourceWithHealthEntry}
|
||||
import net.psforever.objects.vital.{DamagingActivity, RepairFromEquipment, SpawningActivity}
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
|
||||
/**
|
||||
|
|
@ -23,7 +25,7 @@ trait RepairableAmenity extends RepairableEntity {
|
|||
object RepairableAmenity {
|
||||
|
||||
/**
|
||||
* A resotred `Amenity` target dispatches two messages to chance its model and operational states.
|
||||
* A restored `Amenity` target dispatches two messages to chance its model and operational states.
|
||||
* These `PlanetSideAttributeMessage` attributes are the same as reported during zone load client configuration.
|
||||
* @see `AvatarAction.PlanetsideAttributeToAll`
|
||||
* @see `AvatarServiceMessage`
|
||||
|
|
@ -37,5 +39,27 @@ object RepairableAmenity {
|
|||
val targetGUID = target.GUID
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 50, 0))
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 51, 0))
|
||||
RestorationOfHistory(target)
|
||||
}
|
||||
|
||||
/**
|
||||
* The vitality change history will be forgotten as this entity, once destroyed, has been rebuilt.
|
||||
* For the purpose of inheritance of experience due to interaction,
|
||||
* the users who made an effort to repair the entity in its resurgence.
|
||||
* @param target na
|
||||
*/
|
||||
def RestorationOfHistory(target: Repairable.Target): Unit = {
|
||||
val list = target.ClearHistory()
|
||||
val effort = list.slice(
|
||||
list.lastIndexWhere {
|
||||
case dam: DamagingActivity => dam.data.targetAfter.asInstanceOf[SourceWithHealthEntry].health == 0
|
||||
case _ => false
|
||||
},
|
||||
list.size
|
||||
).collect {
|
||||
case entry: RepairFromEquipment => Some(entry.user)
|
||||
case _ => None
|
||||
}.flatten.distinctBy(_.Name)
|
||||
target.LogActivity(SpawningActivity(SourceEntry(target), target.Zone.Number, effort.headOption))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
//Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.repair
|
||||
|
||||
import net.psforever.objects.sourcing.PlayerSource
|
||||
import net.psforever.objects.vital.RepairFromEquipment
|
||||
import net.psforever.objects.{Player, Tool}
|
||||
import net.psforever.packet.game.{InventoryStateMessage, RepairMessage}
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
|
|
@ -92,6 +94,13 @@ trait RepairableEntity extends Repairable {
|
|||
InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine.toLong)
|
||||
)
|
||||
)
|
||||
target.LogActivity(
|
||||
RepairFromEquipment(
|
||||
PlayerSource(player),
|
||||
item.Definition,
|
||||
repairValue
|
||||
)
|
||||
)
|
||||
PerformRepairs(target, repairValue)
|
||||
} else {
|
||||
originalHealth
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ trait RepairableWeaponTurret extends RepairableEntity {
|
|||
|
||||
override def Restoration(target: Repairable.Target): Unit = {
|
||||
super.Restoration(target)
|
||||
RepairableAmenity.RestorationOfHistory(target)
|
||||
RepairableWeaponTurret.Restoration(RepairableObject)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.serverobject.structures
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import akka.actor.ActorContext
|
||||
import net.psforever.actors.zone.BuildingActor
|
||||
import net.psforever.objects.{GlobalDefinitions, NtuContainer, Player}
|
||||
|
|
@ -20,32 +18,37 @@ import akka.actor.typed.scaladsl.adapter._
|
|||
import net.psforever.objects.serverobject.llu.{CaptureFlag, CaptureFlagSocket}
|
||||
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
|
||||
|
||||
import scala.collection.mutable
|
||||
import java.util.concurrent.TimeUnit
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class Building(
|
||||
private val name: String,
|
||||
private val building_guid: Int,
|
||||
private val map_id: Int,
|
||||
private val zone: Zone,
|
||||
private val buildingType: StructureType,
|
||||
private val buildingDefinition: BuildingDefinition
|
||||
) extends AmenityOwner
|
||||
private val name: String,
|
||||
private val building_guid: Int,
|
||||
private val map_id: Int,
|
||||
private val zone: Zone,
|
||||
private val buildingType: StructureType,
|
||||
private val buildingDefinition: BuildingDefinition
|
||||
) extends AmenityOwner
|
||||
with BlockMapEntity {
|
||||
|
||||
private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
|
||||
private var playersInSOI: List[Player] = List.empty
|
||||
private val capitols = List("Thoth", "Voltan", "Neit", "Anguta", "Eisa", "Verica")
|
||||
private var forceDomeActive: Boolean = false
|
||||
private var participationFunc: Building.ParticipationLogic = Building.NoParticipation
|
||||
super.Zone_=(zone)
|
||||
super.GUID_=(PlanetSideGUID(building_guid)) //set
|
||||
Invalidate() //unset; guid can be used during setup, but does not stop being registered properly later
|
||||
|
||||
override def toString = name
|
||||
override def toString: String = name
|
||||
|
||||
def Name: String = name
|
||||
|
||||
/**
|
||||
* The map_id is the identifier number used in BuildingInfoUpdateMessage. This is the index that the building appears in the MPO file starting from index 1
|
||||
* The GUID is the identifier number used in SetEmpireMessage / Facility hacking / PlanetSideAttributeMessage.
|
||||
*/
|
||||
* The map_id is the identifier number used in BuildingInfoUpdateMessage. This is the index that the building appears in the MPO file starting from index 1
|
||||
* The GUID is the identifier number used in SetEmpireMessage / Facility hacking / PlanetSideAttributeMessage.
|
||||
*/
|
||||
def MapId: Int = map_id
|
||||
|
||||
def IsCapitol: Boolean = capitols.contains(name)
|
||||
|
|
@ -82,10 +85,13 @@ class Building(
|
|||
box.Actor ! Painbox.Stop()
|
||||
}
|
||||
}
|
||||
participationFunc.Players(building = this, list)
|
||||
playersInSOI = list
|
||||
playersInSOI
|
||||
}
|
||||
|
||||
def PlayerContribution: Map[Player, Long] = participationFunc.Contribution()
|
||||
|
||||
// Get all lattice neighbours
|
||||
def AllNeighbours: Option[Set[Building]] = {
|
||||
zone.Lattice find this match {
|
||||
|
|
@ -139,7 +145,11 @@ class Building(
|
|||
case _ => false
|
||||
}
|
||||
|
||||
def GetFlagSocket: Option[CaptureFlagSocket] = this.Amenities.find(_.Definition == GlobalDefinitions.llm_socket).asInstanceOf[Option[CaptureFlagSocket]]
|
||||
def GetFlagSocket: Option[CaptureFlagSocket] = {
|
||||
this.Amenities
|
||||
.find(_.Definition == GlobalDefinitions.llm_socket)
|
||||
.map(_.asInstanceOf[CaptureFlagSocket])
|
||||
}
|
||||
def GetFlag: Option[CaptureFlag] = {
|
||||
GetFlagSocket match {
|
||||
case Some(socket) => socket.captureFlag
|
||||
|
|
@ -175,10 +185,10 @@ class Building(
|
|||
val (hacking, hackingFaction, hackTime): (Boolean, PlanetSideEmpire.Value, Long) = CaptureTerminal match {
|
||||
case Some(obj: CaptureTerminal with Hackable) =>
|
||||
obj.HackedBy match {
|
||||
case Some(Hackable.HackInfo(_, _, hfaction, _, start, length)) =>
|
||||
case Some(Hackable.HackInfo(p, _, start, length)) =>
|
||||
val hack_time_remaining_ms =
|
||||
TimeUnit.MILLISECONDS.convert(math.max(0, start + length - System.nanoTime), TimeUnit.NANOSECONDS)
|
||||
(true, hfaction, hack_time_remaining_ms)
|
||||
(true, p.Faction, hack_time_remaining_ms)
|
||||
case _ =>
|
||||
(false, PlanetSideEmpire.NEUTRAL, 0L)
|
||||
}
|
||||
|
|
@ -199,8 +209,8 @@ class Building(
|
|||
}
|
||||
val cavernBenefit: Set[CavernBenefit] = if (
|
||||
generatorState != PlanetSideGeneratorState.Destroyed &&
|
||||
faction != PlanetSideEmpire.NEUTRAL &&
|
||||
connectedCavern().nonEmpty
|
||||
faction != PlanetSideEmpire.NEUTRAL &&
|
||||
connectedCavern().nonEmpty
|
||||
) {
|
||||
Set(CavernBenefit.VehicleModule, CavernBenefit.EquipmentModule)
|
||||
} else {
|
||||
|
|
@ -233,22 +243,28 @@ class Building(
|
|||
}
|
||||
|
||||
def hasLatticeBenefit(wantedBenefit: LatticeBenefit): Boolean = {
|
||||
val genState = Generator match {
|
||||
case Some(obj) => obj.Condition != PlanetSideGeneratorState.Destroyed
|
||||
val baseDownState = (NtuSource match {
|
||||
case Some(ntu) => ntu.NtuCapacitor < 1f
|
||||
case _ => false
|
||||
}
|
||||
if (genState || Faction == PlanetSideEmpire.NEUTRAL) {
|
||||
}) ||
|
||||
(Generator match {
|
||||
case Some(obj) => obj.Condition == PlanetSideGeneratorState.Destroyed
|
||||
case _ => false
|
||||
}) ||
|
||||
Faction == PlanetSideEmpire.NEUTRAL
|
||||
if (baseDownState) {
|
||||
false
|
||||
} else {
|
||||
// Check this Building is on the lattice first
|
||||
zone.Lattice find this match {
|
||||
case Some(_) =>
|
||||
val faction = Faction
|
||||
val subGraph = Zone.Lattice filter (
|
||||
(b : Building) =>
|
||||
b.Faction == this.Faction &&
|
||||
!b.CaptureTerminalIsHacked &&
|
||||
b.NtuLevel > 0 &&
|
||||
(b.Generator.isEmpty || b.Generator.get.Condition != PlanetSideGeneratorState.Destroyed)
|
||||
(b: Building) =>
|
||||
b.Faction == faction &&
|
||||
!b.CaptureTerminalIsHacked &&
|
||||
b.NtuLevel > 0 &&
|
||||
(b.Generator.isEmpty || b.Generator.get.Condition != PlanetSideGeneratorState.Destroyed)
|
||||
)
|
||||
findLatticeBenefit(wantedBenefit, subGraph)
|
||||
case None =>
|
||||
|
|
@ -282,7 +298,10 @@ class Building(
|
|||
case Some(obj) => obj.Condition
|
||||
case _ => PlanetSideGeneratorState.Normal
|
||||
}
|
||||
if (genState == PlanetSideGeneratorState.Destroyed || Faction == PlanetSideEmpire.NEUTRAL) {
|
||||
if (genState == PlanetSideGeneratorState.Destroyed ||
|
||||
Faction == PlanetSideEmpire.NEUTRAL ||
|
||||
CaptureTerminalIsHacked
|
||||
) {
|
||||
Set(LatticeBenefit.None)
|
||||
} else {
|
||||
friendlyFunctionalNeighborhood().map { _.Definition.LatticeLinkBenefit }
|
||||
|
|
@ -296,10 +315,10 @@ class Building(
|
|||
while (currBuilding.nonEmpty) {
|
||||
val building = currBuilding.head
|
||||
val neighborsToAdd = if (!visitedNeighbors.contains(building.MapId)
|
||||
&& (building match { case _ : WarpGate => false; case _ => true })
|
||||
&& !building.CaptureTerminalIsHacked
|
||||
&& building.NtuLevel > 0
|
||||
&& (building.Generator match {
|
||||
&& (building match { case _ : WarpGate => false; case _ => true })
|
||||
&& !building.CaptureTerminalIsHacked
|
||||
&& building.NtuLevel > 0
|
||||
&& (building.Generator match {
|
||||
case Some(o) => o.Condition != PlanetSideGeneratorState.Destroyed
|
||||
case _ => true
|
||||
})
|
||||
|
|
@ -321,14 +340,14 @@ class Building(
|
|||
}
|
||||
|
||||
/**
|
||||
* Starting from an overworld zone facility,
|
||||
* find a lattice connected cavern facility that is the same faction as this starting building.
|
||||
* Except for the necessary examination of the major facility on the other side of a warp gate pair,
|
||||
* do not let the search escape the current zone into another.
|
||||
* If we start in a cavern zone, do not continue a fruitless search;
|
||||
* just fail.
|
||||
* @return the discovered faction-aligned cavern facility
|
||||
*/
|
||||
* Starting from an overworld zone facility,
|
||||
* find a lattice connected cavern facility that is the same faction as this starting building.
|
||||
* Except for the necessary examination of the major facility on the other side of a warp gate pair,
|
||||
* do not let the search escape the current zone into another.
|
||||
* If we start in a cavern zone, do not continue a fruitless search;
|
||||
* just fail.
|
||||
* @return the discovered faction-aligned cavern facility
|
||||
*/
|
||||
def connectedCavern(): Option[Building] = net.psforever.objects.zones.Zone.findConnectedCavernFacility(building = this)
|
||||
|
||||
def BuildingType: StructureType = buildingType
|
||||
|
|
@ -339,10 +358,46 @@ class Building(
|
|||
|
||||
override def Continent_=(zone: String): String = Continent //building never leaves zone after being set in constructor
|
||||
|
||||
override def Amenities_=(obj: Amenity): List[Amenity] = {
|
||||
obj match {
|
||||
case _: CaptureTerminal => participationFunc = Building.FacilityHackParticipation
|
||||
case _ => ;
|
||||
}
|
||||
super.Amenities_=(obj)
|
||||
}
|
||||
|
||||
def Definition: BuildingDefinition = buildingDefinition
|
||||
}
|
||||
|
||||
object Building {
|
||||
trait ParticipationLogic {
|
||||
def Players(building: Building, list: List[Player]): Unit = { }
|
||||
def Contribution(): Map[Player, Long]
|
||||
}
|
||||
|
||||
final case object NoParticipation extends ParticipationLogic {
|
||||
def Contribution(): Map[Player, Long] = Map.empty[Player, Long]
|
||||
}
|
||||
|
||||
final case object FacilityHackParticipation extends ParticipationLogic {
|
||||
private var playerContribution: mutable.HashMap[Player, Long] = mutable.HashMap[Player, Long]()
|
||||
|
||||
override def Players(building: Building, list: List[Player]): Unit = {
|
||||
if (list.isEmpty) {
|
||||
playerContribution.clear()
|
||||
} else {
|
||||
val hackTime = (building.CaptureTerminal.get.Definition.FacilityHackTime + 10.minutes).toMillis
|
||||
val curr = System.currentTimeMillis()
|
||||
val list2 = list.map { p => (p, curr) }
|
||||
playerContribution = playerContribution.filterNot { case (p, t) =>
|
||||
list2.contains(p) || curr - t > hackTime
|
||||
} ++ list2
|
||||
}
|
||||
}
|
||||
|
||||
def Contribution(): Map[Player, Long] = playerContribution.toMap
|
||||
}
|
||||
|
||||
final val NoBuilding: Building =
|
||||
new Building(name = "", 0, map_id = 0, Zone.Nowhere, StructureType.Platform, GlobalDefinitions.building) {
|
||||
override def Faction_=(faction: PlanetSideEmpire.Value): PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
|
||||
|
|
@ -355,11 +410,11 @@ object Building {
|
|||
}
|
||||
|
||||
def Structure(
|
||||
buildingType: StructureType,
|
||||
location: Vector3,
|
||||
rotation: Vector3,
|
||||
definition: BuildingDefinition
|
||||
)(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): Building = {
|
||||
buildingType: StructureType,
|
||||
location: Vector3,
|
||||
rotation: Vector3,
|
||||
definition: BuildingDefinition
|
||||
)(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): Building = {
|
||||
val obj = new Building(name, guid, map_id, zone, buildingType, definition)
|
||||
obj.Position = location
|
||||
obj.Orientation = rotation
|
||||
|
|
@ -368,9 +423,9 @@ object Building {
|
|||
}
|
||||
|
||||
def Structure(
|
||||
buildingType: StructureType,
|
||||
location: Vector3
|
||||
)(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): Building = {
|
||||
buildingType: StructureType,
|
||||
location: Vector3
|
||||
)(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): Building = {
|
||||
val obj = new Building(name, guid, map_id, zone, buildingType, GlobalDefinitions.building)
|
||||
obj.Position = location
|
||||
obj.Actor = context.spawn(BuildingActor(zone, obj), s"$map_id-$buildingType-building").toClassic
|
||||
|
|
@ -378,18 +433,18 @@ object Building {
|
|||
}
|
||||
|
||||
def Structure(
|
||||
buildingType: StructureType
|
||||
)(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): Building = {
|
||||
buildingType: StructureType
|
||||
)(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): Building = {
|
||||
val obj = new Building(name, guid, map_id, zone, buildingType, GlobalDefinitions.building)
|
||||
obj.Actor = context.spawn(BuildingActor(zone, obj), s"$map_id-$buildingType-building").toClassic
|
||||
obj
|
||||
}
|
||||
|
||||
def Structure(
|
||||
buildingType: StructureType,
|
||||
buildingDefinition: BuildingDefinition,
|
||||
location: Vector3
|
||||
)(name: String, guid: Int, id: Int, zone: Zone, context: ActorContext): Building = {
|
||||
buildingType: StructureType,
|
||||
buildingDefinition: BuildingDefinition,
|
||||
location: Vector3
|
||||
)(name: String, guid: Int, id: Int, zone: Zone, context: ActorContext): Building = {
|
||||
val obj = new Building(name, guid, id, zone, buildingType, buildingDefinition)
|
||||
obj.Position = location
|
||||
obj.Actor = context.spawn(BuildingActor(zone, obj), s"$id-$buildingType-building").toClassic
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.terminals
|
||||
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
object CaptureTerminals {
|
||||
private val log = org.log4s.getLogger("CaptureTerminals")
|
||||
|
||||
/**
|
||||
* The process of hacking an object is completed.
|
||||
* Pass the message onto the hackable object and onto the local events system.
|
||||
* @param target the `Hackable` object that has been hacked
|
||||
* @param unk na;
|
||||
* used by `HackMessage` as `unk5`
|
||||
* @see `HackMessage`
|
||||
*/
|
||||
//TODO add params here depending on which params in HackMessage are important
|
||||
def FinishHackingCaptureConsole(target: CaptureTerminal, hackingPlayer: Player, unk: Long)(): Unit = {
|
||||
import akka.pattern.ask
|
||||
import scala.concurrent.duration._
|
||||
log.info(s"${hackingPlayer.toString} hacked a ${target.Definition.Name}")
|
||||
// Wait for the target actor to set the HackedBy property
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
ask(target.Actor, CommonMessages.Hack(hackingPlayer, target))(1 second).mapTo[Boolean].onComplete {
|
||||
case Success(_) =>
|
||||
target.Zone.LocalEvents ! LocalServiceMessage(
|
||||
target.Zone.id,
|
||||
LocalAction.TriggerSound(hackingPlayer.GUID, target.HackSound, hackingPlayer.Position, 30, 0.49803925f)
|
||||
)
|
||||
val isResecured = hackingPlayer.Faction == target.Faction
|
||||
if (isResecured) {
|
||||
// Resecure the CC
|
||||
target.Zone.LocalEvents ! LocalServiceMessage(
|
||||
target.Zone.id,
|
||||
LocalAction.ResecureCaptureTerminal(
|
||||
target
|
||||
)
|
||||
)
|
||||
}
|
||||
else {
|
||||
// Start the CC hack timer
|
||||
target.Zone.LocalEvents ! LocalServiceMessage(
|
||||
target.Zone.id,
|
||||
LocalAction.StartCaptureTerminalHack(
|
||||
target
|
||||
)
|
||||
)
|
||||
}
|
||||
case Failure(_) => log.warn(s"Hack message failed on target guid: ${target.GUID}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,13 @@
|
|||
package net.psforever.objects.serverobject.terminals
|
||||
|
||||
import akka.actor.{ActorRef, Cancellable}
|
||||
import net.psforever.objects.sourcing.AmenitySource
|
||||
import org.log4s.Logger
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.duration._
|
||||
//
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.ballistics.{PlayerSource, VehicleSource}
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
|
||||
|
|
@ -12,39 +17,37 @@ import net.psforever.objects.serverobject.damage.DamageableAmenity
|
|||
import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior}
|
||||
import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableAmenity}
|
||||
import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl}
|
||||
import net.psforever.objects.vital.{HealFromTerm, RepairFromTerm}
|
||||
import net.psforever.objects.vital.{HealFromTerm, RepairFromTerm, Vitality}
|
||||
import net.psforever.objects.zones.ZoneAware
|
||||
import net.psforever.packet.game.InventoryStateMessage
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* An `Actor` that handles messages being dispatched to a specific `ProximityTerminal`.
|
||||
* Although this "terminal" itself does not accept the same messages as a normal `Terminal` object,
|
||||
* it returns the same type of messages - wrapped in a `TerminalMessage` - to the `sender`.
|
||||
* @param term the proximity unit (terminal)
|
||||
*/
|
||||
* An `Actor` that handles messages being dispatched to a specific `ProximityTerminal`.
|
||||
* Although this "terminal" itself does not accept the same messages as a normal `Terminal` object,
|
||||
* it returns the same type of messages - wrapped in a `TerminalMessage` - to the `sender`.
|
||||
* @param term the proximity unit (terminal)
|
||||
*/
|
||||
class ProximityTerminalControl(term: Terminal with ProximityUnit)
|
||||
extends PoweredAmenityControl
|
||||
extends PoweredAmenityControl
|
||||
with FactionAffinityBehavior.Check
|
||||
with HackableBehavior.GenericHackable
|
||||
with DamageableAmenity
|
||||
with RepairableAmenity
|
||||
with AmenityAutoRepair {
|
||||
def FactionObject = term
|
||||
def HackableObject = term
|
||||
def TerminalObject = term
|
||||
def DamageableObject = term
|
||||
def RepairableObject = term
|
||||
def AutoRepairObject = term
|
||||
def FactionObject: Terminal with ProximityUnit = term
|
||||
def HackableObject: Terminal with ProximityUnit = term
|
||||
def TerminalObject: Terminal with ProximityUnit = term
|
||||
def DamageableObject: Terminal with ProximityUnit = term
|
||||
def RepairableObject: Terminal with ProximityUnit = term
|
||||
def AutoRepairObject: Terminal with ProximityUnit = term
|
||||
|
||||
var terminalAction: Cancellable = Default.Cancellable
|
||||
val callbacks: mutable.ListBuffer[ActorRef] = new mutable.ListBuffer[ActorRef]()
|
||||
val log = org.log4s.getLogger
|
||||
val log: Logger = org.log4s.getLogger
|
||||
|
||||
val commonBehavior: Receive = checkBehavior
|
||||
.orElse(takesDamage)
|
||||
|
|
@ -63,7 +66,7 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit)
|
|||
.orElse(hackableBehavior)
|
||||
.orElse {
|
||||
case CommonMessages.Use(player, Some(item: SimpleItem))
|
||||
if item.Definition == GlobalDefinitions.remote_electronics_kit =>
|
||||
if item.Definition == GlobalDefinitions.remote_electronics_kit =>
|
||||
//TODO setup certifications check
|
||||
term.Owner match {
|
||||
case b: Building if (b.Faction != player.Faction || b.CaptureTerminalIsHacked) && term.HackedBy.isEmpty =>
|
||||
|
|
@ -214,16 +217,16 @@ object ProximityTerminalControl {
|
|||
private case class TerminalAction()
|
||||
|
||||
/**
|
||||
* Determine which functionality to pursue by a generic proximity-functional unit given the target for its activity.
|
||||
* @see `VehicleService:receive, ProximityUnit.Action`
|
||||
* @param terminal the proximity-based unit
|
||||
* @param target the object being affected by the unit
|
||||
*/
|
||||
* Determine which functionality to pursue by a generic proximity-functional unit given the target for its activity.
|
||||
* @see `VehicleService:receive, ProximityUnit.Action`
|
||||
* @param terminal the proximity-based unit
|
||||
* @param target the object being affected by the unit
|
||||
*/
|
||||
def selectAndTryProximityUnitBehavior(
|
||||
callback: ActorRef,
|
||||
terminal: Terminal with ProximityUnit,
|
||||
target: PlanetSideGameObject
|
||||
): Boolean = {
|
||||
callback: ActorRef,
|
||||
terminal: Terminal with ProximityUnit,
|
||||
target: PlanetSideGameObject
|
||||
): Boolean = {
|
||||
(terminal.Definition, target) match {
|
||||
case (_: MedicalTerminalDefinition, p: Player) => HealthAndArmorTerminal(terminal, p)
|
||||
case (_: WeaponRechargeTerminalDefinition, p: Player) => WeaponRechargeTerminal(terminal, p)
|
||||
|
|
@ -234,110 +237,131 @@ object ProximityTerminalControl {
|
|||
}
|
||||
|
||||
/**
|
||||
* When standing on the platform of a(n advanced) medical terminal,
|
||||
* restore the player's health and armor points (when they need their health and armor points restored).
|
||||
* If the player is both fully healed and fully repaired, stop using the terminal.
|
||||
* @param unit the medical terminal
|
||||
* @param target the player being healed
|
||||
*/
|
||||
* When standing on the platform of a(n advanced) medical terminal,
|
||||
* restore the player's health and armor points (when they need their health and armor points restored).
|
||||
* If the player is both fully healed and fully repaired, stop using the terminal.
|
||||
* @param unit the medical terminal
|
||||
* @param target the player being healed
|
||||
*/
|
||||
def HealthAndArmorTerminal(unit: Terminal with ProximityUnit, target: Player): Boolean = {
|
||||
val medDef = unit.Definition.asInstanceOf[MedicalTerminalDefinition]
|
||||
val healAmount = medDef.HealAmount
|
||||
val healthFull: Boolean = if (healAmount != 0 && target.Health < target.MaxHealth) {
|
||||
target.History(HealFromTerm(PlayerSource(target), healAmount, 0, medDef))
|
||||
HealAction(target, healAmount)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
val repairAmount = medDef.ArmorAmount
|
||||
val armorFull: Boolean = if (repairAmount != 0 && target.Armor < target.MaxArmor) {
|
||||
target.History(HealFromTerm(PlayerSource(target), 0, repairAmount, medDef))
|
||||
ArmorRepairAction(target, repairAmount)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
healthFull && armorFull
|
||||
val medDef = unit.Definition.asInstanceOf[MedicalTerminalDefinition]
|
||||
val fullHeal = HealAction(unit, target, medDef.HealAmount, PlayerHealthCallback)
|
||||
val fullRepair = ArmorRepairAction(unit, target, medDef.ArmorAmount)
|
||||
fullHeal && fullRepair
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore, at most, a specific amount of health points on a player.
|
||||
* Send messages to connected client and to events system.
|
||||
* @param tplayer the player
|
||||
* @param healValue the amount to heal;
|
||||
* 10 by default
|
||||
* @return whether the player can be repaired for any more health points
|
||||
*/
|
||||
def HealAction(tplayer: Player, healValue: Int = 10): Boolean = {
|
||||
tplayer.Health = tplayer.Health + healValue
|
||||
val zone = tplayer.Zone
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zone.id,
|
||||
AvatarAction.PlanetsideAttributeToAll(tplayer.GUID, 0, tplayer.Health)
|
||||
)
|
||||
tplayer.Health == tplayer.MaxHealth
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore, at most, a specific amount of personal armor points on a player.
|
||||
* Send messages to connected client and to events system.
|
||||
* @param tplayer the player
|
||||
* @param repairValue the amount to repair;
|
||||
* 10 by default
|
||||
* @return whether the player can be repaired for any more armor points
|
||||
*/
|
||||
def ArmorRepairAction(tplayer: Player, repairValue: Int = 10): Boolean = {
|
||||
tplayer.Armor = tplayer.Armor + repairValue
|
||||
val zone = tplayer.Zone
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zone.id,
|
||||
AvatarAction.PlanetsideAttributeToAll(tplayer.GUID, 4, tplayer.Armor)
|
||||
)
|
||||
tplayer.Armor == tplayer.MaxArmor
|
||||
}
|
||||
|
||||
/**
|
||||
* When driving a vehicle close to a rearm/repair silo,
|
||||
* restore the vehicle's health points.
|
||||
* If the vehicle is fully repaired, stop using the terminal.
|
||||
* @param unit the terminal
|
||||
* @param target the vehicle being repaired
|
||||
*/
|
||||
* When driving a vehicle close to a rearm/repair silo,
|
||||
* restore the vehicle's health points.
|
||||
* If the vehicle is fully repaired, stop using the terminal.
|
||||
* @param unit the terminal
|
||||
* @param target the vehicle being repaired
|
||||
*/
|
||||
def VehicleRepairTerminal(unit: Terminal with ProximityUnit, target: Vehicle): Boolean = {
|
||||
val medDef = unit.Definition.asInstanceOf[MedicalTerminalDefinition]
|
||||
val healAmount = medDef.HealAmount
|
||||
val maxHealth = target.MaxHealth
|
||||
val noMoreHeal = if (!target.Destroyed && unit.Validate(target)) {
|
||||
//repair vehicle
|
||||
if (healAmount > 0 && target.Health < maxHealth) {
|
||||
target.Health = target.Health + healAmount
|
||||
target.History(RepairFromTerm(VehicleSource(target), healAmount, medDef))
|
||||
val zone = target.Zone
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zone.id,
|
||||
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 0, target.Health)
|
||||
)
|
||||
target.Health == maxHealth
|
||||
} else {
|
||||
unit.Definition match {
|
||||
case medDef: MedicalTerminalDefinition if !target.Destroyed && unit.Validate(target) =>
|
||||
HealAction(unit, target, medDef.HealAmount, VehicleHealthCallback)
|
||||
case _ =>
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
noMoreHeal
|
||||
}
|
||||
|
||||
/**
|
||||
* When standing in a friendly SOI whose facility is under the influence of an Ancient Weapon Module benefit,
|
||||
* and the player is in possession of Ancient weaponnry whose magazine is not full,
|
||||
* restore some ammunition to its magazine.
|
||||
* If no valid weapons are discovered or the discovered valid weapons have full magazines, stop using the terminal.
|
||||
* @param unit the terminal
|
||||
* @param target the player with weapons being recharged
|
||||
*/
|
||||
* Restore, at most, a specific amount of health points on a player.
|
||||
* Send messages to connected client and to events system.
|
||||
* @param terminal na
|
||||
* @param target that which will accept the health
|
||||
* @param healAmount health value to be given to the target
|
||||
* @param updateFunc callback to update the UI
|
||||
* @return whether the target can be healed any further
|
||||
*/
|
||||
def HealAction(
|
||||
terminal: Terminal,
|
||||
target: PlanetSideGameObject with Vitality with ZoneAware,
|
||||
healAmount: Int,
|
||||
updateFunc: PlanetSideGameObject with Vitality with ZoneAware=>Unit
|
||||
): Boolean = {
|
||||
val health = target.Health
|
||||
val maxHealth = target.MaxHealth
|
||||
val nextHealth = health + healAmount
|
||||
if (healAmount != 0 && health < maxHealth) {
|
||||
val finalHealthAmount = if (nextHealth > maxHealth) {
|
||||
nextHealth - maxHealth
|
||||
} else {
|
||||
healAmount
|
||||
}
|
||||
target.Health = health + finalHealthAmount
|
||||
target.LogActivity(HealFromTerm(AmenitySource(terminal), finalHealthAmount))
|
||||
updateFunc(target)
|
||||
target.Health == maxHealth
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
def PlayerHealthCallback(target: PlanetSideGameObject with Vitality with ZoneAware): Unit = {
|
||||
val zone = target.Zone
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zone.id,
|
||||
AvatarAction.PlanetsideAttributeToAll(target.GUID, 0, target.Health)
|
||||
)
|
||||
}
|
||||
|
||||
def VehicleHealthCallback(target: PlanetSideGameObject with Vitality with ZoneAware): Unit = {
|
||||
val zone = target.Zone
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zone.id,
|
||||
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 0, target.Health)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore, at most, a specific amount of personal armor points on a player.
|
||||
* Send messages to connected client and to events system.
|
||||
* @param terminal na
|
||||
* @param target that which will accept the repair
|
||||
* @param repairAmount armor value to be given to the target
|
||||
* @return whether the target can be repaired any further
|
||||
*/
|
||||
def ArmorRepairAction(
|
||||
terminal: Terminal,
|
||||
target: Player,
|
||||
repairAmount: Int
|
||||
): Boolean = {
|
||||
val armor = target.Armor
|
||||
val maxArmor = target.MaxArmor
|
||||
val nextArmor = armor + repairAmount
|
||||
if (repairAmount != 0 && armor < maxArmor) {
|
||||
val finalRepairAmount = if (nextArmor > maxArmor) {
|
||||
nextArmor - maxArmor
|
||||
} else {
|
||||
repairAmount
|
||||
}
|
||||
target.Armor = armor + finalRepairAmount
|
||||
target.LogActivity(RepairFromTerm(AmenitySource(terminal), finalRepairAmount))
|
||||
val zone = target.Zone
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zone.id,
|
||||
AvatarAction.PlanetsideAttributeToAll(target.GUID, 4, target.Armor)
|
||||
)
|
||||
target.Armor == maxArmor
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When standing in a friendly SOI whose facility is under the influence of an Ancient Weapon Module benefit,
|
||||
* and the player is in possession of Ancient weaponnry whose magazine is not full,
|
||||
* restore some ammunition to its magazine.
|
||||
* If no valid weapons are discovered or the discovered valid weapons have full magazines, stop using the terminal.
|
||||
* @param unit the terminal
|
||||
* @param target the player with weapons being recharged
|
||||
*/
|
||||
def WeaponRechargeTerminal(unit: Terminal with ProximityUnit, target: Player): Boolean = {
|
||||
val result = WeaponsBeingRechargedWithSomeAmmunition(
|
||||
unit.Definition.asInstanceOf[WeaponRechargeTerminalDefinition].AmmoAmount,
|
||||
target.Holsters().map { _.Equipment }.flatten.toIterable ++ target.Inventory.Items.map { _.obj }
|
||||
target.Holsters().flatMap { _.Equipment }.toIterable ++ target.Inventory.Items.map { _.obj }
|
||||
)
|
||||
val events = unit.Zone.AvatarEvents
|
||||
val channel = target.Name
|
||||
|
|
@ -349,17 +373,17 @@ object ProximityTerminalControl {
|
|||
)
|
||||
}
|
||||
}
|
||||
!result.unzip._2.flatten.exists { slot => slot.Magazine < slot.MaxMagazine() }
|
||||
!result.flatMap { _._2 }.exists { slot => slot.Magazine < slot.MaxMagazine() }
|
||||
}
|
||||
|
||||
/**
|
||||
* When driving close to a rearm/repair silo whose facility is under the influence of an Ancient Weapon Module benefit,
|
||||
* and the vehicle is an Ancient vehicle with mounted weaponry whose magazine(s) is not full,
|
||||
* restore some ammunition to the magazine(s).
|
||||
* If no valid weapons are discovered or the discovered valid weapons have full magazines, stop using the terminal.
|
||||
* @param unit the terminal
|
||||
* @param target the vehicle with weapons being recharged
|
||||
*/
|
||||
* When driving close to a rearm/repair silo whose facility is under the influence of an Ancient Weapon Module benefit,
|
||||
* and the vehicle is an Ancient vehicle with mounted weaponry whose magazine(s) is not full,
|
||||
* restore some ammunition to the magazine(s).
|
||||
* If no valid weapons are discovered or the discovered valid weapons have full magazines, stop using the terminal.
|
||||
* @param unit the terminal
|
||||
* @param target the vehicle with weapons being recharged
|
||||
*/
|
||||
def WeaponRechargeTerminal(unit: Terminal with ProximityUnit, target: Vehicle): Boolean = {
|
||||
val result = WeaponsBeingRechargedWithSomeAmmunition(
|
||||
unit.Definition.asInstanceOf[WeaponRechargeTerminalDefinition].AmmoAmount,
|
||||
|
|
@ -375,17 +399,17 @@ object ProximityTerminalControl {
|
|||
)
|
||||
}
|
||||
}
|
||||
!result.unzip._2.flatten.exists { slot => slot.Magazine < slot.MaxMagazine() }
|
||||
!result.flatMap { _._2 }.exists { slot => slot.Magazine < slot.MaxMagazine() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all weapons with magazines that need to have ammunition reloaded,
|
||||
* and reload some ammunition into them.
|
||||
* @param ammoAdded the amount of ammo to be added to a weapon
|
||||
* @param equipment the equipment being considered;
|
||||
* weapons whose ammo will be increased will be isolated
|
||||
* @return na
|
||||
*/
|
||||
* Collect all weapons with magazines that need to have ammunition reloaded,
|
||||
* and reload some ammunition into them.
|
||||
* @param ammoAdded the amount of ammo to be added to a weapon
|
||||
* @param equipment the equipment being considered;
|
||||
* weapons whose ammo will be increased will be isolated
|
||||
* @return na
|
||||
*/
|
||||
def WeaponsBeingRechargedWithSomeAmmunition(
|
||||
ammoAdded: Int,
|
||||
equipment: Iterable[Equipment]
|
||||
|
|
@ -399,12 +423,12 @@ object ProximityTerminalControl {
|
|||
}
|
||||
|
||||
/**
|
||||
* Collect all magazines from this weapon that need to have ammunition reloaded,
|
||||
* and reload some ammunition into them.
|
||||
* @param ammoAdded the amount of ammo to be added to a weapon
|
||||
* @param slots the vehicle with weapons being recharged
|
||||
* @return ammunition slots that were affected
|
||||
*/
|
||||
* Collect all magazines from this weapon that need to have ammunition reloaded,
|
||||
* and reload some ammunition into them.
|
||||
* @param ammoAdded the amount of ammo to be added to a weapon
|
||||
* @param slots the vehicle with weapons being recharged
|
||||
* @return ammunition slots that were affected
|
||||
*/
|
||||
def WeaponAmmoRecharge(
|
||||
ammoAdded: Int,
|
||||
slots: List[Tool.FireModeSlot]
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
package net.psforever.objects.serverobject.terminals.capture
|
||||
|
||||
import net.psforever.objects.serverobject.structures.AmenityDefinition
|
||||
import scala.concurrent.duration.{Duration, FiniteDuration}
|
||||
|
||||
class CaptureTerminalDefinition(objectId: Int) extends AmenityDefinition(objectId) {
|
||||
Name = objectId match {
|
||||
case 158 => "capture_terminal"
|
||||
case 751 => "secondary_capture"
|
||||
case 930 => "vanu_control_console"
|
||||
case _ => throw new IllegalArgumentException("Not a valid capture terminal object id")
|
||||
private var hackTime: FiniteDuration = Duration.Zero
|
||||
|
||||
def FacilityHackTime: FiniteDuration = hackTime
|
||||
|
||||
def FacilityHackTime_=(time: FiniteDuration): FiniteDuration = {
|
||||
hackTime = time
|
||||
FacilityHackTime
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
package net.psforever.objects.serverobject.terminals.capture
|
||||
|
||||
import akka.util.Timeout
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.sourcing.PlayerSource
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
object CaptureTerminals {
|
||||
object CaptureTerminals {import scala.concurrent.duration._
|
||||
private val log = org.log4s.getLogger("CaptureTerminals")
|
||||
private implicit val timeout: Timeout = 1.second
|
||||
|
||||
/**
|
||||
* The process of hacking an object is completed.
|
||||
|
|
@ -22,39 +25,34 @@ object CaptureTerminals {
|
|||
def FinishHackingCaptureConsole(target: CaptureTerminal, hackingPlayer: Player, unk: Long)(): Unit = {
|
||||
import akka.pattern.ask
|
||||
|
||||
import scala.concurrent.duration._
|
||||
log.info(s"${hackingPlayer.toString} hacked a ${target.Definition.Name}")
|
||||
// Wait for the target actor to set the HackedBy property
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
ask(target.Actor, CommonMessages.Hack(hackingPlayer, target))(1 second).mapTo[Boolean].onComplete {
|
||||
ask(target.Actor, CommonMessages.Hack(hackingPlayer, target)).mapTo[Boolean].onComplete {
|
||||
case Success(_) =>
|
||||
target.Zone.LocalEvents ! LocalServiceMessage(
|
||||
target.Zone.id,
|
||||
val zone = target.Zone
|
||||
val zoneid = zone.id
|
||||
val events = zone.LocalEvents
|
||||
val isResecured = hackingPlayer.Faction == target.Faction
|
||||
events ! LocalServiceMessage(
|
||||
zoneid,
|
||||
LocalAction.TriggerSound(hackingPlayer.GUID, target.HackSound, hackingPlayer.Position, 30, 0.49803925f)
|
||||
)
|
||||
|
||||
val isResecured = hackingPlayer.Faction == target.Faction
|
||||
|
||||
if (isResecured) {
|
||||
// Resecure the CC
|
||||
target.Zone.LocalEvents ! LocalServiceMessage(
|
||||
target.Zone.id,
|
||||
LocalAction.ResecureCaptureTerminal(
|
||||
target
|
||||
)
|
||||
events ! LocalServiceMessage(
|
||||
zoneid,
|
||||
LocalAction.ResecureCaptureTerminal(target, PlayerSource(hackingPlayer))
|
||||
)
|
||||
} else {
|
||||
// Start the CC hack timer
|
||||
target.Zone.LocalEvents ! LocalServiceMessage(
|
||||
target.Zone.id,
|
||||
LocalAction.StartCaptureTerminalHack(
|
||||
target
|
||||
)
|
||||
events ! LocalServiceMessage(
|
||||
zoneid,
|
||||
LocalAction.StartCaptureTerminalHack(target)
|
||||
)
|
||||
}
|
||||
case Failure(_) => log.warn(s"Hack message failed on target guid: ${target.GUID}")
|
||||
case Failure(_) =>
|
||||
log.warn(s"Hack message failed on target guid: ${target.GUID}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import net.psforever.objects.serverobject.damage.Damageable.Target
|
|||
import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity, DamageableMountable}
|
||||
import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior}
|
||||
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
|
||||
import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableEntity}
|
||||
import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableAmenity, RepairableEntity}
|
||||
import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl}
|
||||
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAwareBehavior
|
||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||
|
|
@ -27,13 +27,13 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
|
|||
with RepairableEntity
|
||||
with AmenityAutoRepair
|
||||
with CaptureTerminalAwareBehavior {
|
||||
def MountableObject = mech
|
||||
def HackableObject = mech
|
||||
def FactionObject = mech
|
||||
def DamageableObject = mech
|
||||
def RepairableObject = mech
|
||||
def AutoRepairObject = mech
|
||||
def CaptureTerminalAwareObject = mech
|
||||
def MountableObject: ImplantTerminalMech = mech
|
||||
def HackableObject: ImplantTerminalMech = mech
|
||||
def FactionObject: ImplantTerminalMech = mech
|
||||
def DamageableObject: ImplantTerminalMech = mech
|
||||
def RepairableObject: ImplantTerminalMech = mech
|
||||
def AutoRepairObject: ImplantTerminalMech = mech
|
||||
def CaptureTerminalAwareObject: ImplantTerminalMech = mech
|
||||
|
||||
def commonBehavior: Receive =
|
||||
checkBehavior
|
||||
|
|
@ -98,7 +98,6 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
|
|||
override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = {
|
||||
super.DestructionAwareness(target, cause)
|
||||
DamageableMountable.DestructionAwareness(DamageableObject, cause)
|
||||
target.ClearHistory()
|
||||
}
|
||||
|
||||
override def PerformRepairs(target : Damageable.Target, amount : Int) : Int = {
|
||||
|
|
@ -136,4 +135,9 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
|
|||
def powerTurnOnCallback(): Unit = {
|
||||
tryAutoRepair()
|
||||
}
|
||||
|
||||
override def Restoration(obj: Target): Unit = {
|
||||
super.Restoration(obj)
|
||||
RepairableAmenity.RestorationOfHistory(obj)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.serverobject.turret
|
||||
|
||||
import akka.actor.Cancellable
|
||||
import net.psforever.objects.{Default, GlobalDefinitions, Player, Tool}
|
||||
import net.psforever.objects.equipment.{Ammo, JammableMountedWeapons}
|
||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||
|
|
@ -37,16 +38,16 @@ class FacilityTurretControl(turret: FacilityTurret)
|
|||
with AmenityAutoRepair
|
||||
with JammableMountedWeapons
|
||||
with CaptureTerminalAwareBehavior {
|
||||
def FactionObject = turret
|
||||
def MountableObject = turret
|
||||
def JammableObject = turret
|
||||
def DamageableObject = turret
|
||||
def RepairableObject = turret
|
||||
def AutoRepairObject = turret
|
||||
def CaptureTerminalAwareObject = turret
|
||||
def FactionObject: FacilityTurret = turret
|
||||
def MountableObject: FacilityTurret = turret
|
||||
def JammableObject: FacilityTurret = turret
|
||||
def DamageableObject: FacilityTurret = turret
|
||||
def RepairableObject: FacilityTurret = turret
|
||||
def AutoRepairObject: FacilityTurret = turret
|
||||
def CaptureTerminalAwareObject: FacilityTurret = turret
|
||||
|
||||
// Used for timing ammo recharge for vanu turrets in caves
|
||||
var weaponAmmoRechargeTimer = Default.Cancellable
|
||||
var weaponAmmoRechargeTimer: Cancellable = Default.Cancellable
|
||||
|
||||
override def postStop(): Unit = {
|
||||
super.postStop()
|
||||
|
|
@ -186,7 +187,7 @@ class FacilityTurretControl(turret: FacilityTurret)
|
|||
seat.unmount(player)
|
||||
player.VehicleSeated = None
|
||||
if (player.HasGUID) {
|
||||
events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, false, guid))
|
||||
events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, unk2=false, guid))
|
||||
}
|
||||
case None => ;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright (c) 2023 PSForever
|
||||
package net.psforever.objects.sourcing
|
||||
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.serverobject.hackable.Hackable.HackInfo
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.serverobject.structures.Amenity
|
||||
import net.psforever.objects.sourcing
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfile
|
||||
import net.psforever.objects.vital.{Vitality, VitalityDefinition}
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
|
||||
final case class AmenitySource(
|
||||
private val objdef: ObjectDefinition,
|
||||
Faction: PlanetSideEmpire.Value,
|
||||
health: Int,
|
||||
Orientation: Vector3,
|
||||
occupants: List[SourceEntry],
|
||||
hacked: Option[HackInfo],
|
||||
unique: UniqueAmenity
|
||||
) extends SourceWithHealthEntry {
|
||||
private val definition = objdef match {
|
||||
case vital: VitalityDefinition => vital
|
||||
case genericDefinition => NonvitalDefinition(genericDefinition)
|
||||
}
|
||||
private val modifiers = definition match {
|
||||
case nonvital: NonvitalDefinition => nonvital
|
||||
case _ => ObjectSource.FixedResistances
|
||||
}
|
||||
|
||||
def Name: String = SourceEntry.NameFormat(definition.Descriptor)
|
||||
def Definition: ObjectDefinition with VitalityDefinition = definition
|
||||
def Health: Int = health
|
||||
def total: Int = health
|
||||
def Modifiers: ResistanceProfile = modifiers
|
||||
def Position: Vector3 = unique.position
|
||||
def Velocity: Option[Vector3] = None
|
||||
}
|
||||
|
||||
object AmenitySource {
|
||||
def apply(obj: Amenity): AmenitySource = {
|
||||
val health: Int = obj match {
|
||||
case o: Vitality => o.Health
|
||||
case _ => 1
|
||||
}
|
||||
val hackData = obj match {
|
||||
case o: Hackable => o.HackedBy
|
||||
case _ => None
|
||||
}
|
||||
val amenity = AmenitySource(
|
||||
obj.Definition,
|
||||
obj.Faction,
|
||||
health,
|
||||
obj.Orientation,
|
||||
Nil,
|
||||
hackData,
|
||||
sourcing.UniqueAmenity(obj.Zone.Number, obj.GUID, obj.Position)
|
||||
)
|
||||
amenity.copy(occupants = obj match {
|
||||
case o: Mountable =>
|
||||
o.Seats.values.flatMap { _.occupants }.map { p => PlayerSource.inSeat(p, o, amenity) }.toList
|
||||
case _ =>
|
||||
Nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) 2023 PSForever
|
||||
package net.psforever.objects.sourcing
|
||||
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.structures.{Building, BuildingDefinition}
|
||||
import net.psforever.objects.vital.VitalityDefinition
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfile
|
||||
import net.psforever.types.{LatticeBenefit, PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||
|
||||
final case class UniqueBuilding(
|
||||
zone_number: Int,
|
||||
building_guid: PlanetSideGUID
|
||||
) extends SourceUniqueness
|
||||
|
||||
final case class BuildingSource(
|
||||
private val obj_def: BuildingDefinition,
|
||||
Faction: PlanetSideEmpire.Value,
|
||||
Position: Vector3,
|
||||
Orientation: Vector3,
|
||||
benefits: Set[LatticeBenefit],
|
||||
unique: UniqueBuilding
|
||||
) extends SourceEntry {
|
||||
private val definition = NonvitalDefinition(obj_def)
|
||||
def Name: String = SourceEntry.NameFormat(Definition.Name)
|
||||
def Definition: ObjectDefinition with VitalityDefinition = definition
|
||||
def Modifiers: ResistanceProfile = ObjectSource.FixedResistances
|
||||
def Velocity: Option[Vector3] = None
|
||||
}
|
||||
|
||||
object BuildingSource {
|
||||
def apply(b: Building): BuildingSource = {
|
||||
BuildingSource(
|
||||
b.Definition,
|
||||
b.Faction,
|
||||
b.Position,
|
||||
b.Orientation,
|
||||
b.latticeConnectedFacilityBenefits(),
|
||||
UniqueBuilding(b.Zone.Number, b.GUID)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.sourcing
|
||||
|
||||
import net.psforever.objects.ce.Deployable
|
||||
import net.psforever.objects.definition.{DeployableDefinition, ObjectDefinition}
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfile
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
|
||||
final case class DeployableSource(
|
||||
Definition: ObjectDefinition with DeployableDefinition,
|
||||
Faction: PlanetSideEmpire.Value,
|
||||
health: Int,
|
||||
shields: Int,
|
||||
owner: SourceEntry,
|
||||
Position: Vector3,
|
||||
Orientation: Vector3,
|
||||
occupants: List[SourceEntry],
|
||||
unique: UniqueDeployable
|
||||
) extends SourceWithHealthEntry {
|
||||
def Name: String = Definition.Descriptor
|
||||
def Health: Int = health
|
||||
def Shields: Int = shields
|
||||
def OwnerName: String = owner.Name
|
||||
def Velocity: Option[Vector3] = None
|
||||
def Modifiers: ResistanceProfile = Definition.asInstanceOf[ResistanceProfile]
|
||||
|
||||
def total: Int = health + shields
|
||||
}
|
||||
|
||||
object DeployableSource {
|
||||
def apply(obj: Deployable): DeployableSource = {
|
||||
val ownerName = obj.OwnerName
|
||||
val ownerSource = (obj.Zone.LivePlayers ++ obj.Zone.Corpses)
|
||||
.find { p => ownerName.contains(p.Name) }
|
||||
match {
|
||||
case Some(p) => SourceEntry(p)
|
||||
case _ => SourceEntry.None
|
||||
}
|
||||
val occupants = obj match {
|
||||
case o: Mountable =>
|
||||
o.Seats.values.flatMap { _.occupants }.map { PlayerSource(_) }.toList
|
||||
case _ =>
|
||||
Nil
|
||||
}
|
||||
DeployableSource(
|
||||
obj.Definition,
|
||||
obj.Faction,
|
||||
obj.Health,
|
||||
obj.Shields,
|
||||
ownerSource,
|
||||
obj.Position,
|
||||
obj.Orientation,
|
||||
occupants,
|
||||
UniqueDeployable(
|
||||
obj.History.headOption match {
|
||||
case Some(entry) => entry.time
|
||||
case None => 0L
|
||||
},
|
||||
obj.OriginalOwnerName.getOrElse("none")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) 2017-2023 PSForever
|
||||
package net.psforever.objects.sourcing
|
||||
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.vital.VitalityDefinition
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
|
||||
|
||||
/**
|
||||
* A wrapper for a definition that does not represent a `Vitality` object
|
||||
* but needs to look like one internally to satisfy type requirements.
|
||||
* @param definition the original definition
|
||||
*/
|
||||
class NonvitalDefinition(private val definition : ObjectDefinition)
|
||||
extends ObjectDefinition(definition.ObjectId)
|
||||
with ResistanceProfileMutators
|
||||
with VitalityDefinition {
|
||||
Name = definition.Name
|
||||
Packet = definition.Packet
|
||||
|
||||
def canEqual(a: Any) : Boolean = a.isInstanceOf[definition.type]
|
||||
|
||||
override def equals(that: Any): Boolean = definition.equals(that)
|
||||
|
||||
override def hashCode: Int = definition.hashCode
|
||||
}
|
||||
|
||||
object NonvitalDefinition {
|
||||
//single point of contact for all wrapped definitions
|
||||
private val storage: scala.collection.mutable.LongMap[NonvitalDefinition] =
|
||||
new scala.collection.mutable.LongMap[NonvitalDefinition]()
|
||||
|
||||
def apply(definition : ObjectDefinition) : NonvitalDefinition = {
|
||||
storage.get(definition.ObjectId) match {
|
||||
case Some(existing) =>
|
||||
existing
|
||||
case None =>
|
||||
val out = new NonvitalDefinition(definition)
|
||||
storage += definition.ObjectId.toLong -> out
|
||||
out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.sourcing
|
||||
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.vital.VitalityDefinition
|
||||
import net.psforever.objects.vital.resistance.{ResistanceProfile, ResistanceProfileMutators}
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
|
||||
final case class UniqueObject(objectId: Int) extends SourceUniqueness
|
||||
|
||||
final case class ObjectSource(
|
||||
private val obj_def: ObjectDefinition,
|
||||
Faction: PlanetSideEmpire.Value,
|
||||
Position: Vector3,
|
||||
Orientation: Vector3,
|
||||
Velocity: Option[Vector3],
|
||||
unique: UniqueObject
|
||||
) extends SourceEntry {
|
||||
private val definition = obj_def match {
|
||||
case vital : VitalityDefinition => vital
|
||||
case genericDefinition => NonvitalDefinition(genericDefinition)
|
||||
}
|
||||
private val modifiers = definition match {
|
||||
case nonvital : NonvitalDefinition => nonvital
|
||||
case _ => ObjectSource.FixedResistances
|
||||
}
|
||||
def Name: String = SourceEntry.NameFormat(Definition.Name)
|
||||
def Definition: ObjectDefinition with VitalityDefinition = definition
|
||||
def Modifiers: ResistanceProfile = modifiers
|
||||
}
|
||||
|
||||
object ObjectSource {
|
||||
final val FixedResistances = new ResistanceProfileMutators() { }
|
||||
|
||||
def apply(obj: PlanetSideGameObject): ObjectSource = {
|
||||
ObjectSource(
|
||||
obj.Definition,
|
||||
obj match {
|
||||
case aligned: FactionAffinity => aligned.Faction
|
||||
case _ => PlanetSideEmpire.NEUTRAL
|
||||
},
|
||||
obj.Position,
|
||||
obj.Orientation,
|
||||
obj.Velocity,
|
||||
UniqueObject(obj.Definition.ObjectId)
|
||||
)
|
||||
}
|
||||
}
|
||||
135
src/main/scala/net/psforever/objects/sourcing/PlayerSource.scala
Normal file
135
src/main/scala/net/psforever/objects/sourcing/PlayerSource.scala
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.sourcing
|
||||
|
||||
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfile
|
||||
import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player}
|
||||
import net.psforever.types.{CharacterSex, ExoSuitType, PlanetSideEmpire, Vector3}
|
||||
|
||||
final case class UniquePlayer(
|
||||
charId: Long,
|
||||
name: String,
|
||||
sex: CharacterSex,
|
||||
faction: PlanetSideEmpire.Value
|
||||
) extends SourceUniqueness
|
||||
|
||||
final case class PlayerSource(
|
||||
Definition: AvatarDefinition,
|
||||
ExoSuit: ExoSuitType.Value,
|
||||
seatedIn: Option[(SourceEntry, Int)],
|
||||
health: Int,
|
||||
armor: Int,
|
||||
Position: Vector3,
|
||||
Orientation: Vector3,
|
||||
Velocity: Option[Vector3],
|
||||
crouching: Boolean,
|
||||
jumping: Boolean,
|
||||
Modifiers: ResistanceProfile,
|
||||
bep: Long,
|
||||
kills: Seq[Any],
|
||||
unique: UniquePlayer
|
||||
) extends SourceWithHealthEntry {
|
||||
override def Name: String = unique.name
|
||||
override def Faction: PlanetSideEmpire.Value = unique.faction
|
||||
override def CharId: Long = unique.charId
|
||||
|
||||
def Seated: Boolean = seatedIn.nonEmpty
|
||||
def Health: Int = health
|
||||
def Armor: Int = armor
|
||||
def total: Int = health + armor
|
||||
}
|
||||
|
||||
object PlayerSource {
|
||||
def apply(p: Player): PlayerSource = {
|
||||
val exosuit = p.ExoSuit
|
||||
val faction = p.Faction
|
||||
val seatedEntity = mountableAndSeat(p)
|
||||
PlayerSource(
|
||||
p.Definition,
|
||||
exosuit,
|
||||
seatedEntity,
|
||||
p.Health,
|
||||
p.Armor,
|
||||
p.Position,
|
||||
p.Orientation,
|
||||
p.Velocity,
|
||||
p.Crouching,
|
||||
p.Jumping,
|
||||
ExoSuitDefinition.Select(exosuit, faction),
|
||||
p.avatar.bep,
|
||||
kills = Nil,
|
||||
UniquePlayer(p.CharId, p.Name, p.Sex, faction)
|
||||
)
|
||||
}
|
||||
|
||||
def apply(name: String, faction: PlanetSideEmpire.Value, position: Vector3): PlayerSource = {
|
||||
new PlayerSource(
|
||||
GlobalDefinitions.avatar,
|
||||
ExoSuitType.Standard,
|
||||
seatedIn = None,
|
||||
health = 100,
|
||||
armor = 0,
|
||||
position,
|
||||
Orientation = Vector3.Zero,
|
||||
Velocity = None,
|
||||
crouching = false,
|
||||
jumping = false,
|
||||
GlobalDefinitions.Standard,
|
||||
bep = 0L,
|
||||
kills = Nil,
|
||||
UniquePlayer(0L, name, CharacterSex.Male, faction)
|
||||
)
|
||||
}
|
||||
|
||||
def mountableAndSeat(player: Player): Option[(SourceEntry, Int)] = {
|
||||
player.Zone.GUID(player.VehicleSeated) match {
|
||||
case Some(thing: PlanetSideGameObject with Mountable with FactionAffinity) =>
|
||||
Some((SourceEntry(thing), thing.PassengerInSeat(player).get))
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a mostly normal player source entity
|
||||
* but the `seatedIn` field is just a shallow copy of the mountable information.
|
||||
* Said "shallow copy" will not reflect that the player is an occupant of the mountable entity
|
||||
* even if this function is entirely for the purpose of establishing that the player is an occupant of the mountable entity.<br>
|
||||
* Don't think too much about it.
|
||||
* @param player player
|
||||
* @param mount mountable entity in which the player should be seated
|
||||
* @param source a `SourceEntry` for the aforementioned mountable entity
|
||||
* @return a `PlayerSource` entity
|
||||
*/
|
||||
def inSeat(player: Player, mount: Mountable, source: SourceEntry): PlayerSource = {
|
||||
val exosuit = player.ExoSuit
|
||||
val faction = player.Faction
|
||||
PlayerSource(
|
||||
player.Definition,
|
||||
exosuit,
|
||||
Some((source, mount.PassengerInSeat(player).get)),
|
||||
player.Health,
|
||||
player.Armor,
|
||||
player.Position,
|
||||
player.Orientation,
|
||||
player.Velocity,
|
||||
player.Crouching,
|
||||
player.Jumping,
|
||||
ExoSuitDefinition.Select(exosuit, faction),
|
||||
player.avatar.bep,
|
||||
kills = Nil,
|
||||
UniquePlayer(player.CharId, player.Name, player.Sex, faction)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* "Nobody is my name: Nobody they call me –
|
||||
* my mother and my father and all my other companions”
|
||||
* Thus I spoke but he immediately replied to me with a ruthless spirit:
|
||||
* “I shall kill Nobody last of all, after his companions,
|
||||
* the others first: this will be my guest-gift to you.”
|
||||
*/
|
||||
final val Nobody = PlayerSource("Nobody", PlanetSideEmpire.NEUTRAL, Vector3.Zero)
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.sourcing
|
||||
|
||||
import net.psforever.objects.ce.Deployable
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.structures.{Amenity, Building}
|
||||
import net.psforever.objects.serverobject.turret.FacilityTurret
|
||||
import net.psforever.objects.vital.VitalityDefinition
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfile
|
||||
import net.psforever.objects.{PlanetSideGameObject, Player, TurretDeployable, Vehicle}
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
|
||||
trait SourceUniqueness
|
||||
|
||||
trait SourceEntry {
|
||||
def Name: String
|
||||
def Definition: ObjectDefinition with VitalityDefinition
|
||||
def CharId: Long = 0L
|
||||
def Faction: PlanetSideEmpire.Value
|
||||
def Position: Vector3
|
||||
def Orientation: Vector3
|
||||
def Velocity: Option[Vector3]
|
||||
def Modifiers: ResistanceProfile
|
||||
def unique: SourceUniqueness
|
||||
}
|
||||
|
||||
trait SourceWithHealthEntry extends SourceEntry {
|
||||
def health: Int
|
||||
def total: Int
|
||||
}
|
||||
|
||||
trait SourceWithShieldsEntry extends SourceWithHealthEntry {
|
||||
def shields: Int
|
||||
}
|
||||
|
||||
object SourceEntry {
|
||||
final protected val nonUnique: SourceUniqueness = new SourceUniqueness() { }
|
||||
|
||||
final val None = new SourceEntry() {
|
||||
def Name: String = "none"
|
||||
def Definition: ObjectDefinition with VitalityDefinition = null
|
||||
def Faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
|
||||
def Position: Vector3 = Vector3.Zero
|
||||
def Orientation: Vector3 = Vector3.Zero
|
||||
def Velocity: Option[Vector3] = Some(Vector3.Zero)
|
||||
def Modifiers: ResistanceProfile = null
|
||||
def unique: SourceUniqueness = nonUnique
|
||||
}
|
||||
|
||||
def apply(target: PlanetSideGameObject with FactionAffinity): SourceEntry = {
|
||||
target match {
|
||||
case obj: Player => PlayerSource(obj)
|
||||
case obj: Vehicle => VehicleSource(obj)
|
||||
case obj: FacilityTurret => TurretSource(obj)
|
||||
case obj: Amenity => AmenitySource(obj)
|
||||
case obj: TurretDeployable => TurretSource(obj)
|
||||
case obj: Deployable => DeployableSource(obj)
|
||||
case obj: Building => BuildingSource(obj)
|
||||
case _ => ObjectSource(target)
|
||||
}
|
||||
}
|
||||
|
||||
def NameFormat(name: String): String = {
|
||||
name
|
||||
.replace("_", " ")
|
||||
.split(" ")
|
||||
.map(_.capitalize)
|
||||
.mkString(" ")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright (c) 2023 PSForever
|
||||
package net.psforever.objects.sourcing
|
||||
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.serverobject.turret.{FacilityTurret, WeaponTurret}
|
||||
import net.psforever.objects.vital.VitalityDefinition
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfile
|
||||
import net.psforever.objects.{PlanetSideGameObject, TurretDeployable}
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
|
||||
final case class TurretSource(
|
||||
Definition: ObjectDefinition with VitalityDefinition,
|
||||
Faction: PlanetSideEmpire.Value,
|
||||
health: Int,
|
||||
shields: Int,
|
||||
Position: Vector3,
|
||||
Orientation: Vector3,
|
||||
occupants: List[SourceEntry],
|
||||
unique: SourceUniqueness
|
||||
) extends SourceWithHealthEntry with SourceWithShieldsEntry {
|
||||
def Name: String = SourceEntry.NameFormat(Definition.Descriptor)
|
||||
def Health: Int = health
|
||||
def Shields: Int = shields
|
||||
def Velocity: Option[Vector3] = None
|
||||
def Modifiers: ResistanceProfile = Definition.asInstanceOf[ResistanceProfile]
|
||||
|
||||
def total: Int = health + shields
|
||||
}
|
||||
|
||||
object TurretSource {
|
||||
def apply(obj: PlanetSideGameObject with WeaponTurret): TurretSource = {
|
||||
val position = obj.Position
|
||||
val identifer = obj match {
|
||||
case o: TurretDeployable =>
|
||||
UniqueDeployable(
|
||||
o.History.headOption match {
|
||||
case Some(entry) => entry.time
|
||||
case None => 0L
|
||||
},
|
||||
o.OriginalOwnerName.getOrElse("none")
|
||||
)
|
||||
case o: FacilityTurret =>
|
||||
UniqueAmenity(o.Zone.Number, o.GUID, position)
|
||||
case o =>
|
||||
throw new IllegalArgumentException(s"was given ${o.Actor.toString()} when only wanted to model turrets")
|
||||
}
|
||||
val turret = TurretSource(
|
||||
obj.Definition.asInstanceOf[ObjectDefinition with VitalityDefinition],
|
||||
obj.Faction,
|
||||
obj.Health,
|
||||
shields = 0, //TODO implement later
|
||||
position,
|
||||
obj.Orientation,
|
||||
Nil,
|
||||
identifer
|
||||
)
|
||||
turret.copy(occupants = obj match {
|
||||
case o: Mountable =>
|
||||
o.Seats.values.flatMap { _.occupants }.map { p => PlayerSource.inSeat(p, o, turret) }.toList
|
||||
case _ =>
|
||||
Nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) 2023 PSForever
|
||||
package net.psforever.objects.sourcing
|
||||
|
||||
import net.psforever.types.{PlanetSideGUID, Vector3}
|
||||
|
||||
final case class UniqueAmenity(
|
||||
zoneNumber: Int,
|
||||
guid: PlanetSideGUID,
|
||||
position: Vector3
|
||||
) extends SourceUniqueness
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// Copyright (c) 2023 PSForever
|
||||
package net.psforever.objects.sourcing
|
||||
|
||||
final case class UniqueDeployable(
|
||||
spawnTime: Long,
|
||||
originalOwnerName: String
|
||||
) extends SourceUniqueness
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.sourcing
|
||||
|
||||
import net.psforever.objects.Vehicle
|
||||
import net.psforever.objects.definition.VehicleDefinition
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfile
|
||||
import net.psforever.types.{DriveState, PlanetSideEmpire, Vector3}
|
||||
|
||||
final case class UniqueVehicle(spawnTime: Long, originalOwnerName: String) extends SourceUniqueness
|
||||
|
||||
final case class VehicleSource(
|
||||
Definition: VehicleDefinition,
|
||||
Faction: PlanetSideEmpire.Value,
|
||||
health: Int,
|
||||
shields: Int,
|
||||
Position: Vector3,
|
||||
Orientation: Vector3,
|
||||
Velocity: Option[Vector3],
|
||||
deployed: DriveState.Value,
|
||||
occupants: List[SourceEntry],
|
||||
Modifiers: ResistanceProfile,
|
||||
unique: UniqueVehicle
|
||||
) extends SourceWithHealthEntry with SourceWithShieldsEntry {
|
||||
def Name: String = SourceEntry.NameFormat(Definition.Name)
|
||||
def Health: Int = health
|
||||
def Shields: Int = shields
|
||||
def total: Int = health + shields
|
||||
}
|
||||
|
||||
object VehicleSource {
|
||||
def apply(obj: Vehicle): VehicleSource = {
|
||||
val faction = obj.HackedBy match {
|
||||
case Some(o) => o.player.Faction
|
||||
case _ => obj.Faction
|
||||
}
|
||||
val vehicle = VehicleSource(
|
||||
obj.Definition,
|
||||
faction,
|
||||
obj.Health,
|
||||
obj.Shields,
|
||||
obj.Position,
|
||||
obj.Orientation,
|
||||
obj.Velocity,
|
||||
obj.DeploymentState,
|
||||
Nil,
|
||||
obj.Definition.asInstanceOf[ResistanceProfile],
|
||||
UniqueVehicle(
|
||||
obj.History.headOption match {
|
||||
case Some(entry) => entry.time
|
||||
case None => 0L
|
||||
},
|
||||
obj.OriginalOwnerName.getOrElse("none")
|
||||
)
|
||||
)
|
||||
vehicle.copy(occupants = {
|
||||
obj.Seats.values.map { seat =>
|
||||
seat.occupant match {
|
||||
case Some(p) => PlayerSource.inSeat(p, obj, vehicle) //shallow
|
||||
case _ => PlayerSource.Nobody
|
||||
}
|
||||
}.toList
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,8 @@
|
|||
package net.psforever.objects.vehicles
|
||||
|
||||
import net.psforever.objects.Vehicle
|
||||
import net.psforever.objects.ballistics.{Projectile, ProjectileQuality, SourceEntry}
|
||||
import net.psforever.objects.ballistics.{Projectile, ProjectileQuality}
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.base.{DamageResolution, DamageType}
|
||||
import net.psforever.objects.vital.etc.RadiationReason
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ 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}
|
||||
|
|
@ -11,8 +10,9 @@ import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObjec
|
|||
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.sourcing.{SourceEntry, VehicleSource}
|
||||
import net.psforever.objects.vehicles._
|
||||
import net.psforever.objects.vital.VehicleShieldCharge
|
||||
import net.psforever.objects.vital.ShieldCharge
|
||||
import net.psforever.objects.vital.interaction.DamageResult
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game._
|
||||
|
|
@ -37,12 +37,12 @@ class BfrControl(vehicle: Vehicle)
|
|||
* `Cancellable.alreadyCancelled` indicates a permanant cessation of charging activity (vehicle destruction) */
|
||||
var shieldCharge: Cancellable = Default.Cancellable
|
||||
|
||||
def SiphoningObject = vehicle
|
||||
def SiphoningObject: Vehicle = vehicle
|
||||
|
||||
def ChargeTransferObject = vehicle
|
||||
def ChargeTransferObject: Vehicle = vehicle
|
||||
|
||||
if (vehicle.Shields < vehicle.MaxShields) {
|
||||
chargeShields(amount = 0) //start charging if starts as uncharged
|
||||
chargeShields(amount = 0, Some(VehicleSource(vehicle))) //start charging if starts as uncharged
|
||||
}
|
||||
|
||||
override def postStop(): Unit = {
|
||||
|
|
@ -107,7 +107,7 @@ class BfrControl(vehicle: Vehicle)
|
|||
shieldCharge = context.system.scheduler.scheduleOnce(
|
||||
delay = vehicle.Definition.ShieldDamageDelay milliseconds,
|
||||
self,
|
||||
Vehicle.ChargeShields(0)
|
||||
CommonMessages.ChargeShields(0, Some(vehicle))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -242,7 +242,7 @@ class BfrControl(vehicle: Vehicle)
|
|||
val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory)
|
||||
val afterWeapons = weapons
|
||||
.map { item => item.start += 1; item }
|
||||
(culledWeaponMounts(pairedArmSubsys.unzip._2), afterWeapons, stow)
|
||||
(culledWeaponMounts(pairedArmSubsys.map { _._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
|
||||
|
|
@ -312,7 +312,7 @@ class BfrControl(vehicle: Vehicle)
|
|||
)
|
||||
}
|
||||
|
||||
override def chargeShields(amount: Int): Unit = {
|
||||
override def chargeShields(amount: Int, motivator: Option[SourceEntry]): Unit = {
|
||||
chargeShieldsOnly(amount)
|
||||
shieldCharge(vehicle.Shields, vehicle.Definition, delay = 0) //continue charge?
|
||||
}
|
||||
|
|
@ -328,7 +328,7 @@ class BfrControl(vehicle: Vehicle)
|
|||
}).getOrElse(amount) * vehicle.SubsystemStatusMultiplier(sys = "BattleframeShieldGenerator.RechargeRate")).toInt)
|
||||
vehicle.Shields = before + chargeAmount
|
||||
val after = vehicle.Shields
|
||||
vehicle.History(VehicleShieldCharge(VehicleSource(vehicle), after - before))
|
||||
vehicle.LogActivity(ShieldCharge(after - before, Some(VehicleSource(vehicle))))
|
||||
showShieldCharge()
|
||||
if (before == 0 && after > 0) {
|
||||
enableShield()
|
||||
|
|
@ -346,7 +346,7 @@ class BfrControl(vehicle: Vehicle)
|
|||
shieldCharge = context.system.scheduler.scheduleOnce(
|
||||
delay = definition.ShieldPeriodicDelay + delay milliseconds,
|
||||
self,
|
||||
Vehicle.ChargeShields(0)
|
||||
CommonMessages.ChargeShields(0, Some(vehicle))
|
||||
)
|
||||
} else {
|
||||
shieldCharge = Default.Cancellable
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ package net.psforever.objects.vehicles.control
|
|||
import akka.actor.Cancellable
|
||||
import net.psforever.actors.zone.ZoneActor
|
||||
import net.psforever.objects._
|
||||
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
|
||||
|
|
@ -20,9 +19,10 @@ import net.psforever.objects.serverobject.hackable.GenericHackables
|
|||
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
|
||||
import net.psforever.objects.serverobject.repair.RepairableVehicle
|
||||
import net.psforever.objects.serverobject.terminals.Terminal
|
||||
import net.psforever.objects.sourcing.{SourceEntry, VehicleSource}
|
||||
import net.psforever.objects.vehicles._
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
import net.psforever.objects.vital.{DamagingActivity, VehicleShieldCharge, VitalsActivity}
|
||||
import net.psforever.objects.vital.{DamagingActivity, InGameActivity, ShieldCharge}
|
||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||
import net.psforever.objects.vital.etc.SuicideReason
|
||||
import net.psforever.objects.zones._
|
||||
|
|
@ -60,23 +60,15 @@ class VehicleControl(vehicle: Vehicle)
|
|||
//make control actors belonging to utilities when making control actor belonging to vehicle
|
||||
vehicle.Utilities.foreach { case (_, util) => util.Setup }
|
||||
|
||||
def MountableObject = vehicle
|
||||
|
||||
def JammableObject = vehicle
|
||||
|
||||
def FactionObject = vehicle
|
||||
|
||||
def DamageableObject = vehicle
|
||||
|
||||
def SiphonableObject = vehicle
|
||||
|
||||
def RepairableObject = vehicle
|
||||
|
||||
def ContainerObject = vehicle
|
||||
|
||||
def InteractiveObject = vehicle
|
||||
|
||||
def CargoObject = vehicle
|
||||
def MountableObject: Vehicle = vehicle
|
||||
def JammableObject: Vehicle = vehicle
|
||||
def FactionObject: Vehicle = vehicle
|
||||
def DamageableObject: Vehicle = vehicle
|
||||
def SiphonableObject: Vehicle = vehicle
|
||||
def RepairableObject: Vehicle = vehicle
|
||||
def ContainerObject: Vehicle = vehicle
|
||||
def InteractiveObject: Vehicle = vehicle
|
||||
def CargoObject: Vehicle = vehicle
|
||||
|
||||
SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
|
||||
SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
|
||||
|
|
@ -136,8 +128,8 @@ class VehicleControl(vehicle: Vehicle)
|
|||
dismountBehavior.apply(msg)
|
||||
dismountCleanup(seat_num)
|
||||
|
||||
case Vehicle.ChargeShields(amount) =>
|
||||
chargeShields(amount)
|
||||
case CommonMessages.ChargeShields(amount, motivator) =>
|
||||
chargeShields(amount, motivator.collect { case o: PlanetSideGameObject with FactionAffinity => SourceEntry(o) })
|
||||
|
||||
case Vehicle.UpdateZoneInteractionProgressUI(player) =>
|
||||
updateZoneInteractionProgressUI(player)
|
||||
|
|
@ -406,6 +398,7 @@ class VehicleControl(vehicle: Vehicle)
|
|||
TaskWorkflow.execute(GUIDTask.unregisterVehicle(zone.GUID, vehicle))
|
||||
//banished to the shadow realm
|
||||
vehicle.Position = Vector3.Zero
|
||||
vehicle.ClearHistory()
|
||||
//queue final deletion
|
||||
decayTimer = context.system.scheduler.scheduleOnce(5 seconds, self, VehicleControl.Deletion())
|
||||
}
|
||||
|
|
@ -559,14 +552,14 @@ class VehicleControl(vehicle: Vehicle)
|
|||
|
||||
//make certain vehicles don't charge shields too quickly
|
||||
def canChargeShields: Boolean = {
|
||||
val func: VitalsActivity => Boolean = VehicleControl.LastShieldChargeOrDamage(System.currentTimeMillis(), vehicle.Definition)
|
||||
val func: InGameActivity => Boolean = VehicleControl.LastShieldChargeOrDamage(System.currentTimeMillis(), vehicle.Definition)
|
||||
vehicle.Health > 0 && vehicle.Shields < vehicle.MaxShields &&
|
||||
!vehicle.History.exists(func)
|
||||
vehicle.History.findLast(func).isEmpty
|
||||
}
|
||||
|
||||
def chargeShields(amount: Int): Unit = {
|
||||
def chargeShields(amount: Int, motivator: Option[SourceEntry]): Unit = {
|
||||
if (canChargeShields) {
|
||||
vehicle.History(VehicleShieldCharge(VehicleSource(vehicle), amount))
|
||||
vehicle.LogActivity(ShieldCharge(amount, motivator))
|
||||
vehicle.Shields = vehicle.Shields + amount
|
||||
vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
|
||||
s"${vehicle.Actor}",
|
||||
|
|
@ -874,7 +867,7 @@ class VehicleControl(vehicle: Vehicle)
|
|||
}
|
||||
|
||||
object VehicleControl {
|
||||
import net.psforever.objects.vital.{VehicleShieldCharge, VitalsActivity}
|
||||
import net.psforever.objects.vital.{ShieldCharge}
|
||||
|
||||
private case class PrepareForDeletion()
|
||||
|
||||
|
|
@ -893,10 +886,10 @@ object VehicleControl {
|
|||
* @return `true`, if the shield charge would be blocked;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def LastShieldChargeOrDamage(now: Long, vdef: VehicleDefinition)(act: VitalsActivity): Boolean = {
|
||||
def LastShieldChargeOrDamage(now: Long, vdef: VehicleDefinition)(act: InGameActivity): Boolean = {
|
||||
act match {
|
||||
case dact: DamagingActivity => now - dact.time < vdef.ShieldDamageDelay //damage delays next charge
|
||||
case vsc: VehicleShieldCharge => now - vsc.time < vdef.ShieldPeriodicDelay //previous charge delays next
|
||||
case vsc: ShieldCharge => now - vsc.time < vdef.ShieldPeriodicDelay //previous charge delays next
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
241
src/main/scala/net/psforever/objects/vital/InGameHistory.scala
Normal file
241
src/main/scala/net/psforever/objects/vital/InGameHistory.scala
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.vital
|
||||
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.definition.{EquipmentDefinition, KitDefinition, ToolDefinition}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.sourcing.{AmenitySource, ObjectSource, PlayerSource, SourceEntry, SourceWithHealthEntry, VehicleSource}
|
||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||
import net.psforever.objects.vital.etc.{ExplodingEntityReason, PainboxReason, SuicideReason}
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||
import net.psforever.types.{ExoSuitType, ImplantType, TransactionType}
|
||||
|
||||
/* root */
|
||||
|
||||
/**
|
||||
* The root of all chronological activity.
|
||||
* Must keep track of the time (ms) the activity occurred.
|
||||
*/
|
||||
trait InGameActivity {
|
||||
val time: Long = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
/* normal history */
|
||||
|
||||
/**
|
||||
* A generic classification of activity.
|
||||
*/
|
||||
trait GeneralActivity extends InGameActivity
|
||||
|
||||
final case class SpawningActivity(src: SourceEntry, zoneNumber: Int, unit: Option[SourceEntry]) extends GeneralActivity
|
||||
|
||||
final case class ReconstructionActivity(src: SourceEntry, zoneNumber: Int, unit: Option[SourceEntry]) extends GeneralActivity
|
||||
|
||||
final case class ShieldCharge(amount: Int, cause: Option[SourceEntry]) extends GeneralActivity
|
||||
|
||||
final case class TerminalUsedActivity(terminal: AmenitySource, transaction: TransactionType.Value) extends GeneralActivity
|
||||
|
||||
/* vitals history */
|
||||
|
||||
/**
|
||||
* A vital entity can be hurt or damaged or healed or repaired (HDHR).<br>
|
||||
* Shields are not included in the definition of what is a "vital statistic",
|
||||
* and that includes Infantry shields due to the Personal Shield implant
|
||||
* and MAX shields due to being a New Conglomerate soldier.
|
||||
*/
|
||||
trait VitalsActivity extends InGameActivity {
|
||||
def amount: Int
|
||||
}
|
||||
|
||||
trait HealingActivity extends VitalsActivity
|
||||
|
||||
trait RepairingActivity extends VitalsActivity
|
||||
|
||||
trait DamagingActivity extends VitalsActivity {
|
||||
override val time: Long = data.interaction.hitTime
|
||||
def data: DamageResult
|
||||
|
||||
def amount: Int = {
|
||||
(data.targetBefore, data.targetAfter) match {
|
||||
case (pb: SourceWithHealthEntry, pa: SourceWithHealthEntry) => pb.total - pa.total
|
||||
case _ => 0
|
||||
}
|
||||
}
|
||||
|
||||
def health: Int = {
|
||||
(data.targetBefore, data.targetAfter) match {
|
||||
case (pb: PlayerSource, pa: PlayerSource) if pb.ExoSuit == ExoSuitType.MAX => pb.total - pa.total
|
||||
case (pb: SourceWithHealthEntry, pa: SourceWithHealthEntry) => pb.health - pa.health
|
||||
case _ => 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final case class HealFromKit(kit_def: KitDefinition, amount: Int)
|
||||
extends HealingActivity
|
||||
|
||||
final case class HealFromEquipment(user: PlayerSource, equipment_def: EquipmentDefinition, amount: Int)
|
||||
extends HealingActivity
|
||||
|
||||
final case class HealFromTerm(term: AmenitySource, amount: Int)
|
||||
extends HealingActivity
|
||||
|
||||
final case class HealFromImplant(implant: ImplantType, amount: Int)
|
||||
extends HealingActivity
|
||||
|
||||
final case class RepairFromExoSuitChange(exosuit: ExoSuitType.Value, amount: Int)
|
||||
extends RepairingActivity
|
||||
|
||||
final case class RepairFromKit(kit_def: KitDefinition, amount: Int)
|
||||
extends RepairingActivity()
|
||||
|
||||
final case class RepairFromEquipment(user: PlayerSource, equipment_def: EquipmentDefinition, amount: Int)
|
||||
extends RepairingActivity
|
||||
|
||||
final case class RepairFromTerm(term: AmenitySource, amount: Int) extends RepairingActivity
|
||||
|
||||
final case class RepairFromArmorSiphon(siphon_def: ToolDefinition, vehicle: VehicleSource, amount: Int)
|
||||
extends RepairingActivity
|
||||
|
||||
final case class RepairFromAmenityAutoRepair(amount: Int)
|
||||
extends RepairingActivity
|
||||
|
||||
final case class DamageFrom(data: DamageResult)
|
||||
extends DamagingActivity
|
||||
|
||||
final case class DamageFromProjectile(data: DamageResult)
|
||||
extends DamagingActivity
|
||||
|
||||
final case class DamageFromPainbox(data: DamageResult)
|
||||
extends DamagingActivity
|
||||
|
||||
final case class DamageFromEnvironment(data: DamageResult)
|
||||
extends DamagingActivity
|
||||
|
||||
final case class PlayerSuicide(player: PlayerSource)
|
||||
extends DamagingActivity {
|
||||
private lazy val result = {
|
||||
val out = DamageResult(
|
||||
player,
|
||||
player.copy(health = 0),
|
||||
DamageInteraction(player, SuicideReason(), player.Position)
|
||||
)
|
||||
out
|
||||
}
|
||||
def data: DamageResult = result
|
||||
}
|
||||
|
||||
final case class DamageFromExplodingEntity(data: DamageResult)
|
||||
extends DamagingActivity
|
||||
|
||||
|
||||
trait InGameHistory {
|
||||
/** a list of important events that have occurred in chronological order */
|
||||
private var history: List[InGameActivity] = List.empty[InGameActivity]
|
||||
/** the last source of damage that cna be used to indicate blame for damage */
|
||||
private var lastDamage: Option[DamageResult] = None
|
||||
|
||||
def History: List[InGameActivity] = history
|
||||
|
||||
/**
|
||||
* Only the changes to vitality statistics.
|
||||
* @return a list of the chronologically-consistent vitality events
|
||||
*/
|
||||
def VitalsHistory(): List[VitalsActivity] = History.collect {
|
||||
case event: VitalsActivity => event
|
||||
}
|
||||
|
||||
/**
|
||||
* An in-game event must be recorded.
|
||||
* Add new entry to the front of the list (for recent activity).
|
||||
* @param action the fully-informed entry
|
||||
* @return the list of previous changes to this entity
|
||||
*/
|
||||
def LogActivity(action: InGameActivity): List[InGameActivity] = LogActivity(Some(action))
|
||||
|
||||
|
||||
/**
|
||||
* An in-game event must be recorded.
|
||||
* Add new entry to the front of the list (for recent activity).
|
||||
* @param action the fully-informed entry
|
||||
* @return the list of previous changes to this entity
|
||||
*/
|
||||
def LogActivity(action: Option[InGameActivity]): List[InGameActivity] = {
|
||||
action match {
|
||||
case Some(act) =>
|
||||
history = act +: history
|
||||
case None => ()
|
||||
}
|
||||
history
|
||||
}
|
||||
|
||||
/**
|
||||
* Very common example of a `VitalsActivity` event involving damage.
|
||||
* They are repackaged before submission and are often tagged for specific blame.
|
||||
* @param result the fully-informed entry
|
||||
* @return the list of previous changes to this object's vital statistics
|
||||
*/
|
||||
def LogActivity(result: DamageResult): List[InGameActivity] = {
|
||||
result.interaction.cause match {
|
||||
case _: ProjectileReason =>
|
||||
LogActivity(DamageFromProjectile(result))
|
||||
lastDamage = Some(result)
|
||||
case _: ExplodingEntityReason =>
|
||||
LogActivity(DamageFromExplodingEntity(result))
|
||||
lastDamage = Some(result)
|
||||
case _: PainboxReason =>
|
||||
LogActivity(DamageFromPainbox(result))
|
||||
case _: EnvironmentReason =>
|
||||
LogActivity(DamageFromEnvironment(result))
|
||||
case _ => ;
|
||||
LogActivity(DamageFrom(result))
|
||||
if(result.adversarial.nonEmpty) {
|
||||
lastDamage = Some(result)
|
||||
}
|
||||
}
|
||||
History
|
||||
}
|
||||
|
||||
def LastDamage: Option[DamageResult] = lastDamage
|
||||
|
||||
/**
|
||||
* Find, specifically, the last instance of a weapon discharge that caused damage.
|
||||
* @return information about the discharge
|
||||
*/
|
||||
def LastShot: Option[DamageResult] = {
|
||||
History.findLast({ p => p.isInstanceOf[DamageFromProjectile] }).map {
|
||||
case entry: DamageFromProjectile => entry.data
|
||||
}
|
||||
}
|
||||
|
||||
def ClearHistory(): List[InGameActivity] = {
|
||||
lastDamage = None
|
||||
val out = history
|
||||
history = List.empty
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
object InGameHistory {
|
||||
def SpawnReconstructionActivity(
|
||||
obj: PlanetSideGameObject with FactionAffinity with InGameHistory,
|
||||
zoneNumber: Int,
|
||||
unit: Option[SourceEntry]
|
||||
): Unit = {
|
||||
val event: GeneralActivity = if (obj.History.nonEmpty || obj.History.headOption.exists {
|
||||
_.isInstanceOf[SpawningActivity]
|
||||
}) {
|
||||
ReconstructionActivity(ObjectSource(obj), zoneNumber, unit)
|
||||
} else {
|
||||
SpawningActivity(ObjectSource(obj), zoneNumber, unit)
|
||||
}
|
||||
if (obj.History.lastOption match {
|
||||
case Some(evt: SpawningActivity) => evt != event
|
||||
case Some(evt: ReconstructionActivity) => evt != event
|
||||
case _ => true
|
||||
}) {
|
||||
obj.LogActivity(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import net.psforever.objects.vital.resolution.{DamageAndResistance, ResolutionCa
|
|||
* The amount of HDHR is controlled by the damage model of this vital object reacting to stimulus.
|
||||
* The damage model is provided.
|
||||
*/
|
||||
trait Vitality extends VitalsHistory {
|
||||
trait Vitality extends InGameHistory {
|
||||
private var health: Int = Definition.DefaultHealth
|
||||
private var defaultHealth: Option[Int] = None
|
||||
private var maxHealth: Option[Int] = None
|
||||
|
|
|
|||
|
|
@ -1,237 +0,0 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.vital
|
||||
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.ballistics.{PlayerSource, VehicleSource}
|
||||
import net.psforever.objects.definition.{EquipmentDefinition, KitDefinition, ToolDefinition}
|
||||
import net.psforever.objects.serverobject.terminals.TerminalDefinition
|
||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||
import net.psforever.objects.vital.etc.{ExplodingEntityReason, PainboxReason, SuicideReason}
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||
import net.psforever.types.{ExoSuitType, ImplantType}
|
||||
|
||||
trait VitalsActivity {
|
||||
def time: Long
|
||||
}
|
||||
|
||||
trait HealingActivity extends VitalsActivity {
|
||||
def amount: Int
|
||||
val time: Long = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
trait DamagingActivity extends VitalsActivity {
|
||||
def time: Long = data.interaction.hitTime
|
||||
def data: DamageResult
|
||||
}
|
||||
|
||||
final case class HealFromKit(kit_def: KitDefinition, amount: Int)
|
||||
extends HealingActivity
|
||||
|
||||
final case class HealFromEquipment(
|
||||
user: PlayerSource,
|
||||
equipment_def: EquipmentDefinition,
|
||||
amount: Int
|
||||
) extends HealingActivity
|
||||
|
||||
final case class HealFromTerm(term_def: TerminalDefinition, health: Int, armor: Int)
|
||||
extends HealingActivity {
|
||||
def amount: Int = health + armor
|
||||
}
|
||||
|
||||
final case class HealFromImplant(implant: ImplantType, health: Int)
|
||||
extends HealingActivity {
|
||||
def amount: Int = health
|
||||
}
|
||||
|
||||
final case class HealFromExoSuitChange(exosuit: ExoSuitType.Value)
|
||||
extends HealingActivity {
|
||||
def amount: Int = 0
|
||||
}
|
||||
|
||||
final case class RepairFromKit(kit_def: KitDefinition, amount: Int)
|
||||
extends HealingActivity()
|
||||
|
||||
final case class RepairFromEquipment(
|
||||
user: PlayerSource,
|
||||
equipment_def: EquipmentDefinition,
|
||||
amount: Int
|
||||
) extends HealingActivity
|
||||
|
||||
final case class RepairFromTerm(term_def: TerminalDefinition, amount: Int)
|
||||
extends HealingActivity
|
||||
|
||||
final case class RepairFromArmorSiphon(siphon_def: ToolDefinition, amount: Int)
|
||||
extends HealingActivity
|
||||
|
||||
final case class VehicleShieldCharge(amount: Int)
|
||||
extends HealingActivity //TODO facility
|
||||
|
||||
final case class DamageFrom(data: DamageResult)
|
||||
extends DamagingActivity
|
||||
|
||||
final case class DamageFromProjectile(data: DamageResult)
|
||||
extends DamagingActivity
|
||||
|
||||
final case class DamageFromPainbox(data: DamageResult)
|
||||
extends DamagingActivity
|
||||
|
||||
final case class DamageFromEnvironment(data: DamageResult)
|
||||
extends DamagingActivity
|
||||
|
||||
final case class PlayerSuicide(player: PlayerSource)
|
||||
extends DamagingActivity {
|
||||
private lazy val result = {
|
||||
val out = DamageResult(
|
||||
player,
|
||||
player.copy(health = 0),
|
||||
DamageInteraction(player, SuicideReason(), player.Position)
|
||||
)
|
||||
out
|
||||
}
|
||||
def data: DamageResult = result
|
||||
}
|
||||
|
||||
final case class DamageFromExplodingEntity(data: DamageResult)
|
||||
extends DamagingActivity
|
||||
|
||||
/**
|
||||
* A vital object can be hurt or damaged or healed or repaired (HDHR).
|
||||
* A history of the previous changes in vital statistics of the underlying object is recorded
|
||||
* in reverse chronological order.
|
||||
*/
|
||||
trait VitalsHistory {
|
||||
|
||||
/** a reverse-order list of chronological events that have occurred to these vital statistics */
|
||||
private var vitalsHistory: List[VitalsActivity] = List.empty[VitalsActivity]
|
||||
|
||||
private var lastDamage: Option[DamageResult] = None
|
||||
|
||||
def History: List[VitalsActivity] = vitalsHistory
|
||||
|
||||
/**
|
||||
* A `VitalsActivity` event must be recorded.
|
||||
* Add new entry to the front of the list (for recent activity).
|
||||
* @param action the fully-informed entry
|
||||
* @return the list of previous changes to this object's vital statistics
|
||||
*/
|
||||
def History(action: VitalsActivity): List[VitalsActivity] = History(Some(action))
|
||||
|
||||
/**
|
||||
* A `VitalsActivity` event must be recorded.
|
||||
* Add new entry to the front of the list (for recent activity).
|
||||
* @param action the fully-informed entry
|
||||
* @return the list of previous changes to this object's vital statistics
|
||||
*/
|
||||
def History(action: Option[VitalsActivity]): List[VitalsActivity] = {
|
||||
action match {
|
||||
case Some(act) =>
|
||||
vitalsHistory = act +: vitalsHistory
|
||||
case None => ;
|
||||
}
|
||||
vitalsHistory
|
||||
}
|
||||
|
||||
/**
|
||||
* Very common example of a `VitalsActivity` event involving damage.
|
||||
* @param result the fully-informed entry
|
||||
* @return the list of previous changes to this object's vital statistics
|
||||
*/
|
||||
def History(result: DamageResult): List[VitalsActivity] = {
|
||||
result.interaction.cause match {
|
||||
case _: ProjectileReason =>
|
||||
vitalsHistory = DamageFromProjectile(result) +: vitalsHistory
|
||||
lastDamage = Some(result)
|
||||
case _: ExplodingEntityReason =>
|
||||
vitalsHistory = DamageFromExplodingEntity(result) +: vitalsHistory
|
||||
lastDamage = Some(result)
|
||||
case _: PainboxReason =>
|
||||
vitalsHistory = DamageFromPainbox(result) +: vitalsHistory
|
||||
case _: EnvironmentReason =>
|
||||
vitalsHistory = DamageFromEnvironment(result) +: vitalsHistory
|
||||
case _ => ;
|
||||
vitalsHistory = DamageFrom(result) +: vitalsHistory
|
||||
if(result.adversarial.nonEmpty) {
|
||||
lastDamage = Some(result)
|
||||
}
|
||||
}
|
||||
vitalsHistory
|
||||
}
|
||||
|
||||
def LastDamage: Option[DamageResult] = lastDamage
|
||||
|
||||
/**
|
||||
* Find, specifically, the last instance of a weapon discharge vital statistics change.
|
||||
* @return information about the discharge
|
||||
*/
|
||||
def LastShot: Option[DamageResult] = {
|
||||
vitalsHistory.find({ p => p.isInstanceOf[DamageFromProjectile] }) match {
|
||||
case Some(entry: DamageFromProjectile) =>
|
||||
Some(entry.data)
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def ClearHistory(): List[VitalsActivity] = {
|
||||
val out = vitalsHistory
|
||||
vitalsHistory = List.empty[VitalsActivity]
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
//deprecated overrides
|
||||
object HealFromKit {
|
||||
def apply(Target: PlayerSource, amount: Int, kit_def: KitDefinition): HealFromKit =
|
||||
HealFromKit(kit_def, amount)
|
||||
}
|
||||
|
||||
object HealFromEquipment {
|
||||
def apply(
|
||||
Target: PlayerSource,
|
||||
user: PlayerSource,
|
||||
amount: Int,
|
||||
equipment_def: EquipmentDefinition
|
||||
): HealFromEquipment =
|
||||
HealFromEquipment(user, equipment_def, amount)
|
||||
}
|
||||
|
||||
object HealFromTerm {
|
||||
def apply(Target: PlayerSource, health: Int, armor: Int, term_def: TerminalDefinition): HealFromTerm =
|
||||
HealFromTerm(term_def, health, armor)
|
||||
}
|
||||
|
||||
object HealFromImplant {
|
||||
def apply(Target: PlayerSource, amount: Int, implant: ImplantType): HealFromImplant =
|
||||
HealFromImplant(implant, amount)
|
||||
}
|
||||
|
||||
object HealFromExoSuitChange {
|
||||
def apply(Target: PlayerSource, exosuit: ExoSuitType.Value): HealFromExoSuitChange =
|
||||
HealFromExoSuitChange(exosuit)
|
||||
}
|
||||
|
||||
object RepairFromKit {
|
||||
def apply(Target: PlayerSource, amount: Int, kit_def: KitDefinition): RepairFromKit =
|
||||
RepairFromKit(kit_def, amount)
|
||||
}
|
||||
|
||||
object RepairFromEquipment {
|
||||
def apply(
|
||||
Target: PlayerSource,
|
||||
user: PlayerSource,
|
||||
amount: Int,
|
||||
equipment_def: EquipmentDefinition
|
||||
) : RepairFromEquipment =
|
||||
RepairFromEquipment(user, equipment_def, amount)
|
||||
}
|
||||
|
||||
object RepairFromTerm {
|
||||
def apply(Target: VehicleSource, amount: Int, term_def: TerminalDefinition): RepairFromTerm =
|
||||
RepairFromTerm(term_def, amount)
|
||||
}
|
||||
|
||||
object VehicleShieldCharge {
|
||||
def apply(Target: VehicleSource, amount: Int): VehicleShieldCharge =
|
||||
VehicleShieldCharge(amount)
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.vital.base
|
||||
|
||||
import net.psforever.objects.ballistics.SourceEntry
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.damage.DamageProfile
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
import net.psforever.objects.vital.prop.DamageProperties
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package net.psforever.objects.vital.collision
|
||||
|
||||
import net.psforever.objects.ballistics.{DeployableSource, PlayerSource, VehicleSource}
|
||||
import net.psforever.objects.definition.ExoSuitDefinition
|
||||
import net.psforever.objects.sourcing.{DeployableSource, PlayerSource, VehicleSource}
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.vital.collision
|
||||
|
||||
import net.psforever.objects.ballistics.{DeployableSource, SourceEntry, VehicleSource}
|
||||
import net.psforever.objects.sourcing.{DeployableSource, SourceEntry, VehicleSource}
|
||||
import net.psforever.objects.vital.base.{DamageModifiers, DamageReason, DamageResolution, DamageType}
|
||||
import net.psforever.objects.vital.prop.DamageProperties
|
||||
import net.psforever.objects.vital.resolution.DamageAndResistance
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
package net.psforever.objects.vital.damage
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions
|
||||
import net.psforever.objects.ballistics._
|
||||
import net.psforever.objects.sourcing.VehicleSource
|
||||
import net.psforever.objects.vital.base.{DamageModifiers, DamageReason}
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
import net.psforever.objects.vital.prop.DamageWithPosition
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.vital.environment
|
||||
|
||||
import net.psforever.objects.ballistics.PlayerSource
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
import net.psforever.objects.serverobject.environment.EnvironmentAttribute
|
||||
import net.psforever.objects.sourcing.PlayerSource
|
||||
|
||||
/**
|
||||
* The deeper you move into lava, the greater the amount of health you burn through.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.vital.environment
|
||||
|
||||
import net.psforever.objects.ballistics.SourceEntry
|
||||
import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, PieceOfEnvironment}
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
|
||||
import net.psforever.objects.vital.damage.DamageCalculations
|
||||
import net.psforever.objects.vital.prop.DamageProperties
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package net.psforever.objects.vital.etc
|
||||
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
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
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
package net.psforever.objects.vital.etc
|
||||
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.ballistics.SourceEntry
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
|
||||
import net.psforever.objects.vital.prop.DamageWithPosition
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
package net.psforever.objects.vital.etc
|
||||
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.ballistics.SourceEntry
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.{Vitality, VitalityDefinition}
|
||||
import net.psforever.objects.vital.base.{DamageModifiers, DamageReason, DamageResolution}
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.vital.etc
|
||||
|
||||
import net.psforever.objects.ballistics.SourceEntry
|
||||
import net.psforever.objects.serverobject.painbox.Painbox
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions}
|
||||
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
|
||||
import net.psforever.objects.vital.damage.DamageCalculations
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue