diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala index 51683d18..96b6f2c6 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala @@ -23,7 +23,7 @@ trait AuraEffectBehavior { * key - aura effect; value - the timer for that effect * @see `ApplicableEffect` */ - private val effectToTimer: mutable.HashMap[Aura, Cancellable] = mutable.HashMap.empty[Aura, Cancellable] + private val effectToTimer: mutable.HashMap[Aura, AuraEffectBehavior.Entry] = mutable.HashMap.empty[Aura, AuraEffectBehavior.Entry] def AuraTargetObject: AuraEffectBehavior.Target @@ -44,7 +44,7 @@ trait AuraEffectBehavior { */ def ApplicableEffect(effect: Aura): Unit = { //create entry - effectToTimer += effect -> Default.Cancellable + effectToTimer += effect -> AuraEffectBehavior.Entry() } /** @@ -67,27 +67,29 @@ trait AuraEffectBehavior { /** * As long as the effect has been approved for this target, * the timer will either start if it is stopped or has never been started, - * or the timer will stop and be recreated with the new duration if is currently running. + * or the timer will stop and be recreated with the new duration if is currently running for a shoreter amount of time. * @param effect the effect to be emitted * @param duration for how long the effect will be emitted * @return `true`, if the timer was started or restarted; * `false`, otherwise */ private def StartAuraTimer(effect: Aura, duration: Long): Boolean = { - //pair aura effect with id + //pair aura effect with entry (effectToTimer.get(effect) match { - case None => - None - case Some(timer) => + case Some(timer) if timer.start + timer.duration < System.currentTimeMillis() + duration => timer.cancel() Some(effect) + case _ => + None }) match { case None => false case Some(_) => - //paired id with timer; retime - effectToTimer(effect) = + //retime + effectToTimer(effect) = AuraEffectBehavior.Entry( + duration, context.system.scheduler.scheduleOnce(duration milliseconds, self, AuraEffectBehavior.EndEffect(effect)) + ) true } } @@ -102,7 +104,7 @@ trait AuraEffectBehavior { effectToTimer.get(effect) match { case Some(timer) if !timer.isCancelled => timer.cancel() - effectToTimer(effect) = Default.Cancellable + //effectToTimer(effect) = Default.Cancellable AuraTargetObject.RemoveEffectFromAura(effect) true case _ => @@ -116,7 +118,7 @@ trait AuraEffectBehavior { def EndAllEffects() : Unit = { effectToTimer.keysIterator.foreach { effect => effectToTimer(effect).cancel() - effectToTimer(effect) = Default.Cancellable + //effectToTimer(effect) = Default.Cancellable } val obj = AuraTargetObject obj.Aura.foreach { obj.RemoveEffectFromAura } @@ -164,6 +166,18 @@ trait AuraEffectBehavior { object AuraEffectBehavior { type Target = PlanetSideServerObject with AuraContainer + case class Entry(duration: Long, timer: Cancellable) extends Cancellable { + val start: Long = System.currentTimeMillis() + + override def isCancelled : Boolean = timer.isCancelled + + override def cancel: Boolean = timer.cancel + } + + object Entry { + def apply(): Entry = Entry(0, Default.Cancellable) + } + final case class StartEffect(effect: Aura, duration: Long) final case class EndEffect(aura: Aura) diff --git a/common/src/test/scala/objects/AuraTest.scala b/common/src/test/scala/objects/AuraTest.scala index a4af3983..40aec57c 100644 --- a/common/src/test/scala/objects/AuraTest.scala +++ b/common/src/test/scala/objects/AuraTest.scala @@ -116,6 +116,31 @@ class AuraEffectBehaviorStartEffectTest extends ActorTest { } } +class AuraEffectBehaviorStartLongerEffectTest extends ActorTest { + val obj = new AuraTest.Entity() + val updateProbe = new TestProbe(system) + obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor") + + "AuraEffectBehavior" should { + "replace a shorter effect with a longer one" in { + assert(obj.Aura.isEmpty) + obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 500) + val msg1 = updateProbe.receiveOne(100 milliseconds) + assert( + msg1 match { + case AuraTest.DoUpdateAuraEffect() => true + case _ => false + } + ) + assert(obj.Aura.contains(Aura.Plasma)) + obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500) + updateProbe.expectNoMessage(2000 milliseconds) + //first effect has not ended naturally (yet) + assert(obj.Aura.contains(Aura.Plasma)) + } + } +} + class AuraEffectBehaviorNoRedundantStartEffectTest extends ActorTest { val obj = new AuraTest.Entity() val updateProbe = new TestProbe(system) @@ -137,6 +162,29 @@ class AuraEffectBehaviorNoRedundantStartEffectTest extends ActorTest { assert(obj.Aura.contains(Aura.Plasma)) obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500) updateProbe.expectNoMessage(1500 milliseconds) + } + } +} + +class AuraEffectBehaviorNoOverrideStartEffectTest extends ActorTest { + val obj = new AuraTest.Entity() + val updateProbe = new TestProbe(system) + obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor") + + "AuraEffectBehavior" should { + "not replace a long-running effect with a short-running effect" in { + assert(obj.Aura.isEmpty) + obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500) + val msg1 = updateProbe.receiveOne(100 milliseconds) + assert( + msg1 match { + case AuraTest.DoUpdateAuraEffect() => true + case _ => false + } + ) + assert(obj.Aura.contains(Aura.Plasma)) + obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 500) + updateProbe.expectNoMessage(1500 milliseconds) //effect has not ended naturally assert(obj.Aura.contains(Aura.Plasma)) }