Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
175ab9f
Adding initial build recorder and replay prototype
jonoomph Aug 17, 2025
382601a
New build recording command
jonoomph Aug 17, 2025
b7610b7
Replay adjusts eye height (line of sight) so entities look at the sam…
jonoomph Aug 19, 2025
9fbd8ca
Build types and folders are automatically managed now (type and build…
jonoomph Aug 21, 2025
ee357d9
Merge branch 'develop' into build-recorder
jonoomph Aug 21, 2025
c06ac1e
New datagen provider to create index of builds (and calculates build …
jonoomph Aug 21, 2025
273715e
Adding missing copyright header
jonoomph Aug 21, 2025
ff48b59
Fix achievements code to build successfully on 1.20 to 1.21.7
jonoomph Aug 22, 2025
8270aec
New block palette to support builds in any version, and replays in an…
jonoomph Aug 23, 2025
82958cc
Add AFK protection during the build record (to stop long pauses recor…
jonoomph Aug 23, 2025
2711fa8
Merge branch 'develop' into build-recorder
jonoomph Aug 25, 2025
7c4e403
Merge branch 'develop' into build-recorder
jonoomph Aug 27, 2025
31d7d92
Merge branch 'develop' into build-recorder
jonoomph Aug 28, 2025
6593423
Adding some initial builds from owlmaddie (house and garden)
jonoomph Sep 1, 2025
ef4a14d
Initial build goal integration
jonoomph Sep 1, 2025
0cdde3e
Build goal waits to reach the player, starts from the ground at that …
jonoomph Sep 1, 2025
4dc5c96
- Build replays pause when materials are missing, stop four blocks fr…
jonoomph Sep 2, 2025
de13633
Fix build errors for 1.20 to 1.21.7
jonoomph Sep 2, 2025
324c321
LEAD goal guides players to nearby structures, biomes, resources, tag…
jonoomph Sep 3, 2025
61993bb
Experimental attempt to fix a crash by queuing the goal selector upda…
jonoomph Jan 4, 2026
5327456
Relaxed distance to player for builds to begin and end (so entity doe…
jonoomph Jan 4, 2026
e65f9a7
Adding new build particle for the PlayerBuildGoal (replacing placehol…
jonoomph Jan 4, 2026
fdc5653
Require full collision blocks when selecting the build start ground
jonoomph Jan 4, 2026
f7866ab
Fixed distance of hitboxes between entity and player to begin builds,…
jonoomph Jan 4, 2026
71a5922
Fixed canceling builds - to actually remove it from the replay queue
jonoomph Jan 4, 2026
94b6c30
Fixing move logic on "start" of build goal
jonoomph Jan 4, 2026
9e05b77
Support random build categories better.
jonoomph Jan 4, 2026
1e615cd
Friendly mobs with chat data now pick up items thrown by friends into…
jonoomph Jan 4, 2026
ab0af9c
Added debounced LLM message when friends pick up dropped items (1 mes…
jonoomph Jan 4, 2026
9884755
Lots of improvements to PlayerBuildGoal - to pause the goal and wande…
jonoomph Jan 6, 2026
b9f2694
- Build replay command logs replay bounds with and without player mov…
jonoomph Jan 11, 2026
99314c2
- Build replays rotate to the nearest cardinal based on player facing…
jonoomph Jan 11, 2026
473df7e
Improvements to build indexing and build file selection - and updated…
jonoomph Jan 11, 2026
8e5f004
- Build replays no longer randomize variants on existing actors
jonoomph Jan 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,60 @@ All notable changes to **CreatureChat™** are documented in this file. The form

## Unreleased

### Added
## Added
- New build recording command
- Record, Save, and Replay builds (placing, destroying, interacting with blocks + player poses)
- Spawns any entity type to replay build
- Replay speeds can be adjusted (any integer, but defaults to 1X - the original speed)
- Replay adjusts eye height (line of sight) so entities look at the same place as the player
- Record / Stop commands toggle (depending on if recording or not)
- Build types and folders are automatically managed now (type and build height: i.e. 1 block tall builds (bee) vs 3 block tall builds (enderman))
- New datagen provider to create index of builds (and calculates build score)
- New block palette to support builds in any version, and replays in any version
- Add AFK protection during the build record (to stop long pauses recorded on inventory screen or AFK)
- Document SPDX header and changelog requirements in AGENTS.md for contributors
- Build goal and behavior enabling entities to construct structures for players
- Behavior tests now cover BUILD and UNBUILD behaviors with live LLM responses
- Build replays pause when materials are missing, stop four blocks from players, and prompt for supplies with recipe details
- New build particle when a build starts
- Missing-material requests now broadcast the remaining recipe to nearby players in plain chat
- Unit tests ensure build selection covers all height tiers and skill levels
- Build skill increases after successful builds and syncs across clients
- Build goal only begins after the builder reaches the player
- Selecting builds logs skill, type, height tier, and chosen file
- Relaxed build goal proximity checks so builders don't have to overlap players before starting or finishing
- Require full collision blocks when selecting the build start ground
- Friendly mobs with chat data now pick up items thrown by friends into their chat inventory
- Expanded friendly pickup reach and restored pickup sound for chat-data mobs
- Debounced LLM message when friends pick up dropped items

### Changed
- Convert PNG screenshots to JPEG, compress, and remove less useful ones (smaller jar)
- Compressed all textures from 32-bit color to 4-bit indexed color, reduced size massively.
- Build goal now uses build replays, keeps FOLLOW and PROTECT goals active, and system prompts describe build skill and types.
- Build goal completion message is generated through the LLM when a structure finishes
- Build goal waits to reach the player, starts from the ground at that spot, follows the player until then, and returns to thank them when finished
- LEAD goal guides players to nearby structures, biomes, resources, tags, or points of interest and apologizes when none are found within 300 blocks
- LEAD goal no longer uses random coordinates when it can't locate something
- Build replay command logs replay bounds with and without player movement
- Build index now rebuilds at runtime from bundled and local builds
- Build goal bounds now ignore player movement
- Build replays rotate to the nearest cardinal based on player facing, with rotated bounds
- Rebuild build index when the config copy is empty or invalid
- Log build index match counts when selecting a replay file
- Build index scoring now spreads levels within each type and logs per-type summaries
- Build index now rebuilds on mod init every time
- Build selection relaxes height/skill filters when a valid type has no matches
- Expand build selection tests for fallback and unknown types
- Tests now fall back to a local config path when Fabric config dir is unavailable
- Missing-material alerts no longer send a separate system chat broadcast
- Build replays no longer randomize variants on existing actors
- Build replay no longer pauses every 2 seconds during active building

### Fixed
- Defer goal selector updates to end-of-tick to avoid null goal crashes after build completion
- Rotate block states when replaying builds so block facings match the chosen orientation
- Index rebuild now falls back to classpath builds when FabricLoader is unavailable (tests)

## [3.0.0] - 2025-08-27

Expand Down
1 change: 1 addition & 0 deletions src/client/java/com/owlmaddie/ClientInit.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public void onInitializeClient() {
ParticleFactoryRegistry.getInstance().register(Particles.FOLLOW_FRIEND_PARTICLE, CreatureParticleFactory::new);
ParticleFactoryRegistry.getInstance().register(Particles.FOLLOW_ENEMY_PARTICLE, CreatureParticleFactory::new);
ParticleFactoryRegistry.getInstance().register(Particles.PROTECT_PARTICLE, CreatureParticleFactory::new);
ParticleFactoryRegistry.getInstance().register(Particles.BUILD_PARTICLE, CreatureParticleFactory::new);
ParticleFactoryRegistry.getInstance().register(Particles.LEAD_FRIEND_PARTICLE, CreatureParticleFactory::new);
ParticleFactoryRegistry.getInstance().register(Particles.LEAD_ENEMY_PARTICLE, CreatureParticleFactory::new);
ParticleFactoryRegistry.getInstance().register(Particles.LEAD_PARTICLE, LeadParticleFactory::new);
Expand Down
2 changes: 2 additions & 0 deletions src/client/java/com/owlmaddie/network/ClientPackets.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ public static void register() {
ChatDataManager.ChatStatus status = ChatDataManager.ChatStatus.valueOf(status_name);
String sender_name = buffer.readUtf(32767);
ChatDataManager.ChatSender sender = ChatDataManager.ChatSender.valueOf(sender_name);
int buildLevel = buffer.readInt();
Map<String, PlayerData> players = readPlayerDataMap(buffer);

// Update the chat data manager on the client-side
Expand All @@ -139,6 +140,7 @@ public static void register() {
chatData.currentLineNumber = line;
chatData.status = status;
chatData.sender = sender;
chatData.buildLevel = buildLevel;
chatData.players = players;

// Play sound with volume based on distance (from player or entity)
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/com/owlmaddie/ModInit.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
package com.owlmaddie;

import com.owlmaddie.commands.CreatureChatCommands;
import com.owlmaddie.commands.BuildCommands;
import com.owlmaddie.buildrec.BuildRecorder;
import com.owlmaddie.goals.EntityBehaviorManager;
import com.owlmaddie.inventory.ModMenus;
import com.owlmaddie.inventory.PickupMessageBatcher;
import com.owlmaddie.network.ServerPackets;
import net.fabricmc.api.ModInitializer;
import org.slf4j.Logger;
Expand All @@ -27,6 +31,10 @@ public void onInitialize() {

// Register server commands
CreatureChatCommands.register();
BuildCommands.register();
BuildRecorder.init();
EntityBehaviorManager.init();
PickupMessageBatcher.init();

// Register menus and events
ModMenus.register();
Expand Down
97 changes: 97 additions & 0 deletions src/main/java/com/owlmaddie/buildrec/BuildRecordIO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-FileCopyrightText: 2025 owlmaddie LLC
// SPDX-License-Identifier: GPL-3.0-or-later
// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited
package com.owlmaddie.buildrec;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.stream.JsonReader;

import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;

/**
* Utility methods for reading build recording files.
*/
public final class BuildRecordIO {
public static final Gson GSON = new Gson();

private BuildRecordIO() {}

public static Loaded read(Path file) throws IOException, JsonParseException {
try (JsonReader reader = new JsonReader(new InputStreamReader(new GZIPInputStream(Files.newInputStream(file)), StandardCharsets.UTF_8))) {
List<Action> actions = new ArrayList<>();
reader.beginArray();
JsonElement first = GSON.fromJson(reader, JsonElement.class);
Meta meta;
if (first != null && first.isJsonObject() && first.getAsJsonObject().has("action") && "meta".equals(first.getAsJsonObject().get("action").getAsString())) {
meta = GSON.fromJson(first, Meta.class);
} else {
meta = new Meta();
if (first != null) {
Action firstAction = GSON.fromJson(first, Action.class);
if (firstAction != null) actions.add(firstAction);
}
}
while (reader.hasNext()) {
Action a = GSON.fromJson(reader, Action.class);
if (a != null) actions.add(a);
}
reader.endArray();
return new Loaded(meta, actions);
}
}

public static class Loaded {
public final Meta meta;
public final List<Action> actions;
public Loaded(Meta meta, List<Action> actions) {
this.meta = meta;
this.actions = actions;
}
}

public static class Meta {
public String action = "meta";
public double eyeHeight;
public double bbWidth;
public double bbHeight;
public Map<String, Integer> recipe = new LinkedHashMap<>();
public int uniqueBlocks;
public int sizeX;
public int sizeY;
public int sizeZ;
public List<String> palette = new ArrayList<>();
public Meta() {}
public Meta(double eyeHeight, double bbWidth, double bbHeight, Map<String, Integer> recipe, int uniqueBlocks, int sizeX, int sizeY, int sizeZ, List<String> palette) {
this.eyeHeight = eyeHeight;
this.bbWidth = bbWidth;
this.bbHeight = bbHeight;
this.recipe = recipe;
this.uniqueBlocks = uniqueBlocks;
this.sizeX = sizeX;
this.sizeY = sizeY;
this.sizeZ = sizeZ;
this.palette = palette;
}
}

public static class Action {
public String action;
public int blockId;
public int bx, by, bz;
public int dt;
public double px, py, pz;
public float yaw, pitch;
}
}

Loading