|
|
||
|---|---|---|
| .forgejo/workflows | ||
| .githooks | ||
| .gsd/gates | ||
| .kilo/plans | ||
| project | ||
| src | ||
| tests | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| AUDIT.md | ||
| Cargo.toml | ||
| generate_item_textures.py | ||
| generate_skins.py | ||
| gradle | ||
| gradlew | ||
| package.json | ||
| plan.md | ||
| README.md | ||
| SPEC.md | ||
Heroes Mod — Virtual Players for Minecraft
Heroes is a NeoForge 1.21.1 server-side Minecraft mod that adds roaming hero characters — autonomous fake-player actors that perform small, flavorful scenes in your world. Heroes make the world feel inhabited and reactive without adding automation grind or heavy processing overhead.
Mod ID:
heroesmod| Version:1.0.0| License: MIT
Overview
Heroes are rare (1–8 per world) diegetic actors with distinct personalities, roles, and tasks. They spawn as fake-player entities, wander villages, repair walls, light dark paths, keep watch at night, set up camps, and share their catch. Everything is server-side — clients only need standard entity rendering from the active backend (SiliconeDolls or NeoForge's built-in FakePlayer).
Architecture at a glance:
Level Tick → HeroTicker → HeroSpawner / SceneDirector / HeroDespawner
│
┌─────────┼─────────┐
│ │ │
Spawn/Despawn Task Scene
(Backend) Scheduling Lifecycle
Hero Roles
Each hero belongs to one of four built-in roles. Roles define default personality traits, task-weight preferences, and preferred biomes.
| Role | Traits | Likes | Dislikes | Signature |
|---|---|---|---|---|
| Caretaker | Frugal, Cautious | Patching walls (×2.0), lighting paths (×1.8) | Camping (×0.3), watching (×0.5) | Village repair & maintenance |
| Wayfarer | Brave, Curious | Setting up camp (×1.5), visiting villages (×1.0) | Patching walls (×0.1), watching (×0.3) | Wilderness travel & camping |
| Watcher | Brave, Cautious | Keeping watch (×2.5), lighting paths (×1.0) | Fishing (×0.2), wall patching (×0.3) | Night patrol & village defense |
| Fisher | Frugal, Curious | Fishing (×2.5), camping (×0.8) | Wall patching (×0.1), watching (×0.4) | Dawn fishing & food sharing |
Extensibility: Custom roles can be added via JSON datapack — see Custom Roles below.
Tasks & Scenes
Heroes autonomously select tasks based on their role, current traits, location, time of day, and cooldowns. Each task produces a scene — a lifecycle-driven script that the hero performs in the world.
Task Lifecycle
Every scene follows a finite-state machine:
PREPARE → SPAWN → PERFORM → COMMIT → (DESPAWN | IDLE)
↑ │
└──── retry (max 3) ←┘
| Phase | Description |
|---|---|
| PREPARE | Validate preconditions, find target positions, reserve blocks |
| SPAWN | Ensure the hero's fake-player embodiment exists |
| PERFORM | Execute the task script (movement + actions) tick by tick |
| COMMIT | Apply world edits atomically via WorldEditValidator |
| DESPAWN | Remove the embodiment and mark the hero offline |
| IDLE | Keep the embodiment present but idle in the world |
Built-in Tasks
| Task | Role Boost | Preconditions | What Happens | World Edits |
|---|---|---|---|---|
| Visit Village | Wayfarer +0.3, Caretaker +0.1 | Daytime, within 256 blocks of a village | Walk to village center, wander 4 waypoints, leave | None |
| Patch Wall | Caretaker +0.5 | Within 128 blocks of a village, damaged wall blocks found | Walk along wall, inspect, place up to 4 matching blocks | Blocks (≤4) |
| Light Path | Caretaker +0.4, Watcher +0.2 | Nighttime (13000–23000), dark path blocks near village | Walk path, place 1–2 lanterns/torches | Lights (≤2) |
| Keep Watch | Watcher +0.5 | Village edge, nighttime or post-raid | Patrol 3–4 waypoints, look toward threat directions | None |
| Forage Camp | Wayfarer +0.4 | Wilderness (not in village), forest/plains biome, evening | Place campfire + decor, sit/stand for 400–600 ticks, pack up | Campfire (temporary) |
| Fish & Share | Fisher +0.5 | Dawn (0–3000 ticks), within 64 blocks of water | Walk to water, fish, leave a small food crate | Barrel/chest with fish |
Commands
All commands require permission level 2 (operator).
| Command | Description |
|---|---|
/heroes list |
List all heroes with name, role, online status, current task, location |
/heroes info <name> |
Full dump of one hero's persona, state, fatigue, and cooldowns |
/heroes spawn <name> [x y z] |
Force-spawn a hero at a position (or their home region) |
/heroes despawn <name> |
Force-despawn a hero's embodiment |
/heroes task force <name> <taskType> |
Force-assign a specific task to a hero |
/heroes task clear <name> |
Clear a hero's current task |
/heroes config reload |
Reload configuration from disk |
Configuration
Global config is stored in config/heroesmod-common.toml (NeoForge spec format).
[maxHeroes]
| Key | Default | Range | Description |
|---|---|---|---|
maxHeroes |
4 | 1–32 | Maximum heroes active per world |
[spawning]
| Key | Default | Range | Description |
|---|---|---|---|
spawnIntervalTicks |
60 | 20–200 | Ticks between spawn/despawn checks |
maxSpawnAttempts |
2 | 1–10 | Max spawn attempts per check |
spawnBatchSize |
1 | 1–8 | Max heroes to spawn per batch |
minTPS |
15.0 | 10–20 | Minimum TPS to allow spawning |
despawnIdleTicks |
6000 | 1200–72000 | Idle timeout before despawn |
[tasks]
| Key | Default | Range | Description |
|---|---|---|---|
taskFrequencyTicks |
1200 | 200–10000 | Approx ticks between task assignments |
maxSceneDurationTicks |
1200 | 200–6000 | Maximum scene duration before timeout |
[worldEdit]
| Key | Default | Range | Description |
|---|---|---|---|
maxBlocksPerScene |
8 | 1–64 | Max blocks placed per hero per scene |
maxBlocksPerHeroPerHour |
100 | 10–1000 | Hourly cap per hero (real-time) |
maxBlocksPerChunkPerHour |
500 | 10–5000 | Hourly cap per chunk |
[backend]
| Key | Default | Description |
|---|---|---|
backendMode |
"auto" |
"auto", "silicone_dolls", or "fakeplayer_only" |
[integration]
| Key | Default | Description |
|---|---|---|
villageIntegrationEnabled |
true |
Enable Villager Behavior mod integration |
Architecture
com.heroesmod/
├── api/ # Core data model (HeroActor, HeroPersona, HeroRole, HeroTraits,
│ # HeroState, Task, TaskType, Scene, ActorHandle)
├── backend/ # ActorBackend abstraction: SiliconeDollsBackend, FakePlayerBackend,
│ # BackendManager (auto-discovery)
├── commands/ # /heroes command tree (list, info, spawn, despawn, task, config)
├── config/ # HeroesCommonConfig (TOML spec), HeroIndividualConfig (per-hero overrides)
├── data/ # HeroSavedData (world-scoped SavedData), HeroDataAttachments
├── integration/ # VillagerBehaviorIntegration (optional, reflection-based)
├── registry/ # HeroRegistry (CRUD), JsonHeroLoader (datapack JSON loading)
├── scene/ # SceneDirector (lifecycle state machine driver)
├── spawn/ # HeroSpawner, HeroDespawner, SpawnValidator
├── tasks/ # VisitVillageTask, PatchWallTask, LightPathTask, KeepWatchTask,
│ # ForageCampTask, FishAndShareTask, TaskScheduler
├── tick/ # HeroTicker (low-frequency scheduler)
├── util/ # HeroNames (name generation), SafePositionFinder
└── worldedit/ # WorldEditValidator (protection hooks), PatchBuilder, RateLimiter
Key Design Decisions
- Server-side only — All hero logic runs on the server. Clients only need entity rendering (provided by the active backend).
- Low population — 1–8 heroes per entire world, not per village. Each hero is a persistent character, not a generic NPC.
- Scene lifecycle — Every task produces a
Scenewith a well-defined FSM (PREPARE → SPAWN → PERFORM → COMMIT → IDLE/DESPAWN). - Atomic world edits — Edits are planned during
PERFORMand validated/applied atomically atCOMMITthroughWorldEditValidator. - Rate-limited world editing — Per-hero per-hour and per-chunk per-hour caps prevent runaway world modification.
- Backend abstraction — The
ActorBackendinterface decouples hero embodiment from any specific fake-player library. SiliconeDolls is preferred; NeoForge's built-inFakePlayerFactoryis the fallback. - Fatigue & cooldowns — Heroes get tired and have per-task cooldowns, ensuring they don't loop the same behavior endlessly.
Dependencies
| Dependency | Type | Version | Notes |
|---|---|---|---|
| NeoForge | Required | ≥21.1.1 (MC 1.21.1) |
Mod loader and framework |
| Minecraft | Required | 1.21.1 – 1.21.x |
Game version |
| Java | Required | 21+ |
Language and toolchain |
| SiliconeDolls | Optional | ≥1.0.0 |
Preferred fake-player backend (auto-detected) |
| Villager Behavior | Optional | Any | Village needs integration (event-based, reflection) |
Build & Setup
Prerequisites
- JDK 21+
- Gradle (included via wrapper)
Building
cd project/
./gradlew build
The compiled JAR will be in build/libs/heroesmod-1.0.0.jar.
CI (Forgejo Actions)
.forgejo/workflows/ci.yml builds and uploads the mod JAR on every push.
Action references are SHA-pinned to the Forgejo mirror
(https://data.forgejo.org/actions/*); runs-on: linux routes to the
healthy containerized runners (a bare-binary runner on this instance claims
ubuntu-latest but kills every job it picks up). The repo has its own
runner registered (localhost-mcheroes-runner) — if runs ever sit in
waiting, check that the repo still has a registered runner matching the
linux label.
Running in Development
# Client (with NeoForge dev environment)
./gradlew runClient
# Dedicated server
./gradlew runServer
Installing
- Install NeoForge 21.1.1+ for Minecraft 1.21.1 on your server.
- Place
heroesmod-1.0.0.jarin themods/folder. - (Optional) Install SiliconeDolls for better entity rendering.
- Restart the server.
- Heroes will start spawning automatically as chunks load around villages.
First run: The mod seeds the world with 1–4 starter heroes if none exist. Use
/heroes listto see them.
Persistence
Hero state is stored as NeoForge SavedData under the key heroesmod_actors. Data is saved to the world folder automatically on server stop and during normal save cycles.
On-disk NBT structure:
heroesmod_actors
├── heroes (ListTag)
│ └── [0..n] (CompoundTag per hero)
│ ├── id (UUID)
│ ├── name (String)
│ ├── role (String enum)
│ ├── traits (Int bitmask)
│ ├── homeRegionX/Z (Int)
│ ├── homeDimension (ResourceLocation)
│ ├── currentTask (String, nullable)
│ ├── locationX/Y/Z (Double)
│ ├── online (Boolean)
│ ├── fatigue (Float)
│ ├── lastSeenTime (Long)
│ ├── embodimentId (Int)
│ └── cooldowns (CompoundTag)
Extensibility
Custom Hero Roles (JSON Datapack)
Create a datapack with JSON files at:
data/<namespace>/heroes/roles/<role_id>.json
{
"name": "my_role",
"default_traits": ["brave", "curious"],
"task_weights": {
"visit_village": 1.0,
"patch_wall": 0.5,
"forage_camp": 1.5,
"fish_and_share": 0.3,
"light_path": 0.2,
"keep_watch": 0.8
},
"preferred_biomes": ["minecraft:plains", "minecraft:forest"],
"home_structure": "minecraft:village"
}
Roles are loaded at datapack reload and merged with the internal registry. Weights are relative — higher values make the hero more likely to choose that task.
Custom Tasks
Implement the Task interface:
public class MyCustomTask implements Task {
@Override
public TaskType getType() {
return TaskType.get(ResourceLocation.parse("mymod:my_task"));
}
@Override
public boolean canRun(HeroActor hero, ServerLevel level) { ... }
@Override
public double score(HeroActor hero, ServerLevel level) { ... }
@Override
public Scene createScene(HeroActor hero, ServerLevel level) { ... }
@Override
public int getEstimatedDuration() { return 600; }
@Override
public float getFatigueCost() { return 0.2f; }
@Override
public int getCooldownTicks() { return 2400; }
}
Register the task type:
TaskType.register(ResourceLocation.parse("mymod:my_task"), "My Task");
Then add it to any hero role's task_weights in their JSON definition.
Adding Custom Traits
Extend the HeroTraits enum to add new personality flags. Traits are stored as a bitmask integer, so up to 32 distinct traits are supported.
Integration Points
SiliconeDolls (Optional)
Auto-detected at startup. When present, heroes are rendered as fully animated doll entities with smooth movement. When absent, the mod falls back to NeoForge's FakePlayerFactory, which renders heroes as standard player-like entities.
Configure with: backendMode = "silicone_dolls" or "auto" (prefers SiliconeDolls).
Villager Behavior Mod (Optional)
When present, heroes read "village needs" data and adjust their task priorities accordingly — e.g., boosting repair tasks when village damage is detected.
Configure with: villageIntegrationEnabled = true (default).
Protection Mods
WorldEditValidator checks for common protection APIs via reflection (FTB Chunks, WorldGuard, etc.) and skips block edits in protected regions. Each integration is optional and silently skipped if the mod is not present.
Performance
Heroes are designed for minimal server impact:
- Low frequency ticker — The
HeroTickerruns everyspawnIntervalTicks(default 60 ticks = ~3 seconds), not every single tick. - Small population — 1–8 heroes per world keeps entity count negligible.
- TPS gating — Spawning is skipped if server TPS drops below
minTPS(default 15.0). - Chunk unloading — Heroes despawn if their chunk stays unloaded for 600+ ticks.
- Tiny world edits — At most
maxBlocksPerScene(default 8) blocks per scene, with hourly per-hero and per-chunk caps.
Project Structure
heroes-mod/
├── plan.md # Implementation plan (10 stages)
├── SPEC.md # Full design specification
├── project/
│ ├── build.gradle # NeoForge moddev build
│ ├── settings.gradle # Gradle settings
│ ├── gradle.properties # Version & mod metadata
│ ├── src/main/
│ │ ├── java/com/heroesmod/ # Main source (packages above)
│ │ └── resources/
│ │ ├── META-INF/neoforge.mods.toml
│ │ ├── pack.mcmeta
│ │ └── data/heroesmod/heroes/roles/
│ │ ├── caretaker.json
│ │ ├── wayfarer.json
│ │ ├── watcher.json
│ │ └── fisher.json
│ └── build/ # Compiled output
└── README.md # This file
License
This project is licensed under the MIT License — see the LICENSE file for details (or refer to the mod_license field in gradle.properties).