diff --git a/sanctuary/NAMES.md b/sanctuary/NAMES.md index f7d9056..b19d12a 100644 --- a/sanctuary/NAMES.md +++ b/sanctuary/NAMES.md @@ -4,7 +4,9 @@ Les noms sont déterministes à partir de l'UUID de la créature ; les répétitions sont autorisées. -- villageois, zombies, moutons, vaches, cochons et poules : prénoms humains comme Patrick, Henri ou Hervé ; +- villageois, moutons, vaches, cochons et poules : prénoms humains comme Patrick, Henri ou Hervé ; +- toute la famille Zombie, vanilla ou modée : gamertags volontairement « Xbox 360 » comme + `xXDarkSasuke69Xx`, `iTzKevin360` ou `xXNoScope420Xx` ; - Endermen : sonorités extraterrestres rendues avec la police alien vanilla `minecraft:alt` ; - Creepers : prénoms et mots en S, avec quelques sifflements ; - squelettes : rythmes et onomatopées musicales ; diff --git a/sanctuary/build.gradle b/sanctuary/build.gradle index dcc71a1..e363262 100644 --- a/sanctuary/build.gradle +++ b/sanctuary/build.gradle @@ -70,6 +70,14 @@ tasks.register("shopHeaderLayoutSmoke", JavaExec) { mainClass = "fr.koka99cab.sanctuary26.sanctuary.client.ShopDailyHeaderLayoutSmoke" } +tasks.register("zombieNamesSmoke", JavaExec) { + group = "verification" + description = "Checks deterministic Xbox-360-style names for every vanilla Zombie subtype." + dependsOn tasks.named("testClasses") + classpath = sourceSets.test.runtimeClasspath + mainClass = "fr.koka99cab.sanctuary26.sanctuary.client.SanctuaryZombieNamesSmoke" +} + tasks.register("verifyBlackMarket") { group = "verification" description = "Checks the physical sign market, flash Shop, escrow ledger and mailbox delivery." @@ -220,7 +228,7 @@ tasks.register("verifySanctuary") { inputs.files(fileTree("tools")) dependsOn tasks.named("progressionModelSmoke"), tasks.named("serverIdentityModelSmoke"), tasks.named("shopDeliveryTimingSmoke"), tasks.named("blackMarketModelSmoke"), - tasks.named("questBoardModelSmoke"), tasks.named("shopHeaderLayoutSmoke"), + tasks.named("questBoardModelSmoke"), tasks.named("shopHeaderLayoutSmoke"), tasks.named("zombieNamesSmoke"), tasks.named("verifyBlackMarket") doLast { diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryMobNames.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryMobNames.java index e557c09..8fc421f 100644 --- a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryMobNames.java +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryMobNames.java @@ -33,6 +33,15 @@ public final class SanctuaryMobNames { "Raymond", "Colette", "Norbert", "Mireille", "Didier", "Chantal", "Marcel", "Brigitte", "Jean-Michel", "Françoise", "Pascal", "Sylvie", "René", "Martine", "Gilbert", "Odette" }; + private static final String[] ZOMBIE_GAMERTAGS = { + "xXDarkSasuke69Xx", "xXSniperDu59Xx", "OoKilleur93oO", "iTzKevin360", + "xXNoScope420Xx", "DarkKiller2009", "LeBGduMinecraft26", "xXShadowNinja76Xx", + "MLGRaptor67", "FazeKevinDu13", "QuickScope360", "NarutoPro59", + "xXGhostRider666Xx", "TheDarkCreeper42", "L3G3NDKILL3R", "xXEnderBoss77Xx", + "SkyZzOfficiel62", "xXPikaKiller25Xx", "DarkAngel666", "iiTzWarrior360ii", + "xXDragonFeu974Xx", "ProGamerDu59", "xXHeadShot360Xx", "BoGoss2008", + "RoiDuNoScope93", "xXDeathNinja13Xx", "TeamDark59", "KevinLeBoss360" + }; private static final String[] ENDER = { "Xyr-9", "Vaeluun", "Orryx", "Nhal", "Zyphra", "Qor'eth", "Uulma", "Thess-7", "Ixxa", "Vhoor", "Aen-Null", "Kryss", "Omnû", "Yl'zar", "Nexil", "Rhaum" @@ -92,6 +101,7 @@ public final class SanctuaryMobNames { } static String generate(Entity entity) { + if (usesZombieGamertags(entity.getClass())) return zombieName(entity.getUUID()); String[] family = switch (entity) { case EnderMan ignored -> ENDER; case Creeper ignored -> CREEPERS; @@ -99,7 +109,6 @@ public final class SanctuaryMobNames { case Wolf ignored -> DOGS; case Cat ignored -> CATS; case Villager ignored -> HUMANS; - case Zombie ignored -> HUMANS; case Cow ignored -> HUMANS; case Pig ignored -> HUMANS; case Sheep ignored -> HUMANS; @@ -112,6 +121,14 @@ public final class SanctuaryMobNames { return pick(family, entity.getUUID()); } + static boolean usesZombieGamertags(Class entityClass) { + return entityClass != null && Zombie.class.isAssignableFrom(entityClass); + } + + static String zombieName(UUID uuid) { + return pick(ZOMBIE_GAMERTAGS, uuid); + } + private static String pick(String[] values, UUID uuid) { long mixed = uuid.getMostSignificantBits() ^ Long.rotateLeft(uuid.getLeastSignificantBits(), 23); mixed = (mixed ^ (mixed >>> 30)) * 0xBF58476D1CE4E5B9L; diff --git a/sanctuary/src/test/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryZombieNamesSmoke.java b/sanctuary/src/test/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryZombieNamesSmoke.java new file mode 100644 index 0000000..5547c52 --- /dev/null +++ b/sanctuary/src/test/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryZombieNamesSmoke.java @@ -0,0 +1,44 @@ +package fr.koka99cab.sanctuary26.sanctuary.client; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import net.minecraft.world.entity.monster.Creeper; +import net.minecraft.world.entity.monster.zombie.Drowned; +import net.minecraft.world.entity.monster.zombie.Husk; +import net.minecraft.world.entity.monster.zombie.Zombie; +import net.minecraft.world.entity.monster.zombie.ZombieVillager; +import net.minecraft.world.entity.monster.zombie.ZombifiedPiglin; + +/** Executable coverage for deterministic Xbox-360-style names across the complete Zombie family. */ +public final class SanctuaryZombieNamesSmoke { + private SanctuaryZombieNamesSmoke() { + } + + public static void main(String[] args) { + for (Class zombieClass : new Class[] { + Zombie.class, Husk.class, Drowned.class, ZombieVillager.class, ZombifiedPiglin.class + }) { + require(SanctuaryMobNames.usesZombieGamertags(zombieClass), + "Zombie subtype missed gamertag family: " + zombieClass.getSimpleName()); + } + require(!SanctuaryMobNames.usesZombieGamertags(Creeper.class), + "Non-zombie monster entered gamertag family"); + + UUID stableId = UUID.fromString("12345678-1234-5678-9abc-def012345678"); + require(SanctuaryMobNames.zombieName(stableId).equals(SanctuaryMobNames.zombieName(stableId)), + "Zombie gamertag is not deterministic"); + Set observed = new HashSet<>(); + for (int index = 0; index < 4_096; index++) { + String name = SanctuaryMobNames.zombieName(new UUID(index * 31L, ~((long) index * 97L))); + require(name.matches("[A-Za-z0-9_]+"), "Gamertag contains an invalid character: " + name); + require(name.chars().anyMatch(Character::isDigit), "Gamertag has no Xbox-style digits: " + name); + observed.add(name); + } + require(observed.size() >= 24, "Zombie gamertag pool is too small: " + observed.size()); + } + + private static void require(boolean condition, String message) { + if (!condition) throw new IllegalStateException(message); + } +}