Building and Modding MultiMower with Dry Engine
###############################################

:date: 2025-07-13 10:00
:author: Russell Ballestrini
:tags: Programming, Game Development, Code
:slug: building-and-modding-multimower-with-dry-engine
:status: published
:summary: Learn how to download, compile, and mod MultiMower - a multiplayer lawn mower game built with the Dry Engine. We'll explore the codebase structure.

Resources and Community
-----------------------

- **MultiMower Repository**: https://gitlab.com/luckeyproductions/games/MultiMower
- **Dry Engine**: https://gitlab.com/luckeyproductions/dry
- **Dry Engine Documentation**: Available in the Dry repository

Quick Start Summary
-------------------

For experienced developers, here's the TL;DR version:

.. code-block:: bash

    # Install dependencies on Fedora
    sudo dnf install git make cmake gcc gcc-c++ qt5-qtbase-devel \
        libX11-devel libXrandr-devel alsa-lib-devel mesa-libEGL-devel \
        wayland-devel wayland-protocols-devel
    
    # Build Dry engine
    cd ~/git
    git clone https://gitlab.com/luckeyproductions/dry.git
    cd dry && mkdir build && cd build
    cmake .. -DDRY_64BIT=1 -DCMAKE_BUILD_TYPE=Release -DVIDEO_WAYLAND=OFF
    make -j$(nproc)
    
    # Build MultiMower (CRITICAL: set DRY_HOME first!)
    export DRY_HOME=$HOME/git/dry/build
    cd ~/git
    git clone https://gitlab.com/luckeyproductions/games/MultiMower.git
    cd MultiMower && mkdir build && cd build
    qmake ../MultiMower.pro && make -j$(nproc)
    
    # Run the game
    cp -r ../Resources .
    ./multimower

What You'll Learn
-----------------

By building and modding MultiMower, you'll gain experience with:

- **C++ Game Development**: Modern C++11 with game-specific patterns
- **Physics Programming**: Bullet Physics integration for vehicles and projectiles  
- **3D Graphics**: OpenGL with custom shaders and post-processing
- **Game Architecture**: Component systems, scene graphs, and input handling
- **Asset Pipeline**: Working with 3D models, textures, and audio
- **Cross-Platform Development**: CMake, qmake, make, and Linux development tools

This tutorial is perfect for intermediate programmers who want to understand game engine architecture and learn practical modding techniques.

What is MultiMower?
--------------------

MultiMower is a physics-based 3D game where players control armed robotic lawn mowers. Built with the Dry Engine, it's currently in early development but provides an excellent foundation for learning game modding and physics programming.

**Current Features:**
- Single mower type with basic weaponry (machine gun and missiles)
- Multiple stationary computer-controlled mowers that randomly fire missiles in random directions
- 3D arena with grass terrain and collision boundaries
- Real-time physics simulation for movement and projectiles
- Local player control with keyboard and mouse

**Technical Foundation:**
- OpenGL 3D rendering with custom shaders
- Bullet Physics integration for realistic movement
- Component-based entity system
- Sound system with SDL2 audio
- Cross-platform C++ codebase

**Development Status:**
MultiMower is an artistic technical demonstration in its current state. The original version provides a foundation with basic movement and projectile mechanics that we can enhance through modding. Our tutorial expands it into a full-featured tank combat experience.

Prerequisites
-------------

Before we begin, ensure you have the following installed on your Linux system:

For Ubuntu/Debian:

.. code-block:: bash

    sudo apt-get update
    sudo apt-get install -y git make cmake build-essential qtbase5-dev qt5-qmake \
        libx11-dev libxrandr-dev libasound2-dev libegl1-mesa-dev \
        libwayland-dev wayland-protocols

For Fedora:

.. code-block:: bash

    sudo dnf install git make cmake gcc gcc-c++ qt5-qtbase-devel \
        libX11-devel libXrandr-devel alsa-lib-devel mesa-libEGL-devel \
        wayland-devel wayland-protocols-devel

Downloading the Source Code
---------------------------

First, let's clone the MultiMower repository and checkout the specific commit used in this tutorial:

.. code-block:: bash

    cd ~/git
    git clone git@gitlab.com:luckeyproductions/games/MultiMower.git
    cd MultiMower
    git checkout 60b5dcd6597fe6c7a480505f822a70e0a1469cda

Setting Up the Dry Engine
-------------------------

MultiMower uses the Dry Engine. Here's how to build it from source on Fedora:

Building Dry from Source on Fedora
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1. Clone the Dry repository and checkout the specific commit used in this tutorial:

.. code-block:: bash

    cd ~/git
    git clone git@gitlab.com:luckeyproductions/dry.git
    cd dry
    git checkout bc78ed2c3b2c52f21ccb639ace09c016bce8536d

2. Install dependencies (the repository includes a helper script):

.. code-block:: bash

    # This script automatically detects your distro and installs required packages
    ./script/installreq.sh

3. Build Dry using the provided build script:

.. code-block:: bash

    # For a minimal build (recommended for game development)
    ./quick.sh
    
    # Or for a full build with samples and tools (recommended):
    mkdir build && cd build
    cmake .. -DDRY_64BIT=1 -DCMAKE_BUILD_TYPE=Release -DVIDEO_WAYLAND=OFF
    make -j$(nproc)

