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:
Fate-JH 2023-02-14 00:09:28 -05:00 committed by GitHub
parent 40cf783f18
commit 779054fef9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
153 changed files with 3772 additions and 2100 deletions

View file

@ -46,6 +46,7 @@ lazy val psforeverSettings = Seq(
"com.typesafe.akka" %% "akka-stream" % "2.6.17", "com.typesafe.akka" %% "akka-stream" % "2.6.17",
"com.typesafe.akka" %% "akka-testkit" % "2.6.17" % "test", "com.typesafe.akka" %% "akka-testkit" % "2.6.17" % "test",
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.17", "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-slf4j" % "2.6.17",
"com.typesafe.akka" %% "akka-cluster-typed" % "2.6.17", "com.typesafe.akka" %% "akka-cluster-typed" % "2.6.17",
"com.typesafe.akka" %% "akka-coordination" % "2.6.17", "com.typesafe.akka" %% "akka-coordination" % "2.6.17",

View file

@ -6,7 +6,7 @@ import akka.testkit.TestProbe
import base.FreedContextActorTest import base.FreedContextActorTest
import net.psforever.actors.zone.BuildingActor import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.avatar.Avatar 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.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.CommonMessages 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.resourcesilo.{ResourceSilo, ResourceSiloControl}
import net.psforever.objects.serverobject.structures.{AutoRepairStats, Building, StructureType} import net.psforever.objects.serverobject.structures.{AutoRepairStats, Building, StructureType}
import net.psforever.objects.serverobject.terminals.{OrderTerminalDefinition, Terminal, TerminalControl} 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.Vitality
import net.psforever.objects.vital.base.DamageResolution import net.psforever.objects.vital.base.DamageResolution
import net.psforever.objects.vital.damage.DamageProfile import net.psforever.objects.vital.damage.DamageProfile

View file

@ -7,11 +7,12 @@ import base.FreedContextActorTest
import net.psforever.actors.commands.NtuCommand import net.psforever.actors.commands.NtuCommand
import net.psforever.actors.zone.BuildingActor import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.avatar.Avatar 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.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.structures.{AutoRepairStats, Building, StructureType} import net.psforever.objects.serverobject.structures.{AutoRepairStats, Building, StructureType}
import net.psforever.objects.serverobject.terminals.{OrderTerminalDefinition, Terminal, TerminalControl} 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.Vitality
import net.psforever.objects.vital.base.DamageResolution import net.psforever.objects.vital.base.DamageResolution
import net.psforever.objects.vital.damage.DamageProfile import net.psforever.objects.vital.damage.DamageProfile

View file

@ -297,7 +297,7 @@ class MiddlewareActor(
send(ServerStart(nonce, serverNonce), None, None) send(ServerStart(nonce, serverNonce), None, None)
cryptoSetup() cryptoSetup()
case (Unknown30(nonce), _) => case (Unknown30(_), _) =>
/* /*
Unknown30 is used to reuse an existing crypto session when switching from login to world 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 When not handling it, it appears that the client will fall back to using ClientStart

View file

@ -5,8 +5,15 @@ import java.util.concurrent.atomic.AtomicInteger
import akka.actor.Cancellable import akka.actor.Cancellable
import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer}
import akka.actor.typed.{ActorRef, Behavior, PostStop, SupervisorStrategy} 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 org.joda.time.{LocalDateTime, Seconds}
import scala.collection.mutable import scala.collection.mutable
import scala.concurrent.{ExecutionContextExecutor, Future, Promise} import scala.concurrent.{ExecutionContextExecutor, Future, Promise}
import scala.util.{Failure, Success} 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.inventory.InventoryItem
import net.psforever.objects.loadouts.{InfantryLoadout, Loadout, VehicleLoadout} import net.psforever.objects.loadouts.{InfantryLoadout, Loadout, VehicleLoadout}
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.ballistics.PlayerSource
import net.psforever.objects.locker.LockerContainer import net.psforever.objects.locker.LockerContainer
import net.psforever.objects.sourcing.PlayerSource
import net.psforever.objects.vital.HealFromImplant import net.psforever.objects.vital.HealFromImplant
import net.psforever.packet.game.objectcreate.{ObjectClass, RibbonBars} import net.psforever.packet.game.objectcreate.{ObjectClass, RibbonBars}
import net.psforever.packet.game.{Friend => GameFriend, _} import net.psforever.packet.game.{Friend => GameFriend, _}
@ -188,7 +195,7 @@ object AvatarActor {
final case class SuspendStaminaRegeneration(duration: FiniteDuration) extends Command final case class SuspendStaminaRegeneration(duration: FiniteDuration) extends Command
/** Award battle experience points */ /** 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 */ /** Set total battle experience points */
final case class SetBep(bep: Long) extends Command 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 RemoveShortcut(slot: Int) extends Command
final case class UpdateToolDischarge(stat: EquipmentStat) extends Command
final case class AvatarResponse(avatar: Avatar) final case class AvatarResponse(avatar: Avatar)
final case class AvatarLoginResponse(avatar: Avatar) final case class AvatarLoginResponse(avatar: Avatar)
@ -618,22 +627,28 @@ object AvatarActor {
*/ */
def finalSavePlayerData(player: Player): Future[Int] = { def finalSavePlayerData(player: Player): Future[Int] = {
val health = ( val health = (
player.History.find(_.isInstanceOf[DamagingActivity]), player.History.findLast(_.isInstanceOf[DamagingActivity]),
player.History.find(_.isInstanceOf[HealingActivity]) player.History.collect { case h: HealingActivity => h }
) match { ) 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? //between damage and potential healing, which came last?
if (damage.time < heal.time) { if (damage.time < heals.last.time) {
heal.asInstanceOf[HealingActivity].amount % player.MaxHealth health + heals.map { _.amount }.sum
} else { } else {
damage.asInstanceOf[DamagingActivity].data.targetAfter.asInstanceOf[PlayerSource].health health
} }
case (Some(damage), None) => case (Some(damage: DamagingActivity), _) =>
damage.asInstanceOf[DamagingActivity].data.targetAfter.asInstanceOf[PlayerSource].health damage.data.targetAfter.asInstanceOf[PlayerSource].health
case (None, Some(heal)) => case (None, heals) if heals.nonEmpty =>
heal.asInstanceOf[HealingActivity].amount % player.MaxHealth player.History.headOption match {
case Some(es: SpawningActivity) =>
es.src.asInstanceOf[SourceWithHealthEntry].health + heals.map { _.amount }.sum
case _ => case _ =>
player.MaxHealth player.Health
}
case _ =>
player.Health
} }
savePlayerData(player, health) savePlayerData(player, health)
} }
@ -772,6 +787,21 @@ object AvatarActor {
} }
out.future 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( class AvatarActor(
@ -937,7 +967,7 @@ class AvatarActor(
case Success(characters) => case Success(characters) =>
characters.headOption match { characters.headOption match {
case Some(character) => case Some(character) =>
avatar = character.toAvatar avatar = AvatarActor.toAvatar(character)
replyTo ! AvatarResponse(avatar) replyTo ! AvatarResponse(avatar)
case None => case None =>
log.error(s"selected character $charId not found") log.error(s"selected character $charId not found")
@ -1497,7 +1527,7 @@ class AvatarActor(
val events = zone.AvatarEvents val events = zone.AvatarEvents
val guid = player.GUID val guid = player.GUID
val newHealth = player.Health = originalHealth + 1 val newHealth = player.Health = originalHealth + 1
player.History(HealFromImplant(PlayerSource(player), 1, implantType)) player.LogActivity(HealFromImplant(implantType, 1))
events ! AvatarServiceMessage( events ! AvatarServiceMessage(
zone.id, zone.id,
AvatarAction.PlanetsideAttributeToAll(guid, 0, newHealth) AvatarAction.PlanetsideAttributeToAll(guid, 0, newHealth)
@ -1595,67 +1625,24 @@ class AvatarActor(
initializeImplants() initializeImplants()
Behaviors.same Behaviors.same
case AwardBep(bep) => case UpdateToolDischarge(stats) =>
context.self ! SetBep(avatar.bep + bep) updateToolDischarge(stats)
Behaviors.same
case AwardBep(bep, modifier) =>
setBep(avatar.bep + bep, modifier)
Behaviors.same Behaviors.same
case SetBep(bep) => case SetBep(bep) =>
import ctx._ setBep(bep, ExperienceType.Normal)
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")
}
Behaviors.same Behaviors.same
case AwardCep(cep) => case AwardCep(cep) =>
context.self ! SetCep(avatar.cep + cep) setCep(avatar.cep + cep)
Behaviors.same Behaviors.same
case SetCep(cep) => case SetCep(cep) =>
import ctx._ setCep(cep)
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")
}
Behaviors.same Behaviors.same
case SetCosmetics(cosmetics) => case SetCosmetics(cosmetics) =>
@ -1690,7 +1677,7 @@ class AvatarActor(
//short-circuit if the shortcut already exists at the given location //short-circuit if the shortcut already exists at the given location
val isMacroShortcut = shortcut.isInstanceOf[Shortcut.Macro] val isMacroShortcut = shortcut.isInstanceOf[Shortcut.Macro]
val isDifferentShortcut = !(targetShortcut match { val isDifferentShortcut = !(targetShortcut match {
case Some(target) => AvatarShortcut.equals(shortcut, target) case Some(target: AvatarShortcut) => AvatarShortcut.equals(shortcut, target)
case _ => false case _ => false
}) })
if (isDifferentShortcut) { if (isDifferentShortcut) {
@ -1701,7 +1688,7 @@ class AvatarActor(
if (shortcut.isInstanceOf[Shortcut.Implant]) { if (shortcut.isInstanceOf[Shortcut.Implant]) {
//duplicate implant //duplicate implant
targetShortcut match { targetShortcut match {
case Some(existingShortcut) => case Some(existingShortcut: AvatarShortcut) =>
//redraw redundant shortcut slot with existing shortcut //redraw redundant shortcut slot with existing shortcut
sessionActor ! SessionActor.SendResponse( sessionActor ! SessionActor.SendResponse(
CreateShortcutMessage(session.get.player.GUID, slot + 1, Some(AvatarShortcut.convert(existingShortcut))) CreateShortcutMessage(session.get.player.GUID, slot + 1, Some(AvatarShortcut.convert(existingShortcut)))
@ -2116,7 +2103,7 @@ class AvatarActor(
avatars.filter(!_.deleted) foreach { a => avatars.filter(!_.deleted) foreach { a =>
val secondsSinceLastLogin = Seconds.secondsBetween(a.lastLogin, LocalDateTime.now()).getSeconds val secondsSinceLastLogin = Seconds.secondsBetween(a.lastLogin, LocalDateTime.now()).getSeconds
val avatar = a.toAvatar val avatar = AvatarActor.toAvatar(a)
val player = new Player(avatar) val player = new Player(avatar)
player.ExoSuit = ExoSuitType.Reinforced player.ExoSuit = ExoSuitType.Reinforced
@ -2356,11 +2343,12 @@ class AvatarActor(
} }
def refreshLoadouts(loadouts: Iterable[(Option[Loadout], Int)]): Unit = { def refreshLoadouts(loadouts: Iterable[(Option[Loadout], Int)]): Unit = {
val guid = session.get.player.GUID
loadouts loadouts
.map { .map {
case (Some(loadout: InfantryLoadout), index) => case (Some(loadout: InfantryLoadout), index) =>
FavoritesMessage.Infantry( FavoritesMessage.Infantry(
session.get.player.GUID, guid,
index, index,
loadout.label, loadout.label,
InfantryLoadout.DetermineSubtypeB(loadout.exosuit, loadout.subtype) InfantryLoadout.DetermineSubtypeB(loadout.exosuit, loadout.subtype)
@ -2368,14 +2356,14 @@ class AvatarActor(
case (Some(loadout: VehicleLoadout), index) case (Some(loadout: VehicleLoadout), index)
if GlobalDefinitions.isBattleFrameVehicle(loadout.vehicle_definition) => if GlobalDefinitions.isBattleFrameVehicle(loadout.vehicle_definition) =>
FavoritesMessage.Battleframe( FavoritesMessage.Battleframe(
session.get.player.GUID, guid,
index - 15, index - 15,
loadout.label, loadout.label,
VehicleLoadout.DetermineBattleframeSubtype(loadout.vehicle_definition) VehicleLoadout.DetermineBattleframeSubtype(loadout.vehicle_definition)
) )
case (Some(loadout: VehicleLoadout), index) => case (Some(loadout: VehicleLoadout), index) =>
FavoritesMessage.Vehicle( FavoritesMessage.Vehicle(
session.get.player.GUID, guid,
index - 10, index - 10,
loadout.label loadout.label
) )
@ -2389,7 +2377,7 @@ class AvatarActor(
} }
FavoritesMessage( FavoritesMessage(
mtype, mtype,
session.get.player.GUID, guid,
lineNo, lineNo,
"", "",
0 0
@ -2812,4 +2800,110 @@ class AvatarActor(
sessionActor ! SessionActor.UpdateIgnoredPlayers(FriendsResponse(MemberAction.RemoveIgnoredPlayer, GameFriend(name))) sessionActor ! SessionActor.UpdateIgnoredPlayers(FriendsResponse(MemberAction.RemoveIgnoredPlayer, GameFriend(name)))
sessionActor ! SessionActor.CharSaved 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)
}
} }

View file

@ -10,7 +10,7 @@ import net.psforever.objects.definition.ImplantDefinition
import net.psforever.packet.game.{CreateShortcutMessage, Shortcut} import net.psforever.packet.game.{CreateShortcutMessage, Shortcut}
import net.psforever.packet.game.objectcreate.DrawnSlot import net.psforever.packet.game.objectcreate.DrawnSlot
import net.psforever.types.ChatMessageType.{CMT_GMOPEN, UNK_227} 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.collection.mutable
import scala.concurrent.ExecutionContextExecutor import scala.concurrent.ExecutionContextExecutor
@ -820,7 +820,7 @@ class ChatActor(
case (CMT_ADDBATTLEEXPERIENCE, _, contents) if gmCommandAllowed => case (CMT_ADDBATTLEEXPERIENCE, _, contents) if gmCommandAllowed =>
contents.toIntOption match { contents.toIntOption match {
case Some(bep) => avatarActor ! AvatarActor.AwardBep(bep) case Some(bep) => avatarActor ! AvatarActor.AwardBep(bep, ExperienceType.Normal)
case None => case None =>
sessionActor ! SessionActor.SendResponse( sessionActor ! SessionActor.SendResponse(
message.copy(messageType = UNK_229, contents = "@CMT_ADDBATTLEEXPERIENCE_usage") message.copy(messageType = UNK_229, contents = "@CMT_ADDBATTLEEXPERIENCE_usage")

View file

@ -29,6 +29,8 @@ class SessionAvatarHandlers(
chatActor: typed.ActorRef[ChatActor.Command], chatActor: typed.ActorRef[ChatActor.Command],
implicit val context: ActorContext implicit val context: ActorContext
) extends CommonSessionInterfacingFunctionality { ) extends CommonSessionInterfacingFunctionality {
private[support] var lastSeenStreamMessage: Array[Long] = Array.fill[Long](65535)(elem=0L)
/** /**
* na * na
* *
@ -117,7 +119,7 @@ class SessionAvatarHandlers(
sendResponse(sessionData.destroyDisplayMessage(killer, victim, method, unk)) sendResponse(sessionData.destroyDisplayMessage(killer, victim, method, unk))
// TODO Temporary thing that should go somewhere else and use proper xp values // TODO Temporary thing that should go somewhere else and use proper xp values
if (killer.CharId == avatar.id && killer.Faction != victim.Faction) { 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) avatarActor ! AvatarActor.AwardCep((100 * Config.app.game.cepRate).toLong)
} }
@ -287,7 +289,7 @@ class SessionAvatarHandlers(
val r2 = 2 + r.nextInt(4000).toFloat val r2 = 2 + r.nextInt(4000).toFloat
(Vector3(r2, r2, r1), 0L, 0f) (Vector3(r2, r2, r1), 0L, 0f)
} else { } else {
val before = player.lastSeenStreamMessage(guid.guid) val before = lastSeenStreamMessage(guid.guid)
val dist = Vector3.DistanceSquared(player.Position, pos) val dist = Vector3.DistanceSquared(player.Position, pos)
(pos, now - before, dist) (pos, now - before, dist)
} }
@ -307,7 +309,7 @@ class SessionAvatarHandlers(
is_cloaking is_cloaking
) )
) )
player.lastSeenStreamMessage(guid.guid) = now lastSeenStreamMessage(guid.guid) = now
} }
} }

View file

@ -3,6 +3,8 @@ package net.psforever.actors.session.support
import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.adapter._
import akka.actor.{ActorContext, ActorRef, Cancellable, OneForOneStrategy, SupervisorStrategy, typed} import akka.actor.{ActorContext, ActorRef, Cancellable, OneForOneStrategy, SupervisorStrategy, typed}
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
import scala.collection.mutable import scala.collection.mutable
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future import scala.concurrent.Future
@ -940,17 +942,21 @@ class SessionData(
def handleFacilityBenefitShieldChargeRequest(pkt: FacilityBenefitShieldChargeRequestMessage): Unit = { def handleFacilityBenefitShieldChargeRequest(pkt: FacilityBenefitShieldChargeRequestMessage): Unit = {
val FacilityBenefitShieldChargeRequestMessage(_) = pkt val FacilityBenefitShieldChargeRequestMessage(_) = pkt
continent.GUID(player.VehicleSeated) match { val vehicleGuid = player.VehicleSeated
case Some(obj) if obj.Destroyed => () //vehicle will try to charge even if destroyed continent
case Some(obj: Vehicle) => .GUID(vehicleGuid)
obj.Actor ! Vehicle.ChargeShields(15) .foreach {
case Some(_: TurretDeployable) => () //TODO the turret will charge a shield in some circumstances case obj: Vehicle if !obj.Destroyed => //vehicle will try to charge even if destroyed
case None if player.VehicleSeated.nonEmpty => obj.Actor ! CommonMessages.ChargeShields(
log.error( 15,
s"FacilityBenefitShieldChargeRequest: ${player.Name} is seated in a vehicle that can not be found in zone ${continent.id}" 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 _ => case _ =>
log.warn(s"FacilityBenefitShieldChargeRequest: ${player.Name} is seated in an imaginary vehicle") log.warn(s"FacilityBenefitShieldChargeRequest: ${player.Name} is not seated in a vehicle")
} }
} }
@ -2284,7 +2290,7 @@ class SessionData(
* @param tplayer the player to be killed * @param tplayer the player to be killed
*/ */
def suicide(tplayer: Player): Unit = { def suicide(tplayer: Player): Unit = {
tplayer.History(PlayerSuicide(PlayerSource(tplayer))) tplayer.LogActivity(PlayerSuicide(PlayerSource(tplayer)))
tplayer.Actor ! Player.Die() tplayer.Actor ! Player.Die()
} }

View file

@ -128,20 +128,17 @@ class SessionLocalHandlers(
llu.Definition.Packet.ConstructorData(llu).get llu.Definition.Packet.ConstructorData(llu).get
) )
) )
sendResponse(TriggerSoundMessage(TriggeredSound.LLUMaterialize, llu.Position, unk = 20, 0.8000001f)) sendResponse(TriggerSoundMessage(TriggeredSound.LLUMaterialize, llu.Position, unk = 20, 0.8000001f))
case LocalResponse.LluDespawned(llu) => case LocalResponse.LluDespawned(lluGuid, position) =>
sendResponse(TriggerSoundMessage(TriggeredSound.LLUDeconstruct, llu.Position, unk = 20, 0.8000001f)) sendResponse(TriggerSoundMessage(TriggeredSound.LLUDeconstruct, position, unk = 20, 0.8000001f))
sendResponse(ObjectDeleteMessage(llu.GUID, 0)) sendResponse(ObjectDeleteMessage(lluGuid, 0))
// If the player was holding the LLU, remove it from their tracked special item slot // If the player was holding the LLU, remove it from their tracked special item slot
sessionData.specialItemSlotGuid match { sessionData.specialItemSlotGuid match {
case Some(guid) => case Some(guid) if guid == lluGuid =>
if (guid == llu.GUID) {
sessionData.specialItemSlotGuid = None sessionData.specialItemSlotGuid = None
player.Carrying = None player.Carrying = None
} case _ => ()
case _ => ;
} }
case LocalResponse.ObjectDelete(object_guid, unk) => case LocalResponse.ObjectDelete(object_guid, unk) =>

View file

@ -132,6 +132,11 @@ class SessionVehicleHandlers(
sendResponse(ObjectAttachMessage(vehicle_guid, guid, seat)) sendResponse(ObjectAttachMessage(vehicle_guid, guid, seat))
} }
case VehicleResponse.ObjectDelete(itemGuid) =>
if (tplayer_guid != guid) {
sendResponse(ObjectDeleteMessage(itemGuid, 0))
}
case VehicleResponse.Ownership(vehicleGuid) => case VehicleResponse.Ownership(vehicleGuid) =>
if (tplayer_guid == guid) { // Only the player that owns this vehicle needs the ownership packet if (tplayer_guid == guid) { // Only the player that owns this vehicle needs the ownership packet
avatarActor ! AvatarActor.SetVehicle(Some(vehicleGuid)) avatarActor ! AvatarActor.SetVehicle(Some(vehicleGuid))

View file

@ -2,6 +2,8 @@
package net.psforever.actors.session.support package net.psforever.actors.session.support
import akka.actor.{ActorContext, typed} import akka.actor.{ActorContext, typed}
import net.psforever.objects.avatar.scoring.EquipmentStat
import scala.collection.mutable import scala.collection.mutable
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future import scala.concurrent.Future
@ -9,7 +11,7 @@ import scala.concurrent.duration._
// //
import net.psforever.actors.session.{AvatarActor, ChatActor, SessionActor} import net.psforever.actors.session.{AvatarActor, ChatActor, SessionActor}
import net.psforever.login.WorldSession.{CountAmmunition, CountGrenades, FindAmmoBoxThatUses, FindEquipmentStock, FindToolThatUses, PutEquipmentInInventoryOrDrop, PutNewEquipmentInInventoryOrDrop, RemoveOldEquipmentFromInventory} 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.entity.SimpleWorldEntity
import net.psforever.objects.equipment.{ChargeFireModeDefinition, Equipment, EquipmentSize, FireModeSwitch} import net.psforever.objects.equipment.{ChargeFireModeDefinition, Equipment, EquipmentSize, FireModeSwitch}
import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow} 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.vital.projectile.ProjectileReason
import net.psforever.objects.zones.{Zone, ZoneProjectile} import net.psforever.objects.zones.{Zone, ZoneProjectile}
import net.psforever.objects._ 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.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types.{ExoSuitType, PlanetSideGUID, Vector3} 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 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 shootingStart: mutable.HashMap[PlanetSideGUID, Long] = mutable.HashMap[PlanetSideGUID, Long]()
private[support] var shootingStop: 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[support] var shotsWhileDead: Int = 0
private val projectiles: Array[Option[Projectile]] = private val projectiles: Array[Option[Projectile]] =
Array.fill[Option[Projectile]](Projectile.rangeUID - Projectile.baseUID)(None) Array.fill[Option[Projectile]](Projectile.rangeUID - Projectile.baseUID)(None)
@ -112,6 +116,7 @@ private[support] class WeaponAndProjectileOperations(
prefire -= item_guid prefire -= item_guid
shooting += item_guid shooting += item_guid
shootingStart += item_guid -> System.currentTimeMillis() shootingStart += item_guid -> System.currentTimeMillis()
ongoingShotsFired = 0
//special case - suppress the decimator's alternate fire mode, by projectile //special case - suppress the decimator's alternate fire mode, by projectile
if (tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile) { if (tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile) {
continent.AvatarEvents ! AvatarServiceMessage( continent.AvatarEvents ! AvatarServiceMessage(
@ -140,6 +145,7 @@ private[support] class WeaponAndProjectileOperations(
prefire -= item_guid prefire -= item_guid
shooting += item_guid shooting += item_guid
shootingStart += item_guid -> System.currentTimeMillis() shootingStart += item_guid -> System.currentTimeMillis()
ongoingShotsFired = 0
continent.AvatarEvents ! AvatarServiceMessage( continent.AvatarEvents ! AvatarServiceMessage(
continent.id, continent.id,
AvatarAction.ChangeFireState_Start(player.GUID, item_guid) AvatarAction.ChangeFireState_Start(player.GUID, item_guid)
@ -168,8 +174,10 @@ private[support] class WeaponAndProjectileOperations(
continent.id, continent.id,
AvatarAction.ChangeFireState_Start(pguid, item_guid) AvatarAction.ChangeFireState_Start(pguid, item_guid)
) )
ongoingShotsFired = ongoingShotsFired + tool.Discharge()
shootingStart += item_guid -> (System.currentTimeMillis() - 1L) shootingStart += item_guid -> (System.currentTimeMillis() - 1L)
} }
avatarActor ! AvatarActor.UpdateToolDischarge(EquipmentStat(tool.Definition.ObjectId,ongoingShotsFired,0,0))
tool.FireMode match { tool.FireMode match {
case _: ChargeFireModeDefinition => case _: ChargeFireModeDefinition =>
sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, tool.Magazine)) sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, tool.Magazine))
@ -410,6 +418,7 @@ private[support] class WeaponAndProjectileOperations(
) => ) =>
ResolveProjectileInteraction(proj, DamageResolution.Hit, target, hitPos) match { ResolveProjectileInteraction(proj, DamageResolution.Hit, target, hitPos) match {
case Some(resprojectile) => case Some(resprojectile) =>
avatarActor ! AvatarActor.UpdateToolDischarge(EquipmentStat(resprojectile.cause.attribution,0,1,0))
sessionData.handleDealingDamage(target, resprojectile) sessionData.handleDealingDamage(target, resprojectile)
case None => ; case None => ;
} }
@ -447,8 +456,9 @@ private[support] class WeaponAndProjectileOperations(
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) => case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
CheckForHitPositionDiscrepancy(projectile_guid, target.Position, target) CheckForHitPositionDiscrepancy(projectile_guid, target.Position, target)
ResolveProjectileInteraction(projectile, resolution1, target, target.Position) match { ResolveProjectileInteraction(projectile, resolution1, target, target.Position) match {
case Some(_projectile) => case Some(resprojectile) =>
sessionData.handleDealingDamage(target, _projectile) avatarActor ! AvatarActor.UpdateToolDischarge(EquipmentStat(resprojectile.cause.attribution,0,1,0))
sessionData.handleDealingDamage(target, resprojectile)
case None => ; case None => ;
} }
case _ => ; case _ => ;
@ -459,8 +469,9 @@ private[support] class WeaponAndProjectileOperations(
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) => case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target) CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target)
ResolveProjectileInteraction(projectile, resolution2, target, explosion_pos) match { ResolveProjectileInteraction(projectile, resolution2, target, explosion_pos) match {
case Some(_projectile) => case Some(resprojectile) =>
sessionData.handleDealingDamage(target, _projectile) avatarActor ! AvatarActor.UpdateToolDischarge(EquipmentStat(resprojectile.cause.attribution,0,1,0))
sessionData.handleDealingDamage(target, resprojectile)
case None => ; case None => ;
} }
case _ => ; case _ => ;
@ -499,8 +510,9 @@ private[support] class WeaponAndProjectileOperations(
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) => case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
CheckForHitPositionDiscrepancy(projectile_guid, hit_pos, target) CheckForHitPositionDiscrepancy(projectile_guid, hit_pos, target)
ResolveProjectileInteraction(projectile_guid, DamageResolution.Lash, target, hit_pos) match { ResolveProjectileInteraction(projectile_guid, DamageResolution.Lash, target, hit_pos) match {
case Some(projectile) => case Some(resprojectile) =>
sessionData.handleDealingDamage(target, projectile) avatarActor ! AvatarActor.UpdateToolDischarge(EquipmentStat(resprojectile.cause.attribution,0,1,0))
sessionData.handleDealingDamage(target, resprojectile)
case None => ; case None => ;
} }
case _ => ; case _ => ;
@ -554,11 +566,23 @@ private[support] class WeaponAndProjectileOperations(
val distanceToOwner = Vector3.DistanceSquared(shotOrigin, player.Position) val distanceToOwner = Vector3.DistanceSquared(shotOrigin, player.Position)
if (distanceToOwner <= acceptableDistanceToOwner) { if (distanceToOwner <= acceptableDistanceToOwner) {
val projectile_info = tool.Projectile val projectile_info = tool.Projectile
val projectile = val wguid = weaponGUID.guid
Projectile( 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, projectile_info,
tool.Definition, tool.Definition,
tool.FireMode, tool.FireMode,
mountedIn,
PlayerSource(player), PlayerSource(player),
attribution, attribution,
shotOrigin, shotOrigin,
@ -641,7 +665,7 @@ private[support] class WeaponAndProjectileOperations(
} }
avatarActor ! AvatarActor.SuspendStaminaRegeneration(3.seconds) avatarActor ! AvatarActor.SuspendStaminaRegeneration(3.seconds)
prefire += weaponGUID prefire += weaponGUID
tool.Discharge() ongoingShotsFired = ongoingShotsFired + tool.Discharge()
} }
(o, Some(tool)) (o, Some(tool))
} }

View file

@ -6,6 +6,10 @@ import akka.actor.typed.scaladsl.adapter._
import akka.actor.{ActorContext, ActorRef, Cancellable, typed} import akka.actor.{ActorContext, ActorRef, Cancellable, typed}
import akka.pattern.ask import akka.pattern.ask
import akka.util.Timeout 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.collection.mutable
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global 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.vehicles._
import net.psforever.objects.zones.{Zone, ZoneHotSpotProjector, Zoning} import net.psforever.objects.zones.{Zone, ZoneHotSpotProjector, Zoning}
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.packet.game.objectcreate.ObjectClass 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.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.{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.{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.packet.{PlanetSideGamePacket, game}
import net.psforever.persistence.Savedplayer import net.psforever.persistence.Savedplayer
import net.psforever.services.RemoverActor import net.psforever.services.RemoverActor
@ -828,6 +833,10 @@ class ZoningOperations(
spawn.LoadZonePhysicalSpawnPoint(zoneId, position, Vector3.Zero, 0 seconds, None) spawn.LoadZonePhysicalSpawnPoint(zoneId, position, Vector3.Zero, 0 seconds, None)
case _ => // not seated as the driver, in which case we can't move 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 => case None =>
spawn.deadState = DeadState.Release // cancel movement updates spawn.deadState = DeadState.Release // cancel movement updates
player.Position = position player.Position = position
@ -1162,7 +1171,7 @@ class ZoningOperations(
*/ */
def LoadZoneAsPlayer(targetPlayer: Player, zoneId: String): Unit = { def LoadZoneAsPlayer(targetPlayer: Player, zoneId: String): Unit = {
log.debug(s"LoadZoneAsPlayer: ${targetPlayer.avatar.name} loading into $zoneId") 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 if (player.isBackpack) { // important! test the actor-wide player ref, not the parameter
// respawning from unregistered player // respawning from unregistered player
TaskWorkflow.execute(sessionData.registerAvatar(targetPlayer)) TaskWorkflow.execute(sessionData.registerAvatar(targetPlayer))
@ -1436,6 +1445,10 @@ class ZoningOperations(
Deployables.Disown(continent, avatar, context.self) Deployables.Disown(continent, avatar, context.self)
spawn.drawDeloyableIcon = spawn.RedrawDeployableIcons //important for when SetCurrentAvatar initializes the UI next zone spawn.drawDeloyableIcon = spawn.RedrawDeployableIcons //important for when SetCurrentAvatar initializes the UI next zone
sessionData.squad.squadSetup = sessionData.squad.ZoneChangeSquadSetup 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 nextSpawnPoint = physSpawnPoint
shiftPosition = Some(pos) shiftPosition = Some(pos)
shiftOrientation = Some(ori) 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) { 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 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 // new player is spawning
val newPlayer = RespawnClone(player) val newPlayer = RespawnClone(player)
newPlayer.Position = pos newPlayer.Position = pos
newPlayer.Orientation = ori newPlayer.Orientation = ori
newPlayer.LogActivity(SpawningActivity(PlayerSource(newPlayer), toZoneNumber, toSpawnPoint))
LoadZoneAsPlayer(newPlayer, zoneId) LoadZoneAsPlayer(newPlayer, zoneId)
} else { } else {
avatarActor ! AvatarActor.DeactivateActiveImplants() avatarActor ! AvatarActor.DeactivateActiveImplants()
interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match { interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match {
case Some(vehicle: Vehicle) => // driver or passenger in vehicle using a warp gate, or a droppod 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) LoadZoneInVehicle(vehicle, pos, ori, zoneId)
case _ if player.HasGUID => // player is deconstructing self or instant action case _ if player.HasGUID => // player is deconstructing self or instant action
@ -2564,11 +2586,13 @@ class ZoningOperations(
) )
player.Position = pos player.Position = pos
player.Orientation = ori player.Orientation = ori
InGameHistory.SpawnReconstructionActivity(player, toZoneNumber, toSpawnPoint)
LoadZoneAsPlayer(player, zoneId) LoadZoneAsPlayer(player, zoneId)
case _ => //player is logging in case _ => //player is logging in
player.Position = pos player.Position = pos
player.Orientation = ori player.Orientation = ori
InGameHistory.SpawnReconstructionActivity(player, toZoneNumber, toSpawnPoint)
LoadZoneAsPlayer(player, zoneId) LoadZoneAsPlayer(player, zoneId)
} }
} }
@ -2698,7 +2722,6 @@ class ZoningOperations(
} }
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 82, 0)) 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 avatar.shortcuts
.zipWithIndex .zipWithIndex
.collect { case (Some(shortcut), index) => .collect { case (Some(shortcut), index) =>
@ -2739,7 +2762,7 @@ class ZoningOperations(
} }
(0 to 30).foreach(_ => { (0 to 30).foreach(_ => {
//TODO 30 for a new character only? //TODO 30 for a new character only?
sendResponse(AvatarStatisticsMessage(2, Statistics(0L))) sendResponse(AvatarStatisticsMessage(DeathStatistic(0L)))
}) })
if (tplayer.ExoSuit == ExoSuitType.MAX) { if (tplayer.ExoSuit == ExoSuitType.MAX) {
sendResponse(PlanetsideAttributeMessage(guid, 7, tplayer.Capacitor.toLong)) sendResponse(PlanetsideAttributeMessage(guid, 7, tplayer.Capacitor.toLong))
@ -2787,13 +2810,13 @@ class ZoningOperations(
) )
) )
case (Some(vehicle), Some(0)) => case (Some(vehicle), Some(0)) =>
//driver; summon any passengers and cargo vehicles left behind on previous continent //driver of vehicle
if (vehicle.Jammed) { if (vehicle.Jammed) {
//TODO something better than just canceling? //TODO something better than just canceling?
vehicle.Actor ! JammableUnit.ClearJammeredStatus() vehicle.Actor ! JammableUnit.ClearJammeredStatus()
vehicle.Actor ! JammableUnit.ClearJammeredSound() vehicle.Actor ! JammableUnit.ClearJammeredSound()
} }
//positive shield strength // positive shield strength
if (vehicle.Definition.MaxShields > 0) { if (vehicle.Definition.MaxShields > 0) {
sendResponse(PlanetsideAttributeMessage(vehicle.GUID, vehicle.Definition.shieldUiAttribute, vehicle.Shields)) sendResponse(PlanetsideAttributeMessage(vehicle.GUID, vehicle.Definition.shieldUiAttribute, vehicle.Shields))
} }
@ -2805,6 +2828,11 @@ class ZoningOperations(
if (vehicle.Definition.MaxCapacitor > 0) { if (vehicle.Definition.MaxCapacitor > 0) {
sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 113, vehicle.Capacitor)) 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( LoadZoneTransferPassengerMessages(
guid, guid,
continent.id, continent.id,
@ -2825,12 +2853,31 @@ class ZoningOperations(
} else if (originalDeadState == DeadState.Dead || player.Health == 0) { } else if (originalDeadState == DeadState.Dead || player.Health == 0) {
//killed during spawn setup or possibly a relog into a corpse (by accident?) //killed during spawn setup or possibly a relog into a corpse (by accident?)
player.Actor ! Player.Die() player.Actor ! Player.Die()
} } else {
AvatarActor.savePlayerData(player) AvatarActor.savePlayerData(player)
sessionData.displayCharSavedMsgThenRenewTimer( sessionData.displayCharSavedMsgThenRenewTimer(
Config.app.game.savedMsg.short.fixed, Config.app.game.savedMsg.short.fixed,
Config.app.game.savedMsg.short.variable 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
}
upstreamMessageCount = 0 upstreamMessageCount = 0
setAvatar = true setAvatar = true
} }

View file

@ -188,6 +188,10 @@ class BuildingActor(
def setup(details: BuildingControlDetails): Behavior[Command] = { def setup(details: BuildingControlDetails): Behavior[Command] = {
Behaviors.receiveMessage { Behaviors.receiveMessage {
case ReceptionistListing(InterstellarClusterService.InterstellarClusterServiceKey.Listing(listings))
if listings.isEmpty =>
Behaviors.same
case ReceptionistListing(InterstellarClusterService.InterstellarClusterServiceKey.Listing(listings)) => case ReceptionistListing(InterstellarClusterService.InterstellarClusterServiceKey.Listing(listings)) =>
switchToBehavior(details.copy(interstellarCluster = listings.head)) switchToBehavior(details.copy(interstellarCluster = listings.head))

View file

@ -2,7 +2,6 @@ package net.psforever.actors.zone
import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy}
import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors} import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors}
import net.psforever.objects.ballistics.SourceEntry
import net.psforever.objects.ce.Deployable import net.psforever.objects.ce.Deployable
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.objects.serverobject.structures.{StructureType, WarpGate} 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.objects.{ConstructionItem, Player, Vehicle}
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3} import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
import scala.collection.mutable.ListBuffer
import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.zone.building.MajorFacilityLogic import net.psforever.actors.zone.building.MajorFacilityLogic
import net.psforever.objects.sourcing.SourceEntry
import net.psforever.util.Database._ import net.psforever.util.Database._
import net.psforever.persistence import net.psforever.persistence
import scala.collection.mutable
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
@ -26,7 +26,7 @@ object ZoneActor {
.supervise[Command] { .supervise[Command] {
Behaviors.setup(context => new ZoneActor(context, zone)) Behaviors.setup(context => new ZoneActor(context, zone))
} }
.onFailure[Exception](SupervisorStrategy.restart) .onFailure[Exception](SupervisorStrategy.resume)
sealed trait Command sealed trait Command
@ -79,7 +79,7 @@ class ZoneActor(context: ActorContext[ZoneActor.Command], zone: Zone)
import ctx._ import ctx._
private[this] val log = org.log4s.getLogger private[this] val log = org.log4s.getLogger
val players: ListBuffer[Player] = ListBuffer() val players: mutable.ListBuffer[Player] = mutable.ListBuffer()
zone.actor = context.self zone.actor = context.self
zone.init(context.toClassic) zone.init(context.toClassic)
@ -102,7 +102,7 @@ class ZoneActor(context: ActorContext[ZoneActor.Command], zone: Zone)
case Failure(e) => log.error(e.getMessage) case Failure(e) => log.error(e.getMessage)
} }
override def onMessage(msg: Command): Behavior[Command] = { def onMessage(msg: Command): Behavior[Command] = {
msg match { msg match {
case GetZone(replyTo) => case GetZone(replyTo) =>
replyTo ! ZoneResponse(zone) replyTo ! ZoneResponse(zone)

View file

@ -38,17 +38,21 @@ case object CavernFacilityLogic
def amenityStateChange(details: BuildingWrapper, entity: Amenity, data: Option[Any]): Behavior[Command] = { def amenityStateChange(details: BuildingWrapper, entity: Amenity, data: Option[Any]): Behavior[Command] = {
entity match { entity match {
case terminal: CaptureTerminal => case terminal: CaptureTerminal =>
val building = details.building
// Notify amenities that listen for CC hack state changes, e.g. wall turrets to dismount seated players // 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 { data match {
case Some(isResecured: Boolean) => amenity.Actor ! CaptureTerminalAwareBehavior.TerminalStatusChanged(terminal, isResecured) case Some(isResecured: Boolean) =>
case _ => log(details).warn("CaptureTerminal AmenityStateChange was received with no attached data.") //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 // 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) { 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 // No map update needed - will be sent by `HackCaptureActor` when required

View file

@ -38,17 +38,21 @@ case object FacilityLogic
def amenityStateChange(details: BuildingWrapper, entity: Amenity, data: Option[Any]): Behavior[Command] = { def amenityStateChange(details: BuildingWrapper, entity: Amenity, data: Option[Any]): Behavior[Command] = {
entity match { entity match {
case terminal: CaptureTerminal => case terminal: CaptureTerminal =>
val building = details.building
// Notify amenities that listen for CC hack state changes, e.g. wall turrets to dismount seated players // 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 { data match {
case Some(isResecured: Boolean) => amenity.Actor ! CaptureTerminalAwareBehavior.TerminalStatusChanged(terminal, isResecured) case Some(isResecured: Boolean) =>
case _ => log(details).warn("CaptureTerminal AmenityStateChange was received with no attached data.") //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 // 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) { 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 // No map update needed - will be sent by `HackCaptureActor` when required

View file

@ -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.generator.{Generator, GeneratorControl}
import net.psforever.objects.serverobject.structures.{Amenity, Building} import net.psforever.objects.serverobject.structures.{Amenity, Building}
import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalAware, CaptureTerminalAwareBehavior} 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.{InterstellarClusterService, Service}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage} import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage}
@ -164,17 +165,21 @@ case object MajorFacilityLogic
details.building.Zone.actor ! ZoneActor.ZoneMapUpdate() details.building.Zone.actor ! ZoneActor.ZoneMapUpdate()
} }
case terminal: CaptureTerminal => case terminal: CaptureTerminal =>
val building = details.building
// Notify amenities that listen for CC hack state changes, e.g. wall turrets to dismount seated players // 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 { data match {
case Some(isResecured: Boolean) => amenity.Actor ! CaptureTerminalAwareBehavior.TerminalStatusChanged(terminal, isResecured) case Some(isResecured: Boolean) =>
case _ => log(details).warn("CaptureTerminal AmenityStateChange was received with no attached data.") //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 // 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) { 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 // 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] = { def alertToFactionChange(details: BuildingWrapper, building: Building): Behavior[Command] = {
alignForceDomeStatus(details) 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 Behaviors.same
} }

View file

@ -9,7 +9,10 @@ import net.psforever.objects.guid._
import net.psforever.objects.inventory.{Container, InventoryItem} import net.psforever.objects.inventory.{Container, InventoryItem}
import net.psforever.objects.locker.LockerContainer import net.psforever.objects.locker.LockerContainer
import net.psforever.objects.serverobject.PlanetSideServerObject 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.objects.zones.Zone
import net.psforever.types.{ExoSuitType, PlanetSideGUID, TransactionType, Vector3} import net.psforever.types.{ExoSuitType, PlanetSideGUID, TransactionType, Vector3}
import net.psforever.services.Service import net.psforever.services.Service
@ -31,7 +34,8 @@ object WorldSession {
* @return 1 for `true`; 0 for `false` * @return 1 for `true`; 0 for `false`
*/ */
implicit def boolToInt(b: Boolean): Int = if (b) 1 else 0 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, * Use this for placing equipment that has already been registered into a container,
@ -185,7 +189,6 @@ object WorldSession {
val localZone = obj.Zone val localZone = obj.Zone
TaskBundle( TaskBundle(
new StraightforwardTask() { new StraightforwardTask() {
private val localContainer = obj
private val localItem = item private val localItem = item
private val localSlot = slot private val localSlot = slot
private val localFunc: (Equipment, Int) => Future[Any] = PutEquipmentInInventorySlot(obj) private val localFunc: (Equipment, Int) => Future[Any] = PutEquipmentInInventorySlot(obj)
@ -556,10 +559,12 @@ object WorldSession {
val tile = item.Definition.Tile val tile = item.Definition.Tile
destination.Inventory.CheckCollisionsVar(dest, tile.Width, tile.Height) destination.Inventory.CheckCollisionsVar(dest, tile.Width, tile.Height)
} match { } match {
case Success(Nil) => case Success(Nil)
if ContainableBehavior.PermitEquipmentStow(destination, item, dest) =>
//no swap item //no swap item
(true, None) (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 //the swap item is to be registered to the source's zone
(true, Some(swapEntry.obj.GUID)) (true, Some(swapEntry.obj.GUID))
case _ => case _ =>
@ -568,13 +573,13 @@ object WorldSession {
} }
if (performSwap) { if (performSwap) {
def moveItemTaskFunc(toSlot: Int): Task = new StraightforwardTask() { def moveItemTaskFunc(toSlot: Int): Task = new StraightforwardTask() {
val localGUID = swapItemGUID //the swap item's original GUID, if any swap item val localGUID: Option[PlanetSideGUID] = swapItemGUID //the swap item's original GUID, if any swap item
val localChannel = toChannel val localChannel: String = toChannel
val localSource = source val localSource: PlanetSideServerObject with Container = source
val localDestination = destination val localDestination: PlanetSideServerObject with Container = destination
val localItem = item val localItem: Equipment = item
val localDestSlot = dest val localDestSlot: Int = dest
val localSrcSlot = toSlot val localSrcSlot: Int = toSlot
val localMoveOnComplete: Try[Any] => Unit = { val localMoveOnComplete: Try[Any] => Unit = {
case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) => case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) =>
//swapItem is not registered right now, we can not drop the item without re-registering it //swapItem is not registered right now, we can not drop the item without re-registering it
@ -673,13 +678,13 @@ object WorldSession {
} }
if (performSwap) { if (performSwap) {
def moveItemTaskFunc(toSlot: Int): Task = new StraightforwardTask() { def moveItemTaskFunc(toSlot: Int): Task = new StraightforwardTask() {
val localGUID = swapItemGUID //the swap item's original GUID, if any swap item val localGUID: Option[PlanetSideGUID] = swapItemGUID //the swap item's original GUID, if any swap item
val localChannel = toChannel val localChannel: String = toChannel
val localSource = source val localSource: PlanetSideServerObject with Container = source
val localDestination = destination val localDestination: PlanetSideServerObject with Container = destination
val localItem = item val localItem: Equipment = item
val localDestSlot = dest val localDestSlot: Int = dest
val localSrcSlot = toSlot val localSrcSlot: Int = toSlot
val localMoveOnComplete: Try[Any] => Unit = { val localMoveOnComplete: Try[Any] => Unit = {
case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) => case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) =>
//swapItem is not registered right now, we can not drop the item without re-registering it //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)( def TerminalResult(guid: PlanetSideGUID, player: Player, transaction: TransactionType.Value)(
result: Boolean result: Boolean
): Unit = { ): Unit = {
if (result) {
player.Zone.GUID(guid).collect {
case term: Terminal => player.LogActivity(TerminalUsedActivity(AmenitySource(term), transaction))
}
}
player.Zone.AvatarEvents ! AvatarServiceMessage( player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Name, player.Name,
AvatarAction.TerminalOrderResult(guid, transaction, result) AvatarAction.TerminalOrderResult(guid, transaction, result)

View file

@ -2,10 +2,10 @@
package net.psforever.objects package net.psforever.objects
import akka.actor.{ActorContext, Props} import akka.actor.{ActorContext, Props}
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
import net.psforever.objects.ce.{Deployable, DeployedItem} import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow} import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} 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.etc.TriggerUsedReason
import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
@ -35,7 +35,7 @@ class BoomerDeployable(cdef: ExplosiveDeployableDefinition)
} }
class BoomerDeployableDefinition(private val objectId: Int) extends ExplosiveDeployableDefinition(objectId) { 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 = obj.Actor =
context.actorOf(Props(classOf[BoomerDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj)) context.actorOf(Props(classOf[BoomerDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj))
} }
@ -97,7 +97,7 @@ class BoomerDeployableControl(mine: BoomerDeployable)
} }
zone.AvatarEvents! AvatarServiceMessage( zone.AvatarEvents! AvatarServiceMessage(
zone.id, zone.id,
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, trigger.GUID) AvatarAction.ObjectDelete(Service.defaultPlayerGUID, guid)
) )
TaskWorkflow.execute(GUIDTask.unregisterObject(zone.GUID, trigger)) TaskWorkflow.execute(GUIDTask.unregisterObject(zone.GUID, trigger))
case None => ; case None => ;

View file

@ -53,7 +53,7 @@ private class DefinitionWrappedInVitality(definition: ObjectDefinition)
case p: ProjectileDefinition => p case p: ProjectileDefinition => p
case _ => GlobalDefinitions.no_projectile case _ => GlobalDefinitions.no_projectile
} }
Name = { definition.Name }
DefaultHealth = 1 //just cuz DefaultHealth = 1 //just cuz
} }

View file

@ -2,7 +2,6 @@
package net.psforever.objects package net.psforever.objects
import akka.actor.{Actor, ActorContext, ActorRef, Props} import akka.actor.{Actor, ActorContext, ActorRef, Props}
import net.psforever.objects.ballistics.{DeployableSource, PlayerSource, SourceEntry}
import net.psforever.objects.ce._ import net.psforever.objects.ce._
import net.psforever.objects.definition.{DeployableDefinition, ExoSuitDefinition} import net.psforever.objects.definition.{DeployableDefinition, ExoSuitDefinition}
import net.psforever.objects.definition.converter.SmallDeployableConverter 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.PlanetSideServerObject
import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity} import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity}
import net.psforever.objects.serverobject.damage.Damageable.Target 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.etc.TrippedMineReason
import net.psforever.objects.vital.resolution.ResolutionCalculations.Output import net.psforever.objects.vital.resolution.ResolutionCalculations.Output
import net.psforever.objects.vital.{SimpleResolutions, Vitality} import net.psforever.objects.vital.{SimpleResolutions, Vitality}
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
import net.psforever.objects.vital.projectile.ProjectileReason import net.psforever.objects.vital.projectile.ProjectileReason
import net.psforever.objects.zones.Zone 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.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage}
@ -127,7 +127,7 @@ object ExplosiveDeployableControl {
* @param damage na * @param damage na
*/ */
def DamageResolution(target: ExplosiveDeployable, cause: DamageResult, damage: Int): Unit = { def DamageResolution(target: ExplosiveDeployable, cause: DamageResult, damage: Int): Unit = {
target.History(cause) target.LogActivity(cause)
if (cause.interaction.cause.source.SympatheticExplosion) { if (cause.interaction.cause.source.SympatheticExplosion) {
explodes(target, cause) explodes(target, cause)
DestructionAwareness(target, cause) DestructionAwareness(target, cause)
@ -324,32 +324,32 @@ object MineDeployableControl {
val deployableSource = DeployableSource(mine) val deployableSource = DeployableSource(mine)
val blame = mine.OwnerName match { val blame = mine.OwnerName match {
case Some(name) => 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 .LivePlayers
.find { _.Name.equals(name) } match { .find { _.Name.equals(name) } match {
case Some(player) => case Some(player) =>
//if the owner is alive in the same zone as the mine, use data from their body to create the source //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 => case None =>
//if the owner is as dead as a corpse or is not in the same zone as the mine, use defaults //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 val faction = mine.Faction
PlayerSource( PlayerSource(
name,
charId,
GlobalDefinitions.avatar, GlobalDefinitions.avatar,
mine.Faction,
exosuit, exosuit,
seated, seatedIn,
100, health = 100,
100, armor = 0,
mine.Position, mine.Position,
Vector3.Zero, Vector3.Zero,
None, None,
crouching = false, crouching = false,
jumping = false, jumping = false,
ExoSuitDefinition.Select(exosuit, faction) ExoSuitDefinition.Select(exosuit, faction),
bep = 0,
kills = Nil,
UniquePlayer(charId, name, CharacterSex.Male, mine.Faction)
) )
case None => case None =>
//credit where credit is due //credit where credit is due

View file

@ -5647,10 +5647,10 @@ object GlobalDefinitions {
advanced_ace.Name = "advanced_ace" advanced_ace.Name = "advanced_ace"
advanced_ace.Size = EquipmentSize.Rifle advanced_ace.Size = EquipmentSize.Rifle
advanced_ace.Modes += new ConstructionFireMode { 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 { 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 { advanced_ace.Modes += new ConstructionFireMode {
Item(DeployedItem.deployable_shield_generator, Set(Certification.AssaultEngineering)) Item(DeployedItem.deployable_shield_generator, Set(Certification.AssaultEngineering))
@ -9841,14 +9841,17 @@ object GlobalDefinitions {
capture_terminal.Name = "capture_terminal" capture_terminal.Name = "capture_terminal"
capture_terminal.Damageable = false capture_terminal.Damageable = false
capture_terminal.Repairable = false capture_terminal.Repairable = false
capture_terminal.FacilityHackTime = 15.minutes
secondary_capture.Name = "secondary_capture" secondary_capture.Name = "secondary_capture"
secondary_capture.Damageable = false secondary_capture.Damageable = false
secondary_capture.Repairable = false secondary_capture.Repairable = false
secondary_capture.FacilityHackTime = 1.nanosecond
vanu_control_console.Name = "vanu_control_console" vanu_control_console.Name = "vanu_control_console"
vanu_control_console.Damageable = false vanu_control_console.Damageable = false
vanu_control_console.Repairable = false vanu_control_console.Repairable = false
vanu_control_console.FacilityHackTime = 10.minutes
lodestar_repair_terminal.Name = "lodestar_repair_terminal" lodestar_repair_terminal.Name = "lodestar_repair_terminal"
lodestar_repair_terminal.Interval = 1000 lodestar_repair_terminal.Interval = 1000

View file

@ -6,6 +6,7 @@ import net.psforever.types.PlanetSideGUID
trait OwnableByPlayer { trait OwnableByPlayer {
private var owner: Option[PlanetSideGUID] = None private var owner: Option[PlanetSideGUID] = None
private var ownerName: Option[String] = None private var ownerName: Option[String] = None
private var originalOwnerName: Option[String] = None
def Owner: Option[PlanetSideGUID] = owner def Owner: Option[PlanetSideGUID] = owner
@ -33,12 +34,15 @@ trait OwnableByPlayer {
owner match { owner match {
case Some(_) => case Some(_) =>
ownerName = owner ownerName = owner
originalOwnerName = originalOwnerName.orElse(owner)
case None => case None =>
ownerName = None ownerName = None
} }
OwnerName OwnerName
} }
def OriginalOwnerName: Option[String] = originalOwnerName
/** /**
* na * na
* @param player na * @param player na

View file

@ -72,7 +72,6 @@ class Player(var avatar: Avatar)
var spectator: Boolean = false var spectator: Boolean = false
var silenced: Boolean = false var silenced: Boolean = false
var death_by: Int = 0 var death_by: Int = 0
var lastSeenStreamMessage: Array[Long] = Array.fill[Long](65535)(0L)
var lastShotSeq_time: Int = -1 var lastShotSeq_time: Int = -1
/** From PlanetsideAttributeMessage */ /** From PlanetsideAttributeMessage */

View file

@ -60,12 +60,14 @@ object Players {
* @param medic the name of the player doing the reviving * @param medic the name of the player doing the reviving
* @param item the tool being used to revive the target player * @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 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)) val magazine = item.Discharge(Some(25))
target.Zone.AvatarEvents ! AvatarServiceMessage( target.Zone.AvatarEvents ! AvatarServiceMessage(
medic, medicName,
AvatarAction.SendResponse( AvatarAction.SendResponse(
Service.defaultPlayerGUID, Service.defaultPlayerGUID,
InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine) InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine)

View file

@ -89,7 +89,7 @@ class ShieldGeneratorControl(gen: ShieldGeneratorDeployable)
val damageToShields = originalShields - shields val damageToShields = originalShields - shields
val damage = damageToHealth + damageToShields val damage = damageToHealth + damageToShields
if (WillAffectTarget(target, damage, cause)) { if (WillAffectTarget(target, damage, cause)) {
target.History(cause) target.LogActivity(cause)
DamageLog( DamageLog(
target, target,
s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields" s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields"

View file

@ -1,11 +1,11 @@
// Copyright (c) 2021 PSForever // Copyright (c) 2021 PSForever
package net.psforever.objects package net.psforever.objects
import net.psforever.objects.ballistics.SourceEntry
import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.equipment.{EffectTarget, TargetValidation} import net.psforever.objects.equipment.{EffectTarget, TargetValidation}
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.sourcing.SourceEntry
import net.psforever.objects.vital.{Vitality, VitalityDefinition} import net.psforever.objects.vital.{Vitality, VitalityDefinition}
import net.psforever.objects.vital.base.DamageType import net.psforever.objects.vital.base.DamageType
import net.psforever.objects.vital.etc.EmpReason import net.psforever.objects.vital.etc.EmpReason

View file

@ -590,13 +590,6 @@ object Vehicle {
*/ */
final case class Reactivate() 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. * Following a successful shield charge tick, display the results of the update.
* @see `FacilityBenefitShieldChargeRequestMessage` * @see `FacilityBenefitShieldChargeRequestMessage`

View file

@ -2,13 +2,14 @@
package net.psforever.objects.avatar package net.psforever.objects.avatar
import net.psforever.actors.session.AvatarActor import net.psforever.actors.session.AvatarActor
import net.psforever.objects.avatar.scoring.ScoreCard
import net.psforever.objects.definition.{AvatarDefinition, BasicDefinition} import net.psforever.objects.definition.{AvatarDefinition, BasicDefinition}
import net.psforever.objects.equipment.{EquipmentSize, EquipmentSlot} import net.psforever.objects.equipment.{EquipmentSize, EquipmentSlot}
import net.psforever.objects.inventory.LocallyRegisteredInventory import net.psforever.objects.inventory.LocallyRegisteredInventory
import net.psforever.objects.loadouts.{Loadout, SquadLoadout} import net.psforever.objects.loadouts.{Loadout, SquadLoadout}
import net.psforever.objects.locker.{LockerContainer, LockerEquipment} import net.psforever.objects.locker.{LockerContainer, LockerEquipment}
import net.psforever.objects.{GlobalDefinitions, OffhandEquipmentSlot} 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 net.psforever.types._
import org.joda.time.{Duration, LocalDateTime, Seconds} import org.joda.time.{Duration, LocalDateTime, Seconds}
@ -74,6 +75,10 @@ object Avatar {
GlobalDefinitions.super_staminakit -> 5.minutes // Temporary - Default value is 20 minutes 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 = { def makeLocker(): LockerContainer = {
new LockerContainer({ new LockerContainer({
val inv = new LocallyRegisteredInventory(numbers = 40150 until 40450) // TODO var bad val inv = new LocallyRegisteredInventory(numbers = 40150 until 40450) // TODO var bad
@ -113,11 +118,7 @@ case class MemberLists(
case class Avatar( case class Avatar(
/** unique identifier corresponding to a database table row index */ /** unique identifier corresponding to a database table row index */
id: Int, id: Int,
name: String, basic: BasicCharacterData,
faction: PlanetSideEmpire.Value,
sex: CharacterSex,
head: Int,
voice: CharacterVoice.Value,
bep: Long = 0, bep: Long = 0,
cep: Long = 0, cep: Long = 0,
stamina: Int = 100, stamina: Int = 100,
@ -132,7 +133,8 @@ case class Avatar(
decoration: ProgressDecoration = ProgressDecoration(), decoration: ProgressDecoration = ProgressDecoration(),
loadouts: Loadouts = Loadouts(), loadouts: Loadouts = Loadouts(),
cooldowns: Cooldowns = Cooldowns(), cooldowns: Cooldowns = Cooldowns(),
people: MemberLists = MemberLists() people: MemberLists = MemberLists(),
scorecard: ScoreCard = new ScoreCard()
) { ) {
assert(bep >= 0) assert(bep >= 0)
assert(cep >= 0) assert(cep >= 0)
@ -140,6 +142,16 @@ case class Avatar(
val br: BattleRank = BattleRank.withExperience(bep) val br: BattleRank = BattleRank.withExperience(bep)
val cr: CommandRank = CommandRank.withExperience(cep) 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( private def cooldown(
times: Map[String, LocalDateTime], times: Map[String, LocalDateTime],
cooldowns: Map[BasicDefinition, FiniteDuration], cooldowns: Map[BasicDefinition, FiniteDuration],

View file

@ -4,8 +4,7 @@ package net.psforever.objects.avatar
import akka.actor.{Actor, ActorRef, Props, typed} import akka.actor.{Actor, ActorRef, Props, typed}
import net.psforever.actors.session.AvatarActor import net.psforever.actors.session.AvatarActor
import net.psforever.login.WorldSession.{DropEquipmentFromInventory, HoldNewEquipmentUp, PutNewEquipmentInInventoryOrDrop, RemoveOldEquipmentFromInventory} import net.psforever.login.WorldSession.{DropEquipmentFromInventory, HoldNewEquipmentUp, PutNewEquipmentInInventoryOrDrop, RemoveOldEquipmentFromInventory}
import net.psforever.objects.{Player, _} import net.psforever.objects._
import net.psforever.objects.ballistics.PlayerSource
import net.psforever.objects.ce.Deployable import net.psforever.objects.ce.Deployable
import net.psforever.objects.definition.DeployAnimation import net.psforever.objects.definition.DeployAnimation
import net.psforever.objects.definition.converter.OCM import net.psforever.objects.definition.converter.OCM
@ -33,6 +32,7 @@ import net.psforever.objects.locker.LockerContainerControl
import net.psforever.objects.serverobject.environment._ import net.psforever.objects.serverobject.environment._
import net.psforever.objects.serverobject.repair.Repairable import net.psforever.objects.serverobject.repair.Repairable
import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad 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.collision.CollisionReason
import net.psforever.objects.vital.environment.EnvironmentReason import net.psforever.objects.vital.environment.EnvironmentReason
import net.psforever.objects.vital.etc.{PainboxReason, SuicideReason} import net.psforever.objects.vital.etc.{PainboxReason, SuicideReason}
@ -50,21 +50,21 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
with AggravatedBehavior with AggravatedBehavior
with AuraEffectBehavior with AuraEffectBehavior
with RespondsToZoneEnvironment { 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.Plasma)
ApplicableEffect(Aura.Napalm) ApplicableEffect(Aura.Napalm)
ApplicableEffect(Aura.Comet) ApplicableEffect(Aura.Comet)
ApplicableEffect(Aura.Fire) ApplicableEffect(Aura.Fire)
def InteractiveObject = player def InteractiveObject: Player = player
SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater) SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava) SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
SetInteraction(EnvironmentAttribute.Death, doInteractingWithDeath) SetInteraction(EnvironmentAttribute.Death, doInteractingWithDeath)
@ -105,8 +105,18 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
case Player.Die(Some(reason)) => case Player.Die(Some(reason)) =>
if (player.isAlive) { if (player.isAlive) {
//primary death //primary death
PerformDamage(player, reason.calculate()) val health = player.Health
suicide() 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) => 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)) events ! AvatarServiceMessage(zone.id, AvatarAction.PlanetsideAttributeToAll(guid, 0, newHealth))
player.History( player.LogActivity(
HealFromEquipment( HealFromEquipment(
PlayerSource(player),
PlayerSource(user), 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( sender() ! CommonMessages.Progress(
4, 4,
Players.FinishRevivingPlayer(player, user.Name, item), Players.FinishRevivingPlayer(player, user, item),
Players.RevivingTickAction(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)) events ! AvatarServiceMessage(zone.id, AvatarAction.PlanetsideAttributeToAll(guid, 4, player.Armor))
player.History( player.LogActivity(
RepairFromEquipment( RepairFromEquipment(
PlayerSource(player),
PlayerSource(user), 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) { if (player.Health == player.MaxHealth) {
(None, 0, 0, "@HealComplete") (None, 0, 0, "@HealComplete")
} else { } else {
player.History(HealFromKit(PlayerSource(player), 25, kdef)) player.LogActivity(HealFromKit(kdef, 25))
player.Health = player.Health + 25 player.Health = player.Health + 25
(Some(index), 0, player.Health, "") (Some(index), 0, player.Health, "")
} }
@ -250,7 +258,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
if (player.Health == player.MaxHealth) { if (player.Health == player.MaxHealth) {
(None, 0, 0, "@HealComplete") (None, 0, 0, "@HealComplete")
} else { } else {
player.History(HealFromKit(PlayerSource(player), 100, kdef)) player.LogActivity(HealFromKit(kdef, 100))
player.Health = player.Health + 100 player.Health = player.Health + 100
(Some(index), 0, player.Health, "") (Some(index), 0, player.Health, "")
} }
@ -258,7 +266,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
if (player.Armor == player.MaxArmor) { if (player.Armor == player.MaxArmor) {
(None, 0, 0, "Armor at maximum - No repairing required.") (None, 0, 0, "Armor at maximum - No repairing required.")
} else { } else {
player.History(RepairFromKit(PlayerSource(player), 200, kdef)) player.LogActivity(RepairFromKit(kdef, 200))
player.Armor = player.Armor + 200 player.Armor = player.Armor + 200
(Some(index), 4, player.Armor, "") (Some(index), 4, player.Armor, "")
} }
@ -426,13 +434,14 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
val originalArmor = player.Armor val originalArmor = player.Armor
player.ExoSuit = nextSuit player.ExoSuit = nextSuit
val toMaxArmor = player.MaxArmor val toMaxArmor = player.MaxArmor
val toArmor = val toArmor = {
if (originalSuit != nextSuit || originalSubtype != nextSubtype || originalArmor > toMaxArmor) { if (originalSuit != nextSuit || originalSubtype != nextSubtype || originalArmor > toMaxArmor) {
player.History(HealFromExoSuitChange(PlayerSource(player), nextSuit)) player.LogActivity(RepairFromExoSuitChange(nextSuit, toMaxArmor - player.Armor))
player.Armor = toMaxArmor player.Armor = toMaxArmor
} else { } else {
player.Armor = originalArmor player.Armor = originalArmor
} }
}
//ensure arm is down, even if it needs to go back up //ensure arm is down, even if it needs to go back up
if (player.DrawnSlot != Player.HandsDownSlot) { if (player.DrawnSlot != Player.HandsDownSlot) {
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.Zone.AvatarEvents ! AvatarServiceMessage(
player.Name, 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, _, _) => case Zone.Ground.ItemOnGround(item, _, _) =>
@ -528,8 +537,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
val trigger = new BoomerTrigger val trigger = new BoomerTrigger
trigger.Companion = obj.GUID trigger.Companion = obj.GUID
obj.Trigger = trigger obj.Trigger = trigger
//TODO sufficiently delete the tool zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, tool.GUID))
zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.ObjectDelete(player.GUID, tool.GUID))
TaskWorkflow.execute(GUIDTask.unregisterEquipment(zone.GUID, tool)) TaskWorkflow.execute(GUIDTask.unregisterEquipment(zone.GUID, tool))
player.Find(tool) match { player.Find(tool) match {
case Some(index) if player.VisibleSlots.contains(index) => case Some(index) if player.VisibleSlots.contains(index) =>
@ -602,8 +610,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
avatarActor ! AvatarActor.UpdatePurchaseTime(weapon) avatarActor ! AvatarActor.UpdatePurchaseTime(weapon)
true true
} }
} } else {
else {
true true
}) })
if (requestToChangeArmor && allowedToChangeArmor) { if (requestToChangeArmor && allowedToChangeArmor) {
@ -614,13 +621,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
val originalArmor = player.Armor val originalArmor = player.Armor
player.ExoSuit = exosuit //changes the value of MaxArmor to reflect the new exo-suit player.ExoSuit = exosuit //changes the value of MaxArmor to reflect the new exo-suit
val toMaxArmor = player.MaxArmor val toMaxArmor = player.MaxArmor
val toArmor = if (originalSuit != exosuit || originalSubtype != subtype || originalArmor > toMaxArmor) { val toArmor = toMaxArmor
player.History(HealFromExoSuitChange(PlayerSource(player), exosuit)) if (originalArmor != toMaxArmor) {
player.LogActivity(RepairFromExoSuitChange(exosuit, toMaxArmor - originalArmor))
}
player.Armor = toMaxArmor player.Armor = toMaxArmor
}
else {
player.Armor = originalArmor
}
//ensure arm is down, even if it needs to go back up //ensure arm is down, even if it needs to go back up
if (player.DrawnSlot != Player.HandsDownSlot) { if (player.DrawnSlot != Player.HandsDownSlot) {
player.DrawnSlot = Player.HandsDownSlot player.DrawnSlot = Player.HandsDownSlot
@ -804,7 +809,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
})) { })) {
//activate second wind //activate second wind
player.Health += 25 player.Health += 25
player.History(HealFromImplant(PlayerSource(player), 25, ImplantType.SecondWind)) player.LogActivity(HealFromImplant(ImplantType.SecondWind, 25))
avatarActor ! AvatarActor.ResetImplant(ImplantType.SecondWind) avatarActor ! AvatarActor.ResetImplant(ImplantType.SecondWind)
avatarActor ! AvatarActor.RestoreStamina(25) avatarActor ! AvatarActor.RestoreStamina(25)
} }
@ -844,7 +849,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
cause.interaction.cause.source.Aggravated.nonEmpty cause.interaction.cause.source.Aggravated.nonEmpty
} }
//log historical event (always) //log historical event (always)
target.History(cause) target.LogActivity(cause)
//stat changes //stat changes
if (damageToCapacitor > 0) { if (damageToCapacitor > 0) {
events ! AvatarServiceMessage( events ! AvatarServiceMessage(
@ -959,7 +964,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
avatarActor ! AvatarActor.DeinitializeImplants() avatarActor ! AvatarActor.DeinitializeImplants()
//log historical event //log historical event
target.History(cause) target.LogActivity(cause)
//log message //log message
cause.adversarial match { cause.adversarial match {
case Some(a) => case Some(a) =>
@ -1015,7 +1020,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
nameChannel, nameChannel,
AvatarAction.SendResponse( AvatarAction.SendResponse(
Service.defaultPlayerGUID, 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? //TODO other methods of death?

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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)
}
}
}

View file

@ -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)

View file

@ -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
)
}
}

View file

@ -2,6 +2,7 @@
package net.psforever.objects.ballistics package net.psforever.objects.ballistics
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.objects.sourcing.SourceEntry
import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.base.DamageResolution import net.psforever.objects.vital.base.DamageResolution
import net.psforever.objects.vital.etc.RadiationReason import net.psforever.objects.vital.etc.RadiationReason

View file

@ -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
}
}
}

View file

@ -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)
)
}
}

View file

@ -1,8 +1,10 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.ballistics 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.PlanetSideGameObject
import net.psforever.objects.definition.{ProjectileDefinition, ToolDefinition} import net.psforever.objects.definition.{ProjectileDefinition, ToolDefinition}
import net.psforever.objects.entity.SimpleWorldEntity import net.psforever.objects.entity.SimpleWorldEntity
@ -22,6 +24,7 @@ import net.psforever.types.Vector3
* @param profile an explanation of the damage that can be performed by this discharge * @param profile an explanation of the damage that can be performed by this discharge
* @param tool_def the weapon that caused this discharge * @param tool_def the weapon that caused this discharge
* @param fire_mode the current fire mode of the tool used * @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; * @param owner the agency that caused the weapon to produce this projectile;
* most often a player (`PlayerSource`) * most often a player (`PlayerSource`)
* @param attribute_to an object ID that refers to the method of death that would be reported; * @param attribute_to an object ID that refers to the method of death that would be reported;
@ -40,6 +43,7 @@ final case class Projectile(
profile: ProjectileDefinition, profile: ProjectileDefinition,
tool_def: ToolDefinition, tool_def: ToolDefinition,
fire_mode: FireModeDefinition, fire_mode: FireModeDefinition,
mounted_in: Option[(Int, SourceEntry)],
owner: SourceEntry, owner: SourceEntry,
attribute_to: Int, attribute_to: Int,
shot_origin: Vector3, shot_origin: Vector3,
@ -48,7 +52,7 @@ final case class Projectile(
quality: ProjectileQuality = ProjectileQuality.Normal, quality: ProjectileQuality = ProjectileQuality.Normal,
id: Long = Projectile.idGenerator.getAndIncrement(), id: Long = Projectile.idGenerator.getAndIncrement(),
fire_time: Long = System.currentTimeMillis() fire_time: Long = System.currentTimeMillis()
) extends PlanetSideGameObject ) extends PlanetSideGameObject
with BlockMapEntity { with BlockMapEntity {
Position = shot_origin Position = shot_origin
Orientation = shot_angle Orientation = shot_angle
@ -74,10 +78,11 @@ final case class Projectile(
* @return a new `Projectile` entity * @return a new `Projectile` entity
*/ */
def quality(value: ProjectileQuality): Projectile = { def quality(value: ProjectileQuality): Projectile = {
val projectile = Projectile( val projectile = new Projectile(
profile, profile,
tool_def, tool_def,
fire_mode, fire_mode,
mounted_in,
owner, owner,
attribute_to, attribute_to,
shot_origin, shot_origin,
@ -107,7 +112,7 @@ final case class Projectile(
def isMiss: Boolean = resolved == DamageResolution.Missed def isMiss: Boolean = resolved == DamageResolution.Missed
def Definition = profile def Definition: ProjectileDefinition = profile
} }
object Projectile { object Projectile {
@ -140,7 +145,7 @@ object Projectile {
shot_origin: Vector3, shot_origin: Vector3,
shot_angle: Vector3 shot_angle: Vector3
): Projectile = { ): 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)
} }
/** /**
@ -163,7 +168,7 @@ object Projectile {
shot_origin: Vector3, shot_origin: Vector3,
shot_angle: Vector3 shot_angle: Vector3
): Projectile = { ): 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( def apply(
@ -175,6 +180,6 @@ object Projectile {
shot_origin: Vector3, shot_origin: Vector3,
shot_angle: Vector3 shot_angle: Vector3
): Projectile = { ): 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)
} }
} }

View file

@ -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(" ")
}
}

View file

@ -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]
)
}
}

View file

@ -65,7 +65,7 @@ object AvatarConverter {
def MakeAppearanceData(obj: Player): Int => CharacterAppearanceData = { def MakeAppearanceData(obj: Player): Int => CharacterAppearanceData = {
val alt_model_flag: Boolean = obj.isBackpack val alt_model_flag: Boolean = obj.isBackpack
val aa: Int => CharacterAppearanceA = CharacterAppearanceA( val aa: Int => CharacterAppearanceA = CharacterAppearanceA(
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, obj.Voice), obj.avatar.basic,
CommonFieldData( CommonFieldData(
obj.Faction, obj.Faction,
bops = false, bops = false,

View file

@ -3,6 +3,7 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.llu.CaptureFlag import net.psforever.objects.serverobject.llu.CaptureFlag
import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.serverobject.structures.Building
import net.psforever.objects.sourcing.PlayerSource
import net.psforever.packet.game.objectcreate.{CaptureFlagData, PlacementData} import net.psforever.packet.game.objectcreate.{CaptureFlagData, PlacementData}
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3} import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
@ -13,7 +14,7 @@ class CaptureFlagConverter extends ObjectCreateConverter[CaptureFlag]() {
override def ConstructorData(obj : CaptureFlag) : Try[CaptureFlagData] = { override def ConstructorData(obj : CaptureFlag) : Try[CaptureFlagData] = {
val hackInfo = obj.Owner.asInstanceOf[Building].CaptureTerminal.get.HackedBy match { val hackInfo = obj.Owner.asInstanceOf[Building].CaptureTerminal.get.HackedBy match {
case Some(hackInfo) => hackInfo 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) val millisecondsRemaining = TimeUnit.MILLISECONDS.convert(math.max(0, hackInfo.hackStartTime + hackInfo.hackDuration - System.nanoTime), TimeUnit.NANOSECONDS)

View file

@ -39,7 +39,7 @@ class CharacterSelectConverter extends AvatarConverter {
*/ */
private def MakeAppearanceData(obj: Player): Int => CharacterAppearanceData = { private def MakeAppearanceData(obj: Player): Int => CharacterAppearanceData = {
val aa: Int => CharacterAppearanceA = CharacterAppearanceA( val aa: Int => CharacterAppearanceA = CharacterAppearanceA(
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, CharacterVoice.Mute), obj.avatar.basic,
CommonFieldData( CommonFieldData(
obj.Faction, obj.Faction,
bops = false, bops = false,

View file

@ -30,7 +30,7 @@ class CorpseConverter extends AvatarConverter {
*/ */
private def MakeAppearanceData(obj: Player): Int => CharacterAppearanceData = { private def MakeAppearanceData(obj: Player): Int => CharacterAppearanceData = {
val aa: Int => CharacterAppearanceA = CharacterAppearanceA( val aa: Int => CharacterAppearanceA = CharacterAppearanceA(
BasicCharacterData(obj.Name, obj.Faction, CharacterSex.Male, 0, CharacterVoice.Mute), obj.avatar.basic,
CommonFieldData( CommonFieldData(
obj.Faction, obj.Faction,
bops = false, bops = false,

View file

@ -2,10 +2,10 @@
package net.psforever.objects.equipment package net.psforever.objects.equipment
import akka.actor.{Actor, Cancellable} import akka.actor.{Actor, Cancellable}
import net.psforever.objects.ballistics.VehicleSource
import net.psforever.objects.{GlobalDefinitions, Tool, Vehicle} import net.psforever.objects.{GlobalDefinitions, Tool, Vehicle}
import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.damage.Damageable import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.sourcing.VehicleSource
import net.psforever.objects.vital.RepairFromArmorSiphon import net.psforever.objects.vital.RepairFromArmorSiphon
import net.psforever.objects.vital.etc.{ArmorSiphonModifiers, ArmorSiphonReason} import net.psforever.objects.vital.etc.{ArmorSiphonModifiers, ArmorSiphonReason}
import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.interaction.DamageInteraction
@ -76,7 +76,7 @@ object ArmorSiphonBehavior {
if before < obj.MaxHealth => if before < obj.MaxHealth =>
val after = obj.Health += amount val after = obj.Health += amount
if(before < after) { 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 val zone = obj.Zone
zone.VehicleEvents ! VehicleServiceMessage( zone.VehicleEvents ! VehicleServiceMessage(
zone.id, zone.id,

View file

@ -39,20 +39,36 @@ object EffectTarget {
false 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 = def RepairSilo(target: PlanetSideGameObject): Boolean =
target match { target match {
case v: Vehicle => case v: Vehicle => !GlobalDefinitions.isFlightVehicle(v.Definition) && CommonRepairConditions(v)
!GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0 && v.Health < v.MaxHealth && !v.History.takeWhile(System.currentTimeMillis() - _.time <= 5000L).exists(_.isInstanceOf[DamagingActivity]) case _ => false
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 = def PadLanding(target: PlanetSideGameObject): Boolean =
target match { target match {
case v: Vehicle => case v: Vehicle => GlobalDefinitions.isFlightVehicle(v.Definition) && CommonRepairConditions(v)
GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0 && v.Health < v.MaxHealth && !v.History.takeWhile(System.currentTimeMillis() - _.time <= 5000L).exists(_.isInstanceOf[DamagingActivity]) case _ => false
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 = def Player(target: PlanetSideGameObject): Boolean =

View file

@ -1,8 +1,9 @@
// Copyright (c) 2021 PSForever // Copyright (c) 2021 PSForever
package net.psforever.objects.geometry 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.geometry.d3._
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player} import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player}
import net.psforever.types.{ExoSuitType, Vector3} import net.psforever.types.{ExoSuitType, Vector3}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject package net.psforever.objects.serverobject
import net.psforever.objects.Player import net.psforever.objects.{PlanetSideGameObject, Player}
import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.hackable.Hackable
//temporary location for these messages //temporary location for these messages
@ -23,4 +23,15 @@ object CommonMessages {
final case class Progress(delta: Float, completionAction: () => Unit, tickAction: Float => Boolean) { final case class Progress(delta: Float, completionAction: () => Unit, tickAction: Float => Boolean) {
assert(delta > 0, s"progress activity change value must be positive number - $delta") 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])
} }

View file

@ -4,6 +4,7 @@ package net.psforever.objects.serverobject.damage
import akka.actor.{Actor, Cancellable} import akka.actor.{Actor, Cancellable}
import net.psforever.objects.ballistics._ import net.psforever.objects.ballistics._
import net.psforever.objects.serverobject.aura.Aura import net.psforever.objects.serverobject.aura.Aura
import net.psforever.objects.sourcing.SourceEntry
import net.psforever.objects.vital.base._ import net.psforever.objects.vital.base._
import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}

View file

@ -16,7 +16,6 @@ trait DamageableAmenity extends DamageableEntity {
override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = { override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = {
super.DestructionAwareness(target, cause) super.DestructionAwareness(target, cause)
DamageableAmenity.DestructionAwareness(target, cause) DamageableAmenity.DestructionAwareness(target, cause)
target.ClearHistory()
} }
} }

View file

@ -56,7 +56,7 @@ trait DamageableEntity extends Damageable {
val health = target.Health val health = target.Health
val damage = originalHealth - health val damage = originalHealth - health
if (WillAffectTarget(target, damage, cause)) { if (WillAffectTarget(target, damage, cause)) {
target.History(cause) target.LogActivity(cause)
DamageLog(target, s"BEFORE=$originalHealth, AFTER=$health, CHANGE=$damage") DamageLog(target, s"BEFORE=$originalHealth, AFTER=$health, CHANGE=$damage")
HandleDamage(target, cause, damage) HandleDamage(target, cause, damage)
} else { } else {

View file

@ -2,8 +2,8 @@
package net.psforever.objects.serverobject.damage package net.psforever.objects.serverobject.damage
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
import net.psforever.objects.serverobject.mount.Mountable 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.objects.vital.interaction.{DamageInteraction, DamageResult}
import net.psforever.packet.game.DamageWithPositionMessage import net.psforever.packet.game.DamageWithPositionMessage
import net.psforever.services.Service import net.psforever.services.Service

View file

@ -49,7 +49,7 @@ trait DamageableVehicle
//bfrs undergo a shiver spell before exploding //bfrs undergo a shiver spell before exploding
val obj = DamageableObject val obj = DamageableObject
obj.Health = 0 obj.Health = 0
obj.History(cause) obj.LogActivity(cause)
DestructionAwareness(obj, cause) DestructionAwareness(obj, cause)
} }
@ -74,7 +74,7 @@ trait DamageableVehicle
val damageToHealth = originalHealth - health val damageToHealth = originalHealth - health
val damageToShields = originalShields - shields val damageToShields = originalShields - shields
if (WillAffectTarget(target, damageToHealth + damageToShields, cause)) { if (WillAffectTarget(target, damageToHealth + damageToShields, cause)) {
target.History(cause) target.LogActivity(cause)
DamageLog( DamageLog(
target, target,
s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields" s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields"
@ -130,7 +130,7 @@ trait DamageableVehicle
if (obj.MountedIn.nonEmpty) { if (obj.MountedIn.nonEmpty) {
//log historical event //log historical event
target.History(cause) target.LogActivity(cause)
} }
//damage //damage
if (Damageable.CanDamageOrJammer(target, totalDamage, cause.interaction)) { if (Damageable.CanDamageOrJammer(target, totalDamage, cause.interaction)) {
@ -214,7 +214,6 @@ trait DamageableVehicle
} }
//clean up //clean up
target.Actor ! Vehicle.Deconstruct(Some(1 minute)) target.Actor ! Vehicle.Deconstruct(Some(1 minute))
target.ClearHistory()
DamageableWeaponTurret.DestructionAwareness(obj, cause) DamageableWeaponTurret.DestructionAwareness(obj, cause)
case _ => ; case _ => ;
} }

View file

@ -51,7 +51,7 @@ trait DamageableWeaponTurret
} }
//log historical event //log historical event
target.History(cause) target.LogActivity(cause)
//damage //damage
if (Damageable.CanDamageOrJammer(target, damageToHealth, cause.interaction)) { if (Damageable.CanDamageOrJammer(target, damageToHealth, cause.interaction)) {
//jammering //jammering

View file

@ -30,10 +30,10 @@ class GeneratorControl(gen: Generator)
with DamageableEntity with DamageableEntity
with RepairableEntity with RepairableEntity
with AmenityAutoRepair { with AmenityAutoRepair {
def FactionObject = gen def FactionObject: Generator = gen
def DamageableObject = gen def DamageableObject: Generator = gen
def RepairableObject = gen def RepairableObject: Generator = gen
def AutoRepairObject = gen def AutoRepairObject: Generator = gen
/** flagged to explode after some time */ /** flagged to explode after some time */
var imminentExplosion: Boolean = false var imminentExplosion: Boolean = false
/** explode when this timer completes */ /** explode when this timer completes */
@ -123,9 +123,9 @@ class GeneratorControl(gen: Generator)
imminentExplosion = false imminentExplosion = false
//hate on everything nearby //hate on everything nearby
Zone.serverSideDamage(gen.Zone, gen, Zone.explosionDamage(gen.LastDamage), explosionFunc) Zone.serverSideDamage(gen.Zone, gen, Zone.explosionDamage(gen.LastDamage), explosionFunc)
gen.ClearHistory()
case GeneratorControl.Restored() => case GeneratorControl.Restored() =>
gen.ClearHistory()
GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Online)) GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Online))
case _ => ; case _ => ;
@ -153,7 +153,6 @@ class GeneratorControl(gen: Generator)
imminentExplosion = false imminentExplosion = false
gen.Condition = PlanetSideGeneratorState.Destroyed gen.Condition = PlanetSideGeneratorState.Destroyed
GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Destroyed)) GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Destroyed))
gen.ClearHistory()
case _ => case _ =>
} }

View file

@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.hackable
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.hackable.Hackable.HackInfo import net.psforever.objects.serverobject.hackable.Hackable.HackInfo
import net.psforever.objects.sourcing.PlayerSource
import net.psforever.packet.game.TriggeredSound import net.psforever.packet.game.TriggeredSound
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3} import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
@ -24,14 +25,14 @@ trait Hackable {
def HackedBy_=(agent: Option[Player]): Option[HackInfo] = { def HackedBy_=(agent: Option[Player]): Option[HackInfo] = {
(hackedBy, agent) match { (hackedBy, agent) match {
case (None, Some(actor)) => 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)) => case (Some(info), Some(actor)) =>
if (actor.Faction == this.Faction) { if (actor.Faction == this.Faction) {
//hack cleared //hack cleared
hackedBy = None hackedBy = None
} else if (actor.Faction != info.hackerFaction) { } else if (actor.Faction != info.hackerFaction) {
//override the hack state with a new hack state if the new user has different faction affiliation //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) => case (_, None) =>
hackedBy = None hackedBy = None
@ -67,30 +68,19 @@ trait Hackable {
hackDuration = arr hackDuration = arr
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 { object Hackable {
final case class HackInfo( final case class HackInfo(
hackerName: String, player: PlayerSource,
hackerGUID: PlanetSideGUID, hackerGUID: PlanetSideGUID,
hackerFaction: PlanetSideEmpire.Value,
hackerPos: Vector3,
hackStartTime: Long, hackStartTime: Long,
hackDuration: Long hackDuration: Long
) { ) {
def Duration(time: Long): HackInfo = def hackerName: String = player.Name
HackInfo(hackerName, hackerGUID, hackerFaction, hackerPos, hackStartTime, time) def hackerFaction: PlanetSideEmpire.Value = player.Faction
def hackerPos: Vector3 = player.Position
def Duration(time: Long): HackInfo = HackInfo(player, hackerGUID, hackStartTime, time)
} }
} }

View file

@ -1,3 +1,4 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.serverobject.llu package net.psforever.objects.serverobject.llu
import net.psforever.objects.serverobject.structures.{Amenity, AmenityOwner, Building} 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} 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 * 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(tDef: CaptureFlagDefinition) extends Amenity { class CaptureFlag(private val tDef: CaptureFlagDefinition) extends Amenity {
def Definition : CaptureFlagDefinition = tDef def Definition : CaptureFlagDefinition = tDef
private var target: Building = Building.NoBuilding private var target: Building = Building.NoBuilding
@ -15,28 +35,35 @@ class CaptureFlag(tDef: CaptureFlagDefinition) extends Amenity {
private var carrier: Option[Player] = None private var carrier: Option[Player] = None
def Target: Building = target def Target: Building = target
def Target_=(new_target: Building): Building = { def Target_=(newTarget: Building): Building = {
target = new_target target = newTarget
target 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: PlanetSideEmpire.Value = faction
override def Faction_=(new_faction: PlanetSideEmpire.Value): PlanetSideEmpire.Value = { override def Faction_=(newFaction: PlanetSideEmpire.Value): PlanetSideEmpire.Value = {
faction = new_faction faction = newFaction
faction 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) { override def Position: Vector3 = if (Carrier.nonEmpty) {
carrier.get.Position carrier.get.Position
} else { } else {
Entity.Position super.Position
} }
def Carrier: Option[Player] = carrier def Carrier: Option[Player] = carrier
def Carrier_=(new_carrier: Option[Player]) : Option[Player] = { def Carrier_=(newCarrier: Option[Player]) : Option[Player] = {
carrier = new_carrier carrier = newCarrier
carrier carrier
} }
} }
@ -53,7 +80,6 @@ object CaptureFlag {
obj.Target = target obj.Target = target
obj.Owner = owner obj.Owner = owner
obj.Faction = faction obj.Faction = faction
obj obj
} }
} }

View file

@ -13,6 +13,7 @@ import net.psforever.types.Vector3
*/ */
class CaptureFlagSocket(tDef: CaptureFlagSocketDefinition) class CaptureFlagSocket(tDef: CaptureFlagSocketDefinition)
extends Amenity { extends Amenity {
private var lastFlag: Option[CaptureFlag] = None
private var spawnedCaptureFlag: Option[CaptureFlag] = None private var spawnedCaptureFlag: Option[CaptureFlag] = None
def captureFlag: Option[CaptureFlag] = spawnedCaptureFlag 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: CaptureFlag): Option[CaptureFlag] = captureFlag_=(Some(flag))
def captureFlag_=(flag: Option[CaptureFlag]): Option[CaptureFlag] = { def captureFlag_=(flag: Option[CaptureFlag]): Option[CaptureFlag] = {
lastFlag = flag.orElse(lastFlag)
spawnedCaptureFlag = flag spawnedCaptureFlag = flag
captureFlag captureFlag
} }
def previousFlag: Option[CaptureFlag] = lastFlag
def clearOldFlagData(): Unit = {
if (spawnedCaptureFlag.isEmpty) {
lastFlag = None
}
}
def Definition : CaptureFlagSocketDefinition = tDef def Definition : CaptureFlagSocketDefinition = tDef
} }

View file

@ -3,13 +3,13 @@ package net.psforever.objects.serverobject.pad.process
import akka.actor.Props import akka.actor.Props
import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ballistics.SourceEntry
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} 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.etc.{ExplodingEntityReason, VehicleSpawnReason}
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
import net.psforever.objects.vital.prop.DamageProperties import net.psforever.objects.vital.prop.DamageProperties
import net.psforever.objects.vital.Vitality
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global

View file

@ -1,13 +1,14 @@
package net.psforever.objects.serverobject.painbox package net.psforever.objects.serverobject.painbox
import akka.actor.Cancellable import akka.actor.Cancellable
import net.psforever.objects.ballistics.SourceEntry
import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl} 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.Vitality
import net.psforever.objects.vital.etc.PainboxReason import net.psforever.objects.vital.etc.PainboxReason
import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.{Default, GlobalDefinitions, Player} import net.psforever.objects.{Default, GlobalDefinitions, Player}
import net.psforever.services.Service
import net.psforever.types.{PlanetSideEmpire, Vector3} import net.psforever.types.{PlanetSideEmpire, Vector3}
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
@ -58,7 +59,7 @@ class PainboxControl(painbox: Painbox) extends PoweredAmenityControl {
} }
var commonBehavior: Receive = { var commonBehavior: Receive = {
case "startup" => case Service.Startup() =>
if (!disabled && domain.midpoint == Vector3.Zero) { if (!disabled && domain.midpoint == Vector3.Zero) {
initialStartup() initialStartup()
} }

View file

@ -9,6 +9,7 @@ import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.{Default, NtuContainer, NtuStorageBehavior} import net.psforever.objects.{Default, NtuContainer, NtuStorageBehavior}
import net.psforever.objects.serverobject.damage.Damageable import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.serverobject.structures.{Amenity, AutoRepairStats, Building} import net.psforever.objects.serverobject.structures.{Amenity, AutoRepairStats, Building}
import net.psforever.objects.vital.RepairFromAmenityAutoRepair
import net.psforever.util.Config import net.psforever.util.Config
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -96,6 +97,7 @@ trait AmenityAutoRepair
wholeRepairAmount + wholeOverflow wholeRepairAmount + wholeOverflow
} }
PerformRepairs(obj, finalRepairAmount) PerformRepairs(obj, finalRepairAmount)
obj.LogActivity(RepairFromAmenityAutoRepair(finalRepairAmount))
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()
val taskTime = currentTime - autoRepairQueueTask.getOrElse(currentTime) val taskTime = currentTime - autoRepairQueueTask.getOrElse(currentTime)
autoRepairQueueTask = Some(0L) autoRepairQueueTask = Some(0L)
@ -148,7 +150,8 @@ trait AmenityAutoRepair
* or if the current process has stalled. * or if the current process has stalled.
*/ */
private def startAutoRepairIfStopped(): Unit = { private def startAutoRepairIfStopped(): Unit = {
if(autoRepairQueueTask.isEmpty || stallDetection(stallTime = 15000L)) { val stallTime: Long = 15000L
if(autoRepairQueueTask.isEmpty || stallDetection(stallTime)) {
trySetupAutoRepairInitial() trySetupAutoRepairInitial()
} }
} }

View file

@ -3,6 +3,8 @@ package net.psforever.objects.serverobject.repair
import net.psforever.objects.Tool import net.psforever.objects.Tool
import net.psforever.objects.serverobject.structures.Amenity 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} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
/** /**
@ -23,7 +25,7 @@ trait RepairableAmenity extends RepairableEntity {
object RepairableAmenity { 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. * These `PlanetSideAttributeMessage` attributes are the same as reported during zone load client configuration.
* @see `AvatarAction.PlanetsideAttributeToAll` * @see `AvatarAction.PlanetsideAttributeToAll`
* @see `AvatarServiceMessage` * @see `AvatarServiceMessage`
@ -37,5 +39,27 @@ object RepairableAmenity {
val targetGUID = target.GUID val targetGUID = target.GUID
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 50, 0)) events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 50, 0))
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 51, 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))
} }
} }

View file

@ -1,6 +1,8 @@
//Copyright (c) 2020 PSForever //Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.repair 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.objects.{Player, Tool}
import net.psforever.packet.game.{InventoryStateMessage, RepairMessage} import net.psforever.packet.game.{InventoryStateMessage, RepairMessage}
import net.psforever.types.{PlanetSideEmpire, Vector3} import net.psforever.types.{PlanetSideEmpire, Vector3}
@ -92,6 +94,13 @@ trait RepairableEntity extends Repairable {
InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine.toLong) InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine.toLong)
) )
) )
target.LogActivity(
RepairFromEquipment(
PlayerSource(player),
item.Definition,
repairValue
)
)
PerformRepairs(target, repairValue) PerformRepairs(target, repairValue)
} else { } else {
originalHealth originalHealth

View file

@ -16,6 +16,7 @@ trait RepairableWeaponTurret extends RepairableEntity {
override def Restoration(target: Repairable.Target): Unit = { override def Restoration(target: Repairable.Target): Unit = {
super.Restoration(target) super.Restoration(target)
RepairableAmenity.RestorationOfHistory(target)
RepairableWeaponTurret.Restoration(RepairableObject) RepairableWeaponTurret.Restoration(RepairableObject)
} }
} }

View file

@ -1,8 +1,6 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.structures package net.psforever.objects.serverobject.structures
import java.util.concurrent.TimeUnit
import akka.actor.ActorContext import akka.actor.ActorContext
import net.psforever.actors.zone.BuildingActor import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.{GlobalDefinitions, NtuContainer, Player} import net.psforever.objects.{GlobalDefinitions, NtuContainer, Player}
@ -20,6 +18,10 @@ import akka.actor.typed.scaladsl.adapter._
import net.psforever.objects.serverobject.llu.{CaptureFlag, CaptureFlagSocket} import net.psforever.objects.serverobject.llu.{CaptureFlag, CaptureFlagSocket}
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
import scala.collection.mutable
import java.util.concurrent.TimeUnit
import scala.concurrent.duration._
class Building( class Building(
private val name: String, private val name: String,
private val building_guid: Int, private val building_guid: Int,
@ -27,18 +29,19 @@ class Building(
private val zone: Zone, private val zone: Zone,
private val buildingType: StructureType, private val buildingType: StructureType,
private val buildingDefinition: BuildingDefinition private val buildingDefinition: BuildingDefinition
) extends AmenityOwner ) extends AmenityOwner
with BlockMapEntity { with BlockMapEntity {
private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
private var playersInSOI: List[Player] = List.empty private var playersInSOI: List[Player] = List.empty
private val capitols = List("Thoth", "Voltan", "Neit", "Anguta", "Eisa", "Verica") private val capitols = List("Thoth", "Voltan", "Neit", "Anguta", "Eisa", "Verica")
private var forceDomeActive: Boolean = false private var forceDomeActive: Boolean = false
private var participationFunc: Building.ParticipationLogic = Building.NoParticipation
super.Zone_=(zone) super.Zone_=(zone)
super.GUID_=(PlanetSideGUID(building_guid)) //set super.GUID_=(PlanetSideGUID(building_guid)) //set
Invalidate() //unset; guid can be used during setup, but does not stop being registered properly later 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 def Name: String = name
@ -82,10 +85,13 @@ class Building(
box.Actor ! Painbox.Stop() box.Actor ! Painbox.Stop()
} }
} }
participationFunc.Players(building = this, list)
playersInSOI = list playersInSOI = list
playersInSOI playersInSOI
} }
def PlayerContribution: Map[Player, Long] = participationFunc.Contribution()
// Get all lattice neighbours // Get all lattice neighbours
def AllNeighbours: Option[Set[Building]] = { def AllNeighbours: Option[Set[Building]] = {
zone.Lattice find this match { zone.Lattice find this match {
@ -139,7 +145,11 @@ class Building(
case _ => false 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] = { def GetFlag: Option[CaptureFlag] = {
GetFlagSocket match { GetFlagSocket match {
case Some(socket) => socket.captureFlag case Some(socket) => socket.captureFlag
@ -175,10 +185,10 @@ class Building(
val (hacking, hackingFaction, hackTime): (Boolean, PlanetSideEmpire.Value, Long) = CaptureTerminal match { val (hacking, hackingFaction, hackTime): (Boolean, PlanetSideEmpire.Value, Long) = CaptureTerminal match {
case Some(obj: CaptureTerminal with Hackable) => case Some(obj: CaptureTerminal with Hackable) =>
obj.HackedBy match { obj.HackedBy match {
case Some(Hackable.HackInfo(_, _, hfaction, _, start, length)) => case Some(Hackable.HackInfo(p, _, start, length)) =>
val hack_time_remaining_ms = val hack_time_remaining_ms =
TimeUnit.MILLISECONDS.convert(math.max(0, start + length - System.nanoTime), TimeUnit.NANOSECONDS) 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 _ => case _ =>
(false, PlanetSideEmpire.NEUTRAL, 0L) (false, PlanetSideEmpire.NEUTRAL, 0L)
} }
@ -233,19 +243,25 @@ class Building(
} }
def hasLatticeBenefit(wantedBenefit: LatticeBenefit): Boolean = { def hasLatticeBenefit(wantedBenefit: LatticeBenefit): Boolean = {
val genState = Generator match { val baseDownState = (NtuSource match {
case Some(obj) => obj.Condition != PlanetSideGeneratorState.Destroyed case Some(ntu) => ntu.NtuCapacitor < 1f
case _ => false case _ => false
} }) ||
if (genState || Faction == PlanetSideEmpire.NEUTRAL) { (Generator match {
case Some(obj) => obj.Condition == PlanetSideGeneratorState.Destroyed
case _ => false
}) ||
Faction == PlanetSideEmpire.NEUTRAL
if (baseDownState) {
false false
} else { } else {
// Check this Building is on the lattice first // Check this Building is on the lattice first
zone.Lattice find this match { zone.Lattice find this match {
case Some(_) => case Some(_) =>
val faction = Faction
val subGraph = Zone.Lattice filter ( val subGraph = Zone.Lattice filter (
(b : Building) => (b: Building) =>
b.Faction == this.Faction && b.Faction == faction &&
!b.CaptureTerminalIsHacked && !b.CaptureTerminalIsHacked &&
b.NtuLevel > 0 && b.NtuLevel > 0 &&
(b.Generator.isEmpty || b.Generator.get.Condition != PlanetSideGeneratorState.Destroyed) (b.Generator.isEmpty || b.Generator.get.Condition != PlanetSideGeneratorState.Destroyed)
@ -282,7 +298,10 @@ class Building(
case Some(obj) => obj.Condition case Some(obj) => obj.Condition
case _ => PlanetSideGeneratorState.Normal case _ => PlanetSideGeneratorState.Normal
} }
if (genState == PlanetSideGeneratorState.Destroyed || Faction == PlanetSideEmpire.NEUTRAL) { if (genState == PlanetSideGeneratorState.Destroyed ||
Faction == PlanetSideEmpire.NEUTRAL ||
CaptureTerminalIsHacked
) {
Set(LatticeBenefit.None) Set(LatticeBenefit.None)
} else { } else {
friendlyFunctionalNeighborhood().map { _.Definition.LatticeLinkBenefit } friendlyFunctionalNeighborhood().map { _.Definition.LatticeLinkBenefit }
@ -339,10 +358,46 @@ class Building(
override def Continent_=(zone: String): String = Continent //building never leaves zone after being set in constructor 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 def Definition: BuildingDefinition = buildingDefinition
} }
object Building { 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 = final val NoBuilding: Building =
new Building(name = "", 0, map_id = 0, Zone.Nowhere, StructureType.Platform, GlobalDefinitions.building) { new Building(name = "", 0, map_id = 0, Zone.Nowhere, StructureType.Platform, GlobalDefinitions.building) {
override def Faction_=(faction: PlanetSideEmpire.Value): PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL override def Faction_=(faction: PlanetSideEmpire.Value): PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL

View file

@ -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}")
}
}
}

View file

@ -2,8 +2,13 @@
package net.psforever.objects.serverobject.terminals package net.psforever.objects.serverobject.terminals
import akka.actor.{ActorRef, Cancellable} 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._
import net.psforever.objects.ballistics.{PlayerSource, VehicleSource}
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
@ -12,16 +17,14 @@ import net.psforever.objects.serverobject.damage.DamageableAmenity
import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior} import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior}
import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableAmenity} import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableAmenity}
import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl} import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl}
import net.psforever.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.packet.game.InventoryStateMessage
import net.psforever.services.Service import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} 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`. * 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, * Although this "terminal" itself does not accept the same messages as a normal `Terminal` object,
@ -35,16 +38,16 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit)
with DamageableAmenity with DamageableAmenity
with RepairableAmenity with RepairableAmenity
with AmenityAutoRepair { with AmenityAutoRepair {
def FactionObject = term def FactionObject: Terminal with ProximityUnit = term
def HackableObject = term def HackableObject: Terminal with ProximityUnit = term
def TerminalObject = term def TerminalObject: Terminal with ProximityUnit = term
def DamageableObject = term def DamageableObject: Terminal with ProximityUnit = term
def RepairableObject = term def RepairableObject: Terminal with ProximityUnit = term
def AutoRepairObject = term def AutoRepairObject: Terminal with ProximityUnit = term
var terminalAction: Cancellable = Default.Cancellable var terminalAction: Cancellable = Default.Cancellable
val callbacks: mutable.ListBuffer[ActorRef] = new mutable.ListBuffer[ActorRef]() val callbacks: mutable.ListBuffer[ActorRef] = new mutable.ListBuffer[ActorRef]()
val log = org.log4s.getLogger val log: Logger = org.log4s.getLogger
val commonBehavior: Receive = checkBehavior val commonBehavior: Receive = checkBehavior
.orElse(takesDamage) .orElse(takesDamage)
@ -242,57 +245,9 @@ object ProximityTerminalControl {
*/ */
def HealthAndArmorTerminal(unit: Terminal with ProximityUnit, target: Player): Boolean = { def HealthAndArmorTerminal(unit: Terminal with ProximityUnit, target: Player): Boolean = {
val medDef = unit.Definition.asInstanceOf[MedicalTerminalDefinition] val medDef = unit.Definition.asInstanceOf[MedicalTerminalDefinition]
val healAmount = medDef.HealAmount val fullHeal = HealAction(unit, target, medDef.HealAmount, PlayerHealthCallback)
val healthFull: Boolean = if (healAmount != 0 && target.Health < target.MaxHealth) { val fullRepair = ArmorRepairAction(unit, target, medDef.ArmorAmount)
target.History(HealFromTerm(PlayerSource(target), healAmount, 0, medDef)) fullHeal && fullRepair
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
}
/**
* 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
} }
/** /**
@ -303,27 +258,96 @@ object ProximityTerminalControl {
* @param target the vehicle being repaired * @param target the vehicle being repaired
*/ */
def VehicleRepairTerminal(unit: Terminal with ProximityUnit, target: Vehicle): Boolean = { def VehicleRepairTerminal(unit: Terminal with ProximityUnit, target: Vehicle): Boolean = {
val medDef = unit.Definition.asInstanceOf[MedicalTerminalDefinition] unit.Definition match {
val healAmount = medDef.HealAmount case medDef: MedicalTerminalDefinition if !target.Destroyed && unit.Validate(target) =>
HealAction(unit, target, medDef.HealAmount, VehicleHealthCallback)
case _ =>
true
}
}
/**
* 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 maxHealth = target.MaxHealth
val noMoreHeal = if (!target.Destroyed && unit.Validate(target)) { val nextHealth = health + healAmount
//repair vehicle if (healAmount != 0 && health < maxHealth) {
if (healAmount > 0 && target.Health < maxHealth) { val finalHealthAmount = if (nextHealth > maxHealth) {
target.Health = target.Health + healAmount nextHealth - maxHealth
target.History(RepairFromTerm(VehicleSource(target), healAmount, medDef)) } 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 val zone = target.Zone
zone.VehicleEvents ! VehicleServiceMessage( zone.VehicleEvents ! VehicleServiceMessage(
zone.id, zone.id,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 0, target.Health) VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 0, target.Health)
) )
target.Health == maxHealth }
/**
* 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 { } else {
true true
} }
} else {
true
}
noMoreHeal
} }
/** /**
@ -337,7 +361,7 @@ object ProximityTerminalControl {
def WeaponRechargeTerminal(unit: Terminal with ProximityUnit, target: Player): Boolean = { def WeaponRechargeTerminal(unit: Terminal with ProximityUnit, target: Player): Boolean = {
val result = WeaponsBeingRechargedWithSomeAmmunition( val result = WeaponsBeingRechargedWithSomeAmmunition(
unit.Definition.asInstanceOf[WeaponRechargeTerminalDefinition].AmmoAmount, 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 events = unit.Zone.AvatarEvents
val channel = target.Name val channel = target.Name
@ -349,7 +373,7 @@ object ProximityTerminalControl {
) )
} }
} }
!result.unzip._2.flatten.exists { slot => slot.Magazine < slot.MaxMagazine() } !result.flatMap { _._2 }.exists { slot => slot.Magazine < slot.MaxMagazine() }
} }
/** /**
@ -375,7 +399,7 @@ object ProximityTerminalControl {
) )
} }
} }
!result.unzip._2.flatten.exists { slot => slot.Magazine < slot.MaxMagazine() } !result.flatMap { _._2 }.exists { slot => slot.Magazine < slot.MaxMagazine() }
} }
/** /**

View file

@ -1,12 +1,15 @@
package net.psforever.objects.serverobject.terminals.capture package net.psforever.objects.serverobject.terminals.capture
import net.psforever.objects.serverobject.structures.AmenityDefinition import net.psforever.objects.serverobject.structures.AmenityDefinition
import scala.concurrent.duration.{Duration, FiniteDuration}
class CaptureTerminalDefinition(objectId: Int) extends AmenityDefinition(objectId) { class CaptureTerminalDefinition(objectId: Int) extends AmenityDefinition(objectId) {
Name = objectId match { private var hackTime: FiniteDuration = Duration.Zero
case 158 => "capture_terminal"
case 751 => "secondary_capture" def FacilityHackTime: FiniteDuration = hackTime
case 930 => "vanu_control_console"
case _ => throw new IllegalArgumentException("Not a valid capture terminal object id") def FacilityHackTime_=(time: FiniteDuration): FiniteDuration = {
hackTime = time
FacilityHackTime
} }
} }

View file

@ -1,13 +1,16 @@
package net.psforever.objects.serverobject.terminals.capture package net.psforever.objects.serverobject.terminals.capture
import akka.util.Timeout
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.sourcing.PlayerSource
import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
object CaptureTerminals { object CaptureTerminals {import scala.concurrent.duration._
private val log = org.log4s.getLogger("CaptureTerminals") private val log = org.log4s.getLogger("CaptureTerminals")
private implicit val timeout: Timeout = 1.second
/** /**
* The process of hacking an object is completed. * The process of hacking an object is completed.
@ -22,39 +25,34 @@ object CaptureTerminals {
def FinishHackingCaptureConsole(target: CaptureTerminal, hackingPlayer: Player, unk: Long)(): Unit = { def FinishHackingCaptureConsole(target: CaptureTerminal, hackingPlayer: Player, unk: Long)(): Unit = {
import akka.pattern.ask import akka.pattern.ask
import scala.concurrent.duration._
log.info(s"${hackingPlayer.toString} hacked a ${target.Definition.Name}") log.info(s"${hackingPlayer.toString} hacked a ${target.Definition.Name}")
// Wait for the target actor to set the HackedBy property // Wait for the target actor to set the HackedBy property
import scala.concurrent.ExecutionContext.Implicits.global 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(_) => case Success(_) =>
target.Zone.LocalEvents ! LocalServiceMessage( val zone = target.Zone
target.Zone.id, 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) LocalAction.TriggerSound(hackingPlayer.GUID, target.HackSound, hackingPlayer.Position, 30, 0.49803925f)
) )
val isResecured = hackingPlayer.Faction == target.Faction
if (isResecured) { if (isResecured) {
// Resecure the CC // Resecure the CC
target.Zone.LocalEvents ! LocalServiceMessage( events ! LocalServiceMessage(
target.Zone.id, zoneid,
LocalAction.ResecureCaptureTerminal( LocalAction.ResecureCaptureTerminal(target, PlayerSource(hackingPlayer))
target
)
) )
} else { } else {
// Start the CC hack timer // Start the CC hack timer
target.Zone.LocalEvents ! LocalServiceMessage( events ! LocalServiceMessage(
target.Zone.id, zoneid,
LocalAction.StartCaptureTerminalHack( LocalAction.StartCaptureTerminalHack(target)
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}")
} }
} }
} }

View file

@ -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.damage.{Damageable, DamageableEntity, DamageableMountable}
import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior} import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior}
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} 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.structures.{Building, PoweredAmenityControl}
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAwareBehavior import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAwareBehavior
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
@ -27,13 +27,13 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
with RepairableEntity with RepairableEntity
with AmenityAutoRepair with AmenityAutoRepair
with CaptureTerminalAwareBehavior { with CaptureTerminalAwareBehavior {
def MountableObject = mech def MountableObject: ImplantTerminalMech = mech
def HackableObject = mech def HackableObject: ImplantTerminalMech = mech
def FactionObject = mech def FactionObject: ImplantTerminalMech = mech
def DamageableObject = mech def DamageableObject: ImplantTerminalMech = mech
def RepairableObject = mech def RepairableObject: ImplantTerminalMech = mech
def AutoRepairObject = mech def AutoRepairObject: ImplantTerminalMech = mech
def CaptureTerminalAwareObject = mech def CaptureTerminalAwareObject: ImplantTerminalMech = mech
def commonBehavior: Receive = def commonBehavior: Receive =
checkBehavior checkBehavior
@ -98,7 +98,6 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = { override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = {
super.DestructionAwareness(target, cause) super.DestructionAwareness(target, cause)
DamageableMountable.DestructionAwareness(DamageableObject, cause) DamageableMountable.DestructionAwareness(DamageableObject, cause)
target.ClearHistory()
} }
override def PerformRepairs(target : Damageable.Target, amount : Int) : Int = { override def PerformRepairs(target : Damageable.Target, amount : Int) : Int = {
@ -136,4 +135,9 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
def powerTurnOnCallback(): Unit = { def powerTurnOnCallback(): Unit = {
tryAutoRepair() tryAutoRepair()
} }
override def Restoration(obj: Target): Unit = {
super.Restoration(obj)
RepairableAmenity.RestorationOfHistory(obj)
}
} }

View file

@ -1,6 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.turret package net.psforever.objects.serverobject.turret
import akka.actor.Cancellable
import net.psforever.objects.{Default, GlobalDefinitions, Player, Tool} import net.psforever.objects.{Default, GlobalDefinitions, Player, Tool}
import net.psforever.objects.equipment.{Ammo, JammableMountedWeapons} import net.psforever.objects.equipment.{Ammo, JammableMountedWeapons}
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
@ -37,16 +38,16 @@ class FacilityTurretControl(turret: FacilityTurret)
with AmenityAutoRepair with AmenityAutoRepair
with JammableMountedWeapons with JammableMountedWeapons
with CaptureTerminalAwareBehavior { with CaptureTerminalAwareBehavior {
def FactionObject = turret def FactionObject: FacilityTurret = turret
def MountableObject = turret def MountableObject: FacilityTurret = turret
def JammableObject = turret def JammableObject: FacilityTurret = turret
def DamageableObject = turret def DamageableObject: FacilityTurret = turret
def RepairableObject = turret def RepairableObject: FacilityTurret = turret
def AutoRepairObject = turret def AutoRepairObject: FacilityTurret = turret
def CaptureTerminalAwareObject = turret def CaptureTerminalAwareObject: FacilityTurret = turret
// Used for timing ammo recharge for vanu turrets in caves // Used for timing ammo recharge for vanu turrets in caves
var weaponAmmoRechargeTimer = Default.Cancellable var weaponAmmoRechargeTimer: Cancellable = Default.Cancellable
override def postStop(): Unit = { override def postStop(): Unit = {
super.postStop() super.postStop()
@ -186,7 +187,7 @@ class FacilityTurretControl(turret: FacilityTurret)
seat.unmount(player) seat.unmount(player)
player.VehicleSeated = None player.VehicleSeated = None
if (player.HasGUID) { 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 => ; case None => ;
} }

View file

@ -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
})
}
}

View file

@ -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)
)
}
}

View file

@ -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")
)
)
}
}

View file

@ -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
}
}
}

View file

@ -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)
)
}
}

View 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)
}

View file

@ -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(" ")
}
}

View file

@ -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
})
}
}

View file

@ -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

View file

@ -0,0 +1,7 @@
// Copyright (c) 2023 PSForever
package net.psforever.objects.sourcing
final case class UniqueDeployable(
spawnTime: Long,
originalOwnerName: String
) extends SourceUniqueness

View file

@ -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
})
}
}

View file

@ -2,7 +2,8 @@
package net.psforever.objects.vehicles package net.psforever.objects.vehicles
import net.psforever.objects.Vehicle 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.Vitality
import net.psforever.objects.vital.base.{DamageResolution, DamageType} import net.psforever.objects.vital.base.{DamageResolution, DamageType}
import net.psforever.objects.vital.etc.RadiationReason import net.psforever.objects.vital.etc.RadiationReason

View file

@ -3,7 +3,6 @@ package net.psforever.objects.vehicles.control
import akka.actor.Cancellable import akka.actor.Cancellable
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.ballistics.VehicleSource
import net.psforever.objects.definition.{ToolDefinition, VehicleDefinition} import net.psforever.objects.definition.{ToolDefinition, VehicleDefinition}
import net.psforever.objects.equipment._ import net.psforever.objects.equipment._
import net.psforever.objects.inventory.{GridInventory, InventoryItem} 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.containable.ContainableBehavior
import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.serverobject.transfer.TransferBehavior import net.psforever.objects.serverobject.transfer.TransferBehavior
import net.psforever.objects.sourcing.{SourceEntry, VehicleSource}
import net.psforever.objects.vehicles._ 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.vital.interaction.DamageResult
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.game._ import net.psforever.packet.game._
@ -37,12 +37,12 @@ class BfrControl(vehicle: Vehicle)
* `Cancellable.alreadyCancelled` indicates a permanant cessation of charging activity (vehicle destruction) */ * `Cancellable.alreadyCancelled` indicates a permanant cessation of charging activity (vehicle destruction) */
var shieldCharge: Cancellable = Default.Cancellable var shieldCharge: Cancellable = Default.Cancellable
def SiphoningObject = vehicle def SiphoningObject: Vehicle = vehicle
def ChargeTransferObject = vehicle def ChargeTransferObject: Vehicle = vehicle
if (vehicle.Shields < vehicle.MaxShields) { 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 = { override def postStop(): Unit = {
@ -107,7 +107,7 @@ class BfrControl(vehicle: Vehicle)
shieldCharge = context.system.scheduler.scheduleOnce( shieldCharge = context.system.scheduler.scheduleOnce(
delay = vehicle.Definition.ShieldDamageDelay milliseconds, delay = vehicle.Definition.ShieldDamageDelay milliseconds,
self, 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 (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory)
val afterWeapons = weapons val afterWeapons = weapons
.map { item => item.start += 1; item } .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)) { } else if(vWeapons.size == 2 && GlobalDefinitions.isBattleFrameGunnerVehicle(definition)) {
//battleframe is a flight variant but loadout spec is for gunner variant //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 // 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) chargeShieldsOnly(amount)
shieldCharge(vehicle.Shields, vehicle.Definition, delay = 0) //continue charge? 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) }).getOrElse(amount) * vehicle.SubsystemStatusMultiplier(sys = "BattleframeShieldGenerator.RechargeRate")).toInt)
vehicle.Shields = before + chargeAmount vehicle.Shields = before + chargeAmount
val after = vehicle.Shields val after = vehicle.Shields
vehicle.History(VehicleShieldCharge(VehicleSource(vehicle), after - before)) vehicle.LogActivity(ShieldCharge(after - before, Some(VehicleSource(vehicle))))
showShieldCharge() showShieldCharge()
if (before == 0 && after > 0) { if (before == 0 && after > 0) {
enableShield() enableShield()
@ -346,7 +346,7 @@ class BfrControl(vehicle: Vehicle)
shieldCharge = context.system.scheduler.scheduleOnce( shieldCharge = context.system.scheduler.scheduleOnce(
delay = definition.ShieldPeriodicDelay + delay milliseconds, delay = definition.ShieldPeriodicDelay + delay milliseconds,
self, self,
Vehicle.ChargeShields(0) CommonMessages.ChargeShields(0, Some(vehicle))
) )
} else { } else {
shieldCharge = Default.Cancellable shieldCharge = Default.Cancellable

View file

@ -4,7 +4,6 @@ package net.psforever.objects.vehicles.control
import akka.actor.Cancellable import akka.actor.Cancellable
import net.psforever.actors.zone.ZoneActor import net.psforever.actors.zone.ZoneActor
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.ballistics.VehicleSource
import net.psforever.objects.definition.VehicleDefinition import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.definition.converter.OCM import net.psforever.objects.definition.converter.OCM
import net.psforever.objects.entity.WorldEntity 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.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.repair.RepairableVehicle import net.psforever.objects.serverobject.repair.RepairableVehicle
import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.sourcing.{SourceEntry, VehicleSource}
import net.psforever.objects.vehicles._ import net.psforever.objects.vehicles._
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
import net.psforever.objects.vital.{DamagingActivity, VehicleShieldCharge, VitalsActivity} import net.psforever.objects.vital.{DamagingActivity, InGameActivity, ShieldCharge}
import net.psforever.objects.vital.environment.EnvironmentReason import net.psforever.objects.vital.environment.EnvironmentReason
import net.psforever.objects.vital.etc.SuicideReason import net.psforever.objects.vital.etc.SuicideReason
import net.psforever.objects.zones._ import net.psforever.objects.zones._
@ -60,23 +60,15 @@ class VehicleControl(vehicle: Vehicle)
//make control actors belonging to utilities when making control actor belonging to vehicle //make control actors belonging to utilities when making control actor belonging to vehicle
vehicle.Utilities.foreach { case (_, util) => util.Setup } vehicle.Utilities.foreach { case (_, util) => util.Setup }
def MountableObject = vehicle def MountableObject: Vehicle = vehicle
def JammableObject: Vehicle = vehicle
def JammableObject = vehicle def FactionObject: Vehicle = vehicle
def DamageableObject: Vehicle = vehicle
def FactionObject = vehicle def SiphonableObject: Vehicle = vehicle
def RepairableObject: Vehicle = vehicle
def DamageableObject = vehicle def ContainerObject: Vehicle = vehicle
def InteractiveObject: Vehicle = vehicle
def SiphonableObject = vehicle def CargoObject: Vehicle = vehicle
def RepairableObject = vehicle
def ContainerObject = vehicle
def InteractiveObject = vehicle
def CargoObject = vehicle
SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater) SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava) SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
@ -136,8 +128,8 @@ class VehicleControl(vehicle: Vehicle)
dismountBehavior.apply(msg) dismountBehavior.apply(msg)
dismountCleanup(seat_num) dismountCleanup(seat_num)
case Vehicle.ChargeShields(amount) => case CommonMessages.ChargeShields(amount, motivator) =>
chargeShields(amount) chargeShields(amount, motivator.collect { case o: PlanetSideGameObject with FactionAffinity => SourceEntry(o) })
case Vehicle.UpdateZoneInteractionProgressUI(player) => case Vehicle.UpdateZoneInteractionProgressUI(player) =>
updateZoneInteractionProgressUI(player) updateZoneInteractionProgressUI(player)
@ -406,6 +398,7 @@ class VehicleControl(vehicle: Vehicle)
TaskWorkflow.execute(GUIDTask.unregisterVehicle(zone.GUID, vehicle)) TaskWorkflow.execute(GUIDTask.unregisterVehicle(zone.GUID, vehicle))
//banished to the shadow realm //banished to the shadow realm
vehicle.Position = Vector3.Zero vehicle.Position = Vector3.Zero
vehicle.ClearHistory()
//queue final deletion //queue final deletion
decayTimer = context.system.scheduler.scheduleOnce(5 seconds, self, VehicleControl.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 //make certain vehicles don't charge shields too quickly
def canChargeShields: Boolean = { 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.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) { if (canChargeShields) {
vehicle.History(VehicleShieldCharge(VehicleSource(vehicle), amount)) vehicle.LogActivity(ShieldCharge(amount, motivator))
vehicle.Shields = vehicle.Shields + amount vehicle.Shields = vehicle.Shields + amount
vehicle.Zone.VehicleEvents ! VehicleServiceMessage( vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
s"${vehicle.Actor}", s"${vehicle.Actor}",
@ -874,7 +867,7 @@ class VehicleControl(vehicle: Vehicle)
} }
object VehicleControl { object VehicleControl {
import net.psforever.objects.vital.{VehicleShieldCharge, VitalsActivity} import net.psforever.objects.vital.{ShieldCharge}
private case class PrepareForDeletion() private case class PrepareForDeletion()
@ -893,10 +886,10 @@ object VehicleControl {
* @return `true`, if the shield charge would be blocked; * @return `true`, if the shield charge would be blocked;
* `false`, otherwise * `false`, otherwise
*/ */
def LastShieldChargeOrDamage(now: Long, vdef: VehicleDefinition)(act: VitalsActivity): Boolean = { def LastShieldChargeOrDamage(now: Long, vdef: VehicleDefinition)(act: InGameActivity): Boolean = {
act match { act match {
case dact: DamagingActivity => now - dact.time < vdef.ShieldDamageDelay //damage delays next charge 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 case _ => false
} }
} }

View 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)
}
}
}

View file

@ -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 amount of HDHR is controlled by the damage model of this vital object reacting to stimulus.
* The damage model is provided. * The damage model is provided.
*/ */
trait Vitality extends VitalsHistory { trait Vitality extends InGameHistory {
private var health: Int = Definition.DefaultHealth private var health: Int = Definition.DefaultHealth
private var defaultHealth: Option[Int] = None private var defaultHealth: Option[Int] = None
private var maxHealth: Option[Int] = None private var maxHealth: Option[Int] = None

View file

@ -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)
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2020 PSForever // Copyright (c) 2020 PSForever
package net.psforever.objects.vital.base 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.damage.DamageProfile
import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.vital.prop.DamageProperties import net.psforever.objects.vital.prop.DamageProperties

View file

@ -1,8 +1,8 @@
// Copyright (c) 2021 PSForever // Copyright (c) 2021 PSForever
package net.psforever.objects.vital.collision package net.psforever.objects.vital.collision
import net.psforever.objects.ballistics.{DeployableSource, PlayerSource, VehicleSource}
import net.psforever.objects.definition.ExoSuitDefinition import net.psforever.objects.definition.ExoSuitDefinition
import net.psforever.objects.sourcing.{DeployableSource, PlayerSource, VehicleSource}
import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.types.Vector3 import net.psforever.types.Vector3

View file

@ -1,7 +1,7 @@
// Copyright (c) 2020 PSForever // Copyright (c) 2020 PSForever
package net.psforever.objects.vital.collision 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.base.{DamageModifiers, DamageReason, DamageResolution, DamageType}
import net.psforever.objects.vital.prop.DamageProperties import net.psforever.objects.vital.prop.DamageProperties
import net.psforever.objects.vital.resolution.DamageAndResistance import net.psforever.objects.vital.resolution.DamageAndResistance

View file

@ -2,7 +2,7 @@
package net.psforever.objects.vital.damage package net.psforever.objects.vital.damage
import net.psforever.objects.GlobalDefinitions 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.base.{DamageModifiers, DamageReason}
import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.vital.prop.DamageWithPosition import net.psforever.objects.vital.prop.DamageWithPosition

View file

@ -1,9 +1,9 @@
// Copyright (c) 2020 PSForever // Copyright (c) 2020 PSForever
package net.psforever.objects.vital.environment package net.psforever.objects.vital.environment
import net.psforever.objects.ballistics.PlayerSource
import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.serverobject.environment.EnvironmentAttribute 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. * The deeper you move into lava, the greater the amount of health you burn through.

View file

@ -1,8 +1,8 @@
// Copyright (c) 2020 PSForever // Copyright (c) 2020 PSForever
package net.psforever.objects.vital.environment package net.psforever.objects.vital.environment
import net.psforever.objects.ballistics.SourceEntry
import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, PieceOfEnvironment} 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.base.{DamageReason, DamageResolution}
import net.psforever.objects.vital.damage.DamageCalculations import net.psforever.objects.vital.damage.DamageCalculations
import net.psforever.objects.vital.prop.DamageProperties import net.psforever.objects.vital.prop.DamageProperties

View file

@ -1,8 +1,8 @@
// Copyright (c) 2021 PSForever // Copyright (c) 2021 PSForever
package net.psforever.objects.vital.etc package net.psforever.objects.vital.etc
import net.psforever.objects.sourcing.SourceEntry
import net.psforever.objects.{GlobalDefinitions, Tool, Vehicle} 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.base.{DamageModifiers, DamageReason, DamageResolution}
import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.vital.prop.DamageWithPosition import net.psforever.objects.vital.prop.DamageWithPosition

View file

@ -2,8 +2,8 @@
package net.psforever.objects.vital.etc package net.psforever.objects.vital.etc
import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ballistics.SourceEntry
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.sourcing.SourceEntry
import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.base.{DamageReason, DamageResolution} import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
import net.psforever.objects.vital.prop.DamageWithPosition import net.psforever.objects.vital.prop.DamageWithPosition

View file

@ -2,9 +2,9 @@
package net.psforever.objects.vital.etc package net.psforever.objects.vital.etc
import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ballistics.SourceEntry
import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.serverobject.affinity.FactionAffinity 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.{Vitality, VitalityDefinition}
import net.psforever.objects.vital.base.{DamageModifiers, DamageReason, DamageResolution} import net.psforever.objects.vital.base.{DamageModifiers, DamageReason, DamageResolution}
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}

View file

@ -1,8 +1,8 @@
// Copyright (c) 2020 PSForever // Copyright (c) 2020 PSForever
package net.psforever.objects.vital.etc package net.psforever.objects.vital.etc
import net.psforever.objects.ballistics.SourceEntry
import net.psforever.objects.serverobject.painbox.Painbox 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.{NoResistanceSelection, SimpleResolutions}
import net.psforever.objects.vital.base.{DamageReason, DamageResolution} import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
import net.psforever.objects.vital.damage.DamageCalculations import net.psforever.objects.vital.damage.DamageCalculations

Some files were not shown because too many files have changed in this diff Show more