diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf
index df36b5d8..a80dfd49 100644
--- a/src/main/resources/application.conf
+++ b/src/main/resources/application.conf
@@ -362,7 +362,7 @@ game {
# If a player died while carrying an lattice logic unit,
# award the player who is accredited with the kill command experience as long as the time it had been carried longer than this duration.
# Can set to Duration.Inf to never pass.
- llu-slayer-credit-duration = 1 minute
+ llu-slayer-credit-duration = 30 seconds
# If a player died while carrying an lattice logic unit,
# and satisfies the carrying duration,
# award the player who is accredited with the kill command experience.
@@ -389,6 +389,11 @@ game {
# Anyone who is currently enrolled in the promotion system remains enrolled during normal game play.
# Relenting on the promotion debt back to battle rank 2 is still possible.
active = true
+ # This battle rank and any battle ranks of ordinal decrement that allow opt-in to the progression system.
+ broadcast-battle-rank = 1
+ # This is the minimum battle rank that can be set as part of the promotion system.
+ # Used to escape debt and return to normal play.
+ reset-battle-rank = 2
# This is the maximum battle rank that can be set as part of the promotion system.
max-battle-rank = 13
# How much direct combat contributes to paying back promotion debt.
diff --git a/src/main/scala/net/psforever/actors/session/AvatarActor.scala b/src/main/scala/net/psforever/actors/session/AvatarActor.scala
index 9370c981..f6026d8c 100644
--- a/src/main/scala/net/psforever/actors/session/AvatarActor.scala
+++ b/src/main/scala/net/psforever/actors/session/AvatarActor.scala
@@ -1063,6 +1063,9 @@ class AvatarActor(
case DeleteAvatar(id) =>
import ctx._
val performDeletion = for {
+ _ <- ctx.run(query[persistence.Weaponstatsession].filter(_.avatarId == lift(id)).delete)
+ _ <- ctx.run(query[persistence.Kdasession].filter(_.avatarId == lift(id)).delete)
+ _ <- ctx.run(query[persistence.Buildingcapture].filter(_.avatarId == lift(id)).delete)
_ <- ctx.run(query[persistence.Shortcut].filter(_.avatarId == lift(id)).delete)
_ <- ctx.run(query[persistence.Implant].filter(_.avatarId == lift(id)).delete)
_ <- ctx.run(query[persistence.Loadout].filter(_.avatarId == lift(id)).delete)
@@ -1646,11 +1649,16 @@ class AvatarActor(
if ({
val oldBr = BattleRank.withExperience(avatar.bep).value
val newBr = BattleRank.withExperience(bep).value
- if (Config.app.game.promotion.active && oldBr == 1 && newBr > 1 && newBr < Config.app.game.promotion.maxBattleRank + 1) {
+ val resetBr = Config.app.game.promotion.resetBattleRank
+ if (
+ Config.app.game.promotion.active &&
+ oldBr <= Config.app.game.promotion.broadcastBattleRank &&
+ newBr > resetBr && newBr < Config.app.game.promotion.maxBattleRank + 1
+ ) {
experienceDebt = bep
AvatarActor.saveExperienceDebt(avatar.id, bep, bep)
true
- } else if (experienceDebt > 0 && newBr == 2) {
+ } else if (experienceDebt > 0 && newBr == resetBr) {
experienceDebt = 0
AvatarActor.saveExperienceDebt(avatar.id, exp = 0, bep)
true
@@ -1663,7 +1671,6 @@ class AvatarActor(
setCep(0L)
}
restoreBasicCerts()
- removeAllImplants()
sessionActor ! SessionActor.CharSaved
sessionActor ! SessionActor.SendResponse(ChatMsg(ChatMessageType.UNK_229, "@AckSuccessSetBattleRank"))
} else if (experienceDebt > 0) {
@@ -1674,9 +1681,9 @@ class AvatarActor(
Behaviors.same
case AwardCep(cep) =>
- if (experienceDebt > 0L) {
+ if (experienceDebt == 0L) {
setCep(avatar.cep + cep)
- } else {
+ } else if (cep > 0) {
sessionActor ! SessionActor.SendResponse(ExperienceAddedMessage(0))
}
Behaviors.same
@@ -1851,16 +1858,77 @@ class AvatarActor(
}
def performAvatarLogin(avatarId: Long, accountId: Long, replyTo: ActorRef[AvatarLoginResponse]): Unit = {
+ performAvatarLogin0(avatarId, accountId, replyTo)
+// import ctx._
+// val result = for {
+// //log this login
+// _ <- ctx.run(
+// query[persistence.Avatar]
+// .filter(_.id == lift(avatarId))
+// .update(_.lastLogin -> lift(LocalDateTime.now()))
+// )
+// //log this choice of faction (no empire switching)
+// _ <- ctx.run(
+// query[persistence.Account]
+// .filter(_.id == lift(accountId))
+// .update(
+// _.lastFactionId -> lift(avatar.faction.id),
+// _.avatarLoggedIn -> lift(avatarId)
+// )
+// )
+// //retrieve avatar data
+// loadouts <- initializeAllLoadouts()
+// implants <- ctx.run(query[persistence.Implant].filter(_.avatarId == lift(avatarId)))
+// certs <- ctx.run(query[persistence.Certification].filter(_.avatarId == lift(avatarId)))
+// locker <- loadLocker(avatarId)
+// friends <- loadFriendList(avatarId)
+// ignored <- loadIgnoredList(avatarId)
+// shortcuts <- loadShortcuts(avatarId)
+// saved <- AvatarActor.loadSavedAvatarData(avatarId)
+// debt <- AvatarActor.loadExperienceDebt(avatarId)
+// card <- AvatarActor.loadCampaignKdaData(avatarId)
+// } yield (loadouts, implants, certs, locker, friends, ignored, shortcuts, saved, debt, card)
+// result.onComplete {
+// case Success((_loadouts, implants, certs, lockerInv, friendsList, ignoredList, shortcutList, saved, debt, card)) =>
+// avatarCopy(
+// avatar.copy(
+// loadouts = avatar.loadouts.copy(suit = _loadouts),
+// certifications =
+// certs.map(cert => Certification.withValue(cert.id)).toSet ++ Config.app.game.baseCertifications,
+// implants = implants.map(implant => Some(Implant(implant.toImplantDefinition))).padTo(3, None),
+// shortcuts = shortcutList,
+// locker = lockerInv,
+// people = MemberLists(
+// friend = friendsList,
+// ignored = ignoredList
+// ),
+// cooldowns = Cooldowns(
+// purchase = AvatarActor.buildCooldownsFromClob(saved.purchaseCooldowns, Avatar.purchaseCooldowns, log),
+// use = AvatarActor.buildCooldownsFromClob(saved.useCooldowns, Avatar.useCooldowns, log)
+// ),
+// scorecard = card
+// )
+// )
+// // if we need to start stamina regeneration
+// tryRestoreStaminaForSession(stamina = 1).collect { _ => defaultStaminaRegen(initialDelay = 0.5f seconds) }
+// experienceDebt = debt
+// replyTo ! AvatarLoginResponse(avatar)
+// case Failure(e) =>
+// log.error(e)("db failure")
+// }
+ }
+
+ def performAvatarLogin0(avatarId: Long, accountId: Long, replyTo: ActorRef[AvatarLoginResponse]): Unit = {
import ctx._
val result = for {
//log this login
- _ <- ctx.run(
+ loginTime <- ctx.run(
query[persistence.Avatar]
.filter(_.id == lift(avatarId))
.update(_.lastLogin -> lift(LocalDateTime.now()))
)
//log this choice of faction (no empire switching)
- _ <- ctx.run(
+ loginFaction <- ctx.run(
query[persistence.Account]
.filter(_.id == lift(accountId))
.update(
@@ -1868,50 +1936,64 @@ class AvatarActor(
_.avatarLoggedIn -> lift(avatarId)
)
)
- //retrieve avatar data
- loadouts <- initializeAllLoadouts()
+ } yield (loginTime, loginFaction)
+ result.onComplete {
+ case Success(_) =>
+ sessionActor ! SessionActor.AvatarLoadingSync(step = 0)
+ performAvatarLogin1(avatarId, replyTo)
+ case Failure(e) =>
+ log.error(e)("db failure")
+ }
+ }
+
+ def performAvatarLogin1(avatarId: Long, replyTo: ActorRef[AvatarLoginResponse]): Unit = {
+ import ctx._
+ val result = for {
+ //retrieve avatar data for OCDM packet
implants <- ctx.run(query[persistence.Implant].filter(_.avatarId == lift(avatarId)))
certs <- ctx.run(query[persistence.Certification].filter(_.avatarId == lift(avatarId)))
+ debt <- AvatarActor.loadExperienceDebt(avatarId)
locker <- loadLocker(avatarId)
+ } yield (certs, implants, locker, debt)
+ result.onComplete {
+ case Success((certs, implants, lockerInv, debt)) =>
+ avatarCopy(
+ avatar.copy(
+ certifications =
+ certs.map(cert => Certification.withValue(cert.id)).toSet ++ Config.app.game.baseCertifications,
+ implants = implants.map(implant => Some(Implant(implant.toImplantDefinition))).padTo(3, None),
+ locker = lockerInv
+ )
+ )
+ experienceDebt = debt
+ // if we need to start stamina regeneration
+ tryRestoreStaminaForSession(stamina = 1).collect { _ => defaultStaminaRegen(initialDelay = 0.5f seconds) }
+ sessionActor ! SessionActor.AvatarLoadingSync(step = 1)
+ replyTo ! AvatarLoginResponse(avatar)
+ performAvatarLogin2(avatarId, replyTo)
+ case Failure(e) =>
+ log.error(e)("db failure")
+ }
+ }
+
+ //noinspection ScalaUnusedSymbol
+ def performAvatarLogin2(avatarId: Long, replyTo: ActorRef[AvatarLoginResponse]): Unit = {
+ val result = for {
+ //retrieve avatar data for other packets
+ loadouts <- initializeAllLoadouts()
friends <- loadFriendList(avatarId)
ignored <- loadIgnoredList(avatarId)
shortcuts <- loadShortcuts(avatarId)
saved <- AvatarActor.loadSavedAvatarData(avatarId)
- debt <- AvatarActor.loadExperienceDebt(avatarId)
card <- AvatarActor.loadCampaignKdaData(avatarId)
- } yield (loadouts, implants, certs, locker, friends, ignored, shortcuts, saved, debt, card)
+ } yield (loadouts, friends, ignored, shortcuts, saved, card)
result.onComplete {
- case Success((_loadouts, implants, certs, lockerInv, friendsList, ignoredList, shortcutList, saved, debt, card)) =>
- //shortcuts must have a hotbar option for each implant
-// val implantShortcuts = shortcutList.filter {
-// case Some(e) => e.purpose == 0
-// case None => false
-// }
-// implants.filterNot { implant =>
-// implantShortcuts.exists {
-// case Some(a) => a.tile.equals(implant.name)
-// case None => false
-// }
-// }.foreach { c =>
-// shortcutList.indexWhere { _.isEmpty } match {
-// case -1 => ()
-// case index =>
-// shortcutList.update(index, Some(AvatarShortcut(2, c.name)))
-// }
-// }
- //
+ case Success((loadoutList, friendsList, ignoredList, shortcutList, saved, card)) =>
avatarCopy(
avatar.copy(
- loadouts = avatar.loadouts.copy(suit = _loadouts),
- certifications =
- certs.map(cert => Certification.withValue(cert.id)).toSet ++ Config.app.game.baseCertifications,
- implants = implants.map(implant => Some(Implant(implant.toImplantDefinition))).padTo(3, None),
+ loadouts = avatar.loadouts.copy(suit = loadoutList),
shortcuts = shortcutList,
- locker = lockerInv,
- people = MemberLists(
- friend = friendsList,
- ignored = ignoredList
- ),
+ people = MemberLists(friend = friendsList, ignored = ignoredList),
cooldowns = Cooldowns(
purchase = AvatarActor.buildCooldownsFromClob(saved.purchaseCooldowns, Avatar.purchaseCooldowns, log),
use = AvatarActor.buildCooldownsFromClob(saved.useCooldowns, Avatar.useCooldowns, log)
@@ -1919,10 +2001,7 @@ class AvatarActor(
scorecard = card
)
)
- // if we need to start stamina regeneration
- tryRestoreStaminaForSession(stamina = 1).collect { _ => defaultStaminaRegen(initialDelay = 0.5f seconds) }
- experienceDebt = debt
- replyTo ! AvatarLoginResponse(avatar)
+ sessionActor ! SessionActor.AvatarLoadingSync(step = 2)
case Failure(e) =>
log.error(e)("db failure")
}
@@ -3046,7 +3125,6 @@ class AvatarActor(
}
player.HistoryAndContributions()
}
- zone.actor ! ZoneActor.RewardOurSupporters(playerSource, historyTranscript, killStat, exp)
val target = killStat.info.targetAfter.asInstanceOf[PlayerSource]
val targetMounted = target.seatedIn.map { case (v: VehicleSource, seat) =>
val definition = v.Definition
@@ -3077,6 +3155,7 @@ class AvatarActor(
}
if (exp > 0L) {
setBep(avatar.bep + exp, msg)
+ zone.actor ! ZoneActor.RewardOurSupporters(playerSource, historyTranscript, killStat, exp)
}
}
diff --git a/src/main/scala/net/psforever/actors/session/ChatActor.scala b/src/main/scala/net/psforever/actors/session/ChatActor.scala
index 44f82f5c..8f49a556 100644
--- a/src/main/scala/net/psforever/actors/session/ChatActor.scala
+++ b/src/main/scala/net/psforever/actors/session/ChatActor.scala
@@ -1282,7 +1282,10 @@ class ChatActor(
false
}
} else if (contents.startsWith("!progress")) {
- if (!session.account.gm && BattleRank.withExperience(session.avatar.bep).value < Config.app.game.promotion.maxBattleRank + 1) {
+ val ourRank = BattleRank.withExperience(session.avatar.bep).value
+ if (!session.account.gm &&
+ (ourRank <= Config.app.game.promotion.broadcastBattleRank ||
+ ourRank > Config.app.game.promotion.resetBattleRank && ourRank < Config.app.game.promotion.maxBattleRank + 1)) {
setBattleRank(dropFirstWord(contents), session, AvatarActor.Progress)
true
} else {
@@ -1299,7 +1302,7 @@ class ChatActor(
private def dropFirstWord(str: String): String = {
val noExtraSpaces = str.replaceAll("\\s+", " ").toLowerCase.trim
- noExtraSpaces.indexOf({ char: String => char.equals(" ") }) match {
+ noExtraSpaces.indexOf(" ") match {
case -1 => ""
case beforeFirstBlank => noExtraSpaces.drop(beforeFirstBlank + 1)
}
diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala
index 29d69bc6..0f26f117 100644
--- a/src/main/scala/net/psforever/actors/session/SessionActor.scala
+++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala
@@ -81,6 +81,8 @@ object SessionActor {
final case class UpdateIgnoredPlayers(msg: FriendsResponse) extends Command
+ final case class AvatarLoadingSync(step: Int) extends Command
+
final case object CharSaved extends Command
private[session] case object CharSavedMsg extends Command
@@ -255,6 +257,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case SessionActor.SetConnectionState(state) =>
sessionFuncs.connectionState = state
+ case SessionActor.AvatarLoadingSync(state) =>
+ sessionFuncs.zoning.spawn.handleAvatarLoadingSync(state)
+
/* uncommon messages (utility, or once in a while) */
case SessionActor.AvatarAwardMessageBundle(pkts, delay) =>
sessionFuncs.zoning.spawn.performAvatarAwardMessageDelivery(pkts, delay)
diff --git a/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala
index ee6055c4..41588dcc 100644
--- a/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala
+++ b/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala
@@ -3,8 +3,6 @@ package net.psforever.actors.session.support
import akka.actor.typed.scaladsl.adapter._
import akka.actor.{ActorContext, typed}
-import net.psforever.objects.avatar.SpecialCarry
-import net.psforever.objects.serverobject.llu.CaptureFlag
import net.psforever.packet.game.objectcreate.ConstructorData
import net.psforever.services.Service
import net.psforever.objects.zones.exp
@@ -496,25 +494,6 @@ class SessionAvatarHandlers(
sendResponse(ChatMsg(ChatMessageType.UNK_227, "@SVCP_Killed_OnPadOnCreate"))
case _ => ()
}
- adversarial.collect {
- case attacker
- if player.Carrying.contains(SpecialCarry.CaptureFlag) &&
- attacker.Faction != player.Faction &&
- sessionData
- .specialItemSlotGuid
- .flatMap { continent.GUID }
- .collect {
- case llu: CaptureFlag =>
- System.currentTimeMillis() - llu.LastCollectionTime > Config.app.game.experience.cep.lluSlayerCreditDuration.toMillis
- case _ =>
- false
- }
- .contains(true) =>
- continent.AvatarEvents ! AvatarServiceMessage(
- attacker.Name,
- AvatarAction.AwardCep(attacker.CharId, Config.app.game.experience.cep.lluSlayerCredit)
- )
- }
adversarial.map {_.Name }.orElse { Some(s"a ${reason.getClass.getSimpleName}") }
}.getOrElse { s"an unfortunate circumstance (probably ${player.Sex.pronounObject} own fault)" }
log.info(s"${player.Name} has died, killed by $cause")
diff --git a/src/main/scala/net/psforever/actors/session/support/SessionData.scala b/src/main/scala/net/psforever/actors/session/support/SessionData.scala
index 712b71df..46fa170e 100644
--- a/src/main/scala/net/psforever/actors/session/support/SessionData.scala
+++ b/src/main/scala/net/psforever/actors/session/support/SessionData.scala
@@ -1579,6 +1579,24 @@ class SessionData(
case Some(llu: CaptureFlag) => Some((llu, llu.Carrier))
case _ => None
}) match {
+ case Some((llu, Some(carrier: Player)))
+ if carrier.GUID == player.GUID && !player.isAlive =>
+ player.LastDamage.foreach { damage =>
+ damage
+ .interaction
+ .adversarial
+ .map { _.attacker }
+ .collect {
+ case attacker
+ if attacker.Faction != player.Faction &&
+ System.currentTimeMillis() - llu.LastCollectionTime >= Config.app.game.experience.cep.lluSlayerCreditDuration.toMillis =>
+ continent.AvatarEvents ! AvatarServiceMessage(
+ attacker.Name,
+ AvatarAction.AwardCep(attacker.CharId, Config.app.game.experience.cep.lluSlayerCredit)
+ )
+ }
+ }
+ continent.LocalEvents ! CaptureFlagManager.DropFlag(llu)
case Some((llu, Some(carrier: Player))) if carrier.GUID == player.GUID =>
continent.LocalEvents ! CaptureFlagManager.DropFlag(llu)
case Some((_, Some(carrier: Player))) =>
diff --git a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala
index d0a4f408..ce23bd53 100644
--- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala
+++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala
@@ -13,7 +13,7 @@ import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.serverobject.mount.Seat
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, VehicleSource}
-import net.psforever.objects.vital.{InGameHistory, ReconstructionActivity, SpawningActivity}
+import net.psforever.objects.vital.{InGameHistory, IncarnationActivity, ReconstructionActivity, SpawningActivity}
import net.psforever.packet.game.{CampaignStatistic, MailMessage, SessionStatistic}
import scala.collection.mutable
@@ -79,7 +79,7 @@ object ZoningOperations {
"High Command",
"Progress versus Promotion",
"If you consider yourself as a veteran soldier, despite looking so green, please read this.\n" ++
- "You only have this opportunity while you are battle rank 1." ++
+ s"You only have this opportunity while you are less than or equal to battle rank ${Config.app.game.promotion.broadcastBattleRank}." ++
"\n\n" ++
"The normal method of rank advancement comes from the battlefield - fighting enemies, helping allies, and capturing facilities. " ++
"\n\n" ++
@@ -91,7 +91,7 @@ object ZoningOperations {
"In addition, you will be ineligible of having your command experience be recognized during this time." ++
"\n\n" ++
"If you wish to continue, set your desired battle rank now - use '!progress' followed by a battle rank index. " ++
- "If you accept, but it becomes too much of burden, you may ask to revert to battle rank 2 at any time. " ++
+ s"If you accept, but it becomes too much of burden, you may ask to revert to battle rank ${Config.app.game.promotion.resetBattleRank} at any time. " ++
"Your normal sense of progress will be restored."
)
)
@@ -629,22 +629,7 @@ class ZoningOperations(
ServiceManager.serviceManager ! Lookup("propertyOverrideManager")
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 0)) // disable festive backpacks
sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list
- (
- FriendsResponse.packetSequence(
- MemberAction.InitializeFriendList,
- avatar.people.friend
- .map { f =>
- game.Friend(f.name, AvatarActor.onlineIfNotIgnoredEitherWay(avatar, f.name))
- }
- ) ++
- //ignored list (no one ever online)
- FriendsResponse.packetSequence(
- MemberAction.InitializeIgnoreList,
- avatar.people.ignored.map { f => game.Friend(f.name) }
- )
- ).foreach {
- sendResponse
- }
+ spawn.initializeFriendsAndIgnoredLists()
//the following subscriptions last until character switch/logout
galaxyService ! Service.Join("galaxy") //for galaxy-wide messages
galaxyService ! Service.Join(s"${avatar.faction}") //for hotspots, etc.
@@ -2797,16 +2782,7 @@ class ZoningOperations(
}
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 82, 0))
- avatar.shortcuts
- .zipWithIndex
- .collect { case (Some(shortcut), index) =>
- sendResponse(CreateShortcutMessage(
- guid,
- index + 1,
- Some(AvatarShortcut.convert(shortcut))
- ))
- }
- sendResponse(ChangeShortcutBankMessage(guid, 0))
+ initializeShortcutsAndBank(guid)
//Favorites lists
avatarActor ! AvatarActor.InitialRefreshLoadouts()
@@ -2940,20 +2916,28 @@ class ZoningOperations(
}
.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)
- }
- })
+ val lastEntryOpt = player.History.lastOption
+ if (lastEntryOpt.exists { !_.isInstanceOf[IncarnationActivity] }) {
+ player.LogActivity({
+ lastEntryOpt match {
+ case Some(_) =>
+ ReconstructionActivity(PlayerSource(player), continent.Number, effortBy)
+ case None =>
+ SpawningActivity(PlayerSource(player), continent.Number, effortBy)
+ }
+ })
+ }
}
upstreamMessageCount = 0
setAvatar = true
if (
- BattleRank.withExperience(tplayer.avatar.bep).value == 1 &&
- Config.app.game.promotion.active &&
- !account.gm) {
+ !account.gm && /* gm's are excluded */
+ Config.app.game.promotion.active && /* play versus progress system must be active */
+ BattleRank.withExperience(tplayer.avatar.bep).value <= Config.app.game.promotion.broadcastBattleRank && /* must be below a certain battle rank */
+ avatar.scorecard.Lives.isEmpty && /* first life after login */
+ avatar.scorecard.CurrentLife.prior.isEmpty && /* no revives */
+ player.History.size == 1 /* did nothing but come into existence */
+ ) {
ZoningOperations.reportProgressionSystem(context.self)
}
}
@@ -2968,6 +2952,59 @@ class ZoningOperations(
HandleSetCurrentAvatar(tplayer)
}
+ /**
+ * Respond to feedback of how the avatar's data is being handled
+ * in a way that properly reflects the state of the server at the moment.
+ * @param state indicator for the progress of the avatar
+ */
+ def handleAvatarLoadingSync(state: Int): Unit = {
+ if (state == 2 && zoneLoaded.contains(true)) {
+ initializeFriendsAndIgnoredLists()
+ initializeShortcutsAndBank(player.GUID)
+ avatarActor ! AvatarActor.RefreshPurchaseTimes()
+ loginAvatarStatisticsFields()
+ avatarActor ! AvatarActor.InitialRefreshLoadouts()
+ }
+ }
+
+ /**
+ * Set up and dispatch a list of `FriendsResponse` packets related to both formal friends and ignored players.
+ */
+ def initializeFriendsAndIgnoredLists(): Unit = {
+ (
+ FriendsResponse.packetSequence(
+ MemberAction.InitializeFriendList,
+ avatar.people.friend
+ .map { f =>
+ game.Friend(f.name, AvatarActor.onlineIfNotIgnoredEitherWay(avatar, f.name))
+ }
+ ) ++
+ //ignored list (no one ever online)
+ FriendsResponse.packetSequence(
+ MemberAction.InitializeIgnoreList,
+ avatar.people.ignored.map { f => game.Friend(f.name) }
+ )
+ ).foreach {
+ sendResponse
+ }
+ }
+
+ /**
+ * Set up and dispatch a list of `CreateShortcutMessage` packets and a single `ChangeShortcutBankMessage` packet.
+ */
+ def initializeShortcutsAndBank(guid: PlanetSideGUID): Unit = {
+ avatar.shortcuts
+ .zipWithIndex
+ .collect { case (Some(shortcut), index) =>
+ sendResponse(CreateShortcutMessage(
+ guid,
+ index + 1,
+ Some(AvatarShortcut.convert(shortcut))
+ ))
+ }
+ sendResponse(ChangeShortcutBankMessage(guid, 0))
+ }
+
/**
* Draw the icon for this deployable object.
*
@@ -3247,7 +3284,7 @@ class ZoningOperations(
/**
* Accessible method to switch population of the Character Info window's statistics page
- * from whatever it currently js to after each respawn.
+ * from whatever it currently is to after each respawn.
* At the time of "login", only campaign (total, historical) deaths are reported for convenience.
* At the time of "respawn", all fields - campaign and session - should be reported if applicable.
*/
diff --git a/src/main/scala/net/psforever/objects/avatar/scoring/ScoreCard.scala b/src/main/scala/net/psforever/objects/avatar/scoring/ScoreCard.scala
index 40f97f27..0ab5d440 100644
--- a/src/main/scala/net/psforever/objects/avatar/scoring/ScoreCard.scala
+++ b/src/main/scala/net/psforever/objects/avatar/scoring/ScoreCard.scala
@@ -163,6 +163,7 @@ object ScoreCard {
case PlanetSideEmpire.VS => fields.copy(vs_s = fields.vs_s + 1)
case PlanetSideEmpire.NEUTRAL => fields.copy(ps_s = fields.ps_s + 1)
}
+ statisticMap.put(objectId, outEntry)
outEntry
case _ =>
val out = Statistic(0, 0, 0, 0, 0, 0, 0, 0)
diff --git a/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala b/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala
index f2c9890e..026f3f1f 100644
--- a/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala
@@ -27,7 +27,7 @@ class IFFLockControl(lock: IFFLock)
.orElse {
case CommonMessages.Use(player, Some(item: SimpleItem))
if item.Definition == GlobalDefinitions.remote_electronics_kit =>
- if (lock.Faction != player.Faction && lock.HackedBy.isEmpty) {
+ if (lock.Faction != player.Faction) {
sender() ! CommonMessages.Progress(
GenericHackables.GetHackSpeed(player, lock),
GenericHackables.FinishHacking(lock, player, 1114636288L),
@@ -42,8 +42,7 @@ class IFFLockControl(lock: IFFLock)
} else {
val log = org.log4s.getLogger
log.warn(s"IFF lock is being hacked by ${player.Faction}, but don't know how to handle this state:")
- log.warn(s"Lock - Faction=${lock.Faction}, HackedBy=${lock.HackedBy}")
- log.warn(s"Player - Faction=${player.Faction}")
+ log.warn(s"Lock - Faction=${lock.Faction}, HackedBy=${lock.HackedBy.map{_.player}}")
}
case IFFLock.DoorOpenRequest(target, door, replyTo) =>
diff --git a/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala b/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala
index a1cf825b..deba736d 100644
--- a/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala
@@ -192,12 +192,11 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
//experience is reported as normal
val deposit: Long =
(Config.app.game.experience.sep.ntuSiloDepositReward.toFloat *
- math.floor(amount).toFloat /
- math.floor(resourceSilo.MaxNtuCapacitor / resourceSilo.Definition.ChargeTime.toMillis.toFloat)
+ amount / (resourceSilo.MaxNtuCapacitor * resourceSilo.Definition.ChargeTime.toSeconds.toFloat)
).toLong
vehicle.Zone.AvatarEvents ! AvatarServiceMessage(
owner.name,
- AvatarAction.AwardBep(0, deposit, ExperienceType.Normal)
+ AvatarAction.AwardBep(owner.charId, deposit, ExperienceType.Normal)
)
zones.exp.ToDatabase.reportNtuActivity(owner.charId, resourceSilo.Zone.Number, resourceSilo.Owner.GUID.guid, deposit)
}
diff --git a/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala b/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala
index 97d6b857..10efab81 100644
--- a/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala
@@ -133,6 +133,7 @@ trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior {
ActivatePanelsForChargingEvent(ChargeTransferObject)
}
+ //noinspection ScalaUnusedSymbol
def WithdrawAndTransmit(vehicle: Vehicle, maxRequested: Float): Any = {
val chargeable = ChargeTransferObject
var chargeToDeposit = Math.min(Math.min(chargeable.NtuCapacitor, 100), maxRequested)
@@ -188,7 +189,7 @@ trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior {
transferTarget match {
case Some(silo: ResourceSilo) =>
scala.math.min(
- scala.math.min(silo.MaxNtuCapacitor / silo.Definition.ChargeTime.toMillis.toFloat, chargeable.NtuCapacitor),
+ scala.math.min(silo.MaxNtuCapacitor / silo.Definition.ChargeTime.toSeconds.toFloat, chargeable.NtuCapacitor),
max
)
case _ =>
diff --git a/src/main/scala/net/psforever/objects/vital/InGameHistory.scala b/src/main/scala/net/psforever/objects/vital/InGameHistory.scala
index eebc44ec..2e96aeb6 100644
--- a/src/main/scala/net/psforever/objects/vital/InGameHistory.scala
+++ b/src/main/scala/net/psforever/objects/vital/InGameHistory.scala
@@ -50,14 +50,16 @@ trait SupportActivityCausedByAnother {
def amount: Int
}
+trait IncarnationActivity extends GeneralActivity
+
final case class SpawningActivity(src: SourceEntry, zoneNumber: Int, unit: Option[SourceEntry])
- extends GeneralActivity
+ extends IncarnationActivity
final case class ReconstructionActivity(src: SourceEntry, zoneNumber: Int, unit: Option[SourceEntry])
- extends GeneralActivity
+ extends IncarnationActivity
final case class RevivingActivity(target: SourceEntry, user: PlayerSource, amount: Int, equipment: EquipmentDefinition)
- extends GeneralActivity with SupportActivityCausedByAnother
+ extends IncarnationActivity with SupportActivityCausedByAnother
final case class ShieldCharge(amount: Int, cause: Option[SourceEntry])
extends GeneralActivity
diff --git a/src/main/scala/net/psforever/objects/zones/exp/KillAssists.scala b/src/main/scala/net/psforever/objects/zones/exp/KillAssists.scala
index a6da52c9..8b15262b 100644
--- a/src/main/scala/net/psforever/objects/zones/exp/KillAssists.scala
+++ b/src/main/scala/net/psforever/objects/zones/exp/KillAssists.scala
@@ -315,8 +315,10 @@ object KillAssists {
history: Iterable[InGameActivity]
): Long = {
//base value (the kill experience before modifiers)
- val base = Support.baseExperience(victim, history)
- if (base > 1) {
+ lazy val base = Support.baseExperience(victim, history)
+ if (killer.Faction == victim.Faction || killer.unique == victim.unique) {
+ 0L
+ } else if (base > 1) {
//include battle rank disparity modifier
val battleRankDisparity = {
import net.psforever.objects.avatar.BattleRank
diff --git a/src/main/scala/net/psforever/util/Config.scala b/src/main/scala/net/psforever/util/Config.scala
index 7ddcd0f4..ccdf112e 100644
--- a/src/main/scala/net/psforever/util/Config.scala
+++ b/src/main/scala/net/psforever/util/Config.scala
@@ -289,9 +289,11 @@ case class CommandExperiencePoints(
)
case class PromotionSystem(
- active: Boolean,
- maxBattleRank: Int,
- battleExperiencePointsModifier: Float,
- supportExperiencePointsModifier: Float,
- captureExperiencePointsModifier: Float
+ active: Boolean,
+ broadcastBattleRank: Int,
+ resetBattleRank: Int,
+ maxBattleRank: Int,
+ battleExperiencePointsModifier: Float,
+ supportExperiencePointsModifier: Float,
+ captureExperiencePointsModifier: Float
)