**Note**: We disable Wayland (``-DVIDEO_WAYLAND=OFF``) to avoid linking issues. X11 support works perfectly for gaming.

4. Set the DRY_HOME environment variable:

.. code-block:: bash

    # Set DRY_HOME to your Dry build directory (adjust path for your username)
    export DRY_HOME=$HOME/git/dry/build
    
    # Make it permanent (add to ~/.bashrc)
    echo "export DRY_HOME=$HOME/git/dry/build" >> ~/.bashrc
    source ~/.bashrc

Building Dry with Debug Symbols
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If you need debug symbols for development:

.. code-block:: bash

    cd ~/git/dry
    mkdir build-debug && cd build-debug
    cmake .. -DDRY_64BIT=1 -DCMAKE_BUILD_TYPE=Debug -DVIDEO_WAYLAND=OFF
    make -j$(nproc)
    
    # Use this debug build instead
    export DRY_HOME=$HOME/git/dry/build-debug

Compiling MultiMower
--------------------

Now we're ready to build the game in its own directory (much more sensible than building inside the engine):

.. code-block:: bash

    # Navigate to your MultiMower directory and create a build folder
    cd ~/git/MultiMower
    mkdir build && cd build
    
    # CRITICAL: Set DRY_HOME before running qmake
    export DRY_HOME=$HOME/git/dry/build
    
    # Generate the Makefile (qmake will find Dry via DRY_HOME)
    qmake ../MultiMower.pro
    
    # Compile the game with parallel jobs for faster builds
    make -j$(nproc)
    
    # Copy the game resources to the build directory
    cp -r ../Resources .

Build Notes:
~~~~~~~~~~~~

- **Build Location**: The executable is created in ``~/git/MultiMower/build/`` (not in the Dry engine directory!)
- **CRITICAL**: You MUST set ``DRY_HOME=$HOME/git/dry/build`` before running qmake, or compilation will fail with "Dry/Dry.h: No such file or directory"
- **User-Specific Paths**: Replace ``$HOME`` with your actual home directory (e.g., ``/home/yourusername``, ``/Users/yourusername``, etc.)
- **Warnings Expected**: You'll see many compiler warnings about unused parameters and deprecated copy constructors from the Dry engine - these are normal and don't affect functionality
- **Build Time**: Compilation takes about 30-60 seconds on modern hardware
- **File Size**: The final executable is approximately 15MB
- **Dependencies**: Links against libDry.a from your ``$DRY_HOME``, plus pthread, dl, and OpenGL

If everything went well, you should now have a ``multimower`` executable in ``~/git/MultiMower/build/``.

Running the Game
----------------

To run MultiMower, navigate to the build directory:

.. code-block:: bash

    # The game builds in its own build directory  
    cd ~/git/MultiMower/build
    
    # Run the game
    ./multimower

**Important**: The game must be run from the directory containing the ``Resources`` folder, or it won't find its assets.

Game Controls:
~~~~~~~~~~~~~~

**Enhanced Player Controls:**
- **Movement**: WASD keys (W=forward, S=backward, A=turn left, D=turn right)
- **Aim**: Mouse to look around and aim
- **Machine Gun**: Left mouse button (fires bullets)
- **Missiles**: Right mouse button (fires homing missiles)

**Note**: The original demo provides basic movement that we enhance with realistic tank-style acceleration and responsive controls. The mouse aiming works perfectly out of the box.

Expected Output:
~~~~~~~~~~~~~~~~

When you first run the game, you should see:

- A 3D area with tanks surrounding
- Robot mowers that can be controlled by players
- Physics-based movement and projectiles  
- Visual effects like explosions & sparks
- **Working audio**: Launch sounds, explosion effects, and other game audio

**Success Indicators:**
- Log shows: ``Set audio mode 44100 Hz stereo interpolated``
- No "Failed to initialise SDL subsystem" errors
- Sound effects play when firing weapons with mouse clicks

Understanding the Codebase
--------------------------

Before modding, let's explore the project structure:

.. code-block:: text

    MultiMower/
    ├── src/                    # C++ source files
    │   ├── mastercontrol.cpp   # Main game loop
    │   ├── mower.cpp          # Mower implementation
    │   ├── inputmaster.cpp    # Input handling
    │   └── ...
    ├── Resources/             # Game assets
    │   ├── Models/           # 3D models
    │   ├── Textures/         # Texture files
    │   ├── Shaders/          # GLSL shaders
    │   └── ...
    └── blends/               # Blender source files

Key classes to understand:

- **MasterControl**: Manages the game state and scene
- **Mower**: The player-controlled mower entity
- **InputMaster**: Handles keyboard and joystick input
- **SpawnMaster**: Manages entity spawning

Version Compatibility
---------------------

This tutorial was tested and written for specific commits to ensure reproducibility:

- **Dry Engine**: commit ``bc78ed2c3b2c52f21ccb639ace09c016bce8536d``
- **MultiMower**: commit ``60b5dcd6597fe6c7a480505f822a70e0a1469cda``

If you want to follow this tutorial exactly, check out these specific commits:

.. code-block:: bash

    # Check out the exact Dry version used in this tutorial
    cd ~/git/dry
    git checkout bc78ed2c3b2c52f21ccb639ace09c016bce8536d
    
    # Check out the exact MultiMower version used in this tutorial  
    cd ~/git/MultiMower
    git checkout 60b5dcd6597fe6c7a480505f822a70e0a1469cda

