AlgoDaily

Lesson 8: Client Editor - Monster Editor

Read at your own pace. Code execution, quizzes, progress tracking, and the AI tutor are available in the interactive view with JavaScript enabled.

Monsters Need Tools

From tiles to living content

In Lesson 7: Client Editor - Map Editor, you built the first half of god-mode: editing the physical world. Now we add the layer that makes the world feel alive: monster spawn editing.

A monster editor answers questions like:

  • What monster type appears here?
  • Where can it spawn on the map?
  • How many can be alive at once?
  • How long after death should it respawn?
  • Is this spawn valid for the terrain?

The key design rule: the editor creates content data, not live gameplay authority. The server from Lesson 4: Game Server - Basics still owns combat, HP, movement truth, and player interactions.

Think of this as building the internal tool a designer would use before shipping a zone.

Monsters Need Tools

Spawn Data Model

Separate template from placement

A common beginner mistake is storing everything directly on each spawn: name, max HP, damage, sprite, respawn time, position, loot table, and behavior flags.

Instead, split the data:

MonsterTemplate
  Defines what a monster is.

SpawnPoint
  Defines where and how that monster appears.

This mirrors patterns you likely know from Java/Spring or TypeScript systems: reusable configuration plus many concrete references.

For example, one Wolf template can be reused by 100 spawn points across multiple maps.

```cpp
#include <iostream>
#include <string>
#include <vector>

struct MonsterTemplate {
    int id;
    std::string name;
    int maxHp;
    int attack;
};

struct SpawnPoint {
    int id;
    int monsterTemplateId;
    int mapId;
    int tileX;
    int tileY;
    int respawnSeconds;
    int maxAlive;
};

int main() {
    MonsterTemplate wolf{1, "Forest Wolf", 35, 6};

    std::vector<SpawnPoint> spawns = {
        {100, wolf.id, 1, 12, 8, 30, 2},
        {101, wolf.id, 1, 18, 9, 45, 1}
    };

    std::cout << "Template: " << wolf.name
              << " hp=" << wolf.maxHp
              << " attack=" << wolf.attack << "\n";

    for (const SpawnPoint& spawn : spawns) {
        std::cout << "Spawn " << spawn.id
                  << " uses template " << spawn.monsterTemplateId
                  << " at map " << spawn.mapId
                  << " tile(" << spawn.tileX << "," << spawn.tileY << ")"
                  << " respawn=" << spawn.respawnSeconds << "s"
                  << " maxAlive=" << spawn.maxAlive << "\n";
    }

    return 0;
}
```

Content Or Runtime

Which field most clearly belongs in monster editor spawn content, rather than live runtime monster state?

Placement Workflow

Editing flow

The monster editor can reuse much of the structure from Lesson 7: Client Editor - Map Editor:

Mouse click -> pick tile -> preview marker -> apply edit -> push undo command -> save content

But monster placement adds semantic checks:

  • Is the tile inside the map?
  • Is the tile walkable?
  • Is the monster type valid?
  • Is the spawn too close to a blocked area?
  • Does this spawn overlap another spawn marker?

A practical editor mode might have tools like:

  • Place Spawn: click a tile to create a spawn point.
  • Select Spawn: inspect and edit properties.
  • Move Spawn: drag to another valid tile.
  • Delete Spawn: remove from the map.
  • Paint Spawn Group: place several related spawns quickly.

Keep the UI friendly, but keep the data strict.

Placement Workflow

Validate Before Saving

Bad content becomes bad gameplay

Your server can be robust, but it should not need to compensate for sloppy editor output.

Before saving spawn data, validate it. Validation is cheap compared to debugging a monster stuck inside a wall after deployment.

Good validation catches:

  • Out-of-bounds spawn positions
  • Spawns on blocked terrain
  • Duplicate spawn ids
  • Invalid respawn values
  • Unknown monster template ids

This is similar in spirit to the persistence safety checks from Lesson 5: Game Server - Persistence: protect the boundary where data becomes durable.

```cpp
#include <iostream>
#include <set>
#include <string>
#include <vector>

struct SpawnPoint {
    int id;
    int monsterTemplateId;
    int x;
    int y;
    int respawnSeconds;
};

struct MapGrid {
    int width;
    int height;
    std::vector<std::string> rows; // '.' walkable, '#' blocked

    bool inBounds(int x, int y) const {
        return x >= 0 && y >= 0 && x < width && y < height;
    }

    bool walkable(int x, int y) const {
        return inBounds(x, y) && rows[y][x] == '.';
    }
};

int main() {
    MapGrid map{5, 4, {
        ".....",
        "..#..",
        ".....",
        ".#..."
    }};

    std::set<int> validTemplates = {1, 2};
    std::vector<SpawnPoint> spawns = {
        {10, 1, 0, 0, 30},   // ok
        {11, 2, 2, 1, 30},   // blocked tile
        {10, 1, 4, 2, 0},    // duplicate id, bad respawn
        {12, 99, 8, 8, 60}   // unknown template, out of bounds
    };

    std::set<int> seenIds;
    int errors = 0;

    for (const SpawnPoint& s : spawns) {
        if (!seenIds.insert(s.id).second) {
            std::cout << "Error: duplicate spawn id " << s.id << "\n";
            ++errors;
        }
        if (validTemplates.count(s.monsterTemplateId) == 0) {
            std::cout << "Error: spawn " << s.id << " has unknown template "
                      << s.monsterTemplateId << "\n";
            ++errors;
        }
        if (!map.inBounds(s.x, s.y)) {
            std::cout << "Error: spawn " << s.id << " is out of bounds\n";
            ++errors;
        } else if (!map.walkable(s.x, s.y)) {
            std::cout << "Error: spawn " << s.id << " is on blocked tile(" 
                      << s.x << "," << s.y << ")\n";
            ++errors;
        }
        if (s.respawnSeconds <= 0) {
            std::cout << "Error: spawn " << s.id << " has invalid respawn\n";
            ++errors;
        }
    }

    std::cout << "Validation finished with " << errors << " error(s).\n";
    return 0;
}
```

Authority Boundary

True or false: once an editor can place monsters, the game client should be trusted to create those monsters during normal live gameplay.

Undo For Spawns

Reuse the command idea

In Lesson 7, map edits became safer once they were undoable. Monster edits need the same discipline.

A spawn edit is usually small, but mistakes are frequent:

  • Placing the wrong monster type
  • Dragging a spawn to the wrong tile
  • Deleting a rare boss spawn accidentally
  • Changing respawn time across a group

Represent edits as commands with execute() and undo(). This keeps editor behavior predictable and makes future batch tools easier.

```cpp
#include <iostream>
#include <memory>
#include <vector>

struct SpawnPoint {
    int id;
    int templateId;
    int x;
    int y;
};

void printSpawns(const std::vector<SpawnPoint>& spawns) {
    std::cout << "Spawns:";
    if (spawns.empty()) {
        std::cout << " <none>";
    }
    for (const SpawnPoint& s : spawns) {
        std::cout << " [id=" << s.id << " type=" << s.templateId
                  << " tile=" << s.x << "," << s.y << "]";
    }
    std::cout << "\n";
}

class Command {
public:
    virtual ~Command() {}
    virtual void execute() = 0;
    virtual void undo() = 0;
};

class AddSpawnCommand : public Command {
public:
    AddSpawnCommand(std::vector<SpawnPoint>& spawns, SpawnPoint spawn)
        : spawns_(spawns), spawn_(spawn), added_(false) {}

    void execute() override {
        if (!added_) {
            spawns_.push_back(spawn_);
            added_ = true;
        }
    }

    void undo() override {
        if (added_ && !spawns_.empty()) {
            spawns_.pop_back();
            added_ = false;
        }
    }

private:
    std::vector<SpawnPoint>& spawns_;
    SpawnPoint spawn_;
    bool added_;
};

int main() {
    std::vector<SpawnPoint> spawns;
    std::vector<std::unique_ptr<Command> > undoStack;

    printSpawns(spawns);

    std::unique_ptr<Command> cmd(new AddSpawnCommand(spawns, SpawnPoint{200, 1, 7, 3}));
    cmd->execute();
    undoStack.push_back(std::move(cmd));

    printSpawns(spawns);

    undoStack.back()->undo();
    printSpawns(spawns);

    return 0;
}
```

Reusable Monster Kind

A reusable definition of what a monster is, referenced by many spawn points, is commonly called a monster ______.

Save And Load

Treat spawn data like content assets

Your map editor already has a save pipeline. Monster spawns should join that pipeline cleanly, not become hidden state inside the client.

A simple content layout might look like:

assets/
  maps/
    forest.map
  monsters/
    monster_templates.json
  spawns/
    forest_spawns.json

At editor time:

Load map -> load templates -> load spawns -> edit -> validate -> save

At server boot or zone load:

Load map -> load spawn content -> validate again -> create spawn controllers

The second validation matters. Editor validation improves workflow, but the server must still reject broken or tampered content.

In Lesson 9, we will look at keeping this kind of project structure comfortable across platforms without changing the editor design.

Save And Load

Editor Responsibility Check

Which responsibility is the best fit for the monster editor, rather than the live game server?

Open interactive view →