How many times have you played a game where you had to sneak past a guard? Probably a few. There are two ways this is usually done, and they both could use improvement. The first way is to have the guard only notice you if you're in the line of sight, but unless the guard is deaf, he's going to miss obvious cues like when you pull out a weapon behind his back. Another way is to have the guard always notice you, which you usually see if you haven't acquired some item or other or you have to sneak around him some other way. But a well-trained guard should notice any suspicious sights or sounds--not necessarily everything, but just about anything. They may also yell for help if they think something is hinky or they're under attack. You can add this level of realism to your game and improve it drastically.
It doesn't take much to add this concept. Just think about it like this: Every time any action happens, it can be sensed by anything capable of perceiving it. It may be visible, audible, maybe tactile, or even have a smell. In a fantasy game, otherworldly sensations like a sudden fear in the presence of evil may also apply. Try adding a few procs like this:
atom
// src causes the event; loop through all viewers, hearers, etc.
proc/SightEvent(brightness, movement)
proc/SoundEvent(soundname, range, volume)
proc/SmellEvent(smellname, range, intensity)
// src takes notice of the event
proc/NoticeSight(atom/A, brightness, movement)
proc/NoticeSound(atom/A, soundname, range, volume)
proc/NoticeSmell(atom/A, smellname, range, intensity)
A sight event is a change in brightness or some appearance of movement. If you're in the dark for instance, your movement might not be easy to see except for monsters gifted with that sense, but shining a flashlight or walking into the room with a lantern will alert them right away. A sound event consists of the sound effect you want to play, the range where it can be heard, and the maximum volume. A smell can be assumed to spread quickly in a given area, have a certain level of intensity, and then dissipate, unless it's something stuck to your clothes (more on that later).
The sight event might work like this, for example:
atom
proc/SightEvent(brightness, movement)
for(var/mob/M in viewers(src))
NoticeSight(src, brightness, movement)
In each case, you would call ___Event() for the object which caused the event. If you draw a weapon, either you or the weapon should be src calling SightEvent() and SoundEvent(). Then each of these procs should loop through any mobs who might see or hear them, and those mobs would in turn run NoticeSight() and NoticeSound(). Picture this scenario: You've taken an invisibility potion to sneak past a guard. You start walking toward him, but with each step he hears you move and he turns around to look. He sees nothing but his alertness level goes up. So you start tiptoeing forward, until finally he seems to relax and turns around. Getting up close, you draw your sword--but he hears the noise and swings his weapon around, dealing you damage! Even though he can't see you, he's aware something must be there so he starts slashing wildly around. Oh, and he calls his buddy a few rooms away, and his voice carries far enough that the other guard can hear it and come to his aid.
Suddenly the stealth factor in the game has gotten a whole lot more important--and more interesting. I've used this technique myself in Scream of the Stickster Volume II. If you're invisible the Stickster won't see you, but if you make a sound he'll come after you.
You can also have attributes, not events, that could be sensed. If for instance you crawled into the castle through the toilet chute, you might not be generating active smell events every time you move (that could get excessive), but every so often the guard could sniff and notice something smelling funny, and keep sniffing more and more as he got more suspicious. If he happened to bump into you, you would be in trouble. (Also, better hope you wiped your feet, or your footprints could be a dead giveaway.) Or maybe he's hearing a faint high-pitched whine from your enchanted shield. In this case what you would use is sort of the opposite of an event.
atom
proc/Look()
proc/Listen()
proc/Sniff()
Every so often the active NPCs would call these procs to find out if anything was going on that was more of a steady thing, like a light where there shouldn't be one or an unusual odor. This would work especially well for guard dogs, who can smell you before they can see or hear you. Whenever one of these procs latches onto something, they would just call NoticeSight() or what have you to trigger the behavior change.
For the most part, all you really have to do is handle stuff like NoticeSight(), NoticeSound(), and so on to make your AI a little more interesting. Attach all this to a few personality traits like alertness, fear, hunger, aggressiveness, curiosity, etc. and you have the basis for some really smart NPCs. Simple enemies (and friends) can make for good gameplay, but adding just a pinch of depth will really make your game stand out.
Lummox JR is the author of the strategy game Incursion, the dungeon crawl Runt, and the action-oriented Scream of the Stickster games. He has been part of the BYOND community since 2001, but is also a professional programmer with experience in C++, Java, and Perl, among other languages. Currently he works full-time as a developer for BYOND.