Advanced target visibility working

GuiltySpark now extracts the map’s BSP and performs ray casting for better detection of target visibility. In fact, it can determine if the line between any two arbitrary points is obstructed by any part of the level (minus the scenery at this point). How I did this is simple in concept: imagine the map as a number line from 1 to 10 instead of a 3D space. Suppose the two points I want to check are 2 and 5. The BSP works by recursively subdividing the space into two halves. In one half we have the set {1, 2, 3, 4, 5}, and in the other we have {6, 7, 8, 9, 10}. Both points are in the first set, so half of the entire map can now be ignored. The next subdivision gives the sets {1, 2, 3} and {4, 5}. The points, 2 and 5, would now lie in different sets and so we stop here. This means that both {1, 2, 3} and {4, 5} may contain a number between 2 and 5 that intersects their ray. As you can see, we can’t only consider the first set because we would miss 4, and likewise for 3 if we only considered the second set. We next consider all the smallest subdivisions of these two groups: {1}, {2}, {3}, {4}, {5}. These are called the leaves and they represent surfaces. Their surface may be located somewhere between 2 and 5, but that doesn’t mean they intersect the ray. Thus each leaf in the range {2} to {5} is checked for ray intersection.

Last night was a total code-a-thon and I ended up writing all the ray-BSP intersection code. I didn’t get a chance to test it last night due to a couple bugs, which I have resolved just this morning 11:50 (morning for me). One bug was forgetting to check if the BSP node child indices are -1. I was already checking if they had their 0x80000000 bit set, which indicates that they are a leaf containing BSP2D references and not another BSP3D node, but an index of -1 says that there is no node at all on that side of the plane. I think this is due to the way Halo creates its BSPs. When building a BSP, it’s difficult to know where to place planes to divide the level properly. I think that the developers chose arbitrary polygons and used the planes they lie on. If this polygon defines the exterior of the geometry at a concave part of the level then there’s a chance the plane won’t intersect any other part of the map and so one side just faces outside the map. In reality, this happens rarely meaning Bungie chose a good heuristic. The next bug was not handling flagged plane indices in surfaces, causing an out of bounds error when accessing my planes array. I’m not sure what a flagged plane index means, but flipping the flag bit seems to have no negative effect.

In any case, the number of flagged planes is probably so low that any problems caused by it would only rarely affect GuiltySpark’s ability to determine if the target is visible or not. Worst case scenario: it shoots at a few walls accidentally. This new method is still a vast improvement over the old target visibility detection. The BSP is extracted in the blink of an eye, so GuiltySpark can do it during gameplay without any hiccups. I thought I would have to read Halo’s memory in large chunks to extract it quickly, but it turns out that wasn’t necessary. GuiltySpark extracts the BSP only once when the target visibility data source is initially requested by the AI, at which point it’s stored internally until you restart the AI. The actual calculations for the ray intersection are fast too. I found plenty of opportunities to prune my options and limit the amount of calculation required.

To calculate if the ray intersects a polygon, I find the point of intersection between the ray and the polygon’s plane. I then project the polygon and the intersection point into 2D space based on the dominant component of the plane’s normal (ensures best topology). The Jordan curve theorem is used next; if an arbitrary ray from the intersection point crosses an even number of the polygon’s edges, the intersection point lies outside the polygon.

So what does this mean for users of GuiltySpark? The bot now knows if a target is visible despite distance and how much the aimbot is leading them. You could also turn off the aimbot when the target is not visible, so no more locking on through walls. All-in-all, this new addition makes for a more believable and human-like bot. With a half-decent AI script, nobody will be able to tell they are playing against a program.


BSP extraction

Over the past while I’ve made a lot of progress reversing Halo’s BSP. Now that I can extract the information, I just need to write the algorithms to work with it. As I’ve mentioned previously, one goal is determining target visibility by checking if any polygon in the BSP intersects the ray between your player and the target. To do this, I’ll find the smallest possible node of the BSP that contains but does not divide the two points. Every leaf under this node will contain potential ray-intersecting polygons.

Here’s what I learned about Halo’s BSPs:


Extras