Game Development Tutorials
--------------------------

Here are three comprehensive mods that transform MultiMower into a proper tank combat game with realistic movement, damage systems, and spectacular explosions.

Part 1: Tank Movement Controls with Realistic Acceleration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

**Goal**: Enhance the movement system with realistic tank-style acceleration and responsive controls.

**Solution**: Implement proper tank-style movement with realistic acceleration curves.

**Step 1**: Add acceleration variables to ``src/mower.h``:

.. code-block:: cpp

    // Add these private members after existing variables:
    private:
        // Acceleration system
        float currentSpeed_;
        float maxSpeed_;
        float acceleration_;

**Step 2**: Initialize acceleration in ``src/mower.cpp`` constructor:

.. code-block:: cpp

    Mower::Mower(Context* context): Controllable(context),
        // existing initializers...
        lastFiredRight_{ false },
        currentSpeed_{ 0.0f },
        maxSpeed_{ 60.0f },      // 60 units/sec top speed
        acceleration_{ 7.5f }    // 0-60 in 8 seconds
    {
    }

**Step 3**: Update the method signature in ``src/mower.h``:

.. code-block:: cpp

    private:
        void HandleInput(float timeStep);  // Add timeStep parameter

**Step 4**: Replace the ``HandleInput()`` method in ``src/mower.cpp``:

.. code-block:: cpp

    void Mower::HandleInput(float timeStep)
    {
        RigidBody* body = node_->GetComponent<RigidBody>();
        if (!body) return;
        
        // Get inputs
        float forwardInput = move_.z_;   // W/S keys (-1 to 1)
        float rotationInput = move_.x_;  // A/D keys
        
        // Calculate target speed based on input
        float targetSpeed = forwardInput * maxSpeed_;  // Can be negative for reverse
        
        // Gradual acceleration/deceleration (0-60 in 8 seconds)
        if (Abs(targetSpeed - currentSpeed_) > 0.1f)
        {
            if (targetSpeed > currentSpeed_)
            {
                currentSpeed_ += acceleration_ * timeStep;
                if (currentSpeed_ > targetSpeed) currentSpeed_ = targetSpeed;
            }
            else
            {
                currentSpeed_ -= acceleration_ * timeStep;
                if (currentSpeed_ < targetSpeed) currentSpeed_ = targetSpeed;
            }
        }
        else
        {
            currentSpeed_ = targetSpeed;  // Close enough, snap to target
        }
        
        // Get current facing direction and set velocity
        Vector3 forward = node_->GetWorldDirection();
        Vector3 desiredVelocity = forward * currentSpeed_;
        body->SetLinearVelocity(desiredVelocity);
        
        // Handle rotation
        if (Abs(rotationInput) > 0.01f)
        {
            Vector3 torque = Vector3::UP * rotationInput * 15.0f;
            body->ApplyTorque(torque);
        }
        
        // Angular damping for smoother rotation
        Vector3 angularVel = body->GetAngularVelocity();
        body->ApplyTorque(-angularVel * 8.0f);

        // Fire bullet (existing code)
        if (INPUT->GetMouseButtonDown(MOUSEB_LEFT) && sinceBullet >= bulletInterval)
        {
            bullets_.Push(Projectile{ gun_ });
            sinceBullet = 0.f;
        }

        // Fire missile (existing code)
        if (INPUT->GetMouseButtonPress(MOUSEB_RIGHT) && sinceMissile >= missileInterval)
        {
            FireMissile();
        }
    }

**Note**: This is the initial version from Part 1. The health system checks (``isDestroyed_``) will be added in Part 2.

**Step 5**: Update the call site in ``Mower::Update()``:

.. code-block:: cpp

    if (GetPlayer())
    {
        HandleInput(timeStep);  // Pass timeStep parameter
    }

**Result**: Tanks now accelerate realistically from 0-60 in 8 seconds, reach high speeds, and feel like real heavy vehicles!

Part 2: Health and Damage System with Random Damage
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

**Goal**: Add hit points, collision detection, and random weapon damage for balanced combat.

**Step 1**: Add health variables to ``src/mower.h``:

.. code-block:: cpp

    // Add these private members:
    private:
        // Health system
        float health_;
        float maxHealth_;
        bool isDestroyed_;

**Step 2**: Initialize health in ``src/mower.cpp`` constructor:

.. code-block:: cpp

    Mower::Mower(Context* context): Controllable(context),
        // existing initializers...
        acceleration_{ 7.5f },   // 0-60 in 8 seconds
        health_{ 100.0f },
        maxHealth_{ 100.0f },
        isDestroyed_{ false }
    {
    }

**Step 3**: Add damage methods to ``src/mower.h``:

.. code-block:: cpp

    private:
        void TakeDamage(float damage);
        void DestroyMower();
        float GetHealthPercentage() const;

**Step 4**: Implement damage methods in ``src/mower.cpp``:

