From 7caa64d5dfc06baa99323f03d0d31c484448ed35 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Fri, 14 Jun 2024 10:23:41 -0400 Subject: [PATCH] implant initialization timers now saved to the database; uninitialized implants will appear as uninitialized when the character loads; passive initialized implants will always start as activate --- .../db/migration/V014__ImplantInit.sql | 3 + .../actors/session/AvatarActor.scala | 100 +++++++++++++++--- .../session/support/ZoningOperations.scala | 13 --- .../psforever/objects/avatar/Implant.scala | 16 ++- .../net/psforever/persistence/Implant.scala | 3 +- 5 files changed, 100 insertions(+), 35 deletions(-) create mode 100644 server/src/main/resources/db/migration/V014__ImplantInit.sql diff --git a/server/src/main/resources/db/migration/V014__ImplantInit.sql b/server/src/main/resources/db/migration/V014__ImplantInit.sql new file mode 100644 index 000000000..e972e2f9d --- /dev/null +++ b/server/src/main/resources/db/migration/V014__ImplantInit.sql @@ -0,0 +1,3 @@ +/* New */ +ALTER TABLE implant +ADD COLUMN timer SMALLINT NOT NULL DEFAULT 0; diff --git a/src/main/scala/net/psforever/actors/session/AvatarActor.scala b/src/main/scala/net/psforever/actors/session/AvatarActor.scala index 2f9ebcbef..d0588a513 100644 --- a/src/main/scala/net/psforever/actors/session/AvatarActor.scala +++ b/src/main/scala/net/psforever/actors/session/AvatarActor.scala @@ -837,6 +837,24 @@ object AvatarActor { _.useCooldowns -> lift(buildClobfromCooldowns(avatar.cooldowns.use)) ) ) + lazy val curr = System.currentTimeMillis() + avatar + .implants + .collect { + case Some(implant) if implant.timer > 0 => + (implant.definition.Name, math.max(0L, (implant.timer - curr) / 1000L).toInt) + case Some(implant) => + (implant.definition.Name, 0) + } + .foreach { + case (name, delay) => + ctx.run( + query[persistence.Implant] + .filter { _.avatarId == lift(avatarId) } + .filter { _.name.equals(lift(name)) } + .update { _.timer -> lift(delay) } + ) + } out.completeWith(Future(1)) case _ => out.completeWith(Future(0)) @@ -998,6 +1016,16 @@ object AvatarActor { decoration = ProgressDecoration(cosmetics = convertedCosmetics) ) } + + private def initializationTime(implant: Implant): FiniteDuration = { + val timer = implant.timer + val normalDuration = implant.definition.InitializationDuration + (if (timer == 0) { + normalDuration + } else { + math.max(0L, (timer - System.currentTimeMillis()) / 1000L) + }).seconds + } } class AvatarActor( @@ -1809,11 +1837,21 @@ class AvatarActor( } yield (certs, implants, locker, debt) result.onComplete { case Success((certs, implants, lockerInv, debt)) => + val curr = System.currentTimeMillis() 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), + implants = implants.map { imp => + val timerEqualsZero = imp.timer == 0 + val initTimer: Long = if (timerEqualsZero) { + 0L + } else { + curr + imp.timer * 1000L //convert from seconds to milliseconds time in future + } + val definition = imp.toImplantDefinition + Some(Implant(definition, initialized = timerEqualsZero, timer = initTimer, active = timerEqualsZero && definition.Passive)) + }.padTo(3, None), locker = lockerInv ) ) @@ -1983,8 +2021,7 @@ class AvatarActor( } else if (becomeFatigued) { avatarCopy(avatar.copy(implants = avatar.implants.zipWithIndex.collect { case (Some(implant), slot) if implant.active => - implantTimers.lift(slot).foreach(_.cancel()) - implantTimers.update(slot, Default.Cancellable) + cancelImplantInitializedTimer(slot) Some(implant.copy(active = false)) case (out, _) => out @@ -3249,7 +3286,8 @@ class AvatarActor( ItemTransactionResultMessage(terminalGuid, TransactionType.Buy, success = true) ) findImplantByType(definition.implantType).foreach { - case (implant, slot) => updateAvatarForImplant(implant, slot, initializeImplant(implant.definition.InitializationDuration.seconds)) + case (implant, slot) => + updateAvatarForImplant(implant, slot, initializeImplant(AvatarActor.initializationTime(implant))) } sessionActor ! SessionActor.CharSaved case _ => @@ -3365,7 +3403,7 @@ class AvatarActor( Some(implant.definition.implantType.shortcut) ) ) - initializeImplant(implant.definition.InitializationDuration.seconds)(implant, slot) + initializeImplant(AvatarActor.initializationTime(implant))(implant, slot) case (None, _) => () } } @@ -3385,20 +3423,37 @@ class AvatarActor( session.get.zone.id, AvatarAction.AvatarImplant(session.get.player.GUID, ImplantAction.Initialization, slot, 0) ) - initializeImplant(implant.definition.InitializationDuration.seconds)(implant, slot) + initializeImplant(AvatarActor.initializationTime(implant))(implant, slot) } private def initializeImplant(delay: FiniteDuration)(implant: Implant, slot: Int): Implant = { + val curr = System.currentTimeMillis() + val implantTimer = implant.timer + val (actualDelay, futureDelay, actionProgress): (FiniteDuration, Long, Long) = if ( + implantTimer > 0 && + implantTimers.lift(slot).exists(_.isCancelled) + ) { + val countedDelay = math.max(0L, (implantTimer - curr) / 1000L) + val fullNormalDelay = implant.definition.InitializationDuration + val progress = if (countedDelay > fullNormalDelay) { + 0L + } else { + (100f * ((fullNormalDelay - countedDelay).toFloat / fullNormalDelay.toFloat)).toLong + } + (countedDelay.seconds, implantTimer, progress) + } else { + (delay, curr + delay.toMillis, 0L) + } //start initialization process - setImplantInitializedTimer(implant, slot, delay) - // Start client side initialization timer, visible on the character screen + setImplantInitializedTimer(implant, slot, actualDelay) + // Start clien t side initialization timer, visible on the character screen // Progress accumulates according to the client's knowledge of the implant initialization time - // What is normally a 60s timer that is set to 120s on the server will still visually update as if 60s\ + // What is normally a 60s timer that is set to 120s on the server will still visually update as if 60s session.get.zone.AvatarEvents ! AvatarServiceMessage( avatar.name, - AvatarAction.SendResponse(Service.defaultPlayerGUID, ActionProgressMessage(slot + 6, 0)) + AvatarAction.SendResponse(Service.defaultPlayerGUID, ActionProgressMessage(slot + 6, actionProgress)) ) - implant.copy(initialized = false, active = false) + implant.copy(initialized = false, active = false, timer = futureDelay) } private def deinitializeImplants(): Unit = { @@ -3421,7 +3476,12 @@ class AvatarActor( session.get.zone.id, AvatarAction.AvatarImplant(session.get.player.GUID, ImplantAction.Initialization, slot, 0) ) - implant.copy(initialized = false, active = false) + //can not formally stop the initialization time on the character information window; set it to 100 to make it look blank + session.get.zone.AvatarEvents ! AvatarServiceMessage( + avatar.name, + AvatarAction.SendResponse(Service.defaultPlayerGUID, ActionProgressMessage(slot + 6, 100)) + ) + implant.copy(initialized = false, active = false, timer = 0L) } private def setImplantInitialized(implantType: ImplantType): Unit = { @@ -3429,15 +3489,17 @@ class AvatarActor( .collect { case (implant, slot) => sendAvatarImplantMessageToSelf(session.get.player.GUID, ImplantAction.Initialization, slot, value = 1) + cancelImplantInitializedTimer(slot) avatarCopy(avatar.copy(implants = avatar.implants.map { case Some(implant) if implant.definition.implantType == implantType && implant.definition.Passive => activateImplantPackets(implant, slot) - Some(implant.copy(initialized = true, active = true)) + Some(implant.copy(initialized = true, active = true, timer = 0)) case Some(implant) if implant.definition.implantType == implantType => - Some(implant.copy(initialized = true)) - case other => other + Some(implant.copy(initialized = true, timer = 0)) + case other => + other })) Some(implant) } @@ -3456,6 +3518,11 @@ class AvatarActor( )) } + private def cancelImplantInitializedTimer(slot: Int): Unit = { + implantTimers.lift(slot).foreach(_.cancel()) + implantTimers.update(slot, Default.Cancellable) + } + private def deactivateImplant(implantType: ImplantType): Unit = { avatar.implants.zipWithIndex.collectFirst { case (Some(implant), index) if implant.definition.implantType == implantType => (implant, index) @@ -3478,8 +3545,7 @@ class AvatarActor( } private def deactivateImplant(implant: Implant, slot: Int): Implant = { - implantTimers.lift(slot).foreach(_.cancel()) - implantTimers.update(slot, Default.Cancellable) + cancelImplantInitializedTimer(slot) // Deactivation sound / effect session.get.zone.AvatarEvents ! AvatarServiceMessage( session.get.zone.id, 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 dc85da3d3..63c196d86 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -2957,19 +2957,6 @@ class ZoningOperations( tplayer.Actor ! JammableUnit.ClearJammeredSound() } avatarActor ! AvatarActor.SoftResetImplants - tavatar.implants.zipWithIndex.collect { - case (Some(implant), slot) if !implant.initialized => - sendResponse(AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 0)) - } -// tavatar.implants.zipWithIndex.collect { -// case (Some(implant), slot) if implant.active && implant.definition.Passive => -// sendResponse(AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 1)) -// sendResponse(AvatarImplantMessage(guid, ImplantAction.Activation, slot, 1)) -// case (Some(implant), slot) if implant.initialized => -// sendResponse(AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 1)) -// case (Some(implant), _) => -// () //avatarActor ! AvatarActor.ResetImplant(implant.definition.implantType) -// } val originalDeadState = deadState deadState = DeadState.Alive sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 82, 0)) diff --git a/src/main/scala/net/psforever/objects/avatar/Implant.scala b/src/main/scala/net/psforever/objects/avatar/Implant.scala index d42c15996..90b338928 100644 --- a/src/main/scala/net/psforever/objects/avatar/Implant.scala +++ b/src/main/scala/net/psforever/objects/avatar/Implant.scala @@ -6,11 +6,19 @@ import net.psforever.packet.game.objectcreate.ImplantEntry case class Implant( definition: ImplantDefinition, active: Boolean = false, - initialized: Boolean = false - //initializationTime: FiniteDuration + initialized: Boolean = false, + timer: Long = 0L ) { def toEntry: ImplantEntry = { - // TODO initialization time? - new ImplantEntry(definition.implantType, None, active) + val initState = if (!initialized) { + if (timer > 0) { + Some(math.max(0, ((timer - System.currentTimeMillis()) / 1000L).toInt)) + } else { + Some(definition.InitializationDuration.toInt) + } + } else { + None + } + new ImplantEntry(definition.implantType, initState, active) } } diff --git a/src/main/scala/net/psforever/persistence/Implant.scala b/src/main/scala/net/psforever/persistence/Implant.scala index 744c025a8..b207db5aa 100644 --- a/src/main/scala/net/psforever/persistence/Implant.scala +++ b/src/main/scala/net/psforever/persistence/Implant.scala @@ -5,7 +5,8 @@ import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalDefin case class Implant( name: String, - avatarId: Int + avatarId: Int, + timer: Int = 0 //seconds to initialize ) { def toImplantDefinition: ImplantDefinition = { ImplantTerminalDefinition.implants(name)