It looks like I won’t have all the stock node graphs done for release. The maps Blood Gulch, Damnation, and Rat Race have finished graphs. I did some of Hang ‘Em High and Wizard. Freelancer said he did some of Death Island too, so I’d like to include that as well. Freelancer is also going to be making a video showcasing the program’s capabilities. All the documentation is finished now so I’m going to write some more AI scripts while I wait for the video to be done, then it’s time for release!

In addition to the node graphs, AI scripts, tutorials, and example chat file, Halo CE version 1.08 will be included with Version Changer. GuiltySpark is made for 1.08 only, though 1.09 support is something I’ll be adding after release.


No more waiting

Today I’ve been adding a bunch of finishing touches, squashing bugs, and writing documentation and tutorials. Almost everything I wanted to have done by release is done. I couldn’t figure out the vehicle velocity improvement I mentioned earlier so I’m leaving that for later. Player structures don’t seem to store a pointer or object index to what vehicle they’re in.

When I say no more waiting, I’m also referring to busy waiting. I think I finally solved a problem that has been around for quite some time–the program would randomly start hogging the CPU and slowing down itself and the game, even though everything was running smoothly beforehand. I figured it had to do with my aimbot thread. The aimbot needs to keep following a target independently of the AI system. It aims, waits 10 ms, then continues. However, when you want to pause the aimbot then the execution of the thread needs to go somewhere. To avoid creating a new thread every time the AI was started again, I simply looped while a “paused” flag was true. I noticed this took up a lot of my CPU usage, so I added a sleep(150) to the loop which took the CPU usage down but didn’t get rid of the problem completely.

I learned this kind of waiting is called busy waiting and should be avoided because it doesn’t play nice with multithreaded applications. I felt it was kind of hacky from the start anyway. I now use a timer and catch its tick events to trigger the aiming code. I’m not sure how the timer works internally, but I haven’t had the problem since.

Oh, and the bot can type chat messages now. It’ll actually type out entire multi-line files for you in case you want to tell a hundred “Confucius say” jokes to the other players in the server (I may or may not have done this). You can also script it out fairly well by using the $ character, which causes GuiltySpark to wait a second before continuing the message. What’s great is that the chat FID expects numbers for file names and there’s a random number data source. This means you can have the bot select a random message by fiddling with the postfix expression.

Anyway, release soon.


DirectX adopted

GS used to take its sweet time with the CPU when you had the graph view open. This was because it wasn’t taking advantage of the GPU to draw things. Converting the  drawing code over to DirectX turned out to be way easier than I anticipated, and resulting in halving CPU usage when the graph view’s open. I had to learn a bit about the way DirectX expects primitives, then I implemented some wrapping functions to emulate the old ones I had been using in System.Drawing.Graphics. I did have some problems though.

Firstly, the panel control that DirectX was drawing to was flickering. The panel has a bunch of event handlers for clicking and dragging which lets the user zoom, pan, and rotate the graph view. After a long time searching online, I found one person who suggested that a redraw is forced by the panel before the event is raised, which would leave a slight time gap before it is redrawn again. Surely enough, using two panels (using a separate one to draw to) resolved the issue. I just made the clickable one invisible and placed them in the same location.

A second issue was that DirectX only comes in 32 bit. That means GS now has to be 32 bit as well. While it was easy enough to change the build option, it introduced a bug that I didn’t find until the end of the day. Because I was only testing the graph view, the fact that the bot could no longer walk slipped by me. What does walking have to do with drawing the graph? It stumped me at first, then I remembered that I had changed to 32 bit earlier. Little did I know that user32.dll’s SendInput() requires a slightly different argument on 32 bit vs 64 bit. It asks for an array of INPUT structures, but the “type” field at the start of the structure is either 4 or 8 bytes long. When it still didn’t work, I grew pretty frustrated until I finally noticed that my third argument in the call to SendInput was still 40. The third argument is supposed to be the size of the INPUT structure. I made the argument Marshal.SizeOf(typeof(INPUT)), which I should have done at the beginning to save me all this trouble. Lesson learned!


What’s left?