.. code-block:: cpp

    void Mower::TakeDamage(float damage)
    {
        if (isDestroyed_) return;
        
        health_ -= damage;
        DRY_LOGINFOF("Mower took %.1f damage, health now %.1f", damage, health_);
        
        if (health_ <= 0.0f)
        {
            DestroyMower();
        }
    }
    
    void Mower::DestroyMower()
    {
        if (isDestroyed_) return;
        
        isDestroyed_ = true;
        health_ = 0.0f;
        currentSpeed_ = 0.0f;  // Stop acceleration
        
        DRY_LOGINFO("Mower destroyed!");
        
        // Disable physics so it stops moving
        RigidBody* body = node_->GetComponent<RigidBody>();
        if (body)
        {
            body->SetEnabled(false);
        }
        
        // Hide the mower model
        AnimatedModel* model = node_->GetComponent<AnimatedModel>();
        if (model)
        {
            model->SetEnabled(false);
        }
    }
    
    float Mower::GetHealthPercentage() const
    {
        return health_ / maxHealth_;
    }

**Step 5**: Add collision detection in ``Mower::Update()`` method (in the bullet loop):

.. code-block:: cpp

    // Replace the bullet update loop with collision detection
    for (unsigned b{ 0 }; b < bullets_.Size(); )
    {
        Projectile& bullet{ bullets_.At(b)};

        bullet.age_ += timeStep;
        Vector3 bulletPos = bullet.path_.Solve(bullet.age_);
        
        // Check for hits on other mowers
        bool bulletHit = false;
        
        // Check all mowers in the scene
        PODVector<Node*> mowerNodes;
        GetScene()->GetChildrenWithComponent<Mower>(mowerNodes, true);
        
        for (Node* mowerNode : mowerNodes)
        {
            Mower* otherMower = mowerNode->GetComponent<Mower>();
            
            if (otherMower && otherMower != this && !otherMower->isDestroyed_)
            {
                float distance = (bulletPos - otherMower->node_->GetWorldPosition()).Length();
                if (distance < 5.0f)  // Hit radius
                {
                    float damage = 1.0f + Random(4);  // Random 1-4 damage
                    DRY_LOGINFOF("Bullet hit! Distance: %f, Damage: %.0f", distance, damage);
                    otherMower->TakeDamage(damage);
                    bullets_.EraseSwap(b);
                    bulletHit = true;
                    break;
                }
            }
        }
        
        if (!bulletHit && (bullet.age_ > 0.17f || bulletPos.y_ < .075f))
        {
            for (int s{ 0 }; s < 23; ++s)
                Sparkle(bulletPos, 5.f);
            bullets_.EraseSwap(b);
        }
        else if (!bulletHit)
        {
            ++b;
        }
    }

**Step 6**: Add missile collision detection in the missile update loop:

.. code-block:: cpp

    // In the missile update loop, add collision detection
    if (missile.age_ < 4.f/3.f)
    {
        missile.age_ += timeStep;
        Sparkle(missile.path_.Solve(missile.age_), .1f);
        
        Vector3 missilePos = missile.path_.Solve(missile.age_);
        
        // Check for missile hits on other mowers
        bool missileHit = false;
        PODVector<Node*> mowerNodes;
        GetScene()->GetChildrenWithComponent<Mower>(mowerNodes, true);
        
        for (Node* mowerNode : mowerNodes)
        {
            Mower* otherMower = mowerNode->GetComponent<Mower>();
            
            if (otherMower && otherMower != this && !otherMower->isDestroyed_)
            {
                float distance = (missilePos - otherMower->node_->GetWorldPosition()).Length();
                if (distance < 8.0f)  // Larger explosion radius
                {
                    float damage = 10.0f + Random(16);  // Random 10-25 damage
                    DRY_LOGINFOF("Missile hit! Distance: %f, Damage: %.0f", distance, damage);
                    otherMower->TakeDamage(damage);
                    missileHit = true;
                    break;
                }
            }
        }

        if (missileHit || missile.age_ > 4.f/3.f || missilePos.y_ < .25f)
        {
            // Create explosion effects and remove missile
            // ... existing explosion code ...
        }
    }

**Step 7**: Add health bar visualization in ``RenderDebug()`` method:

.. code-block:: cpp

    // Add to RenderDebug() method in mower.cpp
    // Render health bar above mower
    if (!isDestroyed_)
    {
        Vector3 healthBarPos = node_->GetWorldPosition() + Vector3::UP * 3.0f;
        float healthPercent = GetHealthPercentage();
        Color healthColor = Color::GREEN.Lerp(Color::RED, 1.0f - healthPercent);
        
        // Health bar background (dark)
        debug->AddLine(healthBarPos - Vector3::RIGHT,
                      healthBarPos + Vector3::RIGHT,
                      Color::BLACK);
        
        // Health bar (colored based on health)
        debug->AddLine(healthBarPos - Vector3::RIGHT,
                      healthBarPos - Vector3::RIGHT + Vector3::RIGHT * 2.0f * healthPercent,
                      healthColor);
    }

**Weapon Damage Values**:
- **Bullets**: Random 1-4 damage per hit (takes 25-100 bullets to destroy)
- **Missiles**: Random 10-25 damage per hit (takes 4-10 missiles to destroy)
- **Total mower health**: 100 HP

**Step 8**: Update ``HandleInput()`` to prevent firing when dead (modify the method from Part 1):

.. code-block:: cpp

    void Mower::HandleInput(float timeStep)
    {
        // Don't handle input if destroyed
        if (isDestroyed_) return;
        
        // ... existing movement code from Part 1 ...
        
        // Fire bullet (only if not destroyed)
        if (!isDestroyed_ && INPUT->GetMouseButtonDown(MOUSEB_LEFT) && sinceBullet >= bulletInterval)
        {
            bullets_.Push(Projectile{ gun_ });
            sinceBullet = 0.f;
        }

        // Fire missile (only if not destroyed)
        if (!isDestroyed_ && INPUT->GetMouseButtonPress(MOUSEB_RIGHT) && sinceMissile >= missileInterval)
        {
            FireMissile();
        }
    }

