Skip to main content

Character stats

Each wizard in the game has a set of core stats managed through the Character struct:
struct Character {
    float health = 300.0f;
    float mana = 300.0f;
    double health_timer = 0.0;
    bool draining_mana = false;
    bool is_cast = false;
    float deathTime = 0.0f;
    bool isDead = false;
    bool deathSoundPlayed = false;
    bool is_local = false; // to check if its a local character or opponent
    Vector2 pos = {};
    Rectangle rect = { 0, 0, 20, 40 };
};
health
float
default:"300.0f"
Current health points. When health reaches 0.0f, the character dies.
mana
float
default:"300.0f"
Current mana points. Required for casting spells and healing.
rect
Rectangle
default:"{ 0, 0, 20, 40 }"
Character collision box (20 pixels wide, 40 pixels tall).

Health system

Starting health

All characters begin with 300.0f health. This is displayed as a red bar above each character.
DrawRectangle((int)all_players[i].pos.x - 10, (int)all_players[i].pos.y - 20, 40, 5, DARKGRAY);
DrawRectangle((int)all_players[i].pos.x - 10, (int)all_players[i].pos.y - 20, (all_players[i].health / 300.0f) * 40, 5, RED);

Death mechanic

When health drops to or below 0.0f, the character enters death state:
if (all_players[i].health <= 0.0f){
    all_players[i].isDead = true;
}
Leaving the map boundaries instantly kills you by setting health to 0.0f:
if (all_players[0].pos.x < 0 || all_players[0].pos.x > 2000 || 
    all_players[0].pos.y < 0 || all_players[0].pos.y > 2000) {
    all_players[0].health = 0.0f;
}

Mana system

Starting mana

All characters begin with 300.0f mana. This is displayed as a purple bar above each character.
DrawRectangle((int)all_players[i].pos.x - 10, (int)all_players[i].pos.y - 13, 40, 5, DARKGRAY);
DrawRectangle((int)all_players[i].pos.x - 10, (int)all_players[i].pos.y - 13, (all_players[i].mana / 300.0f) * 40, 5, PURPLE);

Mana regeneration

Mana regenerates slowly when camera zoom is above 5.0f:
if(all_players[0].mana < 300 && camera.zoom > 5.0f){
    all_players[0].mana = all_players[0].mana + 0.01f;
    all_players[0].draining_mana = false;
}
Mana regenerates at 0.01f per frame when zoomed in past 5.0f and below max mana.

Mana drain from zoom

Zooming out below 5.0f drains mana at a rate proportional to zoom level:
if (camera.zoom < 5.0f){
    all_players[0].mana = all_players[0].mana - (camera.zoom * 0.03f);
    all_players[0].draining_mana = true;
}
The game displays “DRAINING MANA” when this occurs:
if (all_players[0].draining_mana){
    DrawText("DRAINING MANA", 30, 550, 20, BLACK);
}
The drain rate increases as you zoom out further. At camera.zoom = 1.5f (minimum), you drain 0.045f mana per frame.

Healing mechanic

Hold the right mouse button to convert mana into health:
if (IsMouseButtonDown(MOUSE_RIGHT_BUTTON) && all_players[0].mana>12.0f){
    all_players[0].mana = all_players[0].mana - 10.0f;
    all_players[0].health = all_players[0].health+2.0f;
}
Mana cost
float
10.0f mana per frame
Health gain
float
2.0f health per frame
Minimum mana
float
12.0f (healing disabled below this threshold)
The healing conversion ratio is 5:1 (mana to health). At 60 FPS, you can heal 120 health per second while draining 600 mana per second.

Shields system

When health exceeds 300.0f through healing, shields activate:
if (all_players[0].health > 300.0f){
    DrawText("SHIELDS UP", 30, 580, 20, BLACK);
    all_players[0].health_timer += GetFrameTime();
    if (all_players[0].health_timer >= 2.0) {
        all_players[0].health = all_players[0].health - 3.0f;
        all_players[0].health_timer = 0.0f;
    }
}

Shield mechanics

Shields activate automatically when health > 300.0fThe game displays “SHIELDS UP” message on screen
Shields decay slowly but continuously. You lose 3.0f health every 2.0 seconds (1.5f per second) when above 300.0f.

Collision detection

Player-tree collision

Players cannot move through trees. The game checks collision using rectangles:
bool checkCollision(Vector2 player_pos, std::vector<Vector2>& trees_pos){
    Rectangle player_rect = {player_pos.x, player_pos.y, 20, 40};

    for (int i = 0; i < trees_pos.size(); i++){
        Rectangle tree_rect = {trees_pos[i].x, trees_pos[i].y, 20, 60};

        if (CheckCollisionRecs(player_rect, tree_rect)){
            return true;
        }
    }
    return false;
}
When collision is detected, movement is reverted:
Vector2 old_pos = all_players[0].pos;
if (IsKeyDown(KEY_D)) all_players[0].pos.x += 0.5f;
// ... other movement keys
if (checkCollision(all_players[0].pos,tree_pos)){
    all_players[0].pos = old_pos;
}
Player hitbox: 20x40 pixelsTree hitbox: 20x60 pixels

Spell-tree collision

Spells can collide with and destroy trees:
for (int b = 0; b < ball_vec.size(); b++) {
    for (int t = 0; t < tree_pos.size(); t++) {
        Rectangle tree_rect = { tree_pos[t].x, tree_pos[t].y, 20, 60 };

        if (CheckCollisionCircleRec(
                ball_vec[b].ball_pos,
                ball_vec[b].ball_r,
                tree_rect))
        {
            if (ball_vec[b].ball_r >= 4.0f){
                tree_pos[t] = {0, 0};
            }
            ball_vec[b].ball_r -= 5.0f;
            break;
        }
    }
}
Minimum radius to destroy
float
4.0f - spells smaller than this bounce off trees without destroying them
Radius reduction
float
5.0f - each tree collision reduces spell radius by this amount

Combat tips

  • Keep mana above 35.0f to have both spell options available
  • Don’t overheal unless you need the shield buffer
  • Avoid zooming out below 5.0f for extended periods

Build docs developers (and LLMs) love