Proposed Specification
The more abstract approach is preferred, modeling the weapon as a symbol. A lower-level approach exposing a detailed model to the AI would be unnecessary and complex to handle. It would be trivial to use static definitions (such as a C++ enum) to inform the AI about the types of weapons at compile time. However, this enumeration approach implies the AI is dependent on the engine's implementation. Instead, we'll define symbols that are numerically independent for each of the ten weapons.
We can let the AI know about the weapons available through a specific function of a new inventory interface. This could be extended for generic use later, but leaving the weapon queries separate seems more appropriate—both for the AI and the implementation of the back-end of the interface. The properties (for instance, speed and reload times) of the weapon will not be exposed by a runtime interface; this information has to be innate to the animats (provided by design) or induced online.
Maintaining consistency with the interface for human players also seems worthwhile because it reduces the code required. Players press a key or move the mouse wheel to select a weapon. Cycling through the weapons could be supported by the interface specification indirectly, but direct selection will prove sufficient for the AI.
As for the terrain layout, a high-level query returns a single value as an indication of constriction. This can transparently be implemented using the existing line traces to determine the average distance of nearby obstacles. A more elaborate implementation would cache the resulting values in a spatial data structure so that it does have to be computed every time.
Interfaces
All that's needed in the weapon interface is the capability to execute the decision. This is done via one additional function that takes a symbol and returns a Boolean indication of success:
bool Select(const Symbol& weapon);
Ideally, an inventory is necessary to determine which weapons the animat can use. The personal interface can be extended to support other inventory items, but keeping this separate is more convenient:
void WeaponsAvailable(vector<Symbol>& weapons);
To query the animat's health, we'll use a simple extension to the personal interface:
int Health();
A visual query to determine the level of constriction of the surroundings is also necessary for weapon selection. A high-level query was chosen, easily integrated into the vision interface—allowing it to be implemented within the engine. This function could be kept separate if the implementation needs to be extended:
float GetSpatialRestriction();
This function returns the average distance to an obstacle in any direction from the current position.
Finally, the AI needs a way to determine the amount of damage taken by other players. This takes the shape of an event callback:
void OnDamage( const Event_Damage& e );
The event itself will contain information about the location, an estimate of the amount of damage, and, potentially, a symbol for the player who took damage.
Code Skeleton
So, putting all these different bits together, we get a rough outline of what we need to do to implement our animats:
void Think()
{
// determine what weapons are available
vector<Symbol>& weapons;
personal->WeaponsAvailable( weapons );
// extract properties from the situation
float f = vision->SpatialRestriction();
int h = personal->Health();
// make the weapon decision
weapon->Select( weapons[i] );
}
The callback OnDamage() must be a separate function for C++ function pointers to be usable. The rest of the code will be split, too, but this is a stylistic issue (not a requirement).
|