**Step 9**: Update AI firing in ``Update()`` method:

.. code-block:: cpp

    else
    {
        // AI mowers only fire if not destroyed
        if (!isDestroyed_ && Random(420) == 0)
            FireMissile();
    }

**Result**: Balanced combat with visible health bars and satisfying random damage!

Part 3: Death and Victory Effects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

**Goal**: Create a complete end-game experience with massive explosions, death camera effects, electrical sparking, and victory celebrations.

**Step 1**: Add new variables to ``src/mower.h``:

.. code-block:: cpp

    // Add these private members:
    private:
        // Death camera
        float deathCameraDistance_;
        
        // Continuous sparking effect for destroyed mowers
        float sparkTimer_;
        
        // Victory flag to prevent multiple celebrations
        bool victoryTriggered_;

**Step 2**: Initialize new variables in ``src/mower.cpp`` constructor:

.. code-block:: cpp

    Mower::Mower(Context* context): Controllable(context),
        // existing initializers...
        isDestroyed_{ false },
        deathCameraDistance_{ 10.0f },  // Initial camera distance on death
        sparkTimer_{ 0.0f },     // Timer for continuous sparking
        victoryTriggered_{ false }  // Flag to prevent multiple victory celebrations
    {
    }

**Step 3**: Replace the ``DestroyMower()`` method in ``src/mower.cpp``:

.. code-block:: cpp

    void Mower::DestroyMower()
    {
        if (isDestroyed_) return;
        
        isDestroyed_ = true;
        health_ = 0.0f;
        currentSpeed_ = 0.0f;  // Stop acceleration
        
        DRY_LOGINFO("Mower destroyed! MASSIVE EXPLOSION!");
        
        Vector3 explosionPos = node_->GetWorldPosition();
        
        // HUGE explosion effect - like multiple rockets going off!
        for (int i = 0; i < 200; ++i)  // 200 sparkles instead of 30
        {
            Sparkle(explosionPos + Vector3{RandomOffCenter(5.0f), Random(3.0f), RandomOffCenter(5.0f)}, 
                    30.0f + Random(20.0f));  // Varied speed and position
        }
        
        // Create multiple trail effects radiating outward (like rocket exhaust)
        for (int i = 0; i < 50; ++i)
        {
            Vector3 trailStart = explosionPos + Vector3{RandomOffCenter(2.0f), Random(1.0f), RandomOffCenter(2.0f)};
            Trail(trailStart);
        }
        
        // Multiple explosion sounds for massive effect
        PlaySample(RES(Sound, "Samples/Explode.wav"), 1.5f);  // Main explosion
        
        // Add delayed secondary explosions (ammo cook-off effect)
        for (int i = 0; i < 8; ++i)
        {
            // Create delayed sparkle bursts
            for (int j = 0; j < 25; ++j)
            {
                Vector3 delayedPos = explosionPos + Vector3{RandomOffCenter(8.0f), Random(4.0f), RandomOffCenter(8.0f)};
                Sparkle(delayedPos, 25.0f);
            }
        }
        
        // Create blast lights for dramatic effect
        for (int i = 0; i < 5; ++i)
        {
            Node* lightNode = GetScene()->CreateChild("BlastLight");
            lightNode->CreateComponent<BlastLight>();
            Vector3 lightPos = explosionPos + Vector3{RandomOffCenter(3.0f), Random(2.0f) + 1.0f, RandomOffCenter(3.0f)};
            lightNode->SetPosition(lightPos);
        }
        
        // Disable physics so it stops moving
        RigidBody* body = node_->GetComponent<RigidBody>();
        if (body)
        {
            body->SetEnabled(false);
        }
        
        // Transform into a grayscale wreck instead of hiding
        AnimatedModel* model = node_->GetComponent<AnimatedModel>();
        if (model)
        {
            // Create grayscale material to show battle damage
            SharedPtr<Material> grayscaleMaterial = model->GetMaterial(0)->Clone();
            
            // Convert to grayscale - desaturate while keeping original brightness
            grayscaleMaterial->SetShaderParameter("MatDiffColor", Color(0.4f, 0.4f, 0.4f)); // Grayscale
            grayscaleMaterial->SetShaderParameter("MatSpecColor", Color(0.1f, 0.1f, 0.1f)); // Dull reflection
            grayscaleMaterial->SetShaderParameter("MatEmissiveColor", Color(0.0f, 0.0f, 0.0f)); // No glow
            
            model->SetMaterial(grayscaleMaterial);
            
            DRY_LOGINFO("Applied grayscale material to destroyed mower");
        }
    }

**Explosion Features**:
- **200 sparkles** scattered over a large area (vs 30 in original)
- **50 rocket exhaust trails** radiating outward
- **8 secondary explosion bursts** simulating ammo cook-off
- **5 dramatic blast lights** for cinematic effect
- **Louder explosion sound** (1.5x volume)
- **Varied particle speeds** and positions for realism

**Step 4**: Add continuous sparking in ``Update()`` method:

