From 00423b1e2a8b37f7d46f6b7832f28464a435156a Mon Sep 17 00:00:00 2001 From: xefensor Date: Wed, 12 Aug 2026 22:56:51 +0200 Subject: [PATCH 1/2] Fix tamed wolf owner defense --- CHANGELOG.md | 2 + docs/internal/design_implementation_status.md | 1 + docs/internal/retold_issues.md | 1 + docs/internal/retold_mob_ai_system.md | 6 + .../control/RetoldControlledCombatEvents.java | 90 +++++++-- .../control/RetoldTamedDefenderGameTests.java | 185 ++++++++++++++++++ .../food/RetoldFoodBehaviorEvents.java | 1 + .../RetoldControlledHuntingEvents.java | 1 + .../combat/RetoldFactionTargetMemory.java | 4 +- .../retold/combat/RetoldTargetSource.java | 1 + .../retold/gametest/RetoldGameTests.java | 2 + 11 files changed, 279 insertions(+), 15 deletions(-) create mode 100644 src/main/java/cz/xefensor/retold/behavior/control/RetoldTamedDefenderGameTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index d56d92ad..8bf0db3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Each release should be readable in two passes: - Unknown enchantments on items and enchanted books now show only their three-glyph SGA inscription and level. After a player learns a spell by successfully transferring it from a book, its ordinary readable name returns and the SGA word remains directly below it. Multi-enchanted items reveal each spell independently. - The enchanting table now replaces random bookshelf-powered offers with deterministic spell writing. Insert an item and three lapis, enter three SGA glyphs, choose level I-V, and write the enchantment for five experience levels per requested enchantment level. The editor supports physical A-Z, Backspace, number-row/keypad 1-5, and Enter controls, with duplicate submission blocked while a cast is pending. Invalid or incompatible attempts consume nothing and share one low-note/red-highlight rejection cue without revealing why they failed. Learned table-eligible spells appear in a paginated item-aware list with maximum levels, can refill their glyph word, and disable unsupported levels. A successful cast clears the inscription and briefly highlights the changed item; successfully deducing a new valid word teaches it. - Refined the villager recipe-teaching interface with a dedicated emerald-toned panel, consistent spacing, a centered teaching slot, wrapped status and guidance text, aligned cost and action controls, and clear ready/success/rejection highlights. Enter or keypad Enter can activate an available lesson, successful teaching plays a Villager confirmation sound, and rejected stale requests play the Villager refusal sound. +- Fixed standing tamed Wolves failing to defend an attacked owner or attack a mob their owner hit. Owner defense now overrides an ordinary food or skeleton target while retaining vanilla sitting, ally, PvP, and Creeper exclusions. ### Technical @@ -35,6 +36,7 @@ Each release should be readable in two passes: - Added a ruined-portal placement processor that omits template chests without affecting chests or other containers elsewhere, with focused GameTest coverage. - Added the first Retold enchanting-learning route: completing an anvil operation with an enchanted book now records every book enchantment that actually transferred to or improved the output. Multi-enchantment books update persisted per-player knowledge in one synchronized transaction; incompatible and unchanged enchantments remain unknown. All 43 currently registered enchantments now have unique three-concept words drawn from a fixed 26-concept SGA vocabulary. The validated datapack spell catalog synchronizes to clients on join and reload, while both client catalog and knowledge snapshots clear on disconnect. A client tooltip transformer preserves unrelated and unmapped lines while applying knowledge-aware SGA presentation. Added a server-authoritative table request/menu transaction with active-container validation, atomic costs, vanilla enchantment eligibility/compatibility, plain-book conversion, synchronized item/lapis slots, and successful-cast learning. Vanilla random offer buttons and bookshelf power are disabled; anvil results and costs remain unchanged. - Replaced the teaching panel's disabled-button backdrop with code-rendered panel and slot surfaces. Preview text now remains as translatable components across the version-2 network protocol, and explicit server outcomes drive client feedback. Client and server duplicate guards prevent repeated payment, learning, or Villager XP from rapid requests; focused GameTests cover transaction edge cases and payload round trips. Fixed a client crash caused by trying to inject into `MerchantScreen`'s inherited `containerTick` method; the feedback timer now uses a valid override. +- Added explicit `OWNER_DEFENSE` combat-target ownership and five-second owner interaction memory to the controlled predator combat path. The focused Wolf GameTest covers real mob damage to the owner, owner-selected targets, and replacement of lower-priority combat ownership. ## 0.4.0 - 2026-08-05 diff --git a/docs/internal/design_implementation_status.md b/docs/internal/design_implementation_status.md index 5c0b9c84..b049b54f 100644 --- a/docs/internal/design_implementation_status.md +++ b/docs/internal/design_implementation_status.md @@ -237,6 +237,7 @@ Largest missing or partial design areas: | Design item | Status | Current implementation | | --- | --- | --- | | Living reactive mobs | Partial / broad | Full Retold AI system exists; see AI doc. | +| Tamed predators defend their owner | Implemented / needs in-game verification | `RetoldControlledCombatEvents` now reads both recent owner-attacker and owner-attacked memories before continuing lower-priority combat. A standing tamed Wolf uses explicit `OWNER_DEFENSE` target ownership, which remains protected from hunger cleanup and bypasses territorial warning like direct violence. Vanilla sitting, owner/allied-tame, PvP, Armor Stand, Ghast, and Creeper exclusions remain intact through `isOrderedToSit`, `wantsToAttack`, and the global target policy. The exact Wolf GameTest covers real damage to the owner, the owner selecting a target, and owner defense replacing ordinary combat; natural pursuit and multiplayer remain unverified. | | Passive mobs flee damage sources | Implemented / developer in-game verified | `RetoldControlledFleeEvents` now immediately gives its shared land-prey and fish set remembered `FLEE` movement after every successful health-damaging hit. Causing entities cover Zombie/player/melee attacks, direct source positions cover projectiles and explosions, and source-less environmental damage uses a random panic direction. Predators, defenders, and species with specialized danger owners retain those behaviors. An integrated GameTest covers Zombie, player, environmental, aquatic, and Wolf non-regression cases; the per-mob danger phase now inflicts real damage on shared-flee profiles. The developer reported the ordered natural passive-flee acceptance pass works on 2026-08-03. Dedicated-server, multiplayer, profiler, existing-world, and exhaustive every-species verification remain unconfirmed. | | Mobs interact with each other, not only player | Partial | Factions, predator/prey, herd panic, pack behavior, assist systems. | | Herd and school ecology | Partial / implemented core, developer in-game verified | `RetoldAnimalSocialGroups` now gives Cows and Mooshrooms one bovine range group, retains the mixed equine and llama groups, and keeps other ordinary land herds species-specific. Cod, Salmon, Tropical Fish, and Pufferfish use data-driven `AQUATIC_SCHOOL` profiles; isolated members acquire low-priority `AQUATIC_SCHOOL`/`REGROUP` ownership, use cached exact-species scans, and require a real reachable aquatic path toward the school center. Squid and Glow Squid use `LOOSE_AQUATIC_GROUP` profiles and share successful-damage panic only with their exact species through one bounded cached broadcast plus the existing receiver-side fallback. GameTests cover group compatibility and a persisted mixed bovine range, exact-species fish routing, cross-species exclusion, and Squid/Glow Squid panic isolation. On 2026-08-03, the developer reported that the ordered natural acceptance pass works, including the land-group boundaries, exact-species schools, mixed-fish panic, exact-species Squid panic, bucketed fish, crowding, vertical water obstacles, and route recovery. This does not establish multiplayer, dedicated-server, long-session, or existing-world behavior. Fish diets, seagrass/kelp consumption, Squid hunger, migration, and the player-defined domesticated enclosure/range mechanism remain unspecified or unimplemented and must not be inferred from these profiles. | diff --git a/docs/internal/retold_issues.md b/docs/internal/retold_issues.md index f8fd3137..3e02e0c5 100644 --- a/docs/internal/retold_issues.md +++ b/docs/internal/retold_issues.md @@ -15,6 +15,7 @@ When adding an issue, include: | Area | Issue | Reproduction / Evidence | Status | | --- | --- | --- | --- | +| Tamed Wolf owner defense | Standing tamed Wolves did not reliably defend an owner or attack a mob the owner hit. Retold blocked the vanilla Wolf target goals but its replacement only inspected the owner's attacker, not the owner's attacked target, and checked existing `ATTACK` ownership before higher-priority owner defense. | Developer in-game report on 2026-08-12. The focused regression applies real Zombie damage to a survival owner, then gives the owner a separate attacked target while the Wolf already has ordinary combat ownership. | Fix implemented with recent owner attacker/attacked memory, explicit `OWNER_DEFENSE` target ownership, vanilla `wantsToAttack`/sitting exclusions, and owner-defense priority over ordinary combat. The exact GameTest passes 1/1; needs developer in-game confirmation before moving to Resolved Issues. | | Aender portal | When an automatically created counterpart has no nearby island terrain, replaceable void support allowed it to be placed near the dimension floor instead of at a useful altitude. | Developer in-game report on 2026-07-22. | Fix implemented to skip void columns and use a supported Y=100 fallback; needs real-Aender verification before moving to Resolved Issues. | | Aender portal | Ambient particles spawned partly below the horizontal portal plane and were occluded instead of being visible to a player standing beside it. | Developer screenshot and in-game report on 2026-07-22. | Spawn height moved above the rendered plane with upward/outward drift; needs client visual verification before moving to Resolved Issues. | | Aenderite generation | Aenderite initially appeared not to generate because only about 3.6% of a 9,409-chunk real-sampler probe contained a planned vein. After an initial increase to 8.6%, the developer still could not find it during normal in-game inspection and approved matching diamond-like abundance. | Developer reports on 2026-07-22; the original probe found 341 ore-bearing chunks and 1,134 valid replacements. A full-pipeline `ProtoChunk` GameTest confirms planned Aenderite survives terrain and decoration generation. A read-only scan of the newest local test save (`New World (44)`) found Aenderite in 361 of 1,396 saved chunks whose palettes contain Aender Stone. | Diamond-style tuning now uses mostly 3–4 and 4–8 block veins with rare 8–12 block veins. The 9,409-chunk probe finds 2,917 ore-bearing chunks (31%) and 28,209 placeable blocks, approximately three per sampled chunk. Deterministic tests enforce the frequency, density, size, underside bias, and chunk-border behavior; fresh or legitimately regenerated Aender terrain still needs in-game verification. | diff --git a/docs/internal/retold_mob_ai_system.md b/docs/internal/retold_mob_ai_system.md index b60a8b49..eac82146 100644 --- a/docs/internal/retold_mob_ai_system.md +++ b/docs/internal/retold_mob_ai_system.md @@ -736,6 +736,12 @@ the ordinary `Mob` target field, currently Axolotls and Piglins. Axolotl-to-Guar are source-sensitive: only `RETALIATION` and `FACTION_ASSIST` may pass the global hostility guard. Ordinary vanilla brain writes and Retold hunting remain blocked. +Standing tamed predators inspect both five-second vanilla owner interaction memories: the entity +that hurt the owner and the entity the owner hurt. A valid target receives explicit +`OWNER_DEFENSE` ownership before lower-priority controlled combat continues. That source is +protected from hunger cleanup and warning suppression while still using the tamed animal's vanilla +`wantsToAttack` exclusions; sitting animals do not begin or continue owner defense. + Important target rule: Retold-owned combat is allowed. Vanilla or random prey targeting is blocked where it would bypass Retold rules. diff --git a/src/main/java/cz/xefensor/retold/behavior/control/RetoldControlledCombatEvents.java b/src/main/java/cz/xefensor/retold/behavior/control/RetoldControlledCombatEvents.java index c93556d9..5802b0f6 100644 --- a/src/main/java/cz/xefensor/retold/behavior/control/RetoldControlledCombatEvents.java +++ b/src/main/java/cz/xefensor/retold/behavior/control/RetoldControlledCombatEvents.java @@ -29,6 +29,7 @@ public final class RetoldControlledCombatEvents { private static final int COMBAT_SCAN_CACHE_TICKS = 5; private static final int COMBAT_PATH_INTERVAL_TICKS = 6; private static final int ATTACK_CONTROL_TICKS = 20 * 4; + private static final int OWNER_THREAT_MEMORY_TICKS = 20 * 5; private static final double OWNER_THREAT_RADIUS_BLOCKS = 28.0D; private static final double OWNER_THREAT_RADIUS_SQUARED = @@ -112,23 +113,23 @@ public static void tickControlledCombat( return; } - if (RetoldAiControl.isControlledAs(mob, RetoldAiControlMode.ATTACK)) { - continueAttack( - mob, - gameTime - ); - return; - } - LivingEntity ownerThreat = findOwnerThreat(mob); - if (ownerThreat != null) { + if (ownerThreat != null && !isCurrentOwnerDefenseTarget(mob, ownerThreat)) { beginAttack( mob, ownerThreat, OWNER_DEFENSE_SPEED, gameTime, - RetoldTargetSource.BEHAVIOR_COMBAT + RetoldTargetSource.OWNER_DEFENSE + ); + return; + } + + if (RetoldAiControl.isControlledAs(mob, RetoldAiControlMode.ATTACK)) { + continueAttack( + mob, + gameTime ); return; } @@ -179,6 +180,22 @@ public static void tickControlledCombat( ); } + private static boolean isCurrentOwnerDefenseTarget( + PathfinderMob defender, + LivingEntity target + ) { + return RetoldAiControl.isControlledAs( + defender, + RetoldAiControlMode.ATTACK + ) + && defender.getTarget() == target + && RetoldFactionTargetMemory.isOwnedByAny( + defender, + target, + RetoldTargetSource.OWNER_DEFENSE + ); + } + @SubscribeEvent public static void onLivingDeath(LivingDeathEvent event) { LivingEntity killed = event.getEntity(); @@ -241,7 +258,7 @@ private static LivingEntity findOwnerThreat(PathfinderMob defender) { return null; } - if (!tamableAnimal.isTame()) { + if (!tamableAnimal.isTame() || tamableAnimal.isOrderedToSit()) { return null; } @@ -251,13 +268,48 @@ private static LivingEntity findOwnerThreat(PathfinderMob defender) { return null; } - LivingEntity attacker = owner.getLastHurtByMob(); + LivingEntity attacker = recentOwnerThreat( + owner, + owner.getLastHurtByMob(), + owner.getLastHurtByMobTimestamp() + ); + LivingEntity attacked = recentOwnerThreat( + owner, + owner.getLastHurtMob(), + owner.getLastHurtMobTimestamp() + ); + boolean validAttacker = isValidOwnerThreat(defender, owner, attacker); + boolean validAttacked = isValidOwnerThreat(defender, owner, attacked); + + if (validAttacker && validAttacked) { + return owner.getLastHurtByMobTimestamp() >= owner.getLastHurtMobTimestamp() + ? attacker + : attacked; + } - if (!isValidOwnerThreat(defender, owner, attacker)) { + if (validAttacker) { + return attacker; + } + + return validAttacked ? attacked : null; + } + + private static LivingEntity recentOwnerThreat( + LivingEntity owner, + LivingEntity threat, + int timestamp + ) { + if (owner == null || threat == null) { return null; } - return attacker; + int age = owner.tickCount - timestamp; + + if (age < 0 || age > OWNER_THREAT_MEMORY_TICKS) { + return null; + } + + return threat; } private static boolean isValidOwnerThreat( @@ -285,6 +337,12 @@ private static boolean isValidOwnerThreat( return false; } + if (defender instanceof TamableAnimal tamableAnimal + && (!tamableAnimal.wantsToAttack(threat, owner) + || !tamableAnimal.canAttack(threat))) { + return false; + } + if ( threat instanceof TamableAnimal threatTamable && threatTamable.isTame() @@ -573,6 +631,10 @@ private static boolean isStillValidAttackTarget( } if (attacker instanceof TamableAnimal tamableAnimal && tamableAnimal.isTame()) { + if (tamableAnimal.isOrderedToSit()) { + return false; + } + LivingEntity owner = tamableAnimal.getOwner(); if (owner != null) { diff --git a/src/main/java/cz/xefensor/retold/behavior/control/RetoldTamedDefenderGameTests.java b/src/main/java/cz/xefensor/retold/behavior/control/RetoldTamedDefenderGameTests.java new file mode 100644 index 00000000..3660628c --- /dev/null +++ b/src/main/java/cz/xefensor/retold/behavior/control/RetoldTamedDefenderGameTests.java @@ -0,0 +1,185 @@ +package cz.xefensor.retold.behavior.control; + +import cz.xefensor.retold.Retold; +import cz.xefensor.retold.behavior.core.RetoldBehaviorTargets; +import cz.xefensor.retold.combat.RetoldCombatTargets; +import cz.xefensor.retold.combat.RetoldFactionTargetMemory; +import cz.xefensor.retold.combat.RetoldTargetSource; + +import net.minecraft.core.Holder; +import net.minecraft.gametest.framework.BuiltinTestFunctions; +import net.minecraft.gametest.framework.FunctionGameTestInstance; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.gametest.framework.TestData; +import net.minecraft.gametest.framework.TestEnvironmentDefinition; +import net.minecraft.resources.Identifier; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.EntityTypes; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.animal.wolf.Wolf; +import net.minecraft.world.entity.monster.skeleton.Skeleton; +import net.minecraft.world.entity.monster.zombie.Drowned; +import net.minecraft.world.entity.monster.zombie.Zombie; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.GameType; +import net.neoforged.neoforge.event.RegisterGameTestsEvent; + +import java.util.function.Consumer; + +public final class RetoldTamedDefenderGameTests { + private static final Identifier EMPTY_STRUCTURE = + Identifier.withDefaultNamespace("empty"); + + private RetoldTamedDefenderGameTests() { + } + + public static void register( + RegisterGameTestsEvent event, + Holder> environment + ) { + TestData>> testData = new TestData<>( + environment, + EMPTY_STRUCTURE, + 100, + 0, + true + ); + Identifier testId = Identifier.fromNamespaceAndPath( + Retold.MODID, + "tamed_wolf_defends_owner_and_attacks_owner_target" + ); + + event.registerTest(testId, new InlineGameTest( + testData, + RetoldTamedDefenderGameTests::tamedWolfDefendsOwnerAndAttacksOwnerTarget + )); + } + + private static void tamedWolfDefendsOwnerAndAttacksOwnerTarget( + GameTestHelper helper + ) { + ServerLevel level = helper.getLevel(); + Player owner = helper.makeMockPlayer( + GameType.SURVIVAL + ); + Wolf wolf = helper.spawn(EntityTypes.WOLF, 2, 2, 2); + Zombie ownerAttacker = helper.spawn(EntityTypes.ZOMBIE, 3, 2, 2); + Drowned ownerTarget = helper.spawn(EntityTypes.DROWNED, 4, 2, 2); + Skeleton previousTarget = helper.spawn(EntityTypes.SKELETON, 5, 2, 2); + + try { + wolf.setTame(true, true); + wolf.setOwner(owner); + wolf.setOrderedToSit(false); + + helper.hurt( + owner, + level.damageSources().mobAttack(ownerAttacker), + 1.0F + ); + helper.assertTrue( + owner.getLastHurtByMob() == ownerAttacker, + "The real mob hit must update the owner's attacker memory" + ); + + RetoldControlledCombatEvents.tickControlledCombat( + level, + wolf, + level.getGameTime() + ); + assertOwnerDefenseTarget( + helper, + wolf, + ownerAttacker, + "A standing tamed Wolf must defend its owner" + ); + + RetoldBehaviorTargets.clearTargetAndAggression( + wolf, + ownerAttacker, + true + ); + RetoldAiControl.clear(wolf); + ownerAttacker.discard(); + + helper.assertTrue( + RetoldCombatTargets.applyAttackTarget( + wolf, + previousTarget, + RetoldTargetSource.BEHAVIOR_COMBAT + ), + "The regression setup must begin with an ordinary combat target" + ); + RetoldAiControl.claim( + wolf, + RetoldAiControlMode.ATTACK, + level.getGameTime(), + 80 + ); + owner.setLastHurtMob(ownerTarget); + + RetoldControlledCombatEvents.tickControlledCombat( + level, + wolf, + level.getGameTime() + ); + assertOwnerDefenseTarget( + helper, + wolf, + ownerTarget, + "A standing tamed Wolf must prioritize the mob its owner attacked" + ); + + helper.succeed(); + } finally { + RetoldAiControl.clear(wolf); + wolf.discard(); + ownerAttacker.discard(); + ownerTarget.discard(); + previousTarget.discard(); + owner.discard(); + } + } + + private static void assertOwnerDefenseTarget( + GameTestHelper helper, + Wolf wolf, + LivingEntity expectedTarget, + String message + ) { + helper.assertTrue(wolf.getTarget() == expectedTarget, message); + helper.assertTrue( + RetoldAiControl.isControlledAs( + wolf, + RetoldAiControlMode.ATTACK + ), + message + " under ATTACK control" + ); + helper.assertTrue( + RetoldFactionTargetMemory.isOwnedByAny( + wolf, + expectedTarget, + RetoldTargetSource.OWNER_DEFENSE + ), + message + " with owner-defense target ownership" + ); + } + + private static final class InlineGameTest + extends FunctionGameTestInstance { + private final Consumer test; + + private InlineGameTest( + TestData>> testData, + Consumer test + ) { + super(BuiltinTestFunctions.ALWAYS_PASS, testData); + this.test = test; + } + + @Override + public void run(GameTestHelper helper) { + test.accept(helper); + } + } +} diff --git a/src/main/java/cz/xefensor/retold/behavior/food/RetoldFoodBehaviorEvents.java b/src/main/java/cz/xefensor/retold/behavior/food/RetoldFoodBehaviorEvents.java index 400ba65e..9cc76f06 100644 --- a/src/main/java/cz/xefensor/retold/behavior/food/RetoldFoodBehaviorEvents.java +++ b/src/main/java/cz/xefensor/retold/behavior/food/RetoldFoodBehaviorEvents.java @@ -450,6 +450,7 @@ public static boolean tryPreferDroppedFoodOverHunt( if (target != null && RetoldFactionTargetMemory.isOwnedByAny( mob, target, + RetoldTargetSource.OWNER_DEFENSE, RetoldTargetSource.RETALIATION, RetoldTargetSource.TERRITORY_ATTACK )) { diff --git a/src/main/java/cz/xefensor/retold/behavior/hunting/RetoldControlledHuntingEvents.java b/src/main/java/cz/xefensor/retold/behavior/hunting/RetoldControlledHuntingEvents.java index f784b276..f8105643 100644 --- a/src/main/java/cz/xefensor/retold/behavior/hunting/RetoldControlledHuntingEvents.java +++ b/src/main/java/cz/xefensor/retold/behavior/hunting/RetoldControlledHuntingEvents.java @@ -241,6 +241,7 @@ public static boolean endSatisfiedHuntIfNeeded( || target != null && RetoldFactionTargetMemory.isOwnedByAny( hunter, target, + RetoldTargetSource.OWNER_DEFENSE, RetoldTargetSource.RETALIATION, RetoldTargetSource.TERRITORY_ATTACK )) { diff --git a/src/main/java/cz/xefensor/retold/combat/RetoldFactionTargetMemory.java b/src/main/java/cz/xefensor/retold/combat/RetoldFactionTargetMemory.java index 116f0b50..2dd08c35 100644 --- a/src/main/java/cz/xefensor/retold/combat/RetoldFactionTargetMemory.java +++ b/src/main/java/cz/xefensor/retold/combat/RetoldFactionTargetMemory.java @@ -265,7 +265,9 @@ private static boolean shouldBlockDuringTerritoryWarning( LivingEntity target, RetoldTargetSource source ) { - if (source == RetoldTargetSource.TERRITORY_ATTACK || source == RetoldTargetSource.RETALIATION) { + if (source == RetoldTargetSource.OWNER_DEFENSE + || source == RetoldTargetSource.TERRITORY_ATTACK + || source == RetoldTargetSource.RETALIATION) { return false; } diff --git a/src/main/java/cz/xefensor/retold/combat/RetoldTargetSource.java b/src/main/java/cz/xefensor/retold/combat/RetoldTargetSource.java index c8f6f699..59bf8e36 100644 --- a/src/main/java/cz/xefensor/retold/combat/RetoldTargetSource.java +++ b/src/main/java/cz/xefensor/retold/combat/RetoldTargetSource.java @@ -4,6 +4,7 @@ public enum RetoldTargetSource { BEHAVIOR_COMBAT, FACTION_ASSIST, FACTION_COMBAT, + OWNER_DEFENSE, TERRITORY_ATTACK, RETALIATION } diff --git a/src/main/java/cz/xefensor/retold/gametest/RetoldGameTests.java b/src/main/java/cz/xefensor/retold/gametest/RetoldGameTests.java index 863839cd..55412e12 100644 --- a/src/main/java/cz/xefensor/retold/gametest/RetoldGameTests.java +++ b/src/main/java/cz/xefensor/retold/gametest/RetoldGameTests.java @@ -6,6 +6,7 @@ import cz.xefensor.retold.behavior.control.RetoldAiControlMode; import cz.xefensor.retold.behavior.control.RetoldAiControlOwner; import cz.xefensor.retold.behavior.control.RetoldControlledCombatEvents; +import cz.xefensor.retold.behavior.control.RetoldTamedDefenderGameTests; import cz.xefensor.retold.behavior.breeding.RetoldAnimalBreedingGameTests; import cz.xefensor.retold.behavior.core.RetoldBehaviorMovement; import cz.xefensor.retold.behavior.flee.RetoldCreeperAwareness; @@ -254,6 +255,7 @@ public static void register(RegisterGameTestsEvent event) { RetoldPandaBambooGameTests.register(event); RetoldPolarBearWarningGameTests.register(event, environment); RetoldWolfPackHungerGameTests.register(event, environment); + RetoldTamedDefenderGameTests.register(event, environment); RetoldSpiderEcologyGameTests.register(event); RetoldSpiderLairGameTests.register(event); RetoldSlimeMergeGameTests.register(event); From d3264ab033fa0fd56d9691bbaff8ad4355274ad3 Mon Sep 17 00:00:00 2001 From: xefensor Date: Fri, 14 Aug 2026 00:08:40 +0200 Subject: [PATCH 2/2] Replace Aender sand placeholder and vary terrain --- ASSET_CREDITS.md | 18 +++++++++++++--- CHANGELOG.md | 3 +++ docs/internal/retold_design_risks.md | 2 +- .../blockstates/aender_grass_block.json | 20 +++++++++++++++--- .../retold/blockstates/aender_sand.json | 20 +++++++++++++++--- .../retold/blockstates/aender_soil.json | 20 +++++++++++++++--- .../retold/blockstates/aender_stone.json | 19 ++++++++++++++--- .../models/block/aender_stone_mirrored.json | 6 ++++++ .../retold/textures/block/aender_sand.png | Bin 784 -> 422 bytes 9 files changed, 92 insertions(+), 16 deletions(-) create mode 100644 src/main/resources/assets/retold/models/block/aender_stone_mirrored.json diff --git a/ASSET_CREDITS.md b/ASSET_CREDITS.md index 89b79c9c..5b816572 100644 --- a/ASSET_CREDITS.md +++ b/ASSET_CREDITS.md @@ -8,15 +8,16 @@ This record complements [`LICENSE`](LICENSE) and [`LICENSE-ASSETS.md`](LICENSE-A | --- | --- | --- | | Jesse Schramm | Extinguished-torch textures | Included | | Xefensor | Gameplay screenshots dated 2026-07-18 | Repository documentation | +| Xefensor, with drawing instruction and review from Codex | Hand-drawn Aender Sand texture created in Krita | Original replacement included; 2026-08-14 | | OpenAI image generation, directed and processed by Codex | Aender wood-family placeholder textures: planks, stripped log, sapling, door, trapdoor, signs, boats, and derived model atlases | AI-generated placeholder; 2026-07-22 | | OpenAI image generation, directed and processed by Codex | Aender Chronolith block and Aender Eye/Gale Core spawn-egg placeholder textures | AI-generated placeholder; 2026-07-22 | -| OpenAI image generation, directed and processed by Codex | Experimental Aender Desert placeholder textures: Aender sand, sandstone, and cactus | AI-generated placeholder; 2026-07-22 | +| OpenAI image generation, directed and processed by Codex | Experimental Aender Desert placeholder textures: Aender sand, sandstone, and cactus | AI-generated placeholders; Aender Sand superseded on 2026-08-14 | | OpenAI image generation, directed and processed by Codex | Aenderite Ore, Raw Aenderite, and Aenderite Ingot placeholder textures | AI-generated placeholder; 2026-07-22 | | Future original artists | Final replacements for provisional Aender and other development assets | To be credited when work is accepted | ## Provisional And Generated Assets -The current Aender visuals include AI-generated textures and other provisional development assets. They exist to test terrain, lighting, blocks, portals, and gameplay while original artwork is developed. +The current Aender visuals include AI-generated textures and other provisional development assets. They exist to test terrain, lighting, blocks, portals, and gameplay while original artwork is developed. Aender Sand is the first accepted hand-drawn replacement for one of these generated textures. These assets are explicitly **placeholders** and do not represent the intended final visual identity. Placeholder status does not grant permission to extract or reuse them; the asset license still applies. @@ -26,10 +27,21 @@ The Aender wood-family placeholders were generated specifically for Retold from The Aender Chronolith and spawn-egg placeholders were generated specifically for Retold from prompts describing original purple time-rune stone, a green-eyed Aender egg, and a pale cyan/gold Gale Core egg. Mechanical post-processing removed chroma-key backgrounds and reduced the sources to 16x16 game textures. No Minecraft texture pixels were used in these files. -The experimental Aender Desert placeholders were generated specifically for Retold from prompts describing original deep denim energy sand, medium periwinkle layered sandstone with a separate smoother top face, and lavender ribbed cactus material. Mechanical post-processing reduced the generated material sources to 16x16 game textures and derived the cactus top from the original generated cactus material. Developer-provided inverted-desert screenshots were used only as color, mood, and block-face-layout references; no pixels from them or from Minecraft textures were included. +The experimental Aender Desert placeholders were generated specifically for Retold from prompts describing original deep denim energy sand, medium periwinkle layered sandstone with a separate smoother top face, and lavender ribbed cactus material. Mechanical post-processing reduced the generated material sources to 16x16 game textures and derived the cactus top from the original generated cactus material. Developer-provided inverted-desert screenshots were used only as color, mood, and block-face-layout references; no pixels from them or from Minecraft textures were included. The generated Aender Sand texture is retained only in Git history: Xefensor replaced the active file with an original hand-drawn Krita texture on 2026-08-14. The Aenderite placeholders were generated specifically for Retold from prompts describing muted mint-green and deep teal mineral chips, a raw mineral lump, and a refined ingot. Mechanical post-processing removed chroma-key backgrounds and reduced the item sources to 16x16. The ore pattern was rebuilt as original pixel clusters over Retold's existing animated Aender Stone base and repeated across all animation frames. A developer-provided End Portal Frame image was used only as a green-palette reference; no pixels or frame geometry from that image or from Minecraft textures were included. +## AI Texture Audit + +The active PNG textures explicitly credited as OpenAI-generated were audited against their introduction commits on 2026-08-14. Companion JSON and `.mcmeta` files are implementation metadata and are not counted as creative textures. Of the 24 credited generated PNGs, 23 remain active AI-generated placeholders and one has been replaced: + +| Batch | Active AI-generated placeholder files | Replacement status | +| --- | --- | --- | +| Aender wood family | `block/aender_door_bottom.png`, `block/aender_door_top.png`, `block/aender_hanging_sign.png`, `block/aender_planks.png`, `block/aender_sapling.png`, `block/aender_sign.png`, `block/aender_trapdoor.png`, `block/stripped_aender_log.png`, `block/stripped_aender_log_top.png`, `entity/boat/aender.png`, `entity/chest_boat/aender.png`, `item/aender_boat.png`, `item/aender_chest_boat.png` | 13 placeholders remain | +| Chronolith and spawn eggs | `block/aender_chronolith.png`, `item/aender_eye_spawn_egg.png`, `item/gale_core_spawn_egg.png` | 3 placeholders remain | +| Aender Desert | `block/aender_cactus_side.png`, `block/aender_cactus_top.png`, `block/aender_sandstone.png`, `block/aender_sandstone_top.png` | The generated `block/aender_sand.png` was replaced by Xefensor's hand-drawn texture; 4 placeholders remain | +| Aenderite | `block/aenderite_ore.png`, `item/raw_aenderite.png`, `item/aenderite_ingot.png` | 3 placeholders remain | + ## Third-Party Material Minecraft, NeoForge, and other third-party names, APIs, templates, or materials remain subject to their own terms. NeoForge MDK template files identified by `TEMPLATE_LICENSE.txt` retain that license. diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bf0db3a..a2a55410 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ Each release should be readable in two passes: - Mending can no longer be newly generated by Librarian trades or random loot. Existing Mending items continue to function, and commands and Creative mode retain the enchantment. - Unenchanted Diamond tools and Spears now have only 64 durability, while unenchanted Diamond armor has leather-like 6x durability. Diamond Horse and Nautilus Armor now use 96 durability before enchanting and 528 afterward, and lose durability from real hits like other armor. Any enchantment—including a curse by itself—immediately restores full Diamond durability; removing every enchantment makes the item fragile again. A heavily damaged item stripped beyond that fragile limit retains one final use instead of becoming invalid immediately. - Wolf Armor and every Horse and Nautilus Armor material can now be enchanted with the same enchantment pool as a Chestplate. Each uses the enchanting value of its own armor material. Protection enchantments now affect damage absorbed by Wolf Armor as well as ordinary animal health damage; Fire Protection also retains its normal shorter-burning effect. +- Replaced the AI-generated Aender Sand placeholder with Xefensor's original hand-drawn texture. +- Natural Aender grass, soil, stone, and sand now use deterministic per-block texture variants matching their vanilla counterparts, reducing obvious repeated terrain patterns. - The End no longer produces its periodic celestial flashes, temporary brightness pulses, or delayed flash sounds. Retold's generated End sky remains unchanged. - Trial Chambers, Ancient Cities, and the Deep Dark no longer generate in newly explored terrain. Their blocks, items, mobs, biomes, and already-generated content remain available. - Ruined Nether portals no longer generate with loot chests. @@ -31,6 +33,7 @@ Each release should be readable in two passes: - Added the 48-durability Flint Multi-tool, Flint Spear, and provisional Steel material with data-driven mining, repair, common-ingot, Spear, enchantment-family, and armor tags. A global loot modifier supplies scaled Sticks from leaves and woody bushes, and placed-feature overrides reduce both ordinary and Dripstone Copper attempts from sixteen to six per chunk. Data-driven chest and Villager-trade overrides enforce fixed acquisition tiers without per-player state, while enchantment tags remove Mending from new trade/random-loot generation. Exact Wooden/Stone recipes are removed during recipe loading; Armorer, Toolsmith, and Weaponsmith lessons follow the material ladder. Focused GameTests cover scaled Stick supply, acquisition tables and prices, Mending source removal, reduced Copper placement, Campfire behavior, harvest tiers, Spear/starter/equipment crafting, station processing, and Furnace availability. - Added tag-driven dynamic Diamond durability through a narrow `ItemStack.getMaxDamage` hook and a named progression policy. Diamond Horse and Nautilus Armor receive a 528-durability component and server-side BODY-slot wear from non-bypassing hits. Focused coverage enumerates all twelve enchantable Diamond items and verifies fragile values, enchantment restoration, removal regression, real animal-armor wear, vanilla-tag preservation, and the over-damaged stripped-item safeguard. - Added data-driven Chestplate enchantment compatibility and material-matched enchantability components for all twelve vanilla animal armors. A Wolf-specific incoming-damage bridge applies vanilla enchantment protection before Wolf Armor's special durability-absorption path bypasses ordinary living-entity mitigation. Focused coverage verifies every supported-enchantment set plus equipped Protection-family mitigation, Fire Protection burn duration, Thorns, Unbreaking, Mending, Binding, and Vanishing behavior across Wolf, Horse, and Nautilus armor paths. +- Added vanilla-style randomized Y rotations for Aender Grass, Aender Soil, and Aender Sand, plus normal and mirrored model variants for Aender Stone. Directional, crafted, and natural blocks whose vanilla counterparts keep a fixed model remain unchanged. - Added a client-only vanilla-End hook that clears `EndFlashState` after level construction, suppressing all flash rendering, lightmap, and sound paths while preserving the End skybox and leaving other End-style dimensions untouched. - Replaced the vanilla Trial Chamber and Ancient City biome tags with empty tags so the structures remain registered but have no eligible generation biomes. A narrow Overworld biome-builder hook omits the Deep Dark mapping from the default climate preset without unregistering the biome. Added focused GameTest coverage for each boundary. - Added a ruined-portal placement processor that omits template chests without affecting chests or other containers elsewhere, with focused GameTest coverage. diff --git a/docs/internal/retold_design_risks.md b/docs/internal/retold_design_risks.md index 71c6f0eb..c1b7afb0 100644 --- a/docs/internal/retold_design_risks.md +++ b/docs/internal/retold_design_risks.md @@ -9,7 +9,7 @@ | Progression | Stage 1 Wither/Nether star End access gating is undecided. | Do not implement until developer decides. | | Elements | Fire and Earth still need full item/challenge paths. | Water has the ocean monument path. Air has a WIP Air Temple/Gale Core acquisition path and a provisional Stage 2 cartographer map with an exact marker. Deterministic GameTests cover the map trade's stage gate and uniqueness plus critical encounter state transitions. The basic in-game map path was developer-verified on 2026-07-22; wider seed, upgraded-world retrogen, dedicated-server, and multiplayer verification remains. Encounter pacing, readability, and balance still need in-game tuning. | | Air Temple discovery | An explorer map could select a delayed Air Temple start that retrogen later suppresses because an upgraded-world chunk was edited. The initial synchronous structure search and biome-preview rendering may also cause a noticeable interaction stall. | Basic cartographer interaction and exact-map behavior were developer-verified on 2026-07-22. Failed searches are throttled for five minutes per cartographer. In wider fresh and upgraded test worlds, measure the first interaction and confirm the marker leads to a generated tower, including a world where a nearby deferred start is permanently skipped. | -| Aender | The horizontal portal, 8:1 scaling, counterpart creation, persisted global/regional and per-chunk reality state, generator V3, player-scoped regional volatility, player-centered blank-then-regenerate transitions, adaptive tick-time budgets, indefinite safe-core arrival gate, living terrain blocks, complete wood family, experimental per-island Aender Desert, cave tunnels, Aenderite deposits, and expanded topology/decorations need fresh-world, existing-world, rapid re-entry, high-view-distance, and multiplayer verification. | JUnit tests cover order-independent terrain interval composition, equivalent cached/direct vertical biome selection, biome-owned archetype families, relief differences, erosion silhouettes, satellite distribution, cave continuity/entrance balance/fragile-island exclusion, vegetation clearings, rare-formation planning/intersection, deterministic Aenderite frequency/size/underside bias/chunk-border continuity, adaptive budget scaling, active-region watcher transitions, and the portal transition gate. Deterministic GameTests cover shape lifecycle, scaling, counterpart creation/indexing, living terrain/wood/desert/ore survival data, full-pipeline cave carving and Aenderite block placement, vanilla creative-tab integration, reality/stability serialization, chunk attachment state/cache restoration, entity cleanup, blank transitions, and stable-versus-volatile policy. The GameTest server does not load the custom Aender dimension, so still verify crash recovery, V1/V2/V3 selection, desert frequency/palette/atmosphere, all island archetypes/undersides/detached satellites, cave shape/entrances/chambers and Aenderite exposure, warped satellite coastlines, erosion openings, split ravines, clustered decorations, formation frequency, vertical biome transitions, biome replacement packets, shared and unattended multiplayer regions, ring regeneration, tick-load throttling, V3 borders, arrival safety, and client models in normal clients. Repeat the TPS-graph scenario to measure remaining per-chunk spikes after biome-column caching, paced blanking, measured-cost admission, and removal of synchronous stale-load/portal-search regeneration. `dev_aender_portal_frame` and all credited AI-generated Aender textures remain provisional. | +| Aender | The horizontal portal, 8:1 scaling, counterpart creation, persisted global/regional and per-chunk reality state, generator V3, player-scoped regional volatility, player-centered blank-then-regenerate transitions, adaptive tick-time budgets, indefinite safe-core arrival gate, living terrain blocks, complete wood family, experimental per-island Aender Desert, cave tunnels, Aenderite deposits, and expanded topology/decorations need fresh-world, existing-world, rapid re-entry, high-view-distance, and multiplayer verification. | JUnit tests cover order-independent terrain interval composition, equivalent cached/direct vertical biome selection, biome-owned archetype families, relief differences, erosion silhouettes, satellite distribution, cave continuity/entrance balance/fragile-island exclusion, vegetation clearings, rare-formation planning/intersection, deterministic Aenderite frequency/size/underside bias/chunk-border continuity, adaptive budget scaling, active-region watcher transitions, and the portal transition gate. Deterministic GameTests cover shape lifecycle, scaling, counterpart creation/indexing, living terrain/wood/desert/ore survival data, full-pipeline cave carving and Aenderite block placement, vanilla creative-tab integration, reality/stability serialization, chunk attachment state/cache restoration, entity cleanup, blank transitions, and stable-versus-volatile policy. The GameTest server does not load the custom Aender dimension, so still verify crash recovery, V1/V2/V3 selection, desert frequency/palette/atmosphere, all island archetypes/undersides/detached satellites, cave shape/entrances/chambers and Aenderite exposure, warped satellite coastlines, erosion openings, split ravines, clustered decorations, formation frequency, vertical biome transitions, biome replacement packets, shared and unattended multiplayer regions, ring regeneration, tick-load throttling, V3 borders, arrival safety, and client models in normal clients. Repeat the TPS-graph scenario to measure remaining per-chunk spikes after biome-column caching, paced blanking, measured-cost admission, and removal of synchronous stale-load/portal-search regeneration. `dev_aender_portal_frame` and the 23 remaining credited AI-generated Aender textures remain provisional; Aender Sand has an accepted original replacement. | | Aender | A separate in-dimension teleportation network and late-game rewards are still planned. | Cross-dimension travel is implemented; remaining travel/reward design needs clarification before code. | | End | End City/outer End removal needs fresh-world and existing-world verification. | Data/mixins exist, but generated-world behavior should be tested. | | Elytra | Elytra should remain an item but not be survival-obtainable. | Depends on End Cities not generating and no alternate survival source being added unintentionally. | diff --git a/src/main/resources/assets/retold/blockstates/aender_grass_block.json b/src/main/resources/assets/retold/blockstates/aender_grass_block.json index 28eccf05..ed93263d 100644 --- a/src/main/resources/assets/retold/blockstates/aender_grass_block.json +++ b/src/main/resources/assets/retold/blockstates/aender_grass_block.json @@ -1,8 +1,22 @@ { "variants": { - "snowy=false": { - "model": "retold:block/aender_grass_block" - }, + "snowy=false": [ + { + "model": "retold:block/aender_grass_block" + }, + { + "model": "retold:block/aender_grass_block", + "y": 90 + }, + { + "model": "retold:block/aender_grass_block", + "y": 180 + }, + { + "model": "retold:block/aender_grass_block", + "y": 270 + } + ], "snowy=true": { "model": "retold:block/aender_grass_block_snowy" } diff --git a/src/main/resources/assets/retold/blockstates/aender_sand.json b/src/main/resources/assets/retold/blockstates/aender_sand.json index 1b20883f..87658eb5 100644 --- a/src/main/resources/assets/retold/blockstates/aender_sand.json +++ b/src/main/resources/assets/retold/blockstates/aender_sand.json @@ -1,7 +1,21 @@ { "variants": { - "": { - "model": "retold:block/aender_sand" - } + "": [ + { + "model": "retold:block/aender_sand" + }, + { + "model": "retold:block/aender_sand", + "y": 90 + }, + { + "model": "retold:block/aender_sand", + "y": 180 + }, + { + "model": "retold:block/aender_sand", + "y": 270 + } + ] } } diff --git a/src/main/resources/assets/retold/blockstates/aender_soil.json b/src/main/resources/assets/retold/blockstates/aender_soil.json index 5921c275..102b043a 100644 --- a/src/main/resources/assets/retold/blockstates/aender_soil.json +++ b/src/main/resources/assets/retold/blockstates/aender_soil.json @@ -1,7 +1,21 @@ { "variants": { - "": { - "model": "retold:block/aender_soil" - } + "": [ + { + "model": "retold:block/aender_soil" + }, + { + "model": "retold:block/aender_soil", + "y": 90 + }, + { + "model": "retold:block/aender_soil", + "y": 180 + }, + { + "model": "retold:block/aender_soil", + "y": 270 + } + ] } } diff --git a/src/main/resources/assets/retold/blockstates/aender_stone.json b/src/main/resources/assets/retold/blockstates/aender_stone.json index 9112075d..9b8cd938 100644 --- a/src/main/resources/assets/retold/blockstates/aender_stone.json +++ b/src/main/resources/assets/retold/blockstates/aender_stone.json @@ -1,7 +1,20 @@ { "variants": { - "": { - "model": "retold:block/aender_stone" - } + "": [ + { + "model": "retold:block/aender_stone" + }, + { + "model": "retold:block/aender_stone_mirrored" + }, + { + "model": "retold:block/aender_stone", + "y": 180 + }, + { + "model": "retold:block/aender_stone_mirrored", + "y": 180 + } + ] } } diff --git a/src/main/resources/assets/retold/models/block/aender_stone_mirrored.json b/src/main/resources/assets/retold/models/block/aender_stone_mirrored.json new file mode 100644 index 00000000..27717a6e --- /dev/null +++ b/src/main/resources/assets/retold/models/block/aender_stone_mirrored.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_mirrored_all", + "textures": { + "all": "retold:block/aender_stone" + } +} diff --git a/src/main/resources/assets/retold/textures/block/aender_sand.png b/src/main/resources/assets/retold/textures/block/aender_sand.png index 56d89278368b4420e9dfd0973caa82ccd1001203..c9b91a1388c66fa3b9ed8327d917fc383ed4675a 100644 GIT binary patch delta 407 zcmV;I0cie^2Brg$8Gi-<001BJ|6u?C010qNS#tmYE+YT{E+YYWr9XB600CG@L_t(I zPhFG2N&`_0MV~{#B8ng))P)L)F2t38;ve`yewr(BQBhDA7DTJXB6X#ngwuvT7-o`} z+}zw`RxU2DekbF%wz;+4USA&DYJa^gAMQBBaj?zlbUxqT#DC?_Y&w4k#3lxC4>o>I zM^HY4aO~_J#2Y3iO~#{=Jc@W3^(w8_@`B_HN~-#(x_5NeVB25Y2!Bs#tEqvBHCa}$ z-9J2OpC9k7v+v{6^T4uis`2s_bK%Xt8)#kaS5oh_bGGq zEq4#D^$4!B@+k5SYjE@{_YA4?32*cYYq0k&bqlKT3T+@hWi*taJ;icJeEC4yd&EDfbKonp5i?*5naf;}%q9=UwLHU+EcALO++R?Yd(yh=Vq}DI8$uF|mC7{ATs>Rm3)X}ff z4J2FX5mNL2|Nq>5f1`mRz?kIi?h-g9vqlTZVK4FYb!C6WF2f~}CQwMV#5JNM zC9x#cD!C{XNHG{07@6rBnCluDg%}!H8Jk-f8fqIDSQ!{}*DHKN(U6;;l9^VCTf;A< z8Gb+w8gLs*GILXlOA>Pn(DhhanV3N=vD>-x1W?asPZ!4!iOZ}9JDmE(E6?@E8#Q0wJ#i!pLx&=%N zzrOlc+q0LKOio3uxcx+Iz4`4s>6dp;(wHFYni*y)@iKPP-SrRaIyj40uIk`hoVFk_ ztBYOna?(quZ!