From 7f61206ddd3e3d61b256952e2c5608058c5cf995 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Tue, 7 Nov 2023 16:07:08 -0500 Subject: [PATCH] exp for ntu and ntu silo operation restored; message about progress system given more limited scope; adjusted cep for llu carrier kill; kd accumulates by kills and maintains between lives; ifflock does not discriminate rehack faction; no rewards for killing allies, or self --- src/main/resources/application.conf | 7 +- .../actors/session/AvatarActor.scala | 167 +++++++++++++----- .../psforever/actors/session/ChatActor.scala | 7 +- .../actors/session/SessionActor.scala | 5 + .../support/SessionAvatarHandlers.scala | 21 --- .../actors/session/support/SessionData.scala | 18 ++ .../session/support/ZoningOperations.scala | 117 +++++++----- .../objects/avatar/scoring/ScoreCard.scala | 1 + .../serverobject/locks/IFFLockControl.scala | 5 +- .../resourcesilo/ResourceSiloControl.scala | 5 +- .../vehicles/AntTransferBehavior.scala | 3 +- .../objects/vital/InGameHistory.scala | 8 +- .../objects/zones/exp/KillAssists.scala | 6 +- .../scala/net/psforever/util/Config.scala | 12 +- 14 files changed, 257 insertions(+), 125 deletions(-) diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index df36b5d88..a80dfd492 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 9370c9819..f6026d8c4 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 44f82f5c4..8f49a5562 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 29d69bc6e..0f26f1179 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 ee6055c46..41588dcc5 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 712b71dfe..46fa170e8 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 d0a4f4083..ce23bd530 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 40f97f275..0ab5d4407 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 f2c9890ef..026f3f1f1 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 a1cf825be..deba736d4 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 97d6b8576..10efab816 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 eebc44eca..2e96aeb6c 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 a6da52c96..8b15262bb 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 7ddcd0f47..ccdf112ef 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 )