If you’re a member of the Halo community, you’re probably wondering when you can get your hands on GuiltySpark. What’s left? GS still can’t lead targets in vehicles properly because it doesn’t know they’re in a vehicle. This is something I can fix and also expose to the AI system. I’m considering giving the program the ability to type chat messages to the other players even though I know people will horribly abuse this. I’m not responsible for you getting banned from any servers! Perhaps I should add a disclaimer to the documentation. Speaking of the documentation, I have to finish that before release too.

There’s a few things I’ll add after release like 1.09 support, enhanced target visibility detection, and  a full automation mode for server operators (the program will select the right node graph and AI file to use). Source code will become available after release.


On cheating

In the Modacity.net thread, I said I would attempt to stop people from using GuiltySpark to cheat. Because the AI system both accepts keyboard input from the user and controls aiming, it’s incredibly simple to create an aimbot. I suggested not allowing key press detection and aimbot control in the same AI script, but I realize this won’t stop anything…

  • Aimbots already exist, and they don’t have introduced error either
  • People could just use their flashlight, jumping, crouching, or even a shake of the mouse to trigger the aimbot
  • Blocking input might accidentally get in the way of a useful AI script
  • GuiltySpark will be open source anyway; restrictions can be removed

That’s one thing off my to-do list.


Target visibility

As you may know, when you aim at an enemy ingame, their name fades in near the top of the screen. This name won’t show up when obstacles block the target from sight and it has a little bit of leeway in that you don’t have to be aiming exactly at them. This made it ideal for determining target visibility. Unfortunately, it has some drawbacks.  Firstly, the name won’t show up when the target is too far away, even if they’re in range. Secondly, the leeway I mentioned earlier is limited. In laggy servers or when using slow moving projectiles like rockets, the aimbot needs to aim far ahead of the target to get a hit. That means the name won’t show up anymore. A third drawback is that opening the ingame framerate monitor (ctrl+F12) interferes.

I see 3 alternate solutions:

  1. Use whatever function the game uses to determine target visibility
  2. Compare target distance with the depth buffer at their location
  3. Extract the level geometry and perform ray casting

I’m not a fan of option 2 because it requires a Direct X wrapper and the depth buffer is only so precise. Furthermore, I want something that will work even if the target isn’t within the view frustum. I see option 1 as a plan B because option 3 gives me the most options and opportunities. Option 3 would allow me to automatically generate node graphs or display the map within GuiltySpark. Mind you, those improvements are a long way off and I might not get to them. The priority is target visibility.

Sky told me the map cache data is at 0x40440000, so I fired up Cheat Engine and checked it out:

A list of tags starts 0x10 from the start. It wasn’t too hard to figure out the tag reference structure. What I need to do is scan through the list to find the sbsp tag, then follow its data pointer to get the actual BSP data. The next step is to figure out what I need from the BSP to do target visibility calculations. The picture shows that what’s in memory is basically the same as what’s in the .map file. If what’s in the .map file is similar to the structure of the various tag files, then examining the .scenario_structure_bsp tags with Guerilla (part of Halo CE’s editing kit) will make it easier to understand the sbsp tag in memory.

While we’re at it, here’s a list of changes made to GuiltySpark since 1.0.20

  • Added random number data source ($48)
  • The last 10 commands are now stored and can be listed with the “history” command. The command itself is not stored
  • Added “!!” command to re-execute the last command and “!<#>” to re-execute the <#>th last command. Not stored in history
  • Pressing the up arrow key in the command input box cycles through previous commands
  • Renamed “clear” command to “deleteall” in case people confuse “clear” for “cls”
  • Added a “where <#>” command to focus the graph view on the given node
  • Node numbers are no longer drawn over by links and are 1 pt larger for easier reading

A jack of all trades

Last night I gave the beta testers version 1.0.20. It should drastically increase the abilities of the bot; weapon management is one of the big improvements. I can’t wait to see what people do with this. I should make a video showcasing everything GuiltySpark has to offer. Not only is it a multiplayer bot, but the AI system is configurable enough to do almost anything. GuiltySpark could be very useful in Machinima when background actors are needed, removing the need to use keyboard with your feet (you know who you are). It can help other Halo app developers needing someone to test with and perform simple tasks. Server operators can use it to fill slots in an otherwise empty server without appearing AFK.

