Coroutines - Enemy alert cooldown (Dishonored/Thief/etc)

How would I do an alert cooldown for a stealth game?

It seems simple. If the enemy can see the player, do a countdown. If it reaches 0 increase the alert level. This part’s easy.

If the enemy can’t see the player, do a countdown. If it reaches 0, decrease the alert level.

The problem is that the player can move in/out of sight at any time, requiring the countdown coroutines to stop part-way through, so I can’t use WaitForSeconds. So I have to use a custom timer, but I’m having all kinds of problems restarting/resetting the time, and it’s having to be blocked off using “if (bTimerIsHappening) yield break;” so it doesn’t run more than once, etc.

I was going to give up and do it all in Update, but I can’t set the countdown time in there, because it will set it every frame…It seems so easy but I can’t figure it out.

Example:

  • Enemy not alert - If bCanSeePlayer, count down for 3 seconds, increase alert level. Event !bCanSeePlayer, cancel timer if running, do not increase level.
  • Level 1 - If bCanSeePlayer, countdown 2 seconds, increase level. If !bCanSeePlayer, countdown 10 seconds,
  • Level 2 - If bCanSeePlayer, countdown 2 seconds, increase level. If !bCanSeePlayer, countdown 15 seconds,
  • Level 3 - If !bCanSeePlayer, countdown 30 seconds.

I would use two timers, one for up and other for down time. When bCanSeePlayer is true, reset the down timer and increment the up timer, then check whether the up duration has been reached - if so, increment level and clear both timers. When it’s false, reset the up timer and increment the down timer, then check the down duration and, if reached, decrement level and clear timers. The special cases (levels 0 and 3) can be easily handled by avoiding time counting up/down when there’s no level above/below:

var bCanSeePlayer: boolean;
var curLevel: int = 0;
var upTime: float = 0;
var dnTime: float = 0;

function CheckCountdown(upDuration, downDuration){
  if (bCanSeePlayer){ // player can be seen:
    dnTime = 0; // reset down timer...
    if (curLevel < 3){ // only count up if can increment level:
      upTime += Time.deltaTime; // count up time
      if (upTime >= upDuration){ // if up timer reached upDuration...
        curLevel++; // increment level...
        upTime = 0; // and reset up timer
      }
    }
  } else { // player out of sight:
    upTime = 0; // reset up timer...
    if (curLevel > 0){ // only count down if can decrement level:
      dnTime += Time.deltaTime; // count down time
      if (dnTime > downDuration){ // when down timer reached downDuration...
        curLevel--; // decrement level...
        dnTime = 0; // and reset down timer
      }
    }
  }
}

function Update(){
  switch (curLevel){
    case 0:
      CheckCountdown(3, 0); // only upDuration needed
      break;
    case 1:
      CheckCountdown(2, 10);
      break;
    case 2:
      CheckCountdown(2, 15);
      break;
    case 3:
      CheckCountdown(0, 30); // only downDuration needed
      break;
  }
}