Skip to content

Instantly share code, notes, and snippets.

@tivrfoa
Created February 28, 2025 16:47
Show Gist options
  • Save tivrfoa/e63ed45123cb15f1e8488c65f426628e to your computer and use it in GitHub Desktop.
Save tivrfoa/e63ed45123cb15f1e8488c65f426628e to your computer and use it in GitHub Desktop.
C&C Tiberian Dawn Bullet.CPP Analysis by Google AI Studio Gemini 2.0 Pro Experimental 02-05

Let's break down the BULLET.CPP code from the Command & Conquer: Tiberian Dawn Remastered Collection source. This file defines the behavior of bullets (and similar projectiles) within the game.

Core Concepts and Purpose

This file is responsible for:

  1. Bullet Creation: Defining how bullets are spawned (created) in the game world.
  2. Bullet Movement: Handling the trajectory and movement of bullets across the map.
  3. Bullet Impact: Determining what happens when a bullet hits something (a unit, building, or terrain).
  4. Bullet Lifetime: Managing how long a bullet exists before disappearing.
  5. Visual and Audio Effects: Controlling the appearance and sound effects associated with bullets.

Key Code Sections and Explanations

Here's a breakdown of the important parts of the code, with explanations:

#include "IBULLET.H"  // Interface for bullet-related functions
#include "IPREDRAW.H" // Interface for pre-drawing operations
#include "IMATH.H"    // Interface for mathematical functions
#include "SND.H"      // Interface for sound functions.
#include "SMK.H"     // Interface for Smacker video.
#include "ACTOR.H"    // Interface for Actor-related stuff.
#include "HOUSE.H"	  // Interface for house.
#include "TRIG.H"	  // Interface for Triggers.
#include "IFLASH.H"   // Interface for Firing FLASHes.
#include "IGRAPH.H"  // Interface for drawing and graphics operations
#include "SCENARIO.H" // Scenario definitions
#include "PROFILE.H"  // Game profiling
  • Includes: These lines import necessary header files. The headers provide definitions for functions, classes, and data structures that are used within BULLET.CPP. Notably:
    • IBULLET.H: Likely contains the declaration of the BulletClass itself (the main class in this file).
    • ACTOR.H: Suggests that bullets are treated as a type of "actor" in the game engine, meaning they have a position, can move, and can interact with other objects.
    • HOUSE.H: Handles house and player data.
    • TRIG.H: Handles trigger zones.
    • IFLASH.H: Handles animation of firing animations.
DEFINE_POOLS(BulletClass); //Macro to create pools for bullets
  • DEFINE_POOLS(BulletClass): This is a crucial optimization technique called object pooling. Instead of constantly creating and destroying bullet objects (which is expensive in terms of memory allocation and deallocation), the game pre-allocates a "pool" of BulletClass objects. When a bullet is needed, one is taken from the pool; when it's no longer needed, it's returned to the pool instead of being destroyed. This drastically improves performance, especially in a game like C&C where many bullets are fired.
BulletClass::BulletClass() : ActorClass()
{
	Frame = 0;
	Owner = nullptr;
	Target = nullptr;
	ObjectType = nullptr;
	Damage = 0;
	BaseDamage = 0;
	ScatterRadius = 0;
	IsAttached = false;
	FlashIndex = 0;
	HitWater = false;
	SoundID = -1;
	Type = BulletType::None;
	AttachedToID = 0;
	FrameSkip = 0;
	FrameSkipCount = 0;
}
  • BulletClass::BulletClass() (Constructor): This is the constructor for the BulletClass. It's called when a new BulletClass object is created (or, more accurately, retrieved from the object pool). It initializes the bullet's member variables to their default values:
    • Frame = 0;: The animation frame (starts at 0).
    • Owner = nullptr;: A pointer to the object (unit or building) that fired the bullet. nullptr means no owner (yet).
    • Target = nullptr;: A pointer to the object that is being targeted.
    • ObjectType = nullptr;: A pointer to the "type" of the bullet (e.g., a specific weapon type). This likely links to data defining the bullet's properties.
    • Damage = 0;: The amount of damage the bullet will inflict.
    • BaseDamage = 0;: The weapon's base damage.
    • ScatterRadius = 0;: Used for weapons that have an area of effect (like artillery).
    • IsAttached = false;: Indicates whether the bullet is "attached" to another object (e.g., a muzzle flash).
    • FlashIndex = 0;: Index to use in a firing animation.
    • HitWater = false;: Boolean flag to check if it hit water.
    • SoundID = -1;: The ID of the sound effect to play. -1 likely means no sound.
    • Type = BulletType::None;: An enum value indicating the type of bullet.
    • AttachedToID: ID of whatever actor this is tied to.
    • FrameSkip: How many frames to skip before drawing.
    • FrameSkipCount: Counter to determine when to draw.