.. code-block:: cpp

    // Add to Update() method after existing timer updates
    // Update spark timer for destroyed mowers
    sparkTimer_ += timeStep;
    
    // Continuous sparking effect for destroyed mowers
    if (isDestroyed_ && sparkTimer_ >= 0.1f) // Spark every 0.1 seconds
    {
        sparkTimer_ = 0.0f; // Reset timer
        
        // Create multiple sparks around the destroyed mower
        Vector3 mowerPos = node_->GetWorldPosition();
        for (int i = 0; i < 3; ++i)
        {
            Vector3 sparkPos = mowerPos + Vector3{RandomOffCenter(2.0f), Random(1.0f), RandomOffCenter(2.0f)};
            Sparkle(sparkPos, 3.0f); // Electrical sparking effect
        }
        
        // Occasional larger sizzle effect
        if (Random(5) == 0) // 20% chance
        {
            for (int i = 0; i < 8; ++i)
            {
                Vector3 sizzlePos = mowerPos + Vector3{RandomOffCenter(1.5f), Random(0.8f), RandomOffCenter(1.5f)};
                Sparkle(sizzlePos, 5.0f); // Bigger electrical discharge
            }
        }
    }


**Step 4**: Add dynamic camera zoom-out in ``PostUpdate()`` method:

.. code-block:: cpp

    void Mower::PostUpdate(float timeStep)
    {
        if (!GetPlayer())
            return;

        Jib* jib{ GetPlayer()->GetJib() };
        
        // Handle death camera zoom (both for death and victory)
        if ((isDestroyed_ || victoryTriggered_) && jib && jib->GetCamera())
        {
            // Dynamic zoom speed - fast initially, then slow to a halt
            float maxDistance = 50.0f;  // Maximum zoom distance
            float speedFactor = 1.0f - (deathCameraDistance_ / maxDistance);  // 1.0 to 0.0
            speedFactor = Max(0.1f, speedFactor);  // Don't go below 0.1
            
            // Fast zoom initially (60 units/sec), slowing down as we get further
            deathCameraDistance_ += 60.0f * timeStep * speedFactor;
            deathCameraDistance_ = Min(deathCameraDistance_, maxDistance);  // Cap at max distance
            
            // Set camera to orbit the mower (destroyed or victorious)
            Node* cameraNode = jib->GetCamera()->GetNode();
            Vector3 offset = Vector3::BACK * deathCameraDistance_ + Vector3::UP * (deathCameraDistance_ * 0.4f);
            cameraNode->SetPosition(node_->GetWorldPosition() + offset);
            cameraNode->LookAt(node_->GetWorldPosition(), Vector3::UP);
            
            return; // Skip normal camera update when dead or victorious
        }

        // ... existing PostUpdate code continues here ...
    }

**Visual Effects**:
- **Grayscale Material**: Mower turns gray (0.4 RGB) showing battle damage
- **Continuous Sparking**: 3 electrical sparks every 0.1 seconds around the wreck
- **Sizzle Bursts**: 20% chance of 8 larger electrical discharges
- **Dynamic Camera Movement**: Fast zoom initially (60 units/sec), slowing to a halt at 50 units distance
- **No Weapon Firing**: Both player and AI mowers stop firing when destroyed
- **Persistent Effect**: Sparking continues forever, showing damaged electronics
- **No Self-Damage**: Neither bullets nor missiles can damage the mower that fired them

**Result**: When you die, all firing stops, the camera dramatically pulls back, and your mower becomes a gray wreck with continuous electrical sparking effects - like damaged electronics shorting out!

**Step 5**: Add victory condition system. First add the method declaration to ``src/mower.h``:

.. code-block:: cpp

    void CheckWinCondition();

**Step 6**: Add victory check in ``src/mower.cpp`` Update method:

.. code-block:: cpp

    void Mower::Update(float timeStep)
    {
        // Existing update code...
        
        // Check win condition - if player exists, check if all AI tanks are destroyed
        if (GetPlayer())
        {
            CheckWinCondition();
        }
        
        // Rest of existing update code...
    }

**Step 7**: Implement the victory detection system:

