Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27b29c1340 | ||
|
|
f217bb3661 | ||
|
|
5100091b17 | ||
|
|
398d94e7d1 | ||
|
|
0743ff33a6 | ||
|
|
bfda90f4f4 | ||
|
|
a698140963 | ||
|
|
88ec91e9f4 | ||
|
|
e1d48e9017 | ||
|
|
cce58c27c8 |
+14
-1
@@ -8,7 +8,17 @@ Ce journal suit les **versions du modpack**. Lorsqu'un seul module change, sa ve
|
||||
|
||||
## Non publié
|
||||
|
||||
Modules modifiés : **Sanctuary `0.0.0-alpha.135`** et **Red-Stoner `0.0.0-alpha.17`**.
|
||||
Modules modifiés : **Sanctuary `0.0.0-alpha.137`** et **Red-Stoner `0.0.0-alpha.18`**.
|
||||
|
||||
- transforme le panneau de trophées en plaque verticale de chêne, corrige le texte auparavant masqué et utilise les quatre lignes pour le titre paginé ainsi que les trois places du podium ;
|
||||
- permet de passer à la page suivante d’un clic droit et de revenir à la précédente avec `Maj` + clic droit, sans changer les IDs, les sauvegardes Red-Stoner v4 ni le protocole réseau 7.
|
||||
- garantit sur chaque niveau de la Backroom un coffre par cellule de 3 × 3 chunks dans les nouveaux chunks, au lieu d’un coffre aléatoire réparti entre les cinq niveaux ;
|
||||
- resserre aussi la grille des lits de sortie de 10 × 10 à 3 × 3 chunks sur chacun des cinq niveaux afin qu’ils soient réellement trouvables ;
|
||||
- ne change ni le butin ordinaire des coffres, ni les données Sanctuary v15, ni le protocole réseau 24 ; les nouveaux coffres et lits apparaissent dans les chunks qui n’ont pas encore été générés.
|
||||
|
||||
## `26.2.0-alpha.230`
|
||||
|
||||
Modules modifiés : **Sanctuary `0.0.0-alpha.136`** et **Red-Stoner `0.0.0-alpha.17`**.
|
||||
|
||||
- attribue les demandes de quêtes personnalisées terminées à leur preneur directement sur le panneau et dans les détails au clic, avec son pseudonyme et sa couleur conservés ;
|
||||
- répartit les lits de sortie rares sur chacun des cinq niveaux de la Backroom, avec exactement un lit par cellule de 10 × 10 chunks et par niveau dans les nouveaux chunks ;
|
||||
@@ -16,6 +26,9 @@ Modules modifiés : **Sanctuary `0.0.0-alpha.135`** et **Red-Stoner `0.0.0-alpha
|
||||
- ajoute l’API publique Sanctuary de métriques et de classements persistants par UUID, avec import monotone des statistiques vanilla, podium borné et exclusion des actions créatives, spectateur ou simulées ;
|
||||
- ajoute `redstoner:trophy_panel`, panneau mural configurable par son propriétaire avec un item non consommé, pages publiques, rendu en monde et rafraîchissement serveur limité à une fois par seconde ;
|
||||
- passe Sanctuary aux données v15 et Red-Stoner aux données v4 sans nouveau payload ni changement de leurs protocoles réseau 24 et 7.
|
||||
- corrige la cape blanche décorée en projetant sa couleur et ses motifs avec les sprites de bouclier, aux proportions exactes de la cape ;
|
||||
- permet d’équiper une poule adulte avec l’armure de loup vanilla en écailles de tatou, de la réparer avec des écailles et de la retirer aux cisailles ; l’armure absorbe les dégâts et conserve son usure, ses teintures et ses enchantements ;
|
||||
- conserve les sauvegardes Sanctuary v15 et le protocole réseau 24 : l’armure utilise exclusivement le slot d’équipement vanilla de la poule.
|
||||
|
||||
## `26.2.0-alpha.229`
|
||||
|
||||
|
||||
@@ -22,11 +22,11 @@ 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.229`**. Cette release distribue Red-Stoner **`0.0.0-alpha.16`** ; les neuf autres modules conservent leur version issue de l'alpha.228.
|
||||
Version courante du pack distribué : **`26.2.0-alpha.230`**. Cette release distribue Sanctuary **`0.0.0-alpha.136`** et Red-Stoner **`0.0.0-alpha.17`** ; les huit autres modules conservent leur version issue de l'alpha.229.
|
||||
|
||||
Le prochain module Sanctuary **`0.0.0-alpha.134`** attribue les quêtes communautaires terminées à leur preneur et répartit les lits de sortie rares sur chacun des cinq niveaux de la Backroom. Chaque niveau garantit désormais un lit par cellule de 10 × 10 chunks dans les nouveaux chunks, sans modifier les sauvegardes, les données v14 ni le protocole réseau 24.
|
||||
Le prochain Red-Stoner **`0.0.0-alpha.18`** présente le panneau de trophées comme une plaque verticale de chêne et rend enfin ses quatre lignes lisibles : titre et numéro de page, puis les trois places du podium. Un clic droit avance d’une page et `Maj` + clic droit revient à la précédente ; les données v4 et le protocole réseau 7 restent inchangés.
|
||||
|
||||
Sanctuary **`0.0.0-alpha.135`** et Red-Stoner **`0.0.0-alpha.17`** ajoutent les classements publics et le panneau mural `redstoner:trophy_panel`. Les statistiques vanilla des anciens joueurs sont importées une fois, les scores restent dans Sanctuary et le panneau ne reçoit que sa page visible ainsi qu’un podium de trois noms maximum. Les données passent respectivement à v15 et v4 sans changer les protocoles réseau 24 et 7.
|
||||
L’alpha.230 attribue les quêtes communautaires terminées à leur preneur et répartit les lits de sortie rares sur chacun des cinq niveaux de la Backroom. Elle ajoute aussi les classements publics et le panneau mural `redstoner:trophy_panel`, corrige la projection des couleurs et motifs de la cape blanche avec les sprites de bouclier, et permet d’équiper une poule adulte avec l’armure de loup vanilla en écailles de tatou. Les sauvegardes restent compatibles ; Sanctuary passe aux données v15 et Red-Stoner aux données v4 sans changer leurs protocoles réseau 24 et 7.
|
||||
|
||||
L’alpha.229 ajoute au terminal les tris alphabétique, par quantité croissante et par quantité décroissante. Le tri ne change que sur demande explicite et les rafraîchissements périodiques conservent l’ordre ainsi que les objets sous le curseur. Les sauvegardes, les données Red-Stoner v3 et le protocole réseau 7 restent compatibles.
|
||||
|
||||
|
||||
+328
-224
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -14,7 +14,7 @@ mod_version=0.0.0-alpha.0
|
||||
module_lifecycle=standby
|
||||
ambiance_version=0.0.0-alpha.22
|
||||
ambiance_lifecycle=active
|
||||
redstoner_version=0.0.0-alpha.17
|
||||
redstoner_version=0.0.0-alpha.18
|
||||
redstoner_lifecycle=active
|
||||
anotherworld_version=0.0.0-alpha.57
|
||||
anotherworld_lifecycle=active
|
||||
@@ -28,7 +28,7 @@ iliketomoveit_version=0.0.0-alpha.18
|
||||
iliketomoveit_lifecycle=active
|
||||
onlyfun_version=0.0.0-alpha.27
|
||||
onlyfun_lifecycle=active
|
||||
sanctuary_version=0.0.0-alpha.135
|
||||
sanctuary_version=0.0.0-alpha.137
|
||||
sanctuary_lifecycle=active
|
||||
pack_version=26.2.0-alpha.229
|
||||
pack_version=26.2.0-alpha.230
|
||||
maven_group=fr.koka99cab.sanctuary26
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"status": "released",
|
||||
"source": {
|
||||
"pack_version": "26.2.0-alpha.229",
|
||||
"modules": {
|
||||
"sanctuary": "0.0.0-alpha.132",
|
||||
"redstoner": "0.0.0-alpha.16"
|
||||
}
|
||||
},
|
||||
"target": {
|
||||
"pack_version": "26.2.0-alpha.230",
|
||||
"modules": {
|
||||
"sanctuary": "0.0.0-alpha.136",
|
||||
"redstoner": "0.0.0-alpha.17"
|
||||
}
|
||||
},
|
||||
"sanctuary_polish": {
|
||||
"quest_completion_attribution": true,
|
||||
"backroom_exit_beds_on_every_level": true,
|
||||
"persistent_public_rankings": true,
|
||||
"redstoner_trophy_panel": true,
|
||||
"white_cape_shield_pattern_projection": true,
|
||||
"adult_chicken_wolf_armor": true
|
||||
},
|
||||
"compatibility": {
|
||||
"existing_saves_supported": true,
|
||||
"new_world_required": false,
|
||||
"save_reset_required": false,
|
||||
"persistent_data_formats_changed": true,
|
||||
"network_protocols_changed": false,
|
||||
"sanctuary_data_version": 15,
|
||||
"sanctuary_network_protocol": 24,
|
||||
"redstoner_data_version": 4,
|
||||
"redstoner_network_protocol": 7,
|
||||
"client_server_update_required": true
|
||||
},
|
||||
"distribution": {
|
||||
"pack_release_updated": true,
|
||||
"packwiz_index_refreshed": true,
|
||||
"prism_modrinth_index_updated": true,
|
||||
"shared_client_server_mrpack": true
|
||||
}
|
||||
}
|
||||
+12
-12
@@ -14,15 +14,15 @@ hash = "41f4f4c88c71d0a23446d5014f7407d55ad477441847099cad24492b032335a0"
|
||||
|
||||
[[files]]
|
||||
file = "mods/ambiance-0.0.0-alpha.22.jar"
|
||||
hash = "b4ba14b9ada5b4c1d97b039ee632a158ae266eba2faefee690e710cc5f0f993e"
|
||||
hash = "cdc60ed8f15ca910f41954d9acb6a74ac103e85c73f665fd1bc67be7eab1f15a"
|
||||
|
||||
[[files]]
|
||||
file = "mods/anotherworld-0.0.0-alpha.57.jar"
|
||||
hash = "4c8a3315ba9db6799fb0f046b45d702d56fba65173abccbd81ab995f56690407"
|
||||
hash = "634a122b406bd7206fd19947687fd7cb37e4cc4098822eecf3f0b7d1fea79d90"
|
||||
|
||||
[[files]]
|
||||
file = "mods/events-0.0.0-alpha.0.jar"
|
||||
hash = "e7304da48379eb39737b126431316644250ecbc5b6ec8a439cc12e96a9477fa4"
|
||||
hash = "09bd8dfccca004de46ad3237728ef532de50d04c31b88f1e33721204d3c70cd8"
|
||||
|
||||
[[files]]
|
||||
file = "mods/fabric-api.pw.toml"
|
||||
@@ -31,7 +31,7 @@ metafile = true
|
||||
|
||||
[[files]]
|
||||
file = "mods/iliketomoveit-0.0.0-alpha.18.jar"
|
||||
hash = "bd0467d186737b59996a8b1589a7c1837c990890f1b21154aaca614fb042c556"
|
||||
hash = "cd026c4735e945ec6cc19261165033dc10bcbbba75af412f4145e54437a61854"
|
||||
|
||||
[[files]]
|
||||
file = "mods/iris.pw.toml"
|
||||
@@ -40,7 +40,7 @@ metafile = true
|
||||
|
||||
[[files]]
|
||||
file = "mods/itsalive-0.0.0-alpha.38.jar"
|
||||
hash = "dd1869166cfeeb36b19b7e243e7ece32c925d362aa80782076d493decc6e2871"
|
||||
hash = "d905a05edbcb841b173ef5b0090fe7a117ec06e119fba7e184338fec7671fde6"
|
||||
|
||||
[[files]]
|
||||
file = "mods/jei.pw.toml"
|
||||
@@ -49,23 +49,23 @@ metafile = true
|
||||
|
||||
[[files]]
|
||||
file = "mods/masterkey-0.0.0-alpha.9.jar"
|
||||
hash = "46ef9947e82368fbe60d696174b3a3c99960be29432f3ccd7daf2714927b5c12"
|
||||
hash = "cef973db96c7523fe828a2ca57ce1283ed1da3b1ae3c646a88ea601ecfe4e825"
|
||||
|
||||
[[files]]
|
||||
file = "mods/onlyfun-0.0.0-alpha.27.jar"
|
||||
hash = "d01271cbccef9c3c5f66e3d56748cd69d953f9eb53d21c8a1575523d40e77c56"
|
||||
hash = "1ff5b8853c5a813bbb6c6cf5787c8311d8e72760ed0e9c2adb1da897241e4e1e"
|
||||
|
||||
[[files]]
|
||||
file = "mods/ouch-0.0.0-alpha.11.jar"
|
||||
hash = "ca612f8e660744af81d5a2612f78f8ad61dacf746a22474837e8b52a22bb3204"
|
||||
hash = "fd609cfe5f96ae1d2b41b2da7faf077047514772b56bbe130251db3bf72c2ac8"
|
||||
|
||||
[[files]]
|
||||
file = "mods/redstoner-0.0.0-alpha.16.jar"
|
||||
hash = "e273042c0f7b12ed0a13da3f75f41903a741a29399da353ecd5144e67dcb680e"
|
||||
file = "mods/redstoner-0.0.0-alpha.17.jar"
|
||||
hash = "d7bef2bdf533d2c83f7b05549c01a77028dd5edc60418c7244447ca61160f216"
|
||||
|
||||
[[files]]
|
||||
file = "mods/sanctuary-0.0.0-alpha.132.jar"
|
||||
hash = "72a946358be6039c706cf52563ffe61ec70477ffb4e7db80e71116758f5bd519"
|
||||
file = "mods/sanctuary-0.0.0-alpha.136.jar"
|
||||
hash = "973c12a3f26eb516bc1a053ebd18dab3f845645807448ff5638a1e9b26ba28bd"
|
||||
|
||||
[[files]]
|
||||
file = "mods/simple-voice-chat.pw.toml"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
name = "Sanctuary 26.2"
|
||||
author = "KOKA99CAB"
|
||||
version = "26.2.0-alpha.229"
|
||||
version = "26.2.0-alpha.230"
|
||||
pack-format = "packwiz:1.1.0"
|
||||
|
||||
[index]
|
||||
file = "index.toml"
|
||||
hash-format = "sha256"
|
||||
hash = "6a5b1811c2dd2b905a67cd817b8e377a8b68589431650775e1d8aabe04ff32e9"
|
||||
hash = "a78df91699f1ffa661e1bf59101891a217bd74334ee338b96824f5a9b745e443"
|
||||
|
||||
[versions]
|
||||
fabric = "0.19.3"
|
||||
|
||||
@@ -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.229",
|
||||
"versionId": "26.2.0-alpha.230",
|
||||
"files": [
|
||||
{
|
||||
"path": "mods/fabric-api-0.159.0+26.2.jar",
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"pack_version": "26.2.0-alpha.229",
|
||||
"pack_version": "26.2.0-alpha.230",
|
||||
"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.229.mrpack",
|
||||
"primary_file": "sanctuary-26.2.0-alpha.230.mrpack",
|
||||
"shared_client_server_pack": true,
|
||||
"update_owner": "packwiz-installer",
|
||||
"check_on_launch": true,
|
||||
|
||||
+14
-35
@@ -3,7 +3,6 @@ package fr.koka99cab.sanctuary26.redstoner.block;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import fr.koka99cab.sanctuary26.redstoner.block.entity.TrophyPanelBlockEntity;
|
||||
import fr.koka99cab.sanctuary26.redstoner.registry.RedStonerRegistries;
|
||||
import fr.koka99cab.sanctuary26.sanctuary.api.ranking.RankingUnit;
|
||||
import java.util.Map;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
@@ -45,10 +44,10 @@ public final class TrophyPanelBlock extends BaseEntityBlock {
|
||||
public static final MapCodec<TrophyPanelBlock> CODEC = simpleCodec(TrophyPanelBlock::new);
|
||||
public static final EnumProperty<Direction> FACING = BlockStateProperties.HORIZONTAL_FACING;
|
||||
private static final Map<Direction, VoxelShape> SHAPES = Map.of(
|
||||
Direction.NORTH, Block.box(0.0D, 0.0D, 13.0D, 16.0D, 16.0D, 16.0D),
|
||||
Direction.SOUTH, Block.box(0.0D, 0.0D, 0.0D, 16.0D, 16.0D, 3.0D),
|
||||
Direction.WEST, Block.box(13.0D, 0.0D, 0.0D, 16.0D, 16.0D, 16.0D),
|
||||
Direction.EAST, Block.box(0.0D, 0.0D, 0.0D, 3.0D, 16.0D, 16.0D)
|
||||
Direction.NORTH, Block.box(0.0D, 0.0D, 14.0D, 16.0D, 16.0D, 16.0D),
|
||||
Direction.SOUTH, Block.box(0.0D, 0.0D, 0.0D, 16.0D, 16.0D, 2.0D),
|
||||
Direction.WEST, Block.box(14.0D, 0.0D, 0.0D, 16.0D, 16.0D, 16.0D),
|
||||
Direction.EAST, Block.box(0.0D, 0.0D, 0.0D, 2.0D, 16.0D, 16.0D)
|
||||
);
|
||||
|
||||
public TrophyPanelBlock(BlockBehaviour.Properties properties) {
|
||||
@@ -120,18 +119,20 @@ public final class TrophyPanelBlock extends BaseEntityBlock {
|
||||
if (level.isClientSide()) return InteractionResult.SUCCESS;
|
||||
if (!(player instanceof ServerPlayer serverPlayer)
|
||||
|| !(level.getBlockEntity(pos) instanceof TrophyPanelBlockEntity panel)) return InteractionResult.CONSUME;
|
||||
if (player.isSecondaryUseActive()) return cycle(panel, serverPlayer);
|
||||
if (player.isSecondaryUseActive()) return cycle(panel, serverPlayer, true);
|
||||
|
||||
boolean operator = serverPlayer.createCommandSourceStack().permissions()
|
||||
.hasPermission(Permissions.COMMANDS_GAMEMASTER);
|
||||
if (!panel.canConfigure(serverPlayer, operator)) {
|
||||
serverPlayer.sendSystemMessage(Component.translatable("message.redstoner.trophy_panel.not_owner"));
|
||||
return InteractionResult.CONSUME;
|
||||
return cycle(panel, serverPlayer, false);
|
||||
}
|
||||
TrophyPanelBlockEntity.ConfigureResult result = panel.configure(serverPlayer, stack);
|
||||
if (result == TrophyPanelBlockEntity.ConfigureResult.NO_METRIC) {
|
||||
return cycle(panel, serverPlayer, false);
|
||||
}
|
||||
String key = switch (result) {
|
||||
case SUCCESS -> "configured";
|
||||
case NO_METRIC -> "no_metric";
|
||||
case NO_METRIC -> throw new IllegalStateException("Handled above");
|
||||
case NEWER_DATA -> "newer_data";
|
||||
};
|
||||
serverPlayer.sendSystemMessage(Component.translatable("message.redstoner.trophy_panel." + key));
|
||||
@@ -144,9 +145,7 @@ public final class TrophyPanelBlock extends BaseEntityBlock {
|
||||
if (level.isClientSide()) return InteractionResult.SUCCESS;
|
||||
if (!(player instanceof ServerPlayer serverPlayer)
|
||||
|| !(level.getBlockEntity(pos) instanceof TrophyPanelBlockEntity panel)) return InteractionResult.CONSUME;
|
||||
if (player.isSecondaryUseActive()) return cycle(panel, serverPlayer);
|
||||
showDetails(panel, serverPlayer);
|
||||
return InteractionResult.SUCCESS_SERVER;
|
||||
return cycle(panel, serverPlayer, player.isSecondaryUseActive());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -164,8 +163,9 @@ public final class TrophyPanelBlock extends BaseEntityBlock {
|
||||
return state.rotate(mirror.getRotation(state.getValue(FACING)));
|
||||
}
|
||||
|
||||
private static InteractionResult cycle(TrophyPanelBlockEntity panel, ServerPlayer player) {
|
||||
if (!panel.cyclePage()) {
|
||||
private static InteractionResult cycle(TrophyPanelBlockEntity panel, ServerPlayer player, boolean backwards) {
|
||||
boolean changed = backwards ? panel.previousPage() : panel.nextPage();
|
||||
if (!changed) {
|
||||
player.sendSystemMessage(Component.translatable("message.redstoner.trophy_panel.newer_data"));
|
||||
return InteractionResult.CONSUME;
|
||||
}
|
||||
@@ -173,25 +173,4 @@ public final class TrophyPanelBlock extends BaseEntityBlock {
|
||||
panel.pageNumber(), panel.pageCount()));
|
||||
return InteractionResult.SUCCESS_SERVER;
|
||||
}
|
||||
|
||||
private static void showDetails(TrophyPanelBlockEntity panel, ServerPlayer player) {
|
||||
TrophyPanelBlockEntity.ClientSnapshot snapshot = panel.visibleSnapshot();
|
||||
RankingUnit unit = snapshot.unit();
|
||||
ItemStack icon = panel.currentPage().icon();
|
||||
Component metric = icon.isEmpty() ? Component.translatable(snapshot.translationKey())
|
||||
: Component.translatable(snapshot.translationKey(), icon.getHoverName());
|
||||
player.sendSystemMessage(Component.translatable("message.redstoner.trophy_panel.header", metric,
|
||||
panel.pageNumber(), panel.pageCount()));
|
||||
for (TrophyPanelBlockEntity.ClientEntry entry : snapshot.podium()) {
|
||||
player.sendSystemMessage(Component.translatable("message.redstoner.trophy_panel.podium",
|
||||
entry.rank(), entry.name(), unit.format(entry.value())));
|
||||
}
|
||||
if (snapshot.ownerRank() > 0) {
|
||||
player.sendSystemMessage(Component.translatable("message.redstoner.trophy_panel.owner_rank",
|
||||
panel.ownerName(), snapshot.ownerRank(), unit.format(snapshot.ownerValue())));
|
||||
} else {
|
||||
player.sendSystemMessage(Component.translatable("message.redstoner.trophy_panel.owner_unranked",
|
||||
panel.ownerName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-2
@@ -110,9 +110,17 @@ public final class TrophyPanelBlockEntity extends BlockEntity implements ItemOwn
|
||||
return ConfigureResult.SUCCESS;
|
||||
}
|
||||
|
||||
public boolean cyclePage() {
|
||||
public boolean nextPage() {
|
||||
return movePage(1);
|
||||
}
|
||||
|
||||
public boolean previousPage() {
|
||||
return movePage(-1);
|
||||
}
|
||||
|
||||
private boolean movePage(int offset) {
|
||||
if (!writable() || pages.isEmpty()) return false;
|
||||
currentPage = TrophyPanelRules.nextPage(currentPage, pages.size());
|
||||
currentPage = TrophyPanelRules.pageByOffset(currentPage, pages.size(), offset);
|
||||
syncPersistent();
|
||||
if (level instanceof ServerLevel serverLevel) refresh(serverLevel);
|
||||
return true;
|
||||
|
||||
+28
-43
@@ -11,22 +11,21 @@ import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||
import net.minecraft.client.renderer.feature.ModelFeatureRenderer;
|
||||
import net.minecraft.client.renderer.item.ItemModelResolver;
|
||||
import net.minecraft.client.renderer.state.level.CameraRenderState;
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.util.FormattedCharSequence;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public final class TrophyPanelBlockEntityRenderer
|
||||
implements BlockEntityRenderer<TrophyPanelBlockEntity, TrophyPanelRenderState> {
|
||||
private final ItemModelResolver itemModelResolver;
|
||||
private static final int MAX_TEXT_WIDTH = 90;
|
||||
private static final float TEXT_SCALE = 0.010416667F;
|
||||
private static final float[] LINE_HEIGHTS = {0.70F, 0.59F, 0.48F, 0.37F};
|
||||
private static final int TEXT_COLOR = 0xFF3D281A;
|
||||
private final Font font;
|
||||
|
||||
public TrophyPanelBlockEntityRenderer(BlockEntityRendererProvider.Context context) {
|
||||
itemModelResolver = context.itemModelResolver();
|
||||
font = context.font();
|
||||
}
|
||||
|
||||
@@ -40,65 +39,51 @@ public final class TrophyPanelBlockEntityRenderer
|
||||
float tickProgress, Vec3 cameraPos, ModelFeatureRenderer.CrumblingOverlay crumblingOverlay) {
|
||||
BlockEntityRenderer.super.extractRenderState(panel, state, tickProgress, cameraPos, crumblingOverlay);
|
||||
state.facing = panel.getBlockState().getValue(TrophyPanelBlock.FACING);
|
||||
state.icon.clear();
|
||||
ItemStack icon = panel.currentPage().icon();
|
||||
state.hasIcon = !icon.isEmpty();
|
||||
if (state.hasIcon) {
|
||||
itemModelResolver.updateForTopItem(state.icon, icon, ItemDisplayContext.FIXED,
|
||||
panel.level(), panel, (int) panel.getBlockPos().asLong());
|
||||
}
|
||||
|
||||
TrophyPanelBlockEntity.ClientSnapshot snapshot = panel.visibleSnapshot();
|
||||
RankingUnit unit = snapshot.unit();
|
||||
state.metric = state.hasIcon ? Component.translatable(snapshot.translationKey(), icon.getHoverName())
|
||||
Component metric = !icon.isEmpty() ? Component.translatable(snapshot.translationKey(), icon.getHoverName())
|
||||
: Component.translatable(snapshot.translationKey());
|
||||
state.lines[0] = Component.translatable("block.redstoner.trophy_panel.page_title",
|
||||
panel.pageNumber(), panel.pageCount(), metric);
|
||||
if (snapshot.podium().isEmpty()) {
|
||||
state.champion = Component.translatable("block.redstoner.trophy_panel.no_score");
|
||||
state.lines[1] = Component.translatable("block.redstoner.trophy_panel.no_score");
|
||||
state.lines[2] = Component.empty();
|
||||
state.lines[3] = Component.empty();
|
||||
} else {
|
||||
TrophyPanelBlockEntity.ClientEntry champion = snapshot.podium().getFirst();
|
||||
state.champion = Component.translatable("block.redstoner.trophy_panel.champion",
|
||||
champion.name(), unit.format(champion.value()));
|
||||
for (int index = 0; index < 3; index++) {
|
||||
if (index >= snapshot.podium().size()) {
|
||||
state.lines[index + 1] = Component.empty();
|
||||
continue;
|
||||
}
|
||||
TrophyPanelBlockEntity.ClientEntry entry = snapshot.podium().get(index);
|
||||
state.lines[index + 1] = Component.translatable("block.redstoner.trophy_panel.podium",
|
||||
entry.rank(), entry.name(), unit.format(entry.value()));
|
||||
}
|
||||
}
|
||||
state.owner = snapshot.ownerRank() > 0
|
||||
? Component.translatable("block.redstoner.trophy_panel.owner_rank", panel.ownerName(),
|
||||
snapshot.ownerRank(), unit.format(snapshot.ownerValue()))
|
||||
: Component.translatable("block.redstoner.trophy_panel.owner_unranked", panel.ownerName());
|
||||
state.page = Component.literal(panel.pageNumber() + "/" + panel.pageCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submit(TrophyPanelRenderState state, PoseStack poseStack,
|
||||
SubmitNodeCollector submitNodeCollector, CameraRenderState cameraRenderState) {
|
||||
float yRotation = -state.facing.toYRot();
|
||||
if (state.hasIcon) submitIcon(state, poseStack, submitNodeCollector, yRotation);
|
||||
submitText(state.metric, 0.72F, 0.0065F, state, poseStack, submitNodeCollector, yRotation, 0xFFE8B4);
|
||||
submitText(state.champion, 0.52F, 0.0060F, state, poseStack, submitNodeCollector, yRotation, 0xFFFFFF);
|
||||
submitText(state.owner, 0.36F, 0.0055F, state, poseStack, submitNodeCollector, yRotation, 0xB8D9FF);
|
||||
submitText(state.page, 0.20F, 0.0050F, state, poseStack, submitNodeCollector, yRotation, 0xA0A0A0);
|
||||
for (int index = 0; index < TrophyPanelRenderState.LINE_COUNT; index++) {
|
||||
submitText(state.lines[index], LINE_HEIGHTS[index], state, poseStack, submitNodeCollector, yRotation);
|
||||
}
|
||||
}
|
||||
|
||||
private void submitIcon(TrophyPanelRenderState state, PoseStack poseStack,
|
||||
SubmitNodeCollector submitNodeCollector, float yRotation) {
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(0.5F, 0.89F, 0.5F);
|
||||
poseStack.mulPose(Axis.YP.rotationDegrees(yRotation));
|
||||
poseStack.translate(0.0F, 0.0F, -0.33F);
|
||||
poseStack.scale(0.18F, 0.18F, 0.18F);
|
||||
state.icon.submit(poseStack, submitNodeCollector, state.lightCoords, OverlayTexture.NO_OVERLAY, 0);
|
||||
poseStack.popPose();
|
||||
}
|
||||
|
||||
private void submitText(Component component, float y, float scale, TrophyPanelRenderState state,
|
||||
PoseStack poseStack, SubmitNodeCollector submitNodeCollector, float yRotation, int color) {
|
||||
FormattedCharSequence text = component.getVisualOrderText();
|
||||
private void submitText(Component component, float y, TrophyPanelRenderState state,
|
||||
PoseStack poseStack, SubmitNodeCollector submitNodeCollector, float yRotation) {
|
||||
FormattedCharSequence text = font.split(component, MAX_TEXT_WIDTH).stream().findFirst()
|
||||
.orElse(FormattedCharSequence.EMPTY);
|
||||
float x = -font.width(text) / 2.0F;
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(0.5F, y, 0.5F);
|
||||
poseStack.mulPose(Axis.YP.rotationDegrees(yRotation));
|
||||
poseStack.translate(0.0F, 0.0F, -0.331F);
|
||||
poseStack.scale(scale, -scale, scale);
|
||||
poseStack.scale(TEXT_SCALE, -TEXT_SCALE, TEXT_SCALE);
|
||||
submitNodeCollector.submitText(poseStack, x, 0.0F, text, false, Font.DisplayMode.POLYGON_OFFSET,
|
||||
color, state.lightCoords, 0, 0x30181818);
|
||||
state.lightCoords, TEXT_COLOR, 0, 0);
|
||||
poseStack.popPose();
|
||||
}
|
||||
}
|
||||
|
||||
+4
-7
@@ -1,16 +1,13 @@
|
||||
package fr.koka99cab.sanctuary26.redstoner.client.renderer.state;
|
||||
|
||||
import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState;
|
||||
import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public final class TrophyPanelRenderState extends BlockEntityRenderState {
|
||||
public final ItemStackRenderState icon = new ItemStackRenderState();
|
||||
public static final int LINE_COUNT = 4;
|
||||
public final Component[] lines = {
|
||||
Component.empty(), Component.empty(), Component.empty(), Component.empty()
|
||||
};
|
||||
public Direction facing = Direction.NORTH;
|
||||
public boolean hasIcon;
|
||||
public Component metric = Component.empty();
|
||||
public Component champion = Component.empty();
|
||||
public Component owner = Component.empty();
|
||||
public Component page = Component.empty();
|
||||
}
|
||||
|
||||
+9
-1
@@ -17,7 +17,15 @@ public final class TrophyPanelRules {
|
||||
}
|
||||
|
||||
public static int nextPage(int currentPage, int pageCount) {
|
||||
return pageCount <= 0 ? 0 : Math.floorMod(currentPage + 1, pageCount);
|
||||
return pageByOffset(currentPage, pageCount, 1);
|
||||
}
|
||||
|
||||
public static int previousPage(int currentPage, int pageCount) {
|
||||
return pageByOffset(currentPage, pageCount, -1);
|
||||
}
|
||||
|
||||
public static int pageByOffset(int currentPage, int pageCount, int offset) {
|
||||
return pageCount <= 0 ? 0 : Math.floorMod(currentPage + offset, pageCount);
|
||||
}
|
||||
|
||||
public static List<Identifier> mergeMetricIds(List<Identifier> current, List<Identifier> offered) {
|
||||
|
||||
@@ -64,6 +64,8 @@
|
||||
"screen.redstoner.storage_terminal.error.too_large": "Network too large (maximum 128 storage blocks).",
|
||||
"screen.redstoner.storage_terminal.error.locked": "The network contains a locked storage block.",
|
||||
"block.redstoner.trophy_panel": "Trophy Panel",
|
||||
"block.redstoner.trophy_panel.page_title": "[%s/%s] %s",
|
||||
"block.redstoner.trophy_panel.podium": "#%s %s · %s",
|
||||
"block.redstoner.trophy_panel.no_score": "No score yet",
|
||||
"block.redstoner.trophy_panel.champion": "#1 %s · %s",
|
||||
"block.redstoner.trophy_panel.owner_rank": "%s · #%s · %s",
|
||||
|
||||
@@ -64,6 +64,8 @@
|
||||
"screen.redstoner.storage_terminal.error.too_large": "Réseau trop grand (128 stockages maximum).",
|
||||
"screen.redstoner.storage_terminal.error.locked": "Le réseau contient un stockage verrouillé.",
|
||||
"block.redstoner.trophy_panel": "Panneau de trophées",
|
||||
"block.redstoner.trophy_panel.page_title": "[%s/%s] %s",
|
||||
"block.redstoner.trophy_panel.podium": "#%s %s · %s",
|
||||
"block.redstoner.trophy_panel.no_score": "Aucun score",
|
||||
"block.redstoner.trophy_panel.champion": "#1 %s · %s",
|
||||
"block.redstoner.trophy_panel.owner_rank": "%s · #%s · %s",
|
||||
|
||||
@@ -64,6 +64,8 @@
|
||||
"screen.redstoner.storage_terminal.error.too_large": "Сеть слишком велика (не более 128 хранилищ).",
|
||||
"screen.redstoner.storage_terminal.error.locked": "В сети есть запертое хранилище.",
|
||||
"block.redstoner.trophy_panel": "Панель трофеев",
|
||||
"block.redstoner.trophy_panel.page_title": "[%s/%s] %s",
|
||||
"block.redstoner.trophy_panel.podium": "#%s %s · %s",
|
||||
"block.redstoner.trophy_panel.no_score": "Результатов пока нет",
|
||||
"block.redstoner.trophy_panel.champion": "#1 %s · %s",
|
||||
"block.redstoner.trophy_panel.owner_rank": "%s · #%s · %s",
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
{
|
||||
"parent": "minecraft:block/block",
|
||||
"textures": {
|
||||
"particle": "minecraft:block/dark_oak_planks",
|
||||
"wood": "minecraft:block/dark_oak_planks",
|
||||
"frame": "minecraft:block/gold_block",
|
||||
"screen": "minecraft:block/black_concrete"
|
||||
"particle": "minecraft:block/oak_planks",
|
||||
"wood": "minecraft:block/oak_planks"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [0, 0, 13],
|
||||
"from": [0, 0, 14],
|
||||
"to": [16, 16, 16],
|
||||
"faces": {
|
||||
"north": { "texture": "#wood" },
|
||||
@@ -18,31 +16,6 @@
|
||||
"up": { "texture": "#wood" },
|
||||
"down": { "texture": "#wood" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [1.5, 1.5, 12.5],
|
||||
"to": [14.5, 14.5, 13],
|
||||
"faces": { "north": { "texture": "#screen" } }
|
||||
},
|
||||
{
|
||||
"from": [0.5, 0.5, 12.25],
|
||||
"to": [15.5, 1.5, 13],
|
||||
"faces": { "north": { "texture": "#frame" } }
|
||||
},
|
||||
{
|
||||
"from": [0.5, 14.5, 12.25],
|
||||
"to": [15.5, 15.5, 13],
|
||||
"faces": { "north": { "texture": "#frame" } }
|
||||
},
|
||||
{
|
||||
"from": [0.5, 1.5, 12.25],
|
||||
"to": [1.5, 14.5, 13],
|
||||
"faces": { "north": { "texture": "#frame" } }
|
||||
},
|
||||
{
|
||||
"from": [14.5, 1.5, 12.25],
|
||||
"to": [15.5, 14.5, 13],
|
||||
"faces": { "north": { "texture": "#frame" } }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+4
@@ -15,6 +15,10 @@ public final class TrophyPanelRulesSmoke {
|
||||
require(TrophyPanelRules.canConfigure(owner, visitor, true), "An operator cannot repair a panel");
|
||||
require(TrophyPanelRules.nextPage(0, 3) == 1 && TrophyPanelRules.nextPage(2, 3) == 0,
|
||||
"Public page cycling does not wrap");
|
||||
require(TrophyPanelRules.previousPage(0, 3) == 2 && TrophyPanelRules.previousPage(2, 3) == 1,
|
||||
"Reverse public page cycling does not wrap");
|
||||
require(TrophyPanelRules.pageByOffset(4, 0, 1) == 0,
|
||||
"An empty panel produced an invalid page index");
|
||||
|
||||
Identifier first = Identifier.parse("sanctuary:first");
|
||||
Identifier second = Identifier.parse("sanctuary:second");
|
||||
|
||||
@@ -35,6 +35,7 @@ réelle matérialisée reçoit un identifiant persistant : elle peut passer du r
|
||||
Une perte associée à un joueur lui reste réservée pendant 24 000 ticks avant de pouvoir rejoindre
|
||||
les tirages historiques partagés ; une perte sans propriétaire est immédiatement partagée.
|
||||
|
||||
Chaque niveau génère exactement un coffre par cellule de 3 × 3 chunks dans les nouveaux chunks.
|
||||
Tous les coffres vanilla de la Backroom, générés ou posés, deviennent des nœuds de butin renouvelés
|
||||
toutes les 12 000 ticks lors de leur prochaine ouverture. Le renouvellement rend au registre les
|
||||
objets perdus non pris, efface le contenu ordinaire et produit le plus souvent du vide ou des
|
||||
@@ -75,9 +76,9 @@ numérotés de haut en bas afin qu’une chute fasse toujours avancer vers le ni
|
||||
Les ouvertures entre deux niveaux sont calculées une seule fois puis appliquées à la fois au sol
|
||||
supérieur et au plafond inférieur : une chute annoncée ne débouche donc pas sur un bloc fermé. Les
|
||||
imperfections, morceaux suspendus et raccords volontairement cassés font partie de l’esthétique,
|
||||
mais les portes des couloirs restent traversables. Les lits de sortie restent rares, mais chacun
|
||||
des cinq niveaux en génère exactement un par cellule de 10 × 10 chunks, sur un plancher praticable
|
||||
propre à sa zone. Il n’est donc plus nécessaire de remonter jusqu’aux Archives pour ressortir.
|
||||
mais les portes des couloirs restent traversables. Chacun des cinq niveaux génère exactement un lit
|
||||
de sortie par cellule de 3 × 3 chunks, sur un plancher praticable propre à sa zone. Il n’est donc
|
||||
plus nécessaire de parcourir une centaine de chunks ou de remonter jusqu’aux Archives pour ressortir.
|
||||
|
||||
La Backroom ne possède ni ciel, ni soleil, ni lune, ni étoiles, ni lumière ambiante. Son biome ne
|
||||
déclare aucune créature et le serveur retire aussi toute entité de catégorie monstre qui y serait
|
||||
|
||||
+71
-14
@@ -124,6 +124,14 @@ tasks.register("capeSyncPayloadSmoke", JavaExec) {
|
||||
mainClass = "fr.koka99cab.sanctuary26.sanctuary.network.CapeSyncPayloadSmoke"
|
||||
}
|
||||
|
||||
tasks.register("chickenArmorSmoke", JavaExec) {
|
||||
group = "verification"
|
||||
description = "Checks that only adult unarmored chickens accept armadillo-scute armor."
|
||||
dependsOn tasks.named("testClasses")
|
||||
classpath = sourceSets.test.runtimeClasspath
|
||||
mainClass = "fr.koka99cab.sanctuary26.sanctuary.gameplay.SanctuaryChickenArmorSmoke"
|
||||
}
|
||||
|
||||
tasks.register("inventorySortSmoke", JavaExec) {
|
||||
group = "verification"
|
||||
description = "Checks that storage stacks merge and sort while the hotbar row stays out of the sort."
|
||||
@@ -199,7 +207,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.135"
|
||||
if (project.version.toString() != "0.0.0-alpha.137"
|
||||
|| !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")
|
||||
@@ -2915,7 +2923,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.135"
|
||||
if (project.version.toString() != "0.0.0-alpha.137"
|
||||
|| 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
|
||||
@@ -3081,7 +3089,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.135"
|
||||
if (project.version.toString() != "0.0.0-alpha.137"
|
||||
|| 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
|
||||
@@ -3119,7 +3127,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.135"
|
||||
if (project.version.toString() != "0.0.0-alpha.137"
|
||||
|| 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]
|
||||
@@ -3150,7 +3158,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.135"
|
||||
if (project.version.toString() != "0.0.0-alpha.137"
|
||||
|| migration.target?.pack_version != "26.2.0-alpha.195"
|
||||
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.111"
|
||||
|| migration.compatibility?.sanctuary_data_version != 11
|
||||
@@ -3181,7 +3189,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.135"
|
||||
if (project.version.toString() != "0.0.0-alpha.137"
|
||||
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.112"
|
||||
|| migration.mailbox?.id != "sanctuary:delivery_box"
|
||||
|| migration.mailbox?.horizontal_facing != true
|
||||
@@ -3209,7 +3217,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.135"
|
||||
if (project.version.toString() != "0.0.0-alpha.137"
|
||||
|| !transfer.contains("shouldAppendOverflow")
|
||||
|| !transfer.contains("isVanillaStorageRange")
|
||||
|| !transfer.contains("hotbarStart")
|
||||
@@ -3255,7 +3263,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.135"
|
||||
if (project.version.toString() != "0.0.0-alpha.137"
|
||||
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.113"
|
||||
|| migration.quest_requests?.save_key != "quest_requests"
|
||||
|| migration.quest_requests?.data_version != 1
|
||||
@@ -3317,7 +3325,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.135"
|
||||
if (project.version.toString() != "0.0.0-alpha.137"
|
||||
|| 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"
|
||||
@@ -3386,7 +3394,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.135"
|
||||
if (project.version.toString() != "0.0.0-alpha.137"
|
||||
|| 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"
|
||||
@@ -3465,7 +3473,7 @@ tasks.register("verifyCraftableBannerCape") {
|
||||
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.135"
|
||||
if (project.version.toString() != "0.0.0-alpha.137"
|
||||
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.130"
|
||||
|| migration.white_cape?.crafting_material_count != 6
|
||||
|| migration.white_cape?.maximum_pattern_layers != 6
|
||||
@@ -3493,6 +3501,52 @@ tasks.named("verifySanctuary") {
|
||||
dependsOn tasks.named("verifyCraftableBannerCape")
|
||||
}
|
||||
|
||||
tasks.register("verifyFinalSanctuaryTickets") {
|
||||
group = "verification"
|
||||
description = "Checks the shield-mapped banner cape and armadillo-scute chicken armor contracts."
|
||||
inputs.files(
|
||||
file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryBannerCapeLayer.java"),
|
||||
file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryChickenArmorLayer.java"),
|
||||
file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/SanctuaryChickenArmor.java"),
|
||||
file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/AnimalChickenArmorMixin.java"),
|
||||
file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/LivingEntityChickenArmorMixin.java"),
|
||||
file("src/main/resources/sanctuary.mixins.json"))
|
||||
inputs.files(fileTree("src/main/resources/assets/sanctuary/lang") { include "*.json" })
|
||||
doLast {
|
||||
def capeRenderer = file(
|
||||
"src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryBannerCapeLayer.java").text
|
||||
def armorLayer = file(
|
||||
"src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryChickenArmorLayer.java").text
|
||||
def armorRules = file(
|
||||
"src/main/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/SanctuaryChickenArmor.java").text
|
||||
def armorDamage = file(
|
||||
"src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/LivingEntityChickenArmorMixin.java").text
|
||||
def mixins = new JsonSlurper().parse(file("src/main/resources/sanctuary.mixins.json"))
|
||||
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.137"
|
||||
|| !capeRenderer.contains("SHIELD_PATTERN_X_SCALE = 1.2F")
|
||||
|| !capeRenderer.contains("SHIELD_PATTERN_Y_SCALE = 1.375F")
|
||||
|| !capeRenderer.contains("model, state, false, appearance.baseColor()")
|
||||
|| !armorLayer.contains("EquipmentClientInfo.LayerType.WOLF_BODY")
|
||||
|| !armorRules.contains("held.is(Items.WOLF_ARMOR)")
|
||||
|| !armorRules.contains("DamageTypeTags.BYPASSES_WOLF_ARMOR")
|
||||
|| !armorDamage.contains("armor.hurtAndBreak")
|
||||
|| !mixins.mixins.contains("AnimalChickenArmorMixin")
|
||||
|| !mixins.mixins.contains("LivingEntityChickenArmorMixin")
|
||||
|| !mixins.client.contains("client.ChickenRendererMixin")
|
||||
|| !mixins.client.contains("client.ChickenRenderStateMixin")
|
||||
|| locales.any { !it.containsKey("tooltip.sanctuary.chicken_armor") }) {
|
||||
throw new GradleException("Sanctuary alpha.136 final-ticket contract is incomplete")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("verifySanctuary") {
|
||||
dependsOn tasks.named("verifyFinalSanctuaryTickets")
|
||||
}
|
||||
|
||||
tasks.register("verifyAlphaIndev") {
|
||||
group = "verification"
|
||||
description = "Checks the alpha.122 finite Indev world, migration and attribution contract."
|
||||
@@ -3520,7 +3574,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.135"
|
||||
if (project.version.toString() != "0.0.0-alpha.137"
|
||||
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.122"
|
||||
|| migration.alpha_world?.dimension != "sanctuary:alpha"
|
||||
|| migration.alpha_world?.legacy_dimension != "portals:alpha"
|
||||
@@ -3580,7 +3634,7 @@ tasks.register("verifyBackroom") {
|
||||
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.135"
|
||||
if (project.version.toString() != "0.0.0-alpha.137"
|
||||
|| migration.source_data_version != 13 || migration.target_data_version != 14
|
||||
|| migration.integration_modules?.anotherworld?.source != "0.0.0-alpha.56"
|
||||
|| migration.integration_modules?.anotherworld?.target != "0.0.0-alpha.57"
|
||||
@@ -3606,10 +3660,12 @@ tasks.register("verifyBackroom") {
|
||||
|| !chest.contains("REFRESH_TICKS = 12_000L") || !chest.contains("ordinal == 3")
|
||||
|| !architecture.contains("PLAYABLE_RADIUS = 2_000")
|
||||
|| !architecture.contains("BackroomMaterialApi.anomalyMaterial()")
|
||||
|| !architecture.contains("placeLootLandmark")
|
||||
|| !layout.contains("WORLD_MIN_Y = -64") || !layout.contains("WORLD_MAX_Y_EXCLUSIVE = 384")
|
||||
|| !layout.contains("LEVEL_0(295, 383)") || !layout.contains("LEVEL_4(-64, 27)")
|
||||
|| !layout.contains("mazeConnected") || !layout.contains("RoomCorner")
|
||||
|| !layout.contains("EXIT_BED_CELL_SIZE_CHUNKS = 10")
|
||||
|| !layout.contains("LOOT_CHEST_CELL_SIZE_CHUNKS = 3") || !layout.contains("lootChestChunk")
|
||||
|| !layout.contains("EXIT_BED_CELL_SIZE_CHUNKS = 3")
|
||||
|| !layout.contains("exitBedChunk") || !layout.contains("exitBedFloor")
|
||||
|| !materialApi.contains("registerAnomalyMaterial")
|
||||
|| locales.any { !it.containsKey("dimension.sanctuary.backroom")
|
||||
@@ -3640,6 +3696,7 @@ tasks.named("check") {
|
||||
dependsOn tasks.named("alphaIndevGeneratorSmoke")
|
||||
dependsOn tasks.named("capeJoinSyncSmoke")
|
||||
dependsOn tasks.named("capeSyncPayloadSmoke")
|
||||
dependsOn tasks.named("chickenArmorSmoke")
|
||||
dependsOn tasks.named("inventorySortSmoke")
|
||||
dependsOn tasks.named("entityPolicySmoke")
|
||||
dependsOn tasks.named("verifyAlphaIndev")
|
||||
|
||||
+4
-2
@@ -30,6 +30,8 @@ import net.minecraft.world.item.equipment.Equippable;
|
||||
|
||||
/** Renders a banner-decorated cape with vanilla cape physics and banner pattern sprites. */
|
||||
public final class SanctuaryBannerCapeLayer extends RenderLayer<AvatarRenderState, PlayerModel> {
|
||||
private static final float SHIELD_PATTERN_X_SCALE = 1.2F;
|
||||
private static final float SHIELD_PATTERN_Y_SCALE = 1.375F;
|
||||
private static final ModelLayerLocation MODEL_LAYER =
|
||||
new ModelLayerLocation(SanctuaryMod.id("banner_cape"), "main");
|
||||
private final PlayerCapeModel model;
|
||||
@@ -67,7 +69,7 @@ public final class SanctuaryBannerCapeLayer extends RenderLayer<AvatarRenderStat
|
||||
PartDefinition body = root.getChild("body");
|
||||
body.addOrReplaceChild("cape", CubeListBuilder.create().texOffs(0, 0)
|
||||
.addBox(-5.0F, 0.0F, -1.0F, 10.0F, 16.0F, 1.0F,
|
||||
CubeDeformation.NONE, 2.0F, 2.5F),
|
||||
CubeDeformation.NONE, SHIELD_PATTERN_X_SCALE, SHIELD_PATTERN_Y_SCALE),
|
||||
PartPose.offsetAndRotation(0.0F, 0.0F, 2.0F, 0.0F, (float) Math.PI, 0.0F));
|
||||
return LayerDefinition.create(mesh, 64, 64);
|
||||
}
|
||||
@@ -85,7 +87,7 @@ public final class SanctuaryBannerCapeLayer extends RenderLayer<AvatarRenderStat
|
||||
poseStack.translate(0.0F, -0.053125F, 0.06875F);
|
||||
}
|
||||
BannerRenderer.submitPatterns(context.getSprites(), poseStack, collector, packedLight,
|
||||
OverlayTexture.NO_OVERLAY, model, state, true, appearance.baseColor(),
|
||||
OverlayTexture.NO_OVERLAY, model, state, false, appearance.baseColor(),
|
||||
appearance.patterns(), null);
|
||||
poseStack.popPose();
|
||||
}
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package fr.koka99cab.sanctuary26.sanctuary.client;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import fr.koka99cab.sanctuary26.sanctuary.SanctuaryMod;
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.LivingEntityRenderLayerRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.ModelLayerRegistry;
|
||||
import net.minecraft.client.model.animal.chicken.ChickenModel;
|
||||
import net.minecraft.client.model.geom.ModelLayerLocation;
|
||||
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||
import net.minecraft.client.renderer.entity.ChickenRenderer;
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import net.minecraft.client.renderer.entity.RenderLayerParent;
|
||||
import net.minecraft.client.renderer.entity.layers.EquipmentLayerRenderer;
|
||||
import net.minecraft.client.renderer.entity.layers.RenderLayer;
|
||||
import net.minecraft.client.renderer.entity.state.ChickenRenderState;
|
||||
import net.minecraft.client.resources.model.EquipmentClientInfo;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.world.entity.EntityTypes;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.equipment.Equippable;
|
||||
|
||||
/** Renders vanilla armadillo-scute equipment around an adult chicken's torso. */
|
||||
public final class SanctuaryChickenArmorLayer extends RenderLayer<ChickenRenderState, ChickenModel> {
|
||||
private static final ModelLayerLocation MODEL_LAYER =
|
||||
new ModelLayerLocation(SanctuaryMod.id("chicken_armor"), "main");
|
||||
private final SanctuaryChickenArmorModel model;
|
||||
private final EquipmentLayerRenderer equipmentRenderer;
|
||||
|
||||
private SanctuaryChickenArmorLayer(RenderLayerParent<ChickenRenderState, ChickenModel> parent,
|
||||
EntityRendererProvider.Context context) {
|
||||
super(parent);
|
||||
model = SanctuaryChickenArmorModel.bake(context.bakeLayer(MODEL_LAYER));
|
||||
equipmentRenderer = context.getEquipmentRenderer();
|
||||
}
|
||||
|
||||
public static void initialize() {
|
||||
ModelLayerRegistry.registerModelLayer(MODEL_LAYER, SanctuaryChickenArmorModel::createLayer);
|
||||
LivingEntityRenderLayerRegistrationCallback.EVENT.register((entityType, renderer, helper, context) -> {
|
||||
if (entityType == EntityTypes.CHICKEN && renderer instanceof ChickenRenderer chickenRenderer) {
|
||||
register(helper, chickenRenderer, context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private static void register(LivingEntityRenderLayerRegistrationCallback.RegistrationHelper helper,
|
||||
ChickenRenderer renderer, EntityRendererProvider.Context context) {
|
||||
RenderLayerParent<ChickenRenderState, ChickenModel> parent =
|
||||
(RenderLayerParent<ChickenRenderState, ChickenModel>) (RenderLayerParent) renderer;
|
||||
helper.register(new SanctuaryChickenArmorLayer(parent, context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submit(PoseStack poseStack, SubmitNodeCollector collector, int packedLight,
|
||||
ChickenRenderState state, float yRot, float xRot) {
|
||||
if (state.isBaby || state.isInvisibleToPlayer) return;
|
||||
ItemStack armor = ((SanctuaryChickenArmorRenderState) state).sanctuary$chickenArmor();
|
||||
Equippable equippable = armor.get(DataComponents.EQUIPPABLE);
|
||||
if (equippable == null || equippable.assetId().isEmpty()) return;
|
||||
equipmentRenderer.renderLayers(EquipmentClientInfo.LayerType.WOLF_BODY,
|
||||
equippable.assetId().get(), model, state, armor, poseStack, collector,
|
||||
packedLight, state.outlineColor);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package fr.koka99cab.sanctuary26.sanctuary.client;
|
||||
|
||||
import net.minecraft.client.model.EntityModel;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.client.model.geom.PartPose;
|
||||
import net.minecraft.client.model.geom.builders.CubeDeformation;
|
||||
import net.minecraft.client.model.geom.builders.CubeListBuilder;
|
||||
import net.minecraft.client.model.geom.builders.LayerDefinition;
|
||||
import net.minecraft.client.model.geom.builders.MeshDefinition;
|
||||
import net.minecraft.client.renderer.entity.state.ChickenRenderState;
|
||||
|
||||
/** A thin torso shell using the vanilla armadillo-scute equipment UVs. */
|
||||
public final class SanctuaryChickenArmorModel extends EntityModel<ChickenRenderState> {
|
||||
private SanctuaryChickenArmorModel(ModelPart root) {
|
||||
super(root);
|
||||
}
|
||||
|
||||
public static SanctuaryChickenArmorModel bake(ModelPart root) {
|
||||
return new SanctuaryChickenArmorModel(root);
|
||||
}
|
||||
|
||||
public static LayerDefinition createLayer() {
|
||||
MeshDefinition mesh = new MeshDefinition();
|
||||
mesh.getRoot().addOrReplaceChild("body",
|
||||
CubeListBuilder.create().texOffs(18, 14)
|
||||
.addBox(-3.0F, -4.0F, -3.0F, 6.0F, 8.0F, 6.0F,
|
||||
new CubeDeformation(0.45F)),
|
||||
PartPose.offsetAndRotation(0.0F, 16.0F, 0.0F,
|
||||
(float) (Math.PI / 2.0D), 0.0F, 0.0F));
|
||||
return LayerDefinition.create(mesh, 64, 32);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package fr.koka99cab.sanctuary26.sanctuary.client;
|
||||
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/** Mixin-backed bridge carrying chicken body armor into the extracted render state. */
|
||||
public interface SanctuaryChickenArmorRenderState {
|
||||
ItemStack sanctuary$chickenArmor();
|
||||
|
||||
void sanctuary$setChickenArmor(ItemStack armor);
|
||||
}
|
||||
@@ -120,6 +120,7 @@ public final class SanctuaryClient implements ClientModInitializer {
|
||||
SanctuaryHealthPreview.register();
|
||||
CelestialSpyglassSelection.register();
|
||||
SanctuaryBannerCapeLayer.initialize();
|
||||
SanctuaryChickenArmorLayer.initialize();
|
||||
EntityRendererRegistry.register(SanctuaryRegistries.SEAT, NoopRenderer::new);
|
||||
ClientTickEvents.END_CLIENT_TICK.register(SanctuaryClient::tick);
|
||||
ClientPlayNetworking.registerGlobalReceiver(ProgressionSnapshotPayload.ID, (payload, context) ->
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package fr.koka99cab.sanctuary26.sanctuary.gameplay;
|
||||
|
||||
import net.minecraft.tags.DamageTypeTags;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
|
||||
/** Shared server rules for fitting vanilla armadillo-scute armor to adult chickens. */
|
||||
public final class SanctuaryChickenArmor {
|
||||
private SanctuaryChickenArmor() {
|
||||
}
|
||||
|
||||
public static boolean canEquip(ItemStack equipped, ItemStack held, boolean baby) {
|
||||
return !baby && equipped.isEmpty() && held.is(Items.WOLF_ARMOR);
|
||||
}
|
||||
|
||||
public static boolean canRepair(ItemStack equipped, ItemStack held) {
|
||||
return equipped.is(Items.WOLF_ARMOR) && equipped.isDamaged()
|
||||
&& equipped.isValidRepairItem(held);
|
||||
}
|
||||
|
||||
public static boolean absorbs(ItemStack equipped, DamageSource source) {
|
||||
return equipped.is(Items.WOLF_ARMOR) && !source.is(DamageTypeTags.BYPASSES_WOLF_ARMOR);
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package fr.koka99cab.sanctuary26.sanctuary.mixin;
|
||||
|
||||
import fr.koka99cab.sanctuary26.sanctuary.gameplay.SanctuaryChickenArmor;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.entity.animal.Animal;
|
||||
import net.minecraft.world.entity.animal.chicken.Chicken;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
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.CallbackInfoReturnable;
|
||||
|
||||
/** Lets adult chickens wear and repair the vanilla armadillo-scute wolf armor. */
|
||||
@Mixin(Animal.class)
|
||||
abstract class AnimalChickenArmorMixin {
|
||||
@Inject(method = "mobInteract", at = @At("HEAD"), cancellable = true)
|
||||
private void sanctuary$handleChickenArmor(Player player, InteractionHand hand,
|
||||
CallbackInfoReturnable<InteractionResult> cir) {
|
||||
if (!((Object) this instanceof Chicken chicken)) return;
|
||||
ItemStack held = player.getItemInHand(hand);
|
||||
ItemStack equipped = chicken.getItemBySlot(EquipmentSlot.BODY);
|
||||
if (SanctuaryChickenArmor.canEquip(equipped, held, chicken.isBaby())) {
|
||||
chicken.setItemSlot(EquipmentSlot.BODY, held.copyWithCount(1));
|
||||
chicken.setGuaranteedDrop(EquipmentSlot.BODY);
|
||||
held.consume(1, player);
|
||||
chicken.playSound(SoundEvents.ARMOR_EQUIP_WOLF.value());
|
||||
cir.setReturnValue(InteractionResult.SUCCESS);
|
||||
return;
|
||||
}
|
||||
if (SanctuaryChickenArmor.canRepair(equipped, held)) {
|
||||
held.consume(1, player);
|
||||
int repairUnit = Mth.ceil(equipped.getMaxDamage() * 0.125F);
|
||||
equipped.setDamageValue(Math.max(0, equipped.getDamageValue() - repairUnit));
|
||||
chicken.playSound(SoundEvents.WOLF_ARMOR_REPAIR);
|
||||
cir.setReturnValue(InteractionResult.SUCCESS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,11 @@ package fr.koka99cab.sanctuary26.sanctuary.mixin;
|
||||
|
||||
import fr.koka99cab.sanctuary26.sanctuary.gameplay.SanctuaryCompanionPowers;
|
||||
import java.util.function.Consumer;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.SpawnEggItem;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.item.component.TooltipDisplay;
|
||||
@@ -21,5 +23,9 @@ abstract class ItemTooltipMixin {
|
||||
if (stack.getItem() instanceof SpawnEggItem) {
|
||||
SanctuaryCompanionPowers.appendPowerTooltip(stack, tooltip);
|
||||
}
|
||||
if (stack.is(Items.WOLF_ARMOR)) {
|
||||
tooltip.accept(Component.translatable("tooltip.sanctuary.chicken_armor")
|
||||
.withStyle(ChatFormatting.GRAY));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package fr.koka99cab.sanctuary26.sanctuary.mixin;
|
||||
|
||||
import fr.koka99cab.sanctuary26.sanctuary.gameplay.SanctuaryChickenArmor;
|
||||
import net.minecraft.core.particles.ItemParticleOption;
|
||||
import net.minecraft.core.particles.ParticleTypes;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.entity.Crackiness;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.animal.chicken.Chicken;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
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;
|
||||
|
||||
/** Gives armored chickens the same damage-absorption contract as armored wolves. */
|
||||
@Mixin(LivingEntity.class)
|
||||
abstract class LivingEntityChickenArmorMixin {
|
||||
@Inject(method = "actuallyHurt", at = @At("HEAD"), cancellable = true)
|
||||
private void sanctuary$absorbChickenDamage(ServerLevel level, DamageSource source,
|
||||
float damage, CallbackInfo ci) {
|
||||
if (!((Object) this instanceof Chicken chicken)) return;
|
||||
ItemStack armor = chicken.getItemBySlot(EquipmentSlot.BODY);
|
||||
if (!SanctuaryChickenArmor.absorbs(armor, source)) return;
|
||||
|
||||
int damageBefore = armor.getDamageValue();
|
||||
int maxDamage = armor.getMaxDamage();
|
||||
armor.hurtAndBreak(Mth.ceil(damage), chicken, EquipmentSlot.BODY);
|
||||
chicken.playSound(SoundEvents.WOLF_ARMOR_DAMAGE);
|
||||
if (Crackiness.WOLF_ARMOR.byDamage(damageBefore, maxDamage)
|
||||
!= Crackiness.WOLF_ARMOR.byDamage(chicken.getItemBySlot(EquipmentSlot.BODY))) {
|
||||
chicken.playSound(SoundEvents.WOLF_ARMOR_CRACK);
|
||||
level.sendParticles(new ItemParticleOption(ParticleTypes.ITEM,
|
||||
Items.ARMADILLO_SCUTE), chicken.getX(), chicken.getY() + 0.5D,
|
||||
chicken.getZ(), 12, 0.2D, 0.15D, 0.2D, 0.08D);
|
||||
}
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package fr.koka99cab.sanctuary26.sanctuary.mixin.client;
|
||||
|
||||
import fr.koka99cab.sanctuary26.sanctuary.client.SanctuaryChickenArmorRenderState;
|
||||
import net.minecraft.client.renderer.entity.state.ChickenRenderState;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
/** Stores synchronized chicken body armor on the client render state. */
|
||||
@Mixin(ChickenRenderState.class)
|
||||
abstract class ChickenRenderStateMixin implements SanctuaryChickenArmorRenderState {
|
||||
@Unique private ItemStack sanctuary$chickenArmor = ItemStack.EMPTY;
|
||||
|
||||
@Override
|
||||
public ItemStack sanctuary$chickenArmor() {
|
||||
return sanctuary$chickenArmor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sanctuary$setChickenArmor(ItemStack armor) {
|
||||
sanctuary$chickenArmor = armor == null ? ItemStack.EMPTY : armor;
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package fr.koka99cab.sanctuary26.sanctuary.mixin.client;
|
||||
|
||||
import fr.koka99cab.sanctuary26.sanctuary.client.SanctuaryChickenArmorRenderState;
|
||||
import net.minecraft.client.renderer.entity.ChickenRenderer;
|
||||
import net.minecraft.client.renderer.entity.state.ChickenRenderState;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.entity.animal.chicken.Chicken;
|
||||
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;
|
||||
|
||||
/** Copies vanilla-synchronized body equipment into each chicken render state. */
|
||||
@Mixin(ChickenRenderer.class)
|
||||
abstract class ChickenRendererMixin {
|
||||
@Inject(method = "extractRenderState", at = @At("TAIL"))
|
||||
private void sanctuary$extractChickenArmor(Chicken chicken, ChickenRenderState state,
|
||||
float partialTick, CallbackInfo ci) {
|
||||
((SanctuaryChickenArmorRenderState) state).sanctuary$setChickenArmor(
|
||||
chicken.getItemBySlot(EquipmentSlot.BODY).copy());
|
||||
}
|
||||
}
|
||||
+16
-9
@@ -77,7 +77,7 @@ public final class BackroomArchitectureFeature extends Feature<NoneFeatureConfig
|
||||
buildCity(level, worldSeed, startX, startZ, chunkX, chunkZ);
|
||||
buildUtilities(level, worldSeed, startX, startZ);
|
||||
buildGlitchMaze(level, worldSeed, startX, startZ);
|
||||
placeLootLandmarks(level, worldSeed, startX, startZ, chunkX, chunkZ, random);
|
||||
placeLootLandmarks(level, worldSeed, startX, startZ, chunkX, chunkZ);
|
||||
placeRareAnomaly(level, startX, startZ, random);
|
||||
return true;
|
||||
}
|
||||
@@ -416,14 +416,11 @@ public final class BackroomArchitectureFeature extends Feature<NoneFeatureConfig
|
||||
}
|
||||
|
||||
private static void placeLootLandmarks(WorldGenLevel level, long seed, int x0, int z0,
|
||||
int chunkX, int chunkZ, Random random) {
|
||||
if (random.nextInt(5) == 0) {
|
||||
BackroomLevel zone = BackroomLevel.values()[random.nextInt(BackroomLevel.values().length)];
|
||||
int floorY = BackroomLayout.exitBedFloor(zone);
|
||||
int x = x0 + 7, y = floorY + 1, z = z0 + 7;
|
||||
set(level, x, floorY, z, floorMaterial(zone));
|
||||
set(level, x, y, z, Blocks.CHEST.defaultBlockState());
|
||||
set(level, x, y + 1, z, Blocks.AIR.defaultBlockState());
|
||||
int chunkX, int chunkZ) {
|
||||
for (BackroomLevel zone : BackroomLevel.values()) {
|
||||
if (BackroomLayout.lootChestChunk(seed, zone, chunkX, chunkZ)) {
|
||||
placeLootLandmark(level, zone, x0 + 7, z0 + 7);
|
||||
}
|
||||
}
|
||||
for (BackroomLevel zone : BackroomLevel.values()) {
|
||||
if (!BackroomLayout.exitBedChunk(seed, zone, chunkX, chunkZ)) continue;
|
||||
@@ -438,6 +435,16 @@ public final class BackroomArchitectureFeature extends Feature<NoneFeatureConfig
|
||||
}
|
||||
}
|
||||
|
||||
private static void placeLootLandmark(WorldGenLevel level, BackroomLevel zone, int x, int z) {
|
||||
int floorY = BackroomLayout.exitBedFloor(zone);
|
||||
for (int dx = -1; dx <= 1; dx++) for (int dz = -1; dz <= 1; dz++) {
|
||||
set(level, x + dx, floorY, z + dz, floorMaterial(zone));
|
||||
for (int dy = 1; dy <= 2; dy++) set(level, x + dx, floorY + dy, z + dz,
|
||||
Blocks.AIR.defaultBlockState());
|
||||
}
|
||||
set(level, x, floorY + 1, z, Blocks.CHEST.defaultBlockState());
|
||||
}
|
||||
|
||||
private static void placeRareAnomaly(WorldGenLevel level, int x0, int z0, Random random) {
|
||||
if (random.nextInt(96) == 0) {
|
||||
Block anomaly = BackroomMaterialApi.anomalyMaterial();
|
||||
|
||||
+17
-2
@@ -13,7 +13,8 @@ public final class BackroomLayout {
|
||||
public static final int ROOM_SIZE = 16;
|
||||
public static final int MAZE_FLOOR_Y = 8;
|
||||
public static final int MAZE_ROOF_Y = 18;
|
||||
public static final int EXIT_BED_CELL_SIZE_CHUNKS = 10;
|
||||
public static final int EXIT_BED_CELL_SIZE_CHUNKS = 3;
|
||||
public static final int LOOT_CHEST_CELL_SIZE_CHUNKS = 3;
|
||||
private static final int HOLE_CELL_SIZE = 64;
|
||||
|
||||
private BackroomLayout() {}
|
||||
@@ -91,7 +92,21 @@ public final class BackroomLayout {
|
||||
return Math.floorMod((int) mix(seed ^ 0x43c2a1769f0e85bdL, chunkX, chunkZ), 11) == 0;
|
||||
}
|
||||
|
||||
/** One deterministic, rare exit-bed chunk per grid cell and per vertical level. */
|
||||
/** One easy-to-find loot landmark per three-by-three chunk cell and per vertical level. */
|
||||
public static boolean lootChestChunk(long seed, BackroomLevel level, int chunkX, int chunkZ) {
|
||||
Objects.requireNonNull(level, "level");
|
||||
int cellX = Math.floorDiv(chunkX, LOOT_CHEST_CELL_SIZE_CHUNKS);
|
||||
int cellZ = Math.floorDiv(chunkZ, LOOT_CHEST_CELL_SIZE_CHUNKS);
|
||||
long hash = mix(seed ^ 0x354c6f3a82d9e107L ^ (long) level.ordinal() * 0x9e3779b97f4a7c15L,
|
||||
cellX, cellZ);
|
||||
int targetX = cellX * LOOT_CHEST_CELL_SIZE_CHUNKS
|
||||
+ Math.floorMod((int) hash, LOOT_CHEST_CELL_SIZE_CHUNKS);
|
||||
int targetZ = cellZ * LOOT_CHEST_CELL_SIZE_CHUNKS
|
||||
+ Math.floorMod((int) (hash >>> 32), LOOT_CHEST_CELL_SIZE_CHUNKS);
|
||||
return chunkX == targetX && chunkZ == targetZ;
|
||||
}
|
||||
|
||||
/** One deterministic exit-bed chunk per three-by-three grid cell and per vertical level. */
|
||||
public static boolean exitBedChunk(long seed, BackroomLevel level, int chunkX, int chunkZ) {
|
||||
Objects.requireNonNull(level, "level");
|
||||
int cellX = Math.floorDiv(chunkX, EXIT_BED_CELL_SIZE_CHUNKS);
|
||||
|
||||
@@ -517,6 +517,7 @@
|
||||
"item.sanctuary.mojang_classic_cape": "Mojang Classic Cape",
|
||||
"item.sanctuary.blue_scroll_cape": "Blue Scroll Cape",
|
||||
"item.sanctuary.white_cape": "White Cape",
|
||||
"tooltip.sanctuary.chicken_armor": "Can also equip and protect an adult chicken.",
|
||||
"dimension.sanctuary.alpha": "Alpha / Indev Island",
|
||||
"dimension.sanctuary.backroom": "Backroom",
|
||||
"message.sanctuary.backroom.unavailable": "The Backroom is unavailable in this world.",
|
||||
|
||||
@@ -517,6 +517,7 @@
|
||||
"item.sanctuary.mojang_classic_cape": "Cape Mojang classique",
|
||||
"item.sanctuary.blue_scroll_cape": "Cape parchemin bleu",
|
||||
"item.sanctuary.white_cape": "Cape blanche",
|
||||
"tooltip.sanctuary.chicken_armor": "Peut aussi équiper et protéger une poule adulte.",
|
||||
"dimension.sanctuary.alpha": "Île Alpha / Indev",
|
||||
"dimension.sanctuary.backroom": "Backroom",
|
||||
"message.sanctuary.backroom.unavailable": "La Backroom n’est pas disponible dans ce monde.",
|
||||
|
||||
@@ -473,6 +473,7 @@
|
||||
"item.sanctuary.mojang_classic_cape": "Классический плащ Mojang",
|
||||
"item.sanctuary.blue_scroll_cape": "Плащ синего свитка",
|
||||
"item.sanctuary.white_cape": "Белый плащ",
|
||||
"tooltip.sanctuary.chicken_armor": "Также надевается на взрослую курицу и защищает её.",
|
||||
"dimension.sanctuary.alpha": "Остров Альфа / Indev",
|
||||
"dimension.sanctuary.backroom": "Бэкрум",
|
||||
"message.sanctuary.backroom.unavailable": "Бэкрум недоступен в этом мире.",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"compatibilityLevel": "JAVA_25",
|
||||
"mixins": [
|
||||
"AbstractContainerMenuMixin",
|
||||
"AnimalChickenArmorMixin",
|
||||
"BedBlockMixin",
|
||||
"ChestBlockEntityMixin",
|
||||
"ConsumableMixin",
|
||||
@@ -15,6 +16,7 @@
|
||||
"ItemEntityMixin",
|
||||
"ItemTooltipMixin",
|
||||
"LivingEntityCompanionMixin",
|
||||
"LivingEntityChickenArmorMixin",
|
||||
"PlayerOverflowMixin",
|
||||
"SlotMixin",
|
||||
"ServerGamePacketListenerMixin",
|
||||
@@ -28,6 +30,8 @@
|
||||
"client.AbstractContainerScreenMixin",
|
||||
"client.AbstractRecipeBookScreenMixin",
|
||||
"client.CameraMixin",
|
||||
"client.ChickenRendererMixin",
|
||||
"client.ChickenRenderStateMixin",
|
||||
"client.CreativeModeInventoryScreenMixin",
|
||||
"client.CreativeSlotWrapperAccessor",
|
||||
"client.CreateWorldScreenWorldTabMixin",
|
||||
|
||||
+21
-1
@@ -25,6 +25,7 @@ public final class BackroomModelSmoke {
|
||||
personalRecoveryIsPrioritized();
|
||||
recentLossesRemainPersonal();
|
||||
architectureIsDeterministicAndStacked();
|
||||
lootLandmarksCoverEveryLevel();
|
||||
exitBedsCoverEveryLevel();
|
||||
roomCornersReachEveryVariant();
|
||||
mazeDoorsStayConnected();
|
||||
@@ -130,6 +131,7 @@ public final class BackroomModelSmoke {
|
||||
private static void exitBedsCoverEveryLevel() {
|
||||
long seed = 26_200_133L;
|
||||
int cellSize = BackroomLayout.EXIT_BED_CELL_SIZE_CHUNKS;
|
||||
require(cellSize == 3, "Backroom exit beds are no longer spaced three chunks apart");
|
||||
for (BackroomLevel level : BackroomLevel.values()) {
|
||||
int floorY = BackroomLayout.exitBedFloor(level);
|
||||
require(BackroomLayout.levelAt(floorY) == level,
|
||||
@@ -142,7 +144,25 @@ public final class BackroomModelSmoke {
|
||||
}
|
||||
}
|
||||
require(candidates == 1,
|
||||
"A Backroom level lost its single rare exit bed in a generation cell: " + level);
|
||||
"A Backroom level lost its exit bed in a three-by-three chunk cell: " + level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void lootLandmarksCoverEveryLevel() {
|
||||
long seed = 26_200_137L;
|
||||
int cellSize = BackroomLayout.LOOT_CHEST_CELL_SIZE_CHUNKS;
|
||||
require(cellSize == 3, "Backroom loot landmarks are no longer spaced three chunks apart");
|
||||
for (BackroomLevel level : BackroomLevel.values()) {
|
||||
for (int cellX = -4; cellX <= 4; cellX++) for (int cellZ = -4; cellZ <= 4; cellZ++) {
|
||||
int candidates = 0;
|
||||
for (int chunkX = cellX * cellSize; chunkX < (cellX + 1) * cellSize; chunkX++) {
|
||||
for (int chunkZ = cellZ * cellSize; chunkZ < (cellZ + 1) * cellSize; chunkZ++) {
|
||||
if (BackroomLayout.lootChestChunk(seed, level, chunkX, chunkZ)) candidates++;
|
||||
}
|
||||
}
|
||||
require(candidates == 1,
|
||||
"A Backroom level lost its loot landmark in a three-by-three chunk cell: " + level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package fr.koka99cab.sanctuary26.sanctuary.gameplay;
|
||||
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentMap;
|
||||
|
||||
/** Executable coverage for the bounded adult-chicken armor equip policy. */
|
||||
public final class SanctuaryChickenArmorSmoke {
|
||||
private SanctuaryChickenArmorSmoke() {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
ItemStack armor = stack(Items.WOLF_ARMOR);
|
||||
require(SanctuaryChickenArmor.canEquip(ItemStack.EMPTY, armor, false),
|
||||
"Adult chickens must accept unused armadillo-scute armor");
|
||||
require(!SanctuaryChickenArmor.canEquip(ItemStack.EMPTY, armor, true),
|
||||
"Baby chickens must not accept body armor");
|
||||
require(!SanctuaryChickenArmor.canEquip(armor.copy(), armor, false),
|
||||
"Equipping must not replace an existing chicken armor stack");
|
||||
require(!SanctuaryChickenArmor.canEquip(ItemStack.EMPTY,
|
||||
stack(Items.IRON_CHESTPLATE), false),
|
||||
"Chicken armor must stay limited to the armadillo-scute item");
|
||||
}
|
||||
|
||||
private static ItemStack stack(net.minecraft.world.item.Item item) {
|
||||
return new ItemStack(new Holder.Direct<>(item, DataComponentMap.EMPTY), 1);
|
||||
}
|
||||
|
||||
private static void require(boolean condition, String message) {
|
||||
if (!condition) throw new IllegalStateException(message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user