release: publier Sanctuary 26.2.0-alpha.225 #125

Merged
koka merged 5 commits from release/26.2.0-alpha.225 into main 2026-08-31 09:50:26 +00:00
34 changed files with 749 additions and 294 deletions
+10
View File
@@ -6,6 +6,16 @@ Ce journal suit les **versions du modpack**. Lorsqu'un seul module change, sa ve
- Depuis `26.2.0-alpha.6`, le pack et les modules ont des versions indépendantes. I Like To Move It et Only Fun deviennent les cinquième et sixième modules actifs dans l'alpha 46.
- Les versions ci-dessous ont été reconstituées à partir des JAR archivés, des packs locaux et du code source actuel. Les dates n'étant pas enregistrées de manière fiable, elles ne sont pas inventées.
## `26.2.0-alpha.225`
Modules modifiés : **Another World `0.0.0-alpha.56`**, **It's Alive `0.0.0-alpha.38`**, **I Like To Move It `0.0.0-alpha.18`**, **Only Fun `0.0.0-alpha.27`** et **Sanctuary `0.0.0-alpha.129`**. Les cinq autres modules conservent leur version issue de l'alpha.224.
- exige que lattaquant possède son propre coffre-fort actif avant de commencer un braquage, revalide cette condition pendant le forage et refuse défensivement tout transfert si le coffre disparaît entre-temps ;
- empêche les familiers temporaires Sanctuary de produire du butin vanilla, de lexpérience ou une lootbox Only Fun, et les exclut des contrats de panneau comme des demandes communautaires ;
- conserve la variante Canaplia des seize graines historiques pendant le placement, la croissance naturelle, la poudre dos, la création de la partie haute, le rechargement et la récolte, ce qui réactive notamment lhybridation Tendre + Olive vers Dorée ;
- remplace la laine violette du Magic Carpet par la texture historique 16 × 16 du Totem de Notch, utilisée par lobjet canonique et son alias hérité sans modifier la plateforme 3 × 3 ;
- conserve tous les IDs, sauvegardes et formats de données ; les protocoles réseau restent Another World 2, It's Alive 4, I Like To Move It 5, Only Fun 3 et Sanctuary 23, sans nouveau monde ni réinitialisation.
## `26.2.0-alpha.224`
Modules modifiés : **It's Alive `0.0.0-alpha.37`**, **Ouch `0.0.0-alpha.11`**, **I Like To Move It `0.0.0-alpha.17`**, **Only Fun `0.0.0-alpha.26`**, **Sanctuary `0.0.0-alpha.128`** et **MasterKey `0.0.0-alpha.9`**.
+3 -1
View File
@@ -22,7 +22,9 @@ Licence : **GNU GPL 3.0 or Later** (`GPL-3.0-or-later`)
Les descriptions FR/EN/RU sont déclarées dans chaque `fabric.mod.json` et dans les fichiers de langue. Les images fournies servent d'icônes Fabric de leur module.
Version courante du pack distribué : **`26.2.0-alpha.224`**. Cette release distribue It's Alive **`0.0.0-alpha.37`**, Ouch **`0.0.0-alpha.11`**, I Like To Move It **`0.0.0-alpha.17`**, Only Fun **`0.0.0-alpha.26`**, Sanctuary **`0.0.0-alpha.128`** et MasterKey **`0.0.0-alpha.9`** ; les quatre autres modules conservent leur version issue de l'alpha.223.
Version courante du pack distribué : **`26.2.0-alpha.225`**. Cette release distribue Another World **`0.0.0-alpha.56`**, It's Alive **`0.0.0-alpha.38`**, I Like To Move It **`0.0.0-alpha.18`**, Only Fun **`0.0.0-alpha.27`** et Sanctuary **`0.0.0-alpha.129`** ; les cinq autres modules conservent leur version issue de l'alpha.224.
Lalpha.225 corrige quatre tickets : posséder un coffre-fort devient obligatoire pour en braquer un autre, les familiers temporaires ne donnent plus de butin, dexpérience, de lootbox ni de progression de quête, les seize variantes Canaplia survivent à tout leur cycle de culture, et le Magic Carpet retrouve la texture historique du Totem de Notch à la place de la laine violette. Les IDs, sauvegardes, formats de données et protocoles restent compatibles.
La migration [CANA-01 à CANA-09](CANAPLIA.md) porte Canaplia vers Minecraft 26.2. It's Alive **`0.0.0-alpha.35`** enregistre les seize familles, les découvertes, hybridations, résines, effets serveur et le Livre interdit personnel, partageable temporairement sur un pupitre. Ambiance **`0.0.0-alpha.14`** compose les profils visuels bornés avec ses réglages d'accessibilité, et Only Fun **`0.0.0-alpha.21`** exclut explicitement Canaplia et les raccourcis de progression du Shop.
+5 -2
View File
@@ -1647,6 +1647,7 @@ tasks.register("generateAnotherWorldResources") {
"message.anotherworld.safe_vault.empty_before_break": "Videz le coffre-fort et attendez la fin du forage avant de le démonter.",
"message.anotherworld.safe_vault.drill_own": "Vous ne pouvez pas forer votre propre coffre-fort.",
"message.anotherworld.safe_vault.inactive": "Ce coffre-fort nest pas actif.",
"message.anotherworld.safe_vault.raid_requires_vault": "Vous devez posséder un coffre-fort actif avant den braquer un autre.",
"message.anotherworld.safe_vault.empty": "Ce coffre-fort est vide.",
"message.anotherworld.safe_vault.owner_offline": "Le propriétaire doit être connecté pour que le coffre-fort soit attaquable.",
"message.anotherworld.safe_vault.protected": "Ce coffre-fort reste protégé pendant environ %s minute(s).",
@@ -1671,6 +1672,7 @@ tasks.register("generateAnotherWorldResources") {
"message.anotherworld.safe_vault.empty_before_break": "Опустошите сейф и дождитесь окончания сверления перед разборкой.",
"message.anotherworld.safe_vault.drill_own": "Нельзя сверлить собственный сейф.",
"message.anotherworld.safe_vault.inactive": "Этот сейф не активен.",
"message.anotherworld.safe_vault.raid_requires_vault": "Для ограбления чужого сейфа нужен собственный активный сейф.",
"message.anotherworld.safe_vault.empty": "Этот сейф пуст.",
"message.anotherworld.safe_vault.owner_offline": "Владелец должен быть в сети, чтобы атаковать сейф.",
"message.anotherworld.safe_vault.protected": "Сейф защищён ещё примерно %s минут(ы).",
@@ -1695,6 +1697,7 @@ tasks.register("generateAnotherWorldResources") {
"message.anotherworld.safe_vault.empty_before_break": "Empty the safe and wait for drilling to end before dismantling it.",
"message.anotherworld.safe_vault.drill_own": "You cannot drill your own safe.",
"message.anotherworld.safe_vault.inactive": "This safe is not active.",
"message.anotherworld.safe_vault.raid_requires_vault": "You need an active safe of your own before raiding another one.",
"message.anotherworld.safe_vault.empty": "This safe is empty.",
"message.anotherworld.safe_vault.owner_offline": "The owner must be online before the safe can be raided.",
"message.anotherworld.safe_vault.protected": "This safe remains protected for about %s minute(s).",
@@ -1953,7 +1956,7 @@ tasks.register("verifyAnotherWorldCatalog") {
"src/main/resources/assets/redstoner/textures/item/weather_gauge.png",
"src/main/resources/data/redstoner/recipe/weather_gauge.json"
]
if (project.version.toString() != "0.0.0-alpha.55"
if (project.version.toString() != "0.0.0-alpha.56"
|| sulfurMigration.target?.modules?.anotherworld != "0.0.0-alpha.44"
|| sulfurMigration.sulfur_caves?.sanctuary?.dripstone_cell_period != 2
|| !javaText.contains('Identifier.parse("sanctuary:sulfur_caves")')
@@ -3270,7 +3273,7 @@ tasks.register("verifyClimateBiomeMixer") {
"pack/migrations/26.2.0-alpha.188-beta-climate-provinces-to-alpha.189.json"))
def mixer = file(
"src/main/java/fr/koka99cab/sanctuary26/anotherworld/mixin/MultiNoiseBiomeSourceParameterListPresetMixin.java").text
if (project.version.toString() != "0.0.0-alpha.55"
if (project.version.toString() != "0.0.0-alpha.56"
|| migration.source?.pack_version != "26.2.0-alpha.188"
|| migration.target?.pack_version != "26.2.0-alpha.189"
|| migration.target?.modules?.anotherworld != "0.0.0-alpha.48"
@@ -113,6 +113,12 @@ public final class VaultData extends SavedData {
&& address.pos() == center.asLong() && address.owner().equals(ownerId));
}
public boolean hasRegisteredVault(UUID owner) {
if (owner == null) return false;
String ownerId = owner.toString();
return vaults.stream().anyMatch(address -> address.owner().equals(ownerId));
}
public void unregisterNear(ServerLevel level, BlockPos member) {
String dimension = dimension(level);
boolean changed = vaults.removeIf(address -> {
@@ -163,7 +169,7 @@ public final class VaultData extends SavedData {
/** Transfers the exact 20% raid share into the attacker's unlimited balances without item spam. */
public long transferRaid(UUID owner, UUID attacker) {
if (owner == null || attacker == null || owner.equals(attacker)) return 0L;
if (!canReceiveRaidLoot(owner, attacker, hasRegisteredVault(attacker))) return 0L;
Account source = accounts.get(owner);
if (source == null) return 0L;
Account target = accounts.computeIfAbsent(attacker, ignored -> new Account());
@@ -180,6 +186,10 @@ public final class VaultData extends SavedData {
return total;
}
static boolean canReceiveRaidLoot(UUID owner, UUID attacker, boolean attackerHasRegisteredVault) {
return owner != null && attacker != null && !owner.equals(attacker) && attackerHasRegisteredVault;
}
public long protectionRemaining(UUID owner, long gameTime) {
Account account = accounts.get(owner);
return account == null ? 0L : Math.max(0L, account.protectedUntil - gameTime);
@@ -48,6 +48,10 @@ public final class VaultDrillingService {
attacker.sendSystemMessage(Component.translatable("message.anotherworld.safe_vault.inactive"));
return false;
}
if (!data.hasRegisteredVault(attacker.getUUID())) {
attacker.sendSystemMessage(Component.translatable("message.anotherworld.safe_vault.raid_requires_vault"));
return false;
}
if (!data.hasContents(formed.owner())) {
attacker.sendSystemMessage(Component.translatable("message.anotherworld.safe_vault.empty"));
return false;
@@ -104,6 +108,13 @@ public final class VaultDrillingService {
attacker.stopUsingItem();
return;
}
if (!VaultData.get(level.getServer()).hasRegisteredVault(attacker.getUUID())) {
cancel(attacker, false);
attacker.sendOverlayMessage(Component.translatable(
"message.anotherworld.safe_vault.raid_requires_vault"));
attacker.stopUsingItem();
return;
}
int elapsed = session.elapsedTicks() + 1;
Session progressed = session.withElapsed(elapsed);
@@ -1,5 +1,7 @@
package fr.koka99cab.sanctuary26.anotherworld.vault;
import java.util.UUID;
public final class VaultRulesSmoke {
private VaultRulesSmoke() {
}
@@ -55,6 +57,14 @@ public final class VaultRulesSmoke {
require(VaultRules.raidLoot(Integer.MAX_VALUE) == 429_496_730L, "32-bit balance raid");
require(VaultRules.raidLoot(Long.MAX_VALUE) == 1_844_674_407_370_955_162L,
"64-bit balance raid without overflow");
UUID owner = UUID.fromString("00000000-0000-0000-0000-000000000001");
UUID attacker = UUID.fromString("00000000-0000-0000-0000-000000000002");
require(!VaultData.canReceiveRaidLoot(owner, attacker, false),
"an attacker without a registered vault cannot receive raid loot");
require(VaultData.canReceiveRaidLoot(owner, attacker, true),
"an attacker with a registered vault can receive raid loot");
require(!VaultData.canReceiveRaidLoot(owner, owner, true),
"a vault owner cannot receive loot from their own vault");
require(VaultRules.saturatingAdd(Long.MAX_VALUE - 2L, 10L) == Long.MAX_VALUE,
"balance saturation without overflow");
for (long balance : new long[] {0L, Integer.MAX_VALUE, (long) Integer.MAX_VALUE + 1L, Long.MAX_VALUE}) {
+390 -222
View File
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -16,19 +16,19 @@ ambiance_version=0.0.0-alpha.22
ambiance_lifecycle=active
redstoner_version=0.0.0-alpha.13
redstoner_lifecycle=active
anotherworld_version=0.0.0-alpha.55
anotherworld_version=0.0.0-alpha.56
anotherworld_lifecycle=active
itsalive_version=0.0.0-alpha.37
itsalive_version=0.0.0-alpha.38
itsalive_lifecycle=active
ouch_version=0.0.0-alpha.11
ouch_lifecycle=active
masterkey_version=0.0.0-alpha.9
masterkey_lifecycle=active
iliketomoveit_version=0.0.0-alpha.17
iliketomoveit_version=0.0.0-alpha.18
iliketomoveit_lifecycle=active
onlyfun_version=0.0.0-alpha.26
onlyfun_version=0.0.0-alpha.27
onlyfun_lifecycle=active
sanctuary_version=0.0.0-alpha.128
sanctuary_version=0.0.0-alpha.129
sanctuary_lifecycle=active
pack_version=26.2.0-alpha.224
pack_version=26.2.0-alpha.225
maven_group=fr.koka99cab.sanctuary26
+5 -5
View File
@@ -321,7 +321,7 @@ tasks.register("verifyPortals") {
}
def atlasCavernsMigration = new JsonSlurper().parse(rootProject.file(
"pack/migrations/26.2.0-alpha.149-atlas-caverns-to-alpha.150.json"))
if (project.version.toString() != "0.0.0-alpha.17"
if (project.version.toString() != "0.0.0-alpha.18"
|| atlasCavernsMigration.target?.pack_version != "26.2.0-alpha.150"
|| atlasCavernsMigration.target?.modules?.iliketomoveit != "0.0.0-alpha.9"
|| atlasCavernsMigration.portals?.active_routes != [
@@ -357,7 +357,7 @@ tasks.register("verifySilverAircraft") {
def manifest = new JsonSlurper().parse(file("src/main/resources/fabric.mod.json"))
def migration = new JsonSlurper().parse(rootProject.file(
"pack/migrations/26.2.0-alpha.184-silver-engineering-to-alpha.185.json"))
if (project.version.toString() != "0.0.0-alpha.17"
if (project.version.toString() != "0.0.0-alpha.18"
|| recipe.pattern != ["PGP", "GCG", "PBP"]
|| recipe.key != [B: "anotherworld:silver_block", C: "anotherworld:aircraft_cockpit",
G: "anotherworld:silver_gear", P: "anotherworld:silver_plate"]
@@ -386,7 +386,7 @@ tasks.register("verifyGrapplePrediction") {
def payload = file("src/main/java/fr/koka99cab/sanctuary26/iliketomoveit/network/GrappleStatePayload.java").text
def controller = file("src/main/java/fr/koka99cab/sanctuary26/iliketomoveit/grapple/GrappleController.java").text
def prediction = file("src/main/java/fr/koka99cab/sanctuary26/iliketomoveit/client/grapple/ClientGrapplePrediction.java").text
if (project.version.toString() != "0.0.0-alpha.17"
if (project.version.toString() != "0.0.0-alpha.18"
|| migration.target?.modules?.iliketomoveit != "0.0.0-alpha.13"
|| migration.grappling?.owner_prediction_payload != "iliketomoveit:s2c/grapple_state"
|| migration.compatibility?.iliketomoveit_source_network_protocol != 4
@@ -418,7 +418,7 @@ tasks.register("verifyWaystoneDescendingRelease") {
"src/main/java/fr/koka99cab/sanctuary26/iliketomoveit/waystone/WaystoneElevatorRules.java").text
def teleportation = file(
"src/main/java/fr/koka99cab/sanctuary26/iliketomoveit/network/WaystoneTeleportation.java").text
if (project.version.toString() != "0.0.0-alpha.17"
if (project.version.toString() != "0.0.0-alpha.18"
|| migration.target?.modules?.iliketomoveit != "0.0.0-alpha.14"
|| migration.waystone_elevator?.undiscovered_downward_destination_allowed != true
|| migration.waystone_elevator?.destination_discovered_after_arrival != true
@@ -452,7 +452,7 @@ tasks.register("verifyAlphaPortal") {
def locales = ["fr_fr", "en_us", "ru_ru"].collect {
new JsonSlurper().parse(file("src/main/resources/assets/iliketomoveit/lang/${it}.json"))
}
if (project.version.toString() != "0.0.0-alpha.17"
if (project.version.toString() != "0.0.0-alpha.18"
|| route.schema_version != 2 || route.source_dimension != "minecraft:overworld"
|| route.destination_dimension != "sanctuary:alpha"
|| route.destination_resolver != "fixed"
@@ -1,6 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "minecraft:block/purple_wool"
"layer0": "iliketomoveit:item/magic_carpet"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

+6 -6
View File
@@ -565,7 +565,7 @@ tasks.register("verifyItsAlive") {
def climateSource = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/ClimateCropForaging.java").text
def pineappleSource = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/PineappleItem.java").text
def worldgenSource = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/ItsAliveAgricultureWorldGeneration.java").text
if (project.version.toString() != "0.0.0-alpha.37"
if (project.version.toString() != "0.0.0-alpha.38"
|| climateMigration.target?.pack_version != "26.2.0-alpha.157"
|| climateMigration.target?.modules?.itsalive != "0.0.0-alpha.24"
|| !climateSource.contains("PlayerBlockBreakEvents.AFTER.register")
@@ -1322,7 +1322,7 @@ tasks.register("verifyCulinaryReliability") {
}
}
}
if (project.version.toString() != "0.0.0-alpha.37"
if (project.version.toString() != "0.0.0-alpha.38"
|| migration.target?.modules?.itsalive != "0.0.0-alpha.28"
|| migration.culinary_fixes?.food_press_contents_destroyed_on_break != true
|| migration.culinary_fixes?.comparator_contract?.blocks?.size() != 6
@@ -1360,7 +1360,7 @@ tasks.register("verifyCauldronBatching") {
def recipe = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/recipe/CulinaryProcessRecipe.java").text
def cauldron = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/entity/CookingCauldronBlockEntity.java").text
def timed = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/entity/AbstractTimedCulinaryBlockEntity.java").text
if (project.version.toString() != "0.0.0-alpha.37"
if (project.version.toString() != "0.0.0-alpha.38"
|| migration.target?.modules?.itsalive != "0.0.0-alpha.29"
|| migration.cauldron_batches?.logical_stack_limit != 64
|| migration.cauldron_batches?.identity != "same_item_same_components"
@@ -1398,7 +1398,7 @@ tasks.register("verifyFermentationBarrelAutomation") {
def partialMatching = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/recipe/CulinaryPartialMatching.java").text
def blockstate = new JsonSlurper().parse(
file("src/main/resources/assets/itsalive/blockstates/fermentation_barrel.json"))
if (project.version.toString() != "0.0.0-alpha.37"
if (project.version.toString() != "0.0.0-alpha.38"
|| migration.source?.modules?.itsalive != "0.0.0-alpha.29"
|| migration.target?.modules?.itsalive != "0.0.0-alpha.30"
|| migration.fermentation_barrel?.maximum_units_per_input_slot != 1
@@ -1446,7 +1446,7 @@ tasks.register("verifyCulinaryLecternRelease") {
[(locale): new JsonSlurper().parse(file(
"src/main/resources/assets/itsalive/lang/${locale}.json"))]
}
if (project.version.toString() != "0.0.0-alpha.37"
if (project.version.toString() != "0.0.0-alpha.38"
|| migration.target?.modules?.itsalive != "0.0.0-alpha.31"
|| migration.culinary_lectern?.book_tag != "minecraft:lectern_books"
|| migration.culinary_lectern?.owner_catalog_shared_while_placed != true
@@ -1509,7 +1509,7 @@ tasks.register("verifyCanapliaForbiddenBook") {
fr_fr: "Livre interdit",
en_us: "Forbidden Book",
ru_ru: "Запретная книга"]
if (project.version.toString() != "0.0.0-alpha.37"
if (project.version.toString() != "0.0.0-alpha.38"
|| !pageItem.contains("CanapliaKnowledge.learnRandomUnknown(serverPlayer)")
|| !knowledge.contains("unknownVariants(data.personal(player.getUUID()))")
|| !knowledge.contains("data.personal(inscription.ownerId())")
@@ -34,7 +34,8 @@ public final class CanapliaHarvestService {
public static void onMatureHarvest(ServerLevel level, ServerPlayer player, BlockPos pos, BlockState state) {
if (!state.is(CanapliaContent.HEMP_CROP) || !CanapliaContent.HEMP_CROP.isMaxAge(state)) return;
CanapliaVariantKey parent = state.getValue(CanapliaHempCropBlock.VARIANT);
CanapliaVariantKey parent = CanapliaVariantLifecycle.harvestedFromPlant(
state.getValue(CanapliaHempCropBlock.VARIANT));
CanapliaKnowledge.discover(player, parent, "harvest");
ClimateCropForaging.CropClimate climate = ClimateCropForaging.climate(level.getBiome(pos));
AmbianceLunarHarvestApi.Conditions lunar = AmbianceLunarHarvestApi.evaluate(level, pos);
@@ -65,7 +66,8 @@ public final class CanapliaHarvestService {
if (pos.equals(center)) continue;
BlockState state = level.getBlockState(pos);
if (state.is(CanapliaContent.HEMP_CROP) && CanapliaContent.HEMP_CROP.isMaxAge(state)) {
variants.add(state.getValue(CanapliaHempCropBlock.VARIANT));
variants.add(CanapliaVariantLifecycle.harvestedFromPlant(
state.getValue(CanapliaHempCropBlock.VARIANT)));
}
}
return Set.copyOf(variants);
@@ -0,0 +1,36 @@
package fr.koka99cab.sanctuary26.itsalive.canaplia;
/** Stable variant hand-off across placement, growth, block-state persistence and harvest. */
public final class CanapliaVariantLifecycle {
private CanapliaVariantLifecycle() {
}
public static CanapliaVariantKey plantedFromSeed(CanapliaVariantKey seedVariant) {
return require(seedVariant);
}
public static CanapliaVariantKey afterGrowth(CanapliaVariantKey currentVariant) {
return require(currentVariant);
}
public static CanapliaVariantKey upperFromLower(CanapliaVariantKey lowerVariant) {
return require(lowerVariant);
}
public static CanapliaVariantKey harvestedFromPlant(CanapliaVariantKey plantVariant) {
return require(plantVariant);
}
public static String persistedKey(CanapliaVariantKey variant) {
return require(variant).key();
}
public static CanapliaVariantKey fromPersistedKey(String key) {
return CanapliaVariantKey.byKey(key).orElse(CanapliaVariantKey.COMMON);
}
private static CanapliaVariantKey require(CanapliaVariantKey variant) {
if (variant == null) throw new IllegalArgumentException("Canaplia variant cannot be null");
return variant;
}
}
@@ -2,6 +2,7 @@ package fr.koka99cab.sanctuary26.itsalive.canaplia.block;
import fr.koka99cab.sanctuary26.itsalive.canaplia.CanapliaContent;
import fr.koka99cab.sanctuary26.itsalive.canaplia.CanapliaHarvestService;
import fr.koka99cab.sanctuary26.itsalive.canaplia.CanapliaVariantLifecycle;
import fr.koka99cab.sanctuary26.itsalive.canaplia.CanapliaVariantKey;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
@@ -43,19 +44,28 @@ public final class CanapliaHempCropBlock extends CropBlock {
@Override
protected ItemStack getCloneItemStack(LevelReader level, BlockPos pos, BlockState state, boolean includeData) {
return new ItemStack(CanapliaContent.family(state.getValue(VARIANT)).seeds());
CanapliaVariantKey variant = CanapliaVariantLifecycle.harvestedFromPlant(state.getValue(VARIANT));
return new ItemStack(CanapliaContent.family(variant).seeds());
}
@Override
protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
super.randomTick(state, level, pos, random);
synchronizeUpper(level, pos, level.getBlockState(pos));
if (level.getRawBrightness(pos, 0) < 9) return;
int age = getAge(state);
if (age >= getMaxAge()) return;
float speed = getGrowthSpeed(this, level, pos);
if (random.nextInt((int) (25.0F / speed) + 1) != 0) return;
BlockState grown = state.setValue(AGE, age + 1).setValue(VARIANT,
CanapliaVariantLifecycle.afterGrowth(state.getValue(VARIANT)));
level.setBlock(pos, grown, Block.UPDATE_CLIENTS);
synchronizeUpper(level, pos, grown);
}
@Override
public void growCrops(Level level, BlockPos pos, BlockState state) {
int age = Math.min(getMaxAge(), getAge(state) + getBonemealAgeIncrease(level));
BlockState grown = state.setValue(AGE, age);
BlockState grown = state.setValue(AGE, age).setValue(VARIANT,
CanapliaVariantLifecycle.afterGrowth(state.getValue(VARIANT)));
level.setBlock(pos, grown, Block.UPDATE_CLIENTS);
synchronizeUpper(level, pos, grown);
}
@@ -78,7 +88,7 @@ public final class CanapliaHempCropBlock extends CropBlock {
if (!(level instanceof ServerLevel serverLevel) || !(player instanceof ServerPlayer serverPlayer)) {
return InteractionResult.FAIL;
}
CanapliaVariantKey variant = state.getValue(VARIANT);
CanapliaVariantKey variant = CanapliaVariantLifecycle.harvestedFromPlant(state.getValue(VARIANT));
Block.popResource(serverLevel, pos,
new ItemStack(CanapliaContent.family(variant).bud(), 1 + serverLevel.getRandom().nextInt(3)));
CanapliaHarvestService.onMatureHarvest(serverLevel, serverPlayer, pos, state);
@@ -97,7 +107,8 @@ public final class CanapliaHempCropBlock extends CropBlock {
if (getAge(state) >= UPPER_START_AGE) {
if (aboveState.isAir() || aboveState.is(CanapliaContent.HEMP_TOP)) {
level.setBlock(above, CanapliaContent.HEMP_TOP.defaultBlockState()
.setValue(AGE, getAge(state)).setValue(VARIANT, state.getValue(VARIANT)), Block.UPDATE_CLIENTS);
.setValue(AGE, getAge(state)).setValue(VARIANT,
CanapliaVariantLifecycle.upperFromLower(state.getValue(VARIANT))), Block.UPDATE_CLIENTS);
}
} else if (aboveState.is(CanapliaContent.HEMP_TOP)) {
level.removeBlock(above, false);
@@ -1,6 +1,7 @@
package fr.koka99cab.sanctuary26.itsalive.canaplia.block;
import fr.koka99cab.sanctuary26.itsalive.canaplia.CanapliaContent;
import fr.koka99cab.sanctuary26.itsalive.canaplia.CanapliaVariantLifecycle;
import fr.koka99cab.sanctuary26.itsalive.canaplia.CanapliaVariantKey;
import net.minecraft.core.BlockPos;
import net.minecraft.world.item.ItemStack;
@@ -26,7 +27,9 @@ public final class CanapliaHempTopBlock extends CropBlock {
@Override
protected ItemStack getCloneItemStack(LevelReader level, BlockPos pos, BlockState state, boolean includeData) {
return new ItemStack(CanapliaContent.family(state.getValue(CanapliaHempCropBlock.VARIANT)).seeds());
CanapliaVariantKey variant = CanapliaVariantLifecycle.harvestedFromPlant(
state.getValue(CanapliaHempCropBlock.VARIANT));
return new ItemStack(CanapliaContent.family(variant).seeds());
}
@Override
@@ -1,5 +1,6 @@
package fr.koka99cab.sanctuary26.itsalive.canaplia.item;
import fr.koka99cab.sanctuary26.itsalive.canaplia.CanapliaVariantLifecycle;
import fr.koka99cab.sanctuary26.itsalive.canaplia.CanapliaVariantKey;
import fr.koka99cab.sanctuary26.itsalive.canaplia.block.CanapliaHempCropBlock;
import fr.koka99cab.sanctuary26.itsalive.canaplia.block.CanapliaResinBlock;
@@ -24,12 +25,17 @@ public final class CanapliaVariantBlockItem extends BlockItem {
@Override
protected BlockState getPlacementState(BlockPlaceContext context) {
BlockState state = super.getPlacementState(context);
if (state == null) return null;
return applyVariant(state, variant);
}
public static BlockState applyVariant(BlockState state, CanapliaVariantKey variant) {
if (state == null || variant == null) return state;
CanapliaVariantKey placed = CanapliaVariantLifecycle.plantedFromSeed(variant);
if (state.hasProperty(CanapliaHempCropBlock.VARIANT)) {
return state.setValue(CanapliaHempCropBlock.VARIANT, variant);
return state.setValue(CanapliaHempCropBlock.VARIANT, placed);
}
if (state.hasProperty(CanapliaResinBlock.VARIANT)) {
return state.setValue(CanapliaResinBlock.VARIANT, variant);
return state.setValue(CanapliaResinBlock.VARIANT, placed);
}
return state;
}
@@ -61,6 +61,22 @@ public final class CanapliaGameplaySmoke {
Set.of(), Temperature.COLD, Humidity.BALANCED, 12, false, false, true, true, false)).isEmpty(),
"lunar harvest requires a sky dimension");
for (CanapliaVariantKey variant : CanapliaVariantKey.values()) {
CanapliaVariantKey placed = CanapliaVariantLifecycle.plantedFromSeed(variant);
assertEquals(variant, placed,
"historical seed placement variant " + variant.key());
CanapliaVariantKey grown = CanapliaVariantLifecycle.afterGrowth(placed);
assertEquals(variant, grown,
"natural growth variant " + variant.key());
CanapliaVariantKey persisted = CanapliaVariantLifecycle.fromPersistedKey(
CanapliaVariantLifecycle.persistedKey(grown));
assertEquals(variant, persisted, "persisted variant " + variant.key());
assertEquals(variant, CanapliaVariantLifecycle.upperFromLower(persisted),
"upper crop variant " + variant.key());
assertEquals(variant, CanapliaVariantLifecycle.harvestedFromPlant(persisted),
"harvest variant " + variant.key());
}
CanapliaEffectRules.Plan minimum = CanapliaEffectRules.plan(0, 1, 0, 0);
assertEquals(CanapliaEffectRules.MIN_PRIMARY_TICKS, minimum.primaryTicks(), "minimum primary duration");
assertTrue(!minimum.badTrip(), "nonzero bad-trip roll");
@@ -2,6 +2,7 @@ package fr.koka99cab.sanctuary26.onlyfun.loot;
import fr.koka99cab.sanctuary26.onlyfun.OnlyFunMod;
import fr.koka99cab.sanctuary26.onlyfun.registry.OnlyFunRegistries;
import fr.koka99cab.sanctuary26.sanctuary.api.entity.SanctuaryEntityPolicy;
import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
@@ -38,7 +39,8 @@ public final class OnlyFunLootboxDrops {
}
private static void afterDeath(LivingEntity entity, DamageSource source) {
if (!(entity instanceof Mob mob) || !(entity.level() instanceof ServerLevel level)) return;
if (SanctuaryEntityPolicy.isTemporaryCompanion(entity)
|| !(entity instanceof Mob mob) || !(entity.level() instanceof ServerLevel level)) return;
ServerPlayer killer = playerKiller(entity, source);
if (killer == null || killer.isSpectator()) return;
+1 -1
View File
@@ -27,7 +27,7 @@
"java": ">=25",
"fabric-api": ">=0.154.2+26.2",
"iliketomoveit": ">=0.0.0-alpha.17",
"sanctuary": ">=0.0.0-alpha.127"
"sanctuary": ">=0.0.0-alpha.129"
},
"custom": {
"sanctuary26": {
@@ -0,0 +1,87 @@
{
"schema_version": 1,
"status": "released",
"source": {
"pack_version": "26.2.0-alpha.224",
"modules": {
"anotherworld": "0.0.0-alpha.55",
"itsalive": "0.0.0-alpha.37",
"iliketomoveit": "0.0.0-alpha.17",
"onlyfun": "0.0.0-alpha.26",
"sanctuary": "0.0.0-alpha.128"
}
},
"target": {
"pack_version": "26.2.0-alpha.225",
"modules": {
"anotherworld": "0.0.0-alpha.56",
"itsalive": "0.0.0-alpha.38",
"iliketomoveit": "0.0.0-alpha.18",
"onlyfun": "0.0.0-alpha.27",
"sanctuary": "0.0.0-alpha.129"
}
},
"fixed_issues": [105, 116, 123, 124],
"safe_vault_raid": {
"owner": "anotherworld",
"attacker_requires_registered_vault": true,
"validated_when_drilling_starts": true,
"revalidated_during_drilling": true,
"revalidated_before_balance_transfer": true
},
"temporary_companions": {
"owner": "sanctuary",
"public_policy_api": "SanctuaryEntityPolicy",
"vanilla_loot_suppressed": true,
"experience_suppressed": true,
"quest_board_progress_suppressed": true,
"community_request_progress_suppressed": true,
"onlyfun_lootboxes_suppressed": true
},
"canaplia_variants": {
"owner": "itsalive",
"historical_seed_ids_preserved": 16,
"placement_preserves_variant": true,
"natural_growth_preserves_variant": true,
"bonemeal_preserves_variant": true,
"upper_crop_preserves_variant": true,
"block_state_persistence_preserves_variant": true,
"harvest_and_break_drops_preserve_variant": true,
"mature_neighbors_preserve_variant": true,
"tender_plus_olive_can_produce_gold": true
},
"magic_carpet_texture": {
"owner": "iliketomoveit",
"canonical_item": "iliketomoveit:magic_carpet",
"legacy_item": "portals:notch_totem",
"texture": "iliketomoveit:item/magic_carpet",
"historical_totem_art_restored": true,
"purple_wool_reference_removed": true,
"platform_behavior_changed": false
},
"compatibility": {
"existing_saves_supported": true,
"new_world_required": false,
"save_reset_required": false,
"registry_ids_changed": false,
"persistent_data_formats_changed": false,
"network_protocols_changed": false,
"client_server_update_required": true,
"anotherworld_data_version": 5,
"anotherworld_network_protocol": 2,
"itsalive_data_version": 4,
"itsalive_network_protocol": 4,
"iliketomoveit_data_version": 4,
"iliketomoveit_network_protocol": 5,
"onlyfun_data_version": 2,
"onlyfun_network_protocol": 3,
"sanctuary_data_version": 13,
"sanctuary_network_protocol": 23
},
"distribution": {
"pack_release_updated": true,
"packwiz_index_refreshed": true,
"prism_modrinth_index_updated": true,
"shared_client_server_mrpack": true
}
}
+15 -15
View File
@@ -14,15 +14,15 @@ hash = "41f4f4c88c71d0a23446d5014f7407d55ad477441847099cad24492b032335a0"
[[files]]
file = "mods/ambiance-0.0.0-alpha.22.jar"
hash = "20f1b31b773c7cea9c5cbe01161c2c7e541eaca714213752a8d6227bae85c9b9"
hash = "d6c44f09ddc455ae0cec02a1568e07036f11a3b2d105659e9aa14289357ddb47"
[[files]]
file = "mods/anotherworld-0.0.0-alpha.55.jar"
hash = "2c01fb018cce49b8ef5ff6de1f2c4d0218243c23581333675c60dfe4ab48dfde"
file = "mods/anotherworld-0.0.0-alpha.56.jar"
hash = "ede131b654d26110af8c7e64238c1b5f99634c3e1307346953ad6ab622ccfedc"
[[files]]
file = "mods/events-0.0.0-alpha.0.jar"
hash = "5bc52d22c93203c6c3881cd9086db5a1166feb6b31a67af8e761c9c0d0b87f37"
hash = "9ccc30733117ab9984df3d69b98ab82d731578a6c35116595c96f2362ea22b76"
[[files]]
file = "mods/fabric-api.pw.toml"
@@ -30,8 +30,8 @@ hash = "df032a6b051d0262cf24945c43e0e6b915e8cc1d62af62fe933932e7f35960c4"
metafile = true
[[files]]
file = "mods/iliketomoveit-0.0.0-alpha.17.jar"
hash = "4aa2f5394f4e12301d13026c7b542660334f7c4e4f8057ea3d36dd04dfe461b6"
file = "mods/iliketomoveit-0.0.0-alpha.18.jar"
hash = "f0ccb5a3f411cb01bcf328a139a1f90e8357587c549778f98354d910d69b2616"
[[files]]
file = "mods/iris.pw.toml"
@@ -39,8 +39,8 @@ hash = "84a3cef87679c0d21dceb4641167dc0368b9a3593698f6a3632485e9b3a8d5da"
metafile = true
[[files]]
file = "mods/itsalive-0.0.0-alpha.37.jar"
hash = "926803c09b2247f42363d1042346bff9798b982aaeafa96800711e259c48039d"
file = "mods/itsalive-0.0.0-alpha.38.jar"
hash = "6dab7b2dca11cc1cd9789ed66cc8403b849cb73227d965d979eeebcf5c201309"
[[files]]
file = "mods/jei.pw.toml"
@@ -49,23 +49,23 @@ metafile = true
[[files]]
file = "mods/masterkey-0.0.0-alpha.9.jar"
hash = "3069c9e784f425f59189c255638648ca51598c8626e340df9bf72a2c06f31753"
hash = "ab0241386a0958505622487318c2e2e269e57e793ef873ed71dac8e0f420338b"
[[files]]
file = "mods/onlyfun-0.0.0-alpha.26.jar"
hash = "5d2915c761e87cd94f4d4075c85ebb71f021b59b92ced5eba6b5da8bb1413519"
file = "mods/onlyfun-0.0.0-alpha.27.jar"
hash = "15dbc6a9c3d4fef699c1f62f989c1d47e366cc781747c76640f5de668ea00510"
[[files]]
file = "mods/ouch-0.0.0-alpha.11.jar"
hash = "0e26e6e386781e2e258ef01eecad0129c00eec11783f182a34226213ea5f252e"
hash = "5bc1e244309a8835ef1d7869225201179cbee12853fb2f2390bfd47f2fcc3c4a"
[[files]]
file = "mods/redstoner-0.0.0-alpha.13.jar"
hash = "2e9519b8f8092883616fdce4e5b4ab427b7875565423ea18a90adb40df751878"
hash = "d913aac62b6e7152c5751ea0ddc820a98a0b74c3864f4c0bdbfc3b44eeaa3417"
[[files]]
file = "mods/sanctuary-0.0.0-alpha.128.jar"
hash = "9ac5e370509e0d0be77222aca227b698d449cfbcdde70ebc58fee2cec05e6a7e"
file = "mods/sanctuary-0.0.0-alpha.129.jar"
hash = "0b67c37680e5004b7f08ac9de32b501fc8f2025fe5a7649858948f14a5d8f5f5"
[[files]]
file = "mods/simple-voice-chat.pw.toml"
+2 -2
View File
@@ -1,12 +1,12 @@
name = "Sanctuary 26.2"
author = "KOKA99CAB"
version = "26.2.0-alpha.224"
version = "26.2.0-alpha.225"
pack-format = "packwiz:1.1.0"
[index]
file = "index.toml"
hash-format = "sha256"
hash = "0073565d9342eae7a811696a273094f157b9a6ec113c8282c3e92714c8fe921a"
hash = "19d66dcd7dbbc8e1a9471dcb259a9c9aad727acbc8428f114a48c05c2fd1d8de"
[versions]
fabric = "0.19.3"
+1 -1
View File
@@ -3,7 +3,7 @@
"game": "minecraft",
"name": "Sanctuary 26.2",
"summary": "Sanctuary 26.2 — progression, exploration, worldgen and multiplayer systems by KOKA99CAB.",
"versionId": "26.2.0-alpha.224",
"versionId": "26.2.0-alpha.225",
"files": [
{
"path": "mods/fabric-api-0.158.0+26.2.jar",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"schema_version": 1,
"pack_version": "26.2.0-alpha.224",
"pack_version": "26.2.0-alpha.225",
"release_channel": "alpha",
"minecraft_version": "26.2",
"fabric_loader_version": "0.19.3",
@@ -9,7 +9,7 @@
"project_id": "FgOqyAXd",
"project_slug": "sanctuary-koka99",
"project_url": "https://modrinth.com/modpack/sanctuary-koka99",
"primary_file": "sanctuary-26.2.0-alpha.224.mrpack",
"primary_file": "sanctuary-26.2.0-alpha.225.mrpack",
"shared_client_server_pack": true,
"update_owner": "packwiz-installer",
"check_on_launch": true,
+20 -11
View File
@@ -124,6 +124,14 @@ tasks.register("inventorySortSmoke", JavaExec) {
mainClass = "fr.koka99cab.sanctuary26.sanctuary.gameplay.SanctuaryInventorySortSmoke"
}
tasks.register("entityPolicySmoke", JavaExec) {
group = "verification"
description = "Checks that only tagged temporary companions are excluded from loot and quests."
dependsOn tasks.named("testClasses")
classpath = sourceSets.test.runtimeClasspath
mainClass = "fr.koka99cab.sanctuary26.sanctuary.api.entity.SanctuaryEntityPolicySmoke"
}
tasks.register("alphaIndevGeneratorSmoke", JavaExec) {
group = "verification"
description = "Checks deterministic finite Indev islands, residences and safe portal arrivals."
@@ -175,7 +183,7 @@ tasks.register("verifyBlackMarket") {
def questData = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestBoardData.java").text
def quests = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestBoardService.java").text
def inventoryView = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/inventory/SanctuaryPlayerInventoryView.java").text
if (project.version.toString() != "0.0.0-alpha.128"
if (project.version.toString() != "0.0.0-alpha.129"
|| !data.contains('SAVE_KEY = "black_market"') || !data.contains("DATA_VERSION = 2")
|| !data.contains("ItemStack.CODEC.fieldOf(\"item\")") || !data.contains("replaceListing")
|| !service.contains("LISTING_FEE_RUBIES = 1") || !service.contains("COMMISSION_PERCENT = 5")
@@ -2891,7 +2899,7 @@ tasks.register("verifyShopCurrencyRowsRelease") {
"src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketService.java").text
def shop = file(
"src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopService.java").text
if (project.version.toString() != "0.0.0-alpha.128"
if (project.version.toString() != "0.0.0-alpha.129"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.117"
|| migration.shop_currency_inventory?.all_unlocked_rows_counted != true
|| migration.shop_currency_inventory?.overflow_rows_counted != true
@@ -3057,7 +3065,7 @@ tasks.register("verifyProgressivePauseMenu") {
"pack/migrations/26.2.0-alpha.190-progressive-pause-menu-to-alpha.191.json"))
def pauseMenu = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/PauseScreenMixin.java").text
def costs = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/progression/SanctuaryProgressionCosts.java").text
if (project.version.toString() != "0.0.0-alpha.128"
if (project.version.toString() != "0.0.0-alpha.129"
|| migration.target?.pack_version != "26.2.0-alpha.191"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.109"
|| migration.pause_menu?.progression_full_width != true
@@ -3095,7 +3103,7 @@ tasks.register("verifyShopPricingLayout") {
"pack/migrations/26.2.0-alpha.192-shop-pricing-layout-to-alpha.193.json"))
def screen = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryShopScreen.java").text
def progression = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopProgression.java").text
if (project.version.toString() != "0.0.0-alpha.128"
if (project.version.toString() != "0.0.0-alpha.129"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.110"
|| migration.shop_layout?.locked_offer_buttons != 6
|| migration.shop_layout?.button_slots != [4, 5, 6, 7, 8, 9]
@@ -3126,7 +3134,7 @@ tasks.register("verifyShopCsvBossGates") {
"pack/migrations/26.2.0-alpha.194-shop-csv-boss-gates-to-alpha.195.json"))
def provider = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopCatalogProvider.java").text
def catalog = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopOfferCatalog.java").text
if (project.version.toString() != "0.0.0-alpha.128"
if (project.version.toString() != "0.0.0-alpha.129"
|| migration.target?.pack_version != "26.2.0-alpha.195"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.111"
|| migration.compatibility?.sanctuary_data_version != 11
@@ -3157,7 +3165,7 @@ tasks.register("verifyDeliveryBoxOrientation") {
def blockstate = new JsonSlurper().parse(
file("src/main/resources/assets/sanctuary/blockstates/delivery_box.json"))
def expectedVariants = ["facing=north", "facing=east", "facing=south", "facing=west"].toSet()
if (project.version.toString() != "0.0.0-alpha.128"
if (project.version.toString() != "0.0.0-alpha.129"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.112"
|| migration.mailbox?.id != "sanctuary:delivery_box"
|| migration.mailbox?.horizontal_facing != true
@@ -3185,7 +3193,7 @@ tasks.register("verifyInventoryShiftClick") {
def mixin = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/AbstractContainerMenuMixin.java").text
def screen = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/AbstractContainerScreenMixin.java").text
def gui = file("INVENTORY_GUI.md").text
if (project.version.toString() != "0.0.0-alpha.128"
if (project.version.toString() != "0.0.0-alpha.129"
|| !transfer.contains("shouldAppendOverflow")
|| !transfer.contains("isVanillaStorageRange")
|| !transfer.contains("hotbarStart")
@@ -3231,7 +3239,7 @@ tasks.register("verifyQuestRequests") {
new JsonSlurper().parse(file("src/main/resources/data/sanctuary/loot_table/blocks/${path}.json"))
.pools[0].entries[0].name
}
if (project.version.toString() != "0.0.0-alpha.128"
if (project.version.toString() != "0.0.0-alpha.129"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.113"
|| migration.quest_requests?.save_key != "quest_requests"
|| migration.quest_requests?.data_version != 1
@@ -3289,7 +3297,7 @@ tasks.register("verifyQuestRequestContracts") {
def locales = ["fr_fr", "en_us", "ru_ru"].collectEntries { locale ->
[(locale): new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${locale}.json"))]
}
if (project.version.toString() != "0.0.0-alpha.128"
if (project.version.toString() != "0.0.0-alpha.129"
|| migration.source?.pack_version != "26.2.0-alpha.198"
|| migration.source?.modules?.sanctuary != "0.0.0-alpha.114"
|| migration.target?.pack_version != "26.2.0-alpha.199"
@@ -3358,7 +3366,7 @@ tasks.register("verifyQuestRequestEscrow") {
def locales = ["fr_fr", "en_us", "ru_ru"].collectEntries { locale ->
[(locale): new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${locale}.json"))]
}
if (project.version.toString() != "0.0.0-alpha.128"
if (project.version.toString() != "0.0.0-alpha.129"
|| migration.source?.pack_version != "26.2.0-alpha.200"
|| migration.source?.modules?.sanctuary != "0.0.0-alpha.115"
|| migration.target?.pack_version != "26.2.0-alpha.201"
@@ -3430,7 +3438,7 @@ tasks.register("verifyAlphaIndev") {
def locales = ["fr_fr", "en_us", "ru_ru"].collect {
new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${it}.json"))
}
if (project.version.toString() != "0.0.0-alpha.128"
if (project.version.toString() != "0.0.0-alpha.129"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.122"
|| migration.alpha_world?.dimension != "sanctuary:alpha"
|| migration.alpha_world?.legacy_dimension != "portals:alpha"
@@ -3468,5 +3476,6 @@ tasks.named("check") {
dependsOn tasks.named("alphaIndevGeneratorSmoke")
dependsOn tasks.named("capeJoinSyncSmoke")
dependsOn tasks.named("inventorySortSmoke")
dependsOn tasks.named("entityPolicySmoke")
dependsOn tasks.named("verifyAlphaIndev")
}
@@ -0,0 +1,21 @@
package fr.koka99cab.sanctuary26.sanctuary.api.entity;
import java.util.Set;
import net.minecraft.world.entity.Entity;
/** Public cross-module policy for Sanctuary-owned temporary gameplay entities. */
public final class SanctuaryEntityPolicy {
public static final String TEMPORARY_COMPANION_TAG = "sanctuary_companion";
private SanctuaryEntityPolicy() {
}
/** Temporary companions must never yield loot, experience or quest progress. */
public static boolean isTemporaryCompanion(Entity entity) {
return entity != null && containsTemporaryCompanionTag(entity.entityTags());
}
static boolean containsTemporaryCompanionTag(Set<String> tags) {
return tags != null && tags.contains(TEMPORARY_COMPANION_TAG);
}
}
@@ -1,6 +1,7 @@
package fr.koka99cab.sanctuary26.sanctuary.gameplay;
import fr.koka99cab.sanctuary26.sanctuary.SanctuaryMod;
import fr.koka99cab.sanctuary26.sanctuary.api.entity.SanctuaryEntityPolicy;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@@ -24,7 +25,7 @@ import net.minecraft.world.item.SpawnEggItem;
/** Server-owned lifecycle for the one companion equipped by each player. */
public final class SanctuaryCompanions {
public static final String ENTITY_TAG = "sanctuary_companion";
public static final String ENTITY_TAG = SanctuaryEntityPolicy.TEMPORARY_COMPANION_TAG;
private static final String OWNER_TAG_PREFIX = "sanctuary_companion_owner_";
private static final Identifier MINI_SCALE = SanctuaryMod.id("companion/mini_scale");
private static final double TELEPORT_DISTANCE_SQUARED = 144.0D;
@@ -1,6 +1,6 @@
package fr.koka99cab.sanctuary26.sanctuary.mixin;
import fr.koka99cab.sanctuary26.sanctuary.gameplay.SanctuaryCompanions;
import fr.koka99cab.sanctuary26.sanctuary.api.entity.SanctuaryEntityPolicy;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@@ -12,7 +12,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
abstract class EntityCompanionMixin {
@Inject(method = "shouldBeSaved", at = @At("HEAD"), cancellable = true)
private void sanctuary$doNotSaveTemporaryCompanion(CallbackInfoReturnable<Boolean> cir) {
if (((Entity) (Object) this).entityTags().contains(SanctuaryCompanions.ENTITY_TAG)) {
if (SanctuaryEntityPolicy.isTemporaryCompanion((Entity) (Object) this)) {
cir.setReturnValue(false);
}
}
@@ -0,0 +1,20 @@
package fr.koka99cab.sanctuary26.sanctuary.mixin;
import fr.koka99cab.sanctuary26.sanctuary.api.entity.SanctuaryEntityPolicy;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.LivingEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/** Temporary companions can be defeated, but never become a renewable loot or XP source. */
@Mixin(LivingEntity.class)
abstract class LivingEntityCompanionMixin {
@Inject(method = "dropAllDeathLoot", at = @At("HEAD"), cancellable = true)
private void sanctuary$suppressTemporaryCompanionLoot(ServerLevel level, DamageSource source,
CallbackInfo ci) {
if (SanctuaryEntityPolicy.isTemporaryCompanion((LivingEntity) (Object) this)) ci.cancel();
}
}
@@ -1,5 +1,6 @@
package fr.koka99cab.sanctuary26.sanctuary.shop;
import fr.koka99cab.sanctuary26.sanctuary.api.entity.SanctuaryEntityPolicy;
import fr.koka99cab.sanctuary26.sanctuary.api.inventory.SanctuaryPlayerInventoryView;
import fr.koka99cab.sanctuary26.sanctuary.registry.SanctuaryRegistries;
import java.time.Duration;
@@ -199,7 +200,8 @@ public final class QuestBoardService {
}
private static void afterDeath(LivingEntity entity, DamageSource source) {
if (!(entity.level() instanceof ServerLevel level)) return;
if (SanctuaryEntityPolicy.isTemporaryCompanion(entity)
|| !(entity.level() instanceof ServerLevel level)) return;
ServerPlayer player = killer(entity, source);
if (player == null || player.isSpectator()) return;
Identifier killed = BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType());
@@ -1,5 +1,6 @@
package fr.koka99cab.sanctuary26.sanctuary.shop;
import fr.koka99cab.sanctuary26.sanctuary.api.entity.SanctuaryEntityPolicy;
import fr.koka99cab.sanctuary26.sanctuary.api.inventory.SanctuaryPlayerInventoryView;
import fr.koka99cab.sanctuary26.sanctuary.network.QuestRequestAcceptPayload;
import fr.koka99cab.sanctuary26.sanctuary.network.QuestRequestConfirmPayload;
@@ -323,7 +324,8 @@ public final class QuestRequestService {
}
private static void afterDeath(LivingEntity entity, DamageSource source) {
if (!(entity.level() instanceof ServerLevel level)) return;
if (SanctuaryEntityPolicy.isTemporaryCompanion(entity)
|| !(entity.level() instanceof ServerLevel level)) return;
ServerPlayer killer = killer(entity, source);
if (killer == null || killer.isSpectator()) return;
Identifier killedType = BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType());
@@ -11,6 +11,7 @@
"InventoryMixin",
"InventoryMenuMixin",
"ItemTooltipMixin",
"LivingEntityCompanionMixin",
"PlayerOverflowMixin",
"SlotMixin",
"ServerGamePacketListenerMixin"
@@ -0,0 +1,22 @@
package fr.koka99cab.sanctuary26.sanctuary.api.entity;
import java.util.Set;
public final class SanctuaryEntityPolicySmoke {
private SanctuaryEntityPolicySmoke() {
}
public static void main(String[] arguments) {
require(SanctuaryEntityPolicy.containsTemporaryCompanionTag(
Set.of(SanctuaryEntityPolicy.TEMPORARY_COMPANION_TAG)),
"temporary companion tag was not recognized");
require(!SanctuaryEntityPolicy.containsTemporaryCompanionTag(Set.of("minecraft:mob")),
"ordinary mob was classified as a temporary companion");
require(!SanctuaryEntityPolicy.containsTemporaryCompanionTag(Set.of()),
"untagged mob was classified as a temporary companion");
}
private static void require(boolean value, String message) {
if (!value) throw new AssertionError(message);
}
}