void BulletClass::Init(
	CoordStruct const& location, // Bullet's starting location
	int facing,                // Bullet's initial facing direction
	ObjectClass const* objectType,   // Pointer to the bullet's type definition
	AbstractClass* owner,      // Pointer to the object that fired the bullet
	AbstractClass* target,     // Pointer to the target object
	int soundID,               // ID of the sound effect to play
	int baseDamage,
	int scatterRadius,
	int flashIndex,
	BulletType type)
{
	// ... initialization code ...
	ActorClass::Init(location, facing, objectType, owner);
	// ... more initialization ...
}
  • BulletClass::Init(...): This function is called to initialize a bullet when it's fired. It takes a lot of parameters that define the bullet's initial state:

    • location: Where the bullet starts.
    • facing: The direction the bullet is initially pointing.
    • objectType: The type of bullet (e.g., "machine gun bullet," "tank shell"). This likely points to a data structure that contains information about the bullet's appearance, speed, damage, etc.
    • owner: Who fired the bullet.
    • target: What the bullet is aimed at.
    • soundID: The sound to play when the bullet is fired.
    • baseDamage: Weapon base damage
    • scatterRadius: Area of effect radius.
    • flashIndex: Animation flash index.
    • type: The type of bullet (e.g., BulletType::Normal, BulletType::Missile).

    Inside Init, the bullet's properties are set based on these parameters. Importantly, it calls ActorClass::Init(...). This suggests that BulletClass inherits from ActorClass. ActorClass likely handles basic properties and behaviors common to all moving objects in the game.

void BulletClass::Process()
{
	// ... bullet movement and logic ...
}
  • BulletClass::Process(): This is the heart of the bullet's behavior. This function is called every frame for each active bullet. It's responsible for:
    • Movement: Calculating the bullet's new position based on its speed and direction. This often involves basic physics calculations (e.g., new_x = old_x + velocity_x * time).
    • Collision Detection: Checking if the bullet has hit anything (a unit, building, or terrain).
    • Damage Application: If a collision occurs, applying damage to the target. This likely involves calling functions on the target object to reduce its health.
    • Lifetime Management: Decreasing the bullet's remaining lifetime. If the lifetime reaches zero, the bullet is deactivated (returned to the object pool).
    • Special Effects: Triggering visual effects (like explosions or impact sparks) or playing sounds.
    • Animation: Update the frame for animated projectiles.
bool BulletClass::Draw(int x, int y)
{
	// ... drawing code ...
}
  • BulletClass::Draw(int x, int y): This function handles drawing the bullet on the screen. It takes the bullet's coordinates (x, y) as input. It likely uses functions from IGRAPH.H to draw the appropriate sprite or animation frame for the bullet. The return value (bool) probably indicates whether the drawing was successful.
void BulletClass::Deactivate()
{
	// ... cleanup code ...
	ActorClass::Deactivate();
}
  • BulletClass::Deactivate(): This function is called when the bullet is no longer needed (it hit something, its lifetime expired, etc.). It performs any necessary cleanup, such as stopping sound effects. It calls ActorClass::Deactivate(), which likely handles common deactivation tasks for all actors. The bullet object is then returned to the object pool.

Specific Code Snippets and Explanations