Here’s a list of changes in 1.0.20:

  • Strafing mode no longer has an effect when following jump or look-ahead links
  • Fixed a case where GuiltySpark would encounter an exception if Halo closed first
  • FIDs with boolean parameters (such as for enabling/disabling modes) now intererpret 0 as false and anything else as true (as opposed to strictly 1 = true)
  • START/PAUSE_AIMBOT were replaced with a single FID TOGGLE_AIMBOT (11) with parameter 0 meaning pause, otherwise start
  • Added exchange weapon, action, crouch, jump, switch weapon, switch grenade, melee, reload, backpack reload, zoom, and flashlight FIDs
  • MOUSE1 and MOUSE2 FIDs take a different parameter format now; -1 is click down, 0 is click up, and any value >0 performs a full click with that number of milliseconds in between button down and up
  • Loading an AI file with a missing included file now results in loading failure
  • The AI output text window now automatically scrolls as new lines are added, and the auto-scrolling for the graph output window is smoother
  • Added ZOOM_LEVEL ($36) and FLASHLIGHT ($37) data sources
  • Added support for new operators in postfix expressions: compare (=, >, <), logic (|, &), and math (^, %, `, ~)
  • FID 0 (print) now uses its task name as a label when printing
  • Added data sources and FIDs for weapon management
  • Fixed how the bot would get temporarily stuck following a path when the AI has found a newer one
  • Fixed look-ahead links conflicted with smooth aiming
  • Fixed an exception when pathfinding failed and the walking thread continued, resulting in following an empty path
  • Added hotkeys to start (F11) and stop (F12) the AI

This might be the last beta version. The list of features to add is getting smaller even though I keep adding to it. As much as I want to keep improving it, I know a lot of people are eagerly waiting. I’ll try not to keep you waiting too long. I’m actually looking forward to all the documentation and tutorials I have to make because it’ll be a nice change.

Research into extracting the BSP has already begun, more on that later.

  • Strafing mode no longer has an effect when following jump or look-ahead links
  • Fixed a case where GuiltySpark would encounter an exception if Halo closed first
  • FIDs with boolean parameters (such as for enabling/disabling modes) now intererpret 0 as false and anything else as true (as opposed to strictly 1 = true)
  • START/PAUSE_AIMBOT were replaced with a single FID TOGGLE_AIMBOT (11) with parameter 0 meaning pause, otherwise start
  • Added exchange weapon, action, crouch, jump, switch weapon, switch grenade, melee, reload, backpack reload, zoom, and flashlight FIDs
  • MOUSE1 and MOUSE2 FIDs take a different parameter format now; -1 is click down, 0 is click up, and any value >0 performs a full click with that number of milliseconds in between button down and up

Hmm… upgrades

My beta testers have been quite helpful so far by requesting lots of features. I had a chance to implement a handful of them today and put up the new version for download. They haven’t really found any glitches yet. Is this a good sign?

 

 

Here’s a list of changes in GuiltySpark v1.0.8:

  • Added random node data source ($35)
  • Zoom level goes up to 25 now
  • The size of drawn links and circles is now adjustable
  • Setting the aimbots projectile velocity to 0 now means ignore travel time
  • Fixed a case where setting view angles incorrectly resulted in an ingame glitch
  • Enforced default properties upon starting the AI: storage values are 0, arc mode is off, gravity scale is 1, projectile velocity is 0, look-ahead mode is off, and strafe mode is off
  • Aimbot wobble increased slightly
  • ENABLE/DISABLE_LOOK_AHEAD replaced with a single FID SET_LOOK_AHEAD_MODE (14); a parameter of 0 means off, anything else means on
  • Added strafe mode FID (24); a parameter of 0 means off, anything else means on

I’ve still got about 7 items left on the beta to-do list. After those and other unforeseen requests are done, I’ll finish up by adding some pre-release goodies. The improved target detection will have to be branched off at some point.