.. code-block:: cpp

    void Mower::CheckWinCondition()
    {
        // Only check win condition if this is the player mower
        if (!GetPlayer() || isDestroyed_) return;
        
        // Get all mowers in the scene
        PODVector<Node*> mowerNodes;
        GetScene()->GetChildrenWithComponent<Mower>(mowerNodes, true);
        
        int aliveTanks = 0;
        int totalAITanks = 0;
        
        for (Node* mowerNode : mowerNodes)
        {
            Mower* mower = mowerNode->GetComponent<Mower>();
            if (mower && !mower->GetPlayer()) // AI mower
            {
                totalAITanks++;
                if (!mower->isDestroyed_)
                {
                    aliveTanks++;
                }
            }
        }
        
        // Check if all AI tanks are destroyed
        if (totalAITanks > 0 && aliveTanks == 0 && !victoryTriggered_)
        {
            victoryTriggered_ = true;  // Set flag to prevent multiple celebrations
            
            // Player wins! Display victory message
            DRY_LOGINFO("=== VICTORY! ALL ENEMY TANKS DESTROYED! ===");
            
            // Create a big celebration explosion at player position
            Vector3 playerPos = node_->GetWorldPosition();
            
            // Victory celebration - exciting but controlled (only fires once!)
            for (int i = 0; i < 50; ++i)
            {
                Vector3 fireworkPos = playerPos + Vector3{RandomOffCenter(6.0f), Random(4.0f) + 2.0f, RandomOffCenter(6.0f)};
                Sparkle(fireworkPos, 25.0f + Random(15.0f));
            }
            
            // Victory trail burst
            for (int i = 0; i < 20; ++i)
            {
                Vector3 trailStart = playerPos + Vector3{RandomOffCenter(4.0f), Random(2.0f) + 1.0f, RandomOffCenter(4.0f)};
                Trail(trailStart);
            }
            
            // Multiple celebration lights
            for (int i = 0; i < 3; ++i)
            {
                Node* lightNode = GetScene()->CreateChild("VictoryLight");
                lightNode->CreateComponent<BlastLight>();
                Vector3 lightPos = playerPos + Vector3{RandomOffCenter(3.0f), Random(2.0f) + 3.0f, RandomOffCenter(3.0f)};
                lightNode->SetPosition(lightPos);
            }
            
            // Victory sound
            PlaySample(RES(Sound, "Samples/Explode.wav"), 2.0f);
            
            // Log stats
            DRY_LOGINFOF("Victory achieved! Defeated %d enemy tanks!", totalAITanks);
        }
    }

**Victory Effects**:
- **Victory Sparkles**: 50 sparkles in a large area around the player (exciting celebration)
- **Victory Trails**: 20 colored trail effects radiating outward from player position
- **Celebration Lights**: 3 blast lights scattered around the player
- **Audio Feedback**: Victory explosion sound at double volume
- **Console Message**: Clear victory announcement with enemy count
- **Single Trigger**: Victory only fires once when condition is first met (prevents multiple celebrations per frame)

**How It Works**:

1. **Continuous Checking**: Every frame, the player mower scans all tanks in the scene
2. **AI Detection**: Counts tanks that don't have a player component (AI-controlled)
3. **Status Verification**: Checks if each AI tank is destroyed using ``isDestroyed_`` flag
4. **Victory Trigger**: When ``aliveTanks == 0`` and ``totalAITanks > 0``, victory fires
5. **Celebration**: Immediate massive fireworks display centered on player position
6. **Performance**: Efficient - only player mower checks, stops when victory achieved

**Testing Victory**:

.. code-block:: bash

    # Run the game and destroy all enemy tanks
    ./multimower
    
    # Victory triggers automatically when last enemy dies
    # Watch for log message: "=== VICTORY! ALL ENEMY TANKS DESTROYED! ==="
    # Enjoy the massive fireworks celebration!

**Result**: When you destroy the last enemy tank, a massive and exciting victory celebration appears around your mower with 50 sparkles, 20 trails, 3 lights, and victory sound - making you feel like a true tank commander with a spectacular fireworks display!

Complete Game Development Results
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

After implementing all three parts, you'll have:

1. **Realistic Tank Movement**: 0-60 acceleration in 8 seconds, high top speeds, smooth control
2. **Balanced Combat**: Random damage (1-4 bullets, 10-25 missiles), health bars, collision detection
3. **Complete End-Game Experience**: Massive explosions, death camera effects, electrical sparking, and victory celebrations

**Total Changes**:
- **Files Modified**: ``src/mower.h``, ``src/mower.cpp``, ``src/inputmaster.cpp``, ``src/mastercontrol.cpp``
- **Lines Added**: +392 lines of code, -16 lines removed
- **Net Addition**: +376 lines of code
- **New Features**: Acceleration system, health system, collision detection, massive explosions, death effects, victory celebrations
- **Gameplay Impact**: Transforms MultiMower into a complete cinematic tank combat experience

**Testing Your Mods**:

.. code-block:: bash

    # Compile with your changes
    make clean && make -j$(nproc)
    
    # Run the enhanced game
    ./multimower
    
    # Test the features:
    # - W/S for realistic acceleration/deceleration
    # - A/D for tank turning
    # - Left click for machine gun (1-4 damage)
    # - Right click for missiles (10-25 damage)
    # - Destroy enemy tanks for massive explosions!
    # - Destroy all enemy tanks for victory fireworks!

Project Statistics
------------------

Here are the actual numbers behind this modding tutorial, showing the scope and impact of our changes:

Engine and Game Size
~~~~~~~~~~~~~~~~~~~~

**Dry Engine (commit bc78ed2c)**:
- **Source Files**: 4,330 files (.cpp, .h, .c)
- **Lines of Code**: 199,687 total lines
- **Repository Size**: 1.1 GB (includes samples, tools, third-party libraries)
- **Static Library**: 44 MB (libDry.a)
- **Build Time**: ~5-8 minutes on modern hardware

**MultiMower Game (commit 60b5dcd)**:
- **Source Files**: 18 files (.cpp, .h)
- **Lines of Code**: 2,346 total lines
- **Repository Size**: 31 MB (includes assets, models, textures)
- **Game Resources**: 5.3 MB (3D models, textures, sounds)
- **Final Executable**: 15 MB (statically linked with Dry)
- **Build Time**: ~30-60 seconds

Our Game Development Impact
~~~~~~~~~~~~~~~~~~~~~~~~~~~

**Code Changes Made**:
- **Files Modified**: 4 source files
- **Lines Added**: +392 lines of code
- **Lines Removed**: -16 lines of code  
- **Net Addition**: +376 lines of code

**Breakdown by File**:
- ``src/mower.cpp``: +359 lines, -13 lines (main implementation)
- ``src/mower.h``: +21 lines, -1 line (new variables and methods)
- ``src/inputmaster.cpp``: +9 lines, -1 line (debug logging)
- ``src/mastercontrol.cpp``: +3 lines, -1 line (debug logging)

**Feature Implementation by Parts**:
- **Part 1 - Tank Movement**: ~80 lines (acceleration system, realistic physics)
- **Part 2 - Health & Damage**: ~120 lines (collision detection, health bars, damage system)
- **Part 3 - Death & Victory**: ~160 lines (explosions, camera effects, sparking, victory system)
- **Supporting Code**: ~16 lines (debugging, initialization, cleanup)

Scale Perspective
~~~~~~~~~~~~~~~~~

**Code Efficiency**:
- Our 376 lines represent **0.16%** of the total MultiMower codebase
- Yet they transform the game from basic technical demonstration to complete tank combat with victory conditions
- Shows the power of focused, strategic modifications

**Engine vs Game**:
- Dry Engine: **85x larger** than MultiMower (199K vs 2.3K lines)
- MultiMower: **35x smaller** repository than Dry (31MB vs 1.1GB)
- Game executable includes the entire engine statically linked

**Development Effort**:
- **Engine Development**: Years of work, complex graphics/physics systems
- **Game Development**: Weeks of work, focused gameplay mechanics  
- **Our Game Development**: Hours of work, targeted feature additions

**Build Performance**:
- **Dry Engine**: Takes 5-8 minutes to compile from scratch
- **MultiMower**: Takes 30-60 seconds with incremental changes
- **Our Patches**: Compile in 5-10 seconds after initial build


Troubleshooting on Fedora
-------------------------

Common Build Issues
~~~~~~~~~~~~~~~~~~~

1. **"Dry/Dry.h: No such file or directory"**: This happens when DRY_HOME isn't set before running qmake:

.. code-block:: bash

    # Check if DRY_HOME is set correctly
    echo $DRY_HOME
    # Should print: /home/yourusername/git/dry/build (with your actual username)
    
    # If empty or wrong, fix it:
    export DRY_HOME=$HOME/git/dry/build
    
    # Remove old Makefile and regenerate
    rm -f Makefile
    qmake ../MultiMower.pro
    make -j$(nproc)

2. **Build directory confusion**: Always build in MultiMower's own build directory, not in the Dry engine:

.. code-block:: bash

    # Correct approach - build in game directory
    cd ~/git/MultiMower/build
    
    # Check your executable is where you expect
    ls -la multimower

3. **Resources not found at runtime**: The game needs the Resources folder:

.. code-block:: bash

    # Copy resources to the build directory
    cd ~/git/MultiMower/build
    cp -r ../Resources .

4. **Library not found errors**: Ensure all dependencies are installed:

.. code-block:: bash

    # Re-run the dependency installer
    cd ~/git/dry
    ./script/installreq.sh

5. **Qt6 vs Qt5 conflicts**: The build uses Qt6 by default on newer Fedora:

.. code-block:: bash

    # If you need to force Qt5 (though Qt6 works fine)
    /usr/lib64/qt5/bin/qmake MultiMower.pro

6. **Compiler warnings**: Expect many warnings - they're from the Dry engine and don't affect functionality:

   - Unused parameter warnings
   - Deprecated copy constructor warnings  
   - Type-punned pointer warnings
   - These are normal and the game will work fine

7. **Wayland linking or audio issues**: If you see ``undefined reference to 'wl_proxy_*'`` errors or "No available audio device" errors:

.. code-block:: bash

    # Both issues are typically resolved by rebuilding Dry with proper dependencies
    # Make sure audio packages are installed first:
    sudo dnf install alsa-lib-devel
    
    # Then rebuild Dry without Wayland (X11 is preferred for gaming anyway)
    cd ~/git/dry
    rm -rf build && mkdir build && cd build
    cmake .. -DDRY_64BIT=1 -DCMAKE_BUILD_TYPE=Release -DVIDEO_WAYLAND=OFF
    make -j$(nproc)

Real-World Build Experience
~~~~~~~~~~~~~~~~~~~~~~~~~~~

When I tested this process on Fedora, here's what actually happened:

- **Final executable**: 15MB located in ``~/git/MultiMower/build/multimower``
- **Warnings**: About 50+ compiler warnings (all safe to ignore)
- **Memory usage**: Game links against a 45MB libDry.a static library
- **Success rate**: 100% when following the exact steps above
- **Common issues**: Wayland linking errors (fixed by disabling Wayland), audio initialization failures (fixed by rebuilding Dry with audio packages installed)
- **Audio confirmation**: Working audio with Launch.wav and Explode.wav sound effects
- **Enhanced controls**: WASD movement with realistic tank acceleration

Conclusion
----------

MultiMower provides an excellent platform for learning game development and modding. The clean codebase and use of the Dry Engine make it accessible for beginners while offering enough depth for advanced modifications. Whether you're adding new mower types, creating custom game modes, or just tweaking the physics, there's plenty of room for creativity.

The build process is straightforward on Fedora when you follow these tested steps, and the Dry engine's helper scripts make dependency management painless.

Happy mowing & modding!

.. contents::
