Heist – Up Until Now

Heist is a s&box project made Yoran Mandema (Naroii) and MuffinTastic. I joined it after gaining developer access to play around with existing code and come up with new stuff. You may find the GitHub project here.

The first thing I did was check out the viewmodel code and make it look better with the differently scaled CSS viewmodels.

Improved Viewmodel sway and bob. Most of it was written beforehand, but it worked very poorly on the CSS models up until now.

After that, I thought about what the game needs – there wasn’t really a goal or design outline, and we were just playing around with what s&box can do. I found Facepunch’s AI Lab example showcasing use of Source 2’s navmesh capabilities and went: “We’ve got to have something to shoot at”. Hence, NPC enemies.

Working with GMod NPCs was a pain – you either had to make nextbots, which had little documentation and were more expensive, or go off the NPC entities, which has a ton of hardcoded behavior from the Source era, and can only use nodegraphs.

By comparison, making an NPC move in s&box was a breeze. The core library was Sandbox.NavMesh, which built a list of Vector3 points from a position to another. All that’s left is getting a vector direction from that list and setting the NPC’s velocity to that direction, handled by AI Lab’s NavPath and NavSteer class respectively. The NPC itself is simply an AnimEntity, with animation variables set every tick and velocity handled by NavSteer.

I was impressed by how fast I did all this – it took no more than two hours to figure all of this out and get an NPC that wanders around and chases the player when it’s hurt. Much of this is thanks to the hotloading system, which was even better than GMod’s with clear indicators when exceptions or compile errors happen.

The NPC does a Terminator styled slow walk towards the player. He’ll get his guns… soon.

Next step was giving it a gun. It would be too simple to just have them point at the player and use terrible accuracy to compensate for the aim – I liked the idea of parity, where the NPCs are restricted by vision and turn rates, so they can’t aimbot you or see you behind walls. So, instead of snapping to the player, the NPC turns their EyeRot at a fixed rate.

The aiming was a bit too slow and couldn’t hit the player at all to begin with, but the idea of outplaying these NPCs were quite great and I wanted to kept it going.

The NPC tries (and fails) to track the player. There was a lot of tuning done on this end to make sure the player can outmaneuver the NPCs, but also still making NPCs threatening.

After that, I thought a bit more about the purpose of NPCs in this game, as well as how better to implement parity. The biggest necessity is some sort of behavior system, like a Finite State Machine. The idea is quite simple: the NPC can be in one of several defined states. Certain events/commands can fire, which changes the state from one to another. There is a predefined list of transitions, which define which state and command transitions into which new state.

NPC States and Events.

As part of the parity concept, I tried to make NPCs fight smartly. This means a bit of unpredictability in their movement – they can strafe when engaging, and have a degree of randomness when searching (which may make them take flank routes). I also eventually made them use player weapons instead of custom NPC weapons – but it involved working around the Simulate function not being called on NPC-owned weapons.

I also implemented vision checks. If the NPC can’t see the player, it can only act based off the player’s last known position. When spotting an enemy the NPC didn’t see for a bit (or ever), the NPC can’t act for a short while, allowing the player to ambush them.

Finally, I added a more complex target memory. Previously, the NPC could only memorize one target, and would fight that target only until disengaged. I added a NpcTargetInfo struct, which holds some variables about what an NPC knows about the target, as well as how dangerous it is (an Enmity value).

The NpcTargetInfo struct.

I had a bit of struggle with null values – in Lua, which had dynamic typing, any type can be nil, which meant it’s easy to check if something exists or not. You can’t really do that without causing Nullable headaches in C#, so I implemented an IsValid function that ensures the target stored in the struct is valid (otherwise, the NPC has no target).

That’s all the stuff I’ve done on my part. There’s still a long ways to go, and I’ve identified some issues and features I might work on:

  1. There’s a crash that happens like 80% of the time when a crossbow bolt tries to parent to the NPC. Dunno why that is.
  2. The NPC needs to be able to patrol in a given path, deviate from it to investigate things or fight players, and return to it when done.
  3. Perhaps the NPC should be able to identify and use cover dynamically. H3VR has a similar system, but I need to do some more research.
  4. The state machine has some issues with the multi-target system right now, and sometimes the NPC engages targets when in Search or Patrol states.
  5. It’s hard to tell which state the NPC is in. Perhaps it can bark lines (with speech bubbles) so the player knows what the NPC is doing.

I also gave some thought into what this game should actually have as its core gameplay. Perhaps it’s a mix of stealth and gunplay, like Payday 2? Or it can be some more straightforward combat experience like H3VR’s Take and Hold. All of these ideas need a map though, and the current Construct proves a little too open to properly test NPCs’ combat behavior.

Writing code for s&box is really fun, and I’m excited to see where this project leads me to.

You’re pretty good.