Let's look at some more detailed examples from within these functions:

  • Movement (inside Process()):

    //Simplified pseudo code.  Not exact from the file, but showing the concept.
    CoordStruct velocity = CalculateVelocity(Facing); // Get velocity vector based on facing
    Location.X += velocity.X; // Move in X direction
    Location.Y += velocity.Y; // Move in Y direction

    This snippet shows how the bullet's position (Location) is updated each frame. CalculateVelocity (not shown in the provided file, likely in IMATH.H or a related file) converts the Facing (direction) into a velocity vector (X and Y components). The bullet's position is then incremented by this velocity.

  • Collision Detection (inside Process()):

    //Simplified pseudo code
    AbstractClass* hitObject = CheckCollision(Location); // Check for collision at current location
    if (hitObject) {
        // Handle the collision
        hitObject->ApplyDamage(Damage, Owner);  // Apply damage to the hit object
        Deactivate(); // Deactivate the bullet
    }

    This shows a simplified collision check. CheckCollision (again, likely defined elsewhere) determines if the bullet's current location overlaps with another object. If a collision is detected, the ApplyDamage function of the hit object is called, and the bullet is deactivated.

  • Drawing (inside Draw()):

    //Simplified pseudo code.
    if (ObjectType)
    {
    	Draw_Object(ObjectType->Image, x, y, Frame); // Draw the bullet's sprite
    }

    This shows how the game might draw the appropriate graphic for the projectile based on its type's image data.

  • Area Damage ```c++ if (ScatterRadius > 0) { // Apply splash damage XYZCoordStruct xyz; xyz.X = Location.X; xyz.Y = Location.Y; xyz.Z = 0; // Assuming Z is not relevant for 2D damage

    // This is a crucial function that applies damage in an area. TechnoClass::InflictAreaDamage( xyz, // Center of the area ScatterRadius, // Radius of the area BaseDamage, // Damage to apply Owner, // Who is causing the damage true, // Whether to do full damage (as opposed to a percentage) true, // "Splash" flag true, // Damage is applied true // Ignore Owner, so self-damaging units are damaged ); } ``` This handles splash damage. It checks if the ScatterRadius is greater than zero. If it is, it calculates the center point of the damage area (`xyz`) and then calls `TechnoClass::InflictAreaDamage`. `InflictAreaDamage` would be responsible for finding all objects within the `ScatterRadius` of the impact point and applying damage to them. This is how explosions and artillery work.

  • Animation Updates:

    if (ObjectType->Animation) {
    	// Update the frame based on FrameSkip
    	if (++FrameSkipCount >= FrameSkip) {
    		FrameSkipCount = 0;
    		Frame = (Frame + 1) % ObjectType->Animation->NumFrames;
    	}
    }

    This block handles animation. If the bullet's ObjectType has an associated animation (ObjectType->Animation), the code updates the current frame (Frame) to be displayed. The FrameSkip and FrameSkipCount variables control the animation speed. FrameSkip determines how many game frames to wait before advancing to the next animation frame. FrameSkipCount keeps track of how many frames have passed. The modulo operator (%) ensures that the Frame value wraps around to 0 when it reaches the end of the animation (ObjectType->Animation->NumFrames).

Summary and Key Takeaways

  • Object Pooling: The use of DEFINE_POOLS is a critical performance optimization.
  • Actor-Based System: Bullets are treated as "actors," inheriting common functionality from a base ActorClass.
  • Modular Design: The code is well-structured, with separate functions for initialization, processing (movement, collision), drawing, and deactivation.
  • Type-Based Properties: Bullet properties (appearance, damage, etc.) are defined by the ObjectClass associated with the bullet, making it easy to create different types of bullets.
  • Frame-Based Animation: Simple frame-based animation is used for animated projectiles.
  • Area Damage: The ScatterRadius and TechnoClass::InflictAreaDamage system implement splash damage.
  • Inheritance: The inheritance of the ActorClass and reliance on other includes gives insight to how other objects in the game may be designed, since a bullet is one of the more simple objects.

This breakdown gives you a solid understanding of how bullets are implemented in the Tiberian Dawn engine. By studying this code, you can learn valuable techniques for game development, including object pooling, actor systems, collision detection, and basic physics simulation. Remember this is a simplified explanation of complex interactions. The actual code interacts with many other parts of the game engine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment