Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00d175cb98 | ||
|
|
a7bdfe6998 | ||
|
|
f9c2ce8835 | ||
|
|
3d56d7de7f | ||
|
|
f2ac23b4f4 |
@@ -6,6 +6,31 @@ Créer un monde **Sanctuary** en Petit, Moyen ou Grand, puis activer les ancres
|
||||
correspondantes. Le module Sanctuary Test n’est pas requis.
|
||||
[Intégration et vérification du pack beta.224](docs/four-islands-base224.md).
|
||||
|
||||
## beta.235 — Essai réversible du menu Pause
|
||||
|
||||
[Disposition adaptative](docs/pause-columns-beta235.md) : avant Atlas, Aventure
|
||||
à gauche et Communauté à droite, avec Partie / Game juste dessous. Avec Atlas,
|
||||
une colonne étroite à gauche, Communauté → Aventure → Game, carte au centre.
|
||||
Bloc centré verticalement, libellés de navigation entiers et ordre clavier
|
||||
adapté. Parcours Pause et Story Vulkan FR/EN GUI 2/3/4, `check build assemblePack`
|
||||
et quatre GameTests ciblés réussis.
|
||||
Le commit de disposition `a7bdfe6` peut être annulé indépendamment de la
|
||||
livraison. [MRpack local beta.235](build/Sanctuary-beta.235.mrpack) vérifié,
|
||||
sans publication ni mise à jour personnelle. Le rendu reste à apprécier.
|
||||
|
||||
## beta.234 — Story, recherche et filtres
|
||||
|
||||
[Premier menu Story](docs/story-beta234.md), sous Progression dans Aventure :
|
||||
100 histoires inconnues et désactivées, recherche instantanée et filtre
|
||||
Toutes/Découvertes/Inconnues, cadre de Discovery et retour à Pause. Saisie et
|
||||
défilement conservés au redimensionnement ; trois boutons Aventure centrés
|
||||
avec ou sans Atlas. [Guide](docs/story/README.md) et
|
||||
[modèle Markdown](docs/story/TEMPLATE.md) pour les futurs récits personnels.
|
||||
Textes et déblocages restent à intégrer. `check build assemblePack`, quatre
|
||||
GameTests requis et parcours Vulkan FR/EN GUI 2/3/4 réussis.
|
||||
[MRpack local beta.234](build/Sanctuary-beta.234.mrpack) vérifié ; aucune
|
||||
publication ni mise à jour d'installation personnelle.
|
||||
|
||||
## beta.233 — Navigation centrée et rédaction simplifiée
|
||||
|
||||
[Ajustements du menu et des formulaires](docs/pause-community-beta233.md) : même
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# Backlog Sanctuary
|
||||
|
||||
## PAUSE-235 — groupes côte à côte avant Atlas — essai livré localement
|
||||
|
||||
Branche `codex/pause-columns-beta235`, base beta.234. Avant Atlas : Aventure à
|
||||
gauche, Communauté à droite et Game juste dessous, bloc centré. Avec Atlas :
|
||||
une colonne étroite à gauche dans l'ordre Communauté → Aventure → Game,
|
||||
carte centrale et fils à droite. [Contrat et vérifications](pause-columns-beta235.md),
|
||||
parcours Vulkan Pause et Story FR/EN GUI 2/3/4, build, quatre GameTests ciblés
|
||||
et MRpack local vérifiés ; rendu à apprécier.
|
||||
Commit de disposition `a7bdfe6` séparé des métadonnées pour permettre son retour.
|
||||
|
||||
## STORY-234 — recueil personnel avec recherche — livré localement
|
||||
|
||||
Branche `codex/story-beta234`, base beta.233. Story sous Progression dans
|
||||
Aventure ; 100 histoires inconnues, barre de recherche et filtre de découverte.
|
||||
Cadre commun avec Discovery, état de recherche et défilement conservés au
|
||||
redimensionnement, accès avant et après Atlas. [Résultat et vérifications](story-beta234.md) :
|
||||
build, quatre GameTests requis et parcours Vulkan FR/EN GUI 2/3/4 réussis.
|
||||
[Modèle Markdown](story/TEMPLATE.md) pour les textes de l'auteur.
|
||||
Les récits et leurs conditions de découverte restent des tickets suivants ;
|
||||
aucun moteur de quête, format persistant ou déploiement ajouté.
|
||||
|
||||
## WG-RESTORE-191 — annuler les plateaux et leurs affleurements
|
||||
|
||||
La visite beta.190 ne valide pas le relief corrigé. [Retour exact à la base
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# PAUSE-235 — Aventure et Communauté côte à côte
|
||||
|
||||
Branche d'essai `codex/pause-columns-beta235`, base beta.234, commit `f9c2ce8`,
|
||||
Minecraft 26.3. Le rendu reste à apprécier par le créateur.
|
||||
|
||||
## Disposition et réversibilité
|
||||
|
||||
Avant Atlas, Aventure et Communauté occupent deux colonnes au même niveau :
|
||||
Discovery, Progression et Story à gauche ; Habitant, Faction et Battle à droite.
|
||||
Partie / Game suit juste dessous avec Options, World Options et la sortie
|
||||
native. L'ensemble est centré verticalement dans le corps de Pause, avec de
|
||||
l'espace sous Game ; Reprendre la partie reste dans le pied de page.
|
||||
|
||||
Sans Atlas, ce bloc est centré entre Gazette à gauche et Notice Board à droite.
|
||||
Avec Atlas, une seule colonne étroite est serrée à gauche : Communauté, puis
|
||||
Aventure, puis Partie / Game, selon l'ordre confirmé par le créateur. La carte
|
||||
garde la région centrale et les deux fils communautaires restent à droite.
|
||||
Le bloc complet reste centré verticalement. La largeur minimale des boutons
|
||||
de navigation tient compte des libellés localisés et de leur marge native.
|
||||
Aux petites hauteurs, boutons et espacements se compactent. Tab suit l'ordre
|
||||
visuel : par rangée de gauche à droite avant Atlas, puis de haut en bas avec
|
||||
Atlas. Les libellés FR/EN existants sont réutilisés.
|
||||
|
||||
Le changement de disposition et les assertions correspondantes sont isolés
|
||||
dans le commit `a7bdfe6`, séparé de la livraison et de ses métadonnées. La branche
|
||||
`codex/story-beta234` conserve la disposition précédente. Un retour s'effectue
|
||||
avec `git revert a7bdfe6` ; une éventuelle nouvelle
|
||||
livraison doit prendre le numéro beta suivant, sans diminuer le compteur.
|
||||
L'application inverse du patch a été vérifiée avec `git apply --reverse --check`,
|
||||
sans modifier les fichiers de travail.
|
||||
|
||||
Ce ticket ne change ni génération, sauvegarde, données communautaires, protocole,
|
||||
conditions de progression ou contenus Story. Aucune installation personnelle
|
||||
ou publication n'est demandée.
|
||||
|
||||
## Vérifications
|
||||
|
||||
```sh
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true -PsanctuaryPause233ClientTests=true -PsanctuaryClientGraphicsBackend=vulkan
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true -PsanctuaryStory234ClientTests=true -PsanctuaryClientGraphicsBackend=vulkan
|
||||
./gradlew check build assemblePack -PsanctuaryFocusedTests=menus -PsanctuaryAtlasOnly=true
|
||||
```
|
||||
|
||||
Le parcours Pause final réussit en 1 min 22 s (`PAUSE233_PASS`), dans un monde
|
||||
plat neuf de développement, Survie sans commandes. FR/EN, GUI 2/3/4, fenêtre
|
||||
1280 × 960, avant et après Atlas : six actions Sanctuary, alignement et absence
|
||||
de chevauchement, largeur des libellés avec la marge native, ordre de Tab et
|
||||
Game sous les groupes, avec de l'espace avant le pied. Le test attend désormais
|
||||
un créneau de requête communautaire pour son amorçage et envoie le code de
|
||||
raccourci natif de Tab, requis par Minecraft 26.3.
|
||||
|
||||
Le parcours communautaire existant passe également : publication et photo,
|
||||
retours des sélecteurs, brouillons, édition d'une ancienne catégorie, inventaire
|
||||
réel conservé et aucun changement du fichier communautaire pendant les lectures.
|
||||
La configuration temporaire est restaurée à la fin du banc.
|
||||
Log : `build/pause235-client-vulkan-final.log`. 38 captures finales copiées dans
|
||||
`build/pause235-screenshots/`. Captures inspectées : sans Atlas FR/4, avec Atlas
|
||||
FR/2 et EN/4. Vulkan confirmé : MoltenVK 1.4.2 sur Apple M1.
|
||||
|
||||
Le parcours Story final réussit en 29 s (`STORY234_PASS`), avec les mêmes
|
||||
langues, GUI et états Atlas. Accès depuis les deux dispositions de Pause,
|
||||
100 entrées désactivées, recherche/filtres, saisie continue et focus, molette,
|
||||
redimensionnement à 1344 × 1008 et retours Échap / Terminé vérifiés. Le record
|
||||
de progression serveur ne change pas. Log :
|
||||
`build/pause235-story-client-vulkan-final.log` ; 21 captures copiées dans
|
||||
`build/pause235-story-screenshots/`.
|
||||
|
||||
Aux GUI étroits, les longs libellés natifs des options/sortie et les titres des
|
||||
fils gardent leur défilement ou découpage existant ; les boutons de navigation
|
||||
Sanctuary montrent leur libellé entier. Les infobulles des actions Game sont
|
||||
conservées. Aucun essai OpenGL, Windows ou monde personnel n'est revendiqué.
|
||||
|
||||
Le contrôle `check build assemblePack` réussit en 3 min 1 s : quatre GameTests
|
||||
requis et contrôles purs du dépôt, dont les quatre contrôles communautaires
|
||||
fichier/photos/demandes/recherche. Log : `build/pause235-check-build-pack.log`.
|
||||
Le banc menus conserve sa sélection Atlas existante ; le conflit de générateur
|
||||
sur le chemin natif par défaut, documenté en beta.232, reste hors de ce ticket.
|
||||
|
||||
## Livraison locale
|
||||
|
||||
`mod_version`, `pack_version` et `packwiz/pack.toml` sont alignés sur beta.235.
|
||||
Le commit de livraison conserve ces métadonnées et cette documentation séparées
|
||||
du commit de disposition.
|
||||
|
||||
Export [Sanctuary-beta.235.mrpack](../build/Sanctuary-beta.235.mrpack),
|
||||
12 776 222 octets : intégrité ZIP, Minecraft 26.3 et Fabric Loader 0.19.5,
|
||||
un seul JAR Sanctuary embarqué, identique au JAR construit, sans monde ni module
|
||||
de test. Les 3 303 fichiers d'assets, données et resourcepacks sont identiques
|
||||
à beta.234, traductions incluses. Story reste dans le JAR de production et les
|
||||
classes des bancs client en sont absentes. Reçu :
|
||||
`build/pause235-mrpack-receipt.json`.
|
||||
|
||||
- JAR : `aad94732c749161d775ee634bdd85de70de511ef875c6c00fa167d230d0d600b`.
|
||||
- MRpack : `5f1bd18aa8a4145b7e708da0abfff22068365847dddbc95ae2b490b88bbb305e`.
|
||||
|
||||
Aucune publication distante, modification du canal packwiz, synchronisation
|
||||
Prism ou ouverture d'un monde personnel effectuée.
|
||||
@@ -0,0 +1,80 @@
|
||||
# STORY-234 — recherche et filtres
|
||||
|
||||
Branche `codex/story-beta234`, base beta.233, commit `9c096b2`, Minecraft 26.3.
|
||||
|
||||
## Contrat
|
||||
|
||||
Story apparaît sous Progression dans Aventure, avant et après Atlas. Il reprend
|
||||
le cadre de Discovery : titre, séparateurs et Terminé / Done. La recherche et
|
||||
le filtre Toutes / Découvertes / Inconnues remplacent les boutons de catégories.
|
||||
|
||||
La liste réserve 100 histoires numérotées et inconnues, toutes désactivées.
|
||||
Toutes et Inconnues montrent les entrées correspondant à la recherche, dans
|
||||
l'ordre numérique ; Découvertes indique qu'aucune histoire n'est découverte.
|
||||
Une recherche sans résultat dispose d'un autre message. La recherche porte sur
|
||||
le numéro et le libellé visible, ignore la casse et les espaces extérieurs.
|
||||
Recherche et filtre se combinent et ramènent le défilement en haut. La saisie
|
||||
garde son focus et son curseur ; redimensionner garde recherche, filtre et
|
||||
défilement. Terminé et Échap reviennent directement au menu Pause d'origine.
|
||||
|
||||
Aventure comprend maintenant Discovery, Progression et Story. Les trois groupes
|
||||
de Pause conservent leur ordre avec ou sans carte. Aux petites hauteurs, les
|
||||
boutons et espaces se compactent pour éviter les chevauchements.
|
||||
|
||||
[Guide de rédaction](story/README.md) et [modèle Markdown](story/TEMPLATE.md) :
|
||||
un fichier `001.md` à `100.md`, avec numéro, titre, texte et idée de découverte
|
||||
facultative. Aucun récit n'est inventé, chargé ou révélé dans ce lot. Leur
|
||||
intégration et leurs déblocages seront des tickets suivants. Pas de nouveau
|
||||
paquet réseau, dépendance, sauvegarde ou changement de génération.
|
||||
|
||||
## Vérifications
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack -PsanctuaryFocusedTests=menus -PsanctuaryAtlasOnly=true
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true -PsanctuaryStory234ClientTests=true -PsanctuaryClientGraphicsBackend=vulkan
|
||||
```
|
||||
|
||||
Build final réussi en 2 min 34 s : quatre GameTests requis et contrôles purs du
|
||||
dépôt, dont les contrôles communautaires fichier, photos, demandes et recherche.
|
||||
Log : `build/story234-check-build-pack-final.log`. Le banc menus utilise la
|
||||
sélection Atlas existante ; le conflit du générateur sur le chemin par défaut,
|
||||
documenté en beta.232, n'est pas modifié par ce lot.
|
||||
|
||||
Parcours client final réussi en 30 s (`STORY234_PASS`), sur un monde plat neuf
|
||||
de développement en Survie sans commandes. Vulkan confirmé par le journal :
|
||||
MoltenVK 1.4.2 sur macOS, Apple M1. Log : `build/story234-client-vulkan-final.log`.
|
||||
|
||||
- FR/EN, GUI 2/3/4, fenêtre 1280 × 960, avec et sans Atlas : six actions
|
||||
Sanctuary dans l'ordre visuel et clavier, trois groupes sans chevauchement,
|
||||
groupe Discovery/Progression/Story centré verticalement.
|
||||
- 100 numéros consécutifs sans doublon, entrées désactivées, recherche du numéro
|
||||
100, espaces extérieurs et recherche en majuscules dans le libellé localisé.
|
||||
Recherche et filtre combinés, deux états vides distincts.
|
||||
- Saisie par événements natifs, focus et curseur conservés, molette et accès à
|
||||
la dernière entrée ; défilement remis en haut après recherche ou filtre.
|
||||
Redimensionnement réel à 1344 × 1008 : recherche, filtre et défilement conservés.
|
||||
- Terminé et Échap reviennent à la même Pause. La page reste une pause solo ;
|
||||
le record de progression serveur est identique avant et après les consultations.
|
||||
|
||||
21 captures dans `build/story234-screenshots/`. Captures inspectées : Story FR/2
|
||||
et EN/4, Pause sans Atlas FR/4 et avec Atlas EN/3, états Découvertes et recherche
|
||||
vides, recherche de l'histoire 100. L'indication de recherche reste visible
|
||||
lorsque le champ vide a le focus. Aucun essai OpenGL ou Windows n'est revendiqué.
|
||||
|
||||
## Livraison locale
|
||||
|
||||
`mod_version`, `pack_version` et `packwiz/pack.toml` sont alignés sur beta.234.
|
||||
Export [Sanctuary-beta.234.mrpack](../build/Sanctuary-beta.234.mrpack),
|
||||
12 775 502 octets : intégrité ZIP, Minecraft 26.3, Fabric Loader 0.19.5,
|
||||
un seul JAR Sanctuary embarqué, identique au JAR construit, sans monde ni
|
||||
module de test. Le JAR de production inclut Story et aucune classe de son banc
|
||||
client. Assets et données identiques à beta.233, hors les neuf traductions
|
||||
ajoutées dans chacune des langues FR/EN ; les libellés existants sont conservés.
|
||||
Reçu : `build/story234-mrpack-receipt.json`.
|
||||
|
||||
- JAR : `25dc161468679379e642f9746761905045d1135b6a300e3bc3f58f37a4c8f9a8`.
|
||||
- MRpack : `0b16514e4dec16d14061fd25c7cb42d53fdb28c73f06a1c6361d16d059977c69`.
|
||||
|
||||
Les trois commits couvrent l'interface, les tests/documentation et la livraison.
|
||||
Aucune publication distante, modification de canal packwiz, synchronisation
|
||||
Prism ou ouverture de monde personnel effectuée.
|
||||
@@ -0,0 +1,19 @@
|
||||
# Écrire les histoires de Sanctuary
|
||||
|
||||
Story accueille des textes personnels sur Sanctuary, son développement, Steve,
|
||||
Minecraft et ce que signifie habiter un jeu. Les numéros de 1 à 100 sont des
|
||||
repères durables ; ils n'imposent ni un ordre de lecture ni une chronologie.
|
||||
|
||||
Copier [le modèle](TEMPLATE.md) dans un fichier nommé `001.md` à `100.md`, puis
|
||||
remplacer le titre et écrire librement dans la section Texte. Les paragraphes,
|
||||
les hésitations et la voix de l'auteur peuvent être conservés tels quels.
|
||||
La section Idée de découverte est facultative et peut rester vide.
|
||||
|
||||
Transmettre les fichiers au fil des séances pour préparer les prochains tickets.
|
||||
Ne pas renuméroter une histoire déjà intégrée. La traduction éventuelle des récits
|
||||
se décide avec l'auteur ; les interfaces du jeu disposent de leurs libellés FR/EN.
|
||||
|
||||
En beta.234, les 100 histoires sont encore inconnues et désactivées. Ces fichiers
|
||||
sont des sources de rédaction : le mod ne les charge pas automatiquement.
|
||||
L'intégration des premiers textes et leurs conditions de découverte restent à
|
||||
implémenter, avec autorité serveur si elles dépendent de la partie.
|
||||
@@ -0,0 +1,12 @@
|
||||
# Titre de l'histoire
|
||||
|
||||
Numéro : 001
|
||||
|
||||
## Texte
|
||||
|
||||
Écrire ici le texte, avec autant de paragraphes que nécessaire.
|
||||
|
||||
## Idée de découverte (facultative)
|
||||
|
||||
Décrire éventuellement un geste, une rencontre, un lieu ou une autre idée qui
|
||||
pourrait révéler cette histoire. Cette note est une intention de conception.
|
||||
+2
-2
@@ -9,8 +9,8 @@ loom_version=1.17.20
|
||||
fabric_api_version=0.160.5+26.3
|
||||
|
||||
# Release counter: beta.001, .002, .003, ... (see docs/versioning.md).
|
||||
mod_version=beta.233
|
||||
pack_version=beta.233
|
||||
mod_version=beta.235
|
||||
pack_version=beta.235
|
||||
resource_pack_version=beta.121
|
||||
maven_group=fr.koka.sanctuary
|
||||
jei_version=30.32.0-sanctuary.3
|
||||
|
||||
@@ -337,6 +337,9 @@ tasks.named('processGametestResources') {
|
||||
def pause233Client = providers.gradleProperty('sanctuaryPause233ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('sanctuaryPause233ClientTests', pause233Client)
|
||||
if (pause233Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Pause233ClientChecks') }
|
||||
def story234Client = providers.gradleProperty('sanctuaryStory234ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('sanctuaryStory234ClientTests', story234Client)
|
||||
if (story234Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Story234ClientChecks') }
|
||||
def balance080Client = providers.gradleProperty('sanctuaryBalance080ClientTests').map { it.toBoolean() }.getOrElse(false)
|
||||
inputs.property('sanctuaryBalance080ClientTests', balance080Client)
|
||||
if (balance080Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Balance080ClientChecks') }
|
||||
|
||||
+39
-15
@@ -1,6 +1,7 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import fr.koka.sanctuary.community.*;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
@@ -15,6 +16,7 @@ import net.minecraft.client.gui.components.*;
|
||||
import net.minecraft.client.gui.screens.*;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.network.chat.contents.TranslatableContents;
|
||||
import net.minecraft.client.input.KeyEvent;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.*;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
@@ -46,7 +48,8 @@ public final class Pause233ClientChecks implements FabricClientGameTest {
|
||||
var server=world.getServer();
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.getAbilities().invulnerable=true;p.getInventory().setItem(6,new ItemStack(Items.OAK_LOG,17));p.getInventory().setItem(7,new ItemStack(Items.DIAMOND,3));});
|
||||
String legacy=UUID.randomUUID().toString();
|
||||
c.runOnClient(m->check(CommunityClient.request(null,"create",legacy,"","notice","event","Ancienne annonce","Texte historique",0,0,r->check(!r.has("error"),"Legacy-compatible notice seeded through the server")),"Seed request dispatched"));
|
||||
// Wait and dispatch together so the periodic following request cannot take the slot between them.
|
||||
c.waitFor(m->CommunityClient.request(null,"create",legacy,"","notice","event","Ancienne annonce","Texte historique",0,0,r->check(!r.has("error"),"Legacy-compatible notice seeded through the server")),200);
|
||||
c.waitFor(m->!CommunityClient.pending(),200);c.waitTicks(24);
|
||||
|
||||
c.runOnClient(m->{m.getWindow().setWindowed(1280,960);m.options.guiScale().set(2);m.resizeGui();CommunityClient.open(new PauseScreen(true),"notice","",true);edit(m.gui.screen()).setValue("Du bois pour le pont");body(m.gui.screen()).setValue("Une annonce commune, avec une demande facultative.");composer(m.gui.screen(),"notice");press(m.gui.screen(),"sanctuary.community.task_configure");press(m.gui.screen(),"sanctuary.community.location_current");press(m.gui.screen(),"sanctuary.community.materials");edit(m.gui.screen()).setValue("minecraft:oak_log");});
|
||||
@@ -86,7 +89,7 @@ public final class Pause233ClientChecks implements FabricClientGameTest {
|
||||
}
|
||||
}
|
||||
server.runOnServer(s->checked(()->{check(Arrays.equals(saved,Files.readAllBytes(s.getWorldPath(net.minecraft.world.level.storage.LevelResource.ROOT).resolve("data/sanctuary-community.json"))),"Reading/resizing old and new posts never rewrites stored categories");return null;}));
|
||||
System.out.println("PAUSE233_PASS: FR/EN GUI 2/3/4, identity/faction/battle at top, discovery/progression centered, options below, unified notice composer, descriptive task and photo publication, legacy category preservation");
|
||||
System.out.println("PAUSE233_PASS: FR/EN GUI 2/3/4, parallel Adventure/Community before Atlas, narrow Community/Adventure stack beside Atlas, Game directly below, centered navigation block, visual Tab order, unified notice composer, descriptive task and photo publication, legacy category preservation");
|
||||
}finally {intro.set(false);checked(()->{if(previous==null)Files.deleteIfExists(config);else Files.write(config,previous);Files.deleteIfExists(fixture);return null;});}
|
||||
}
|
||||
private FileCommunityStore store(net.minecraft.server.MinecraftServer s)throws Exception{return new FileCommunityStore(s.getWorldPath(net.minecraft.world.level.storage.LevelResource.ROOT).resolve("data/sanctuary-community.json"),serverId);}
|
||||
@@ -101,23 +104,44 @@ public final class Pause233ClientChecks implements FabricClientGameTest {
|
||||
if(kind.equals("notice")){var summary=widget(s,"task_summary");check(extra.getY()>body.getBottom()&&summary.getY()>extra.getBottom()&&publish.getY()>summary.getBottom(),"Text, request configuration, summary and Publish are in order");}
|
||||
else {check(extra.getBottom()<edit(s).getY()&&publish.getY()>body.getBottom(),"Screenshot selector is before title, then text and Publish");widgets(s).filter(CommunityPhotoWidget.class::isInstance).map(CommunityPhotoWidget.class::cast).forEach(w->check(w.getBottom()<extra.getY(),"Photo preview is first, before selector and title"));}
|
||||
}
|
||||
private static void pause(Screen s,boolean unlocked){
|
||||
static void pause(Screen s,boolean unlocked){
|
||||
check((((PauseAtlasAccess)s).sanctuary$pauseMap()!=null)==unlocked,"Pause map appears only after Atlas purchase");
|
||||
var keys=Set.of("sanctuary.progression.blocks","sanctuary.progression.progress","sanctuary.progression.identity","sanctuary.cycle.faction","sanctuary.arena.title");
|
||||
var nav=s.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->keys.contains(key(b))).toList();check(nav.size()==5,"Five Sanctuary navigation actions");
|
||||
int top=nav.stream().mapToInt(AbstractWidget::getY).min().orElseThrow(),bottom=nav.stream().mapToInt(AbstractWidget::getBottom).max().orElseThrow();
|
||||
int options=s.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->key(b).equals("menu.options")).findFirst().orElseThrow().getY();
|
||||
check(bottom+8<=options,"Navigation stays separated from Options/World Options/Quit");
|
||||
var keys=Set.of("sanctuary.progression.blocks","sanctuary.progression.progress","sanctuary.story.title","sanctuary.progression.identity","sanctuary.cycle.faction","sanctuary.arena.title");
|
||||
var nav=s.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->keys.contains(key(b))).toList();check(nav.size()==6,"Six Sanctuary navigation actions");
|
||||
var options=s.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->key(b).equals("menu.options")).findFirst().orElseThrow();
|
||||
var worldOptions=s.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->key(b).equals("options.worldOptions.button")).findFirst().orElseThrow();
|
||||
var quit=s.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->Set.of("menu.returnToMenu","menu.disconnect").contains(key(b))).findFirst().orElseThrow();
|
||||
var community=widget(s,"pause_group_community");var adventure=widget(s,"pause_group_adventure");var game=widget(s,"pause_group_game");
|
||||
check(community.getY()==50&&community.getBottom()<nav.get(0).getY()&&adventure.getBottom()<nav.get(3).getY()&&game.getBottom()<options,"All three headings precede their buttons");
|
||||
check(nav.stream().map(CommunityPausePanels::key).toList().equals(List.of("sanctuary.progression.identity","sanctuary.cycle.faction","sanctuary.arena.title","sanctuary.progression.blocks","sanctuary.progression.progress")),"Same visual and keyboard order with or without the map");
|
||||
check(nav.get(0).getBottom()<nav.get(1).getY()&&nav.get(1).getBottom()<nav.get(2).getY()&&nav.get(2).getBottom()<adventure.getY()&&nav.get(3).getBottom()<nav.get(4).getY()&&nav.get(4).getBottom()<game.getY(),"Three distinct titled groups without overlap");
|
||||
double center=(nav.get(3).getY()+nav.get(4).getBottom())/2.;check(Math.abs(center-s.height/2.)<=.5,"Only discovery/progression are vertically centered");
|
||||
check(nav.stream().map(CommunityPausePanels::key).toList().equals(unlocked
|
||||
?List.of("sanctuary.progression.identity","sanctuary.cycle.faction","sanctuary.arena.title","sanctuary.progression.blocks","sanctuary.progression.progress","sanctuary.story.title")
|
||||
:List.of("sanctuary.progression.blocks","sanctuary.progression.identity","sanctuary.progression.progress","sanctuary.cycle.faction","sanctuary.story.title","sanctuary.arena.title")),"Navigation follows visual order in both layouts");
|
||||
for(var button:nav)check(s.getFont().width(button.getMessage())+8<=button.getWidth(),"Navigation labels fit with native button padding");
|
||||
check(nav.stream().allMatch(b->b.getBottom()<game.getY())&&game.getBottom()<options.getY()&&options.getBottom()<worldOptions.getY()&&worldOptions.getBottom()<quit.getY(),"Game follows below Sanctuary navigation");
|
||||
check(options.getX()==nav.get(0).getX()&&worldOptions.getX()==options.getX()&&quit.getX()==options.getX()&&worldOptions.getWidth()==options.getWidth()&&quit.getWidth()==options.getWidth(),"Native Game actions share the navigation block width");
|
||||
int bodyTop=s.height<280?46:50,bodyBottom=s.height-39;
|
||||
check(Math.abs((Math.min(adventure.getY(),community.getY())+quit.getBottom())/2.-(bodyTop+bodyBottom)/2.)<=.5&&quit.getBottom()+8<=bodyBottom,"Complete block centered with clear space below Game");
|
||||
// KeyEvent carries the physical key and the SDL shortcut key separately.
|
||||
s.setFocused(nav.get(0));s.keyPressed(new KeyEvent(InputConstants.KEY_TAB,'\t',0));check(s.getFocused()==nav.get(1),"Tab follows the second visible action");
|
||||
s.keyPressed(new KeyEvent(InputConstants.KEY_TAB,'\t',0));check(s.getFocused()==nav.get(2),"Tab follows the third visible action");
|
||||
if(!unlocked){
|
||||
for(var b:nav)check(Math.abs(b.getX()+b.getWidth()/2.-s.width/2.)<=.5,"Navigation centered horizontally");
|
||||
check(widget(s,"article").getX()==8&&widget(s,"notice").getX()>nav.getFirst().getRight(),"Gazette remains left and Notice Board right");
|
||||
check(adventure.getY()==community.getY()&&adventure.getX()==nav.get(0).getX()&&community.getX()==nav.get(1).getX()&&adventure.getBottom()<nav.get(0).getY(),"Both headings align above their columns");
|
||||
for(int row=0;row<3;row++) {
|
||||
var left=nav.get(2*row);var right=nav.get(2*row+1);
|
||||
check(left.getY()==right.getY()&&left.getHeight()==right.getHeight()&&left.getRight()<right.getX(),"Two parallel columns without overlap");
|
||||
if(row>0)check(nav.get(2*row-2).getBottom()<left.getY(),"Rows remain separated");
|
||||
}
|
||||
check(options.getRight()==nav.get(1).getRight(),"Game spans both columns before Atlas");
|
||||
check(Math.abs((nav.get(0).getX()+nav.get(1).getRight())/2.-s.width/2.)<=.5,"Two-column block centered horizontally");
|
||||
check(widget(s,"article").getX()==8&&widget(s,"article").getRight()<nav.get(0).getX()&&widget(s,"notice").getX()>nav.get(1).getRight(),"Gazette remains left and Notice Board right");
|
||||
}
|
||||
else {
|
||||
check(community.getBottom()<nav.get(0).getY()&&nav.get(2).getBottom()<adventure.getY()&&adventure.getBottom()<nav.get(3).getY(),"Community then Adventure stacked above Game with Atlas");
|
||||
for(int i=0;i<nav.size();i++) {
|
||||
var button=nav.get(i);check(button.getX()==8&&button.getWidth()==options.getWidth()&&button.getWidth()<=132,"All actions use one narrow left column");
|
||||
if(i>0)check(nav.get(i-1).getBottom()<button.getY(),"Stacked buttons remain separated");
|
||||
}
|
||||
var map=((PauseAtlasAccess)s).sanctuary$pauseMap();check(nav.get(0).getRight()<map.getX()&&map.getRight()<widget(s,"article").getX()&&widget(s,"article").getX()==widget(s,"notice").getX(),"Narrow navigation left, map center and community feeds right");
|
||||
}
|
||||
else check(nav.getFirst().getX()==8&&widget(s,"article").getX()==widget(s,"notice").getX(),"Unlocked map retains left navigation and community feeds on the right");
|
||||
}
|
||||
private void shot(ClientGameTestContext c,String name){var path=c.takeScreenshot("pause233-"+name);checked(()->{var dir=game.resolve("../../../../../build/pause233-screenshots").normalize();Files.createDirectories(dir);Files.copy(path,dir.resolve(name+".png"),StandardCopyOption.REPLACE_EXISTING);return null;});}
|
||||
private interface Checked<T>{T run()throws Exception;}
|
||||
|
||||
+5
-4
@@ -49,12 +49,13 @@ public final class Solo232ClientChecks implements FabricClientGameTest {
|
||||
c.runOnClient(m->{m.getWindow().setWindowed(1280,960);m.options.guiScale().set(scale);m.resizeGui();m.getLanguageManager().setSelected(language);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.gui.setScreen(new PauseScreen(true));});
|
||||
c.waitForScreen(PauseScreen.class);c.waitTicks(3);
|
||||
c.runOnClient(m->{var screen=m.gui.screen();var map=((PauseAtlasAccess)screen).sanctuary$pauseMap();check((map!=null)==unlocked,"Map widget exists only after Atlas purchase");
|
||||
var buttons=screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->Set.of("sanctuary.progression.blocks","sanctuary.progression.progress","sanctuary.progression.identity","sanctuary.cycle.faction","sanctuary.arena.title","menu.options","options.worldOptions.button","menu.returnToMenu","menu.disconnect").contains(CommunityPausePanels.key(b))).toList();
|
||||
check(buttons.size()==8,"All eight pause menu actions remain present");
|
||||
var buttons=screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->Set.of("sanctuary.progression.blocks","sanctuary.progression.progress","sanctuary.story.title","sanctuary.progression.identity","sanctuary.cycle.faction","sanctuary.arena.title","menu.options","options.worldOptions.button","menu.returnToMenu","menu.disconnect").contains(CommunityPausePanels.key(b))).toList();
|
||||
check(buttons.size()==9,"All nine pause menu actions remain present");
|
||||
var article=heading(screen,"sanctuary.community.article");var notice=heading(screen,"sanctuary.community.notice");
|
||||
if(!unlocked){check(article.getX()==8&&article.getY()==50&¬ice.getY()==50,"Locked map uses full-height Gazette on the left and Notice Board on the right");for(var button:buttons)check(article.getRight()<button.getX()&¬ice.getX()>button.getRight(),"Community columns do not overlap centered navigation");}
|
||||
if(!unlocked){check(article.getX()==8&&article.getY()==(screen.height<280?46:50)&¬ice.getY()==article.getY(),"Locked map uses full-height Gazette on the left and Notice Board on the right");for(var button:buttons)check(article.getRight()<button.getX()&¬ice.getX()>button.getRight(),"Community columns do not overlap centered navigation");}
|
||||
else check(article.getX()==notice.getX()&&article.getY()<notice.getY(),"Unlocked map retains both community feeds stacked on the right");
|
||||
for(var button:buttons){check(button.getY()>=50&&button.getBottom()<=screen.height-33,"Button stays in pause body");if(!unlocked)check(Math.abs(button.getX()+button.getWidth()/2.-screen.width/2.)<=.5,"Locked-map navigation is horizontally centered");else check(button.getX()==8,"Unlocked layout retains original left column");}
|
||||
for(var button:buttons)check(button.getY()>=50&&button.getBottom()<=screen.height-33,"Button stays in pause body");
|
||||
Pause233ClientChecks.pause(screen,unlocked);
|
||||
});
|
||||
String shot="solo232-pause-"+(unlocked?"unlocked-":"locked-")+language+"-"+scale;
|
||||
var image=c.takeScreenshot(shot);checked(()->{var dir=net.fabricmc.loader.api.FabricLoader.getInstance().getGameDir().resolve("../../../../../build/solo232-screenshots").normalize();java.nio.file.Files.createDirectories(dir);java.nio.file.Files.copy(image,dir.resolve(shot+".png"),java.nio.file.StandardCopyOption.REPLACE_EXISTING);});
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import fr.koka.sanctuary.gametest.Starter039TestSupport;
|
||||
import fr.koka.sanctuary.progression.*;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.client.gui.components.*;
|
||||
import net.minecraft.client.gui.screens.*;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
import net.minecraft.client.input.CharacterEvent;
|
||||
import net.minecraft.client.input.KeyEvent;
|
||||
import net.minecraft.network.chat.contents.TranslatableContents;
|
||||
import static fr.koka.sanctuary.client.Community154ClientChecks.*;
|
||||
|
||||
/** Real native events on Story and Pause, using a fresh non-operator solo world. */
|
||||
public final class Story234ClientChecks implements FabricClientGameTest {
|
||||
@Override public void runTest(ClientGameTestContext c) {
|
||||
var intro=new AtomicBoolean(true);
|
||||
fr.koka.sanctuary.testing.QuickTestWorld.enable();
|
||||
c.runOnClient(m->{m.options.renderDistance().set(5);m.options.simulationDistance().set(5);m.options.pauseOnLostFocus=false;});
|
||||
ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen screen){
|
||||
IntroClient.setQuickTestIntro(true);Starter039TestSupport.selectFirst(screen);
|
||||
screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
|
||||
press(screen,"sanctuary.progression.enter");intro.set(false);
|
||||
}});
|
||||
try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Story234 "+UUID.randomUUID());s.setSeed("234");s.setAllowCommands(false);s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("minecraft:flat")).findFirst().orElseThrow());}).create()) {
|
||||
c.waitFor(m->ProgressionClient.active()&&m.player!=null&&m.gui.screen()==null&&!IntroSpawnFlash.active(),1200);
|
||||
var server=world.getServer();
|
||||
server.runOnServer(s->s.getPlayerList().getPlayers().getFirst().getAbilities().invulnerable=true);
|
||||
for(boolean unlocked:new boolean[]{false,true}) {
|
||||
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var r=ProgressRecord.INITIAL;if(unlocked)r=r.buy("atlas",r.cost("atlas",ProgressionService.session(s).config));p.setAttached(ProgressionService.RECORD,r.json());ProgressEffects.apply(p,r);ProgressionService.send(p,"");});
|
||||
c.waitFor(m->ProgressionClient.progress.atlas()==unlocked,200);
|
||||
String before=server.computeOnServer(s->s.getPlayerList().getPlayers().getFirst().getAttached(ProgressionService.RECORD));
|
||||
for(String language:List.of("fr_fr","en_us"))for(int scale:List.of(2,3,4)) {
|
||||
c.runOnClient(m->{m.getWindow().setWindowed(1280,960);m.options.guiScale().set(scale);m.resizeGui();m.getLanguageManager().setSelected(language);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.gui.setScreen(new PauseScreen(true));});
|
||||
c.waitTicks(3);
|
||||
c.runOnClient(m->Pause233ClientChecks.pause(m.gui.screen(),unlocked));
|
||||
shot(c,"pause-"+unlocked+"-"+language+"-"+scale);
|
||||
c.runOnClient(m->{var pause=m.gui.screen();press(pause,"sanctuary.story.title");check(m.gui.screen() instanceof StoryScreen,"Story opens from Pause without Atlas gating");check(m.gui.screen().isPauseScreen(),"Story retains solo pause");check(m.gui.screen().getTitle().getString().equals("Story"),"Story has its own accessible screen title");check(edit(m.gui.screen()).getValue().isEmpty()&&cycle(m.gui.screen()).getValue().toString().equals("ALL"),"Empty search and All on opening");assertEntries(m.gui.screen(),100);layout(m.gui.screen());});
|
||||
shot(c,"story-"+language+"-"+scale);
|
||||
c.runOnClient(m->{var s=m.gui.screen();var search=edit(s);s.setFocused(search);for(int codepoint:"100".codePoints().toArray()){check(s.charTyped(new CharacterEvent(codepoint)),"Native typing handled");check(s.getFocused()==search&&search.isFocused(),"List refresh retains search focus");}check(search.getValue().equals("100")&&search.getCursorPosition()==3,"Continuous typing retains cursor");assertEntries(s,1);check(entries(s).getFirst().getMessage().getString().equals(StoryScreen.label("unknown_entry",100).getString()),"Search selects story 100");search.setValue(" 100 ");assertEntries(s,1);cycle(s).onPress(enter());check(has(s,"sanctuary.story.no_discoveries")&&!has(s,"sanctuary.story.no_results")&&entries(s).isEmpty(),"Discovered has a distinct empty state");cycle(s).onPress(enter());check(cycle(s).getValue().toString().equals("UNKNOWN"),"Third filter is Unknown");assertEntries(s,1);search.setValue("absent-story");check(has(s,"sanctuary.story.no_results")&&!has(s,"sanctuary.story.no_discoveries"),"Search empty state is distinct");search.setValue(language.equals("fr_fr")?"INCONNUE":"UNKNOWN");assertEntries(s,100);var area=area(s);check(s.mouseScrolled(area.getX()+5,area.getY()+5,0,-5)&&area.scrollAmount()>0,"Native wheel scrolls Story");area.setScrollAmount(area.maxScrollAmount());check(entries(s).getLast().getBottom()<=area.getBottom(),"Story 100 can be reached at the end");area.setScrollAmount(100);});
|
||||
c.runOnClient(m->{m.getWindow().setWindowed(1344,1008);m.resizeGui();});c.waitTicks(3);
|
||||
c.runOnClient(m->{var s=m.gui.screen();check(edit(s).getValue().equals(language.equals("fr_fr")?"INCONNUE":"UNKNOWN")&&cycle(s).getValue().toString().equals("UNKNOWN"),"Resize retains query and filter");check(area(s).scrollAmount()==100,"Resize retains scroll position");layout(s);edit(s).setValue("100");check(area(s).scrollAmount()==0,"Search resets scroll");edit(s).setValue("");area(s).setScrollAmount(100);cycle(s).onPress(enter());check(cycle(s).getValue().toString().equals("ALL")&&area(s).scrollAmount()==0,"Filter resets scroll and cycles back to All");assertEntries(s,100);});
|
||||
if(!unlocked&&language.equals("fr_fr")&&scale==2) {
|
||||
c.runOnClient(m->cycle(m.gui.screen()).onPress(enter()));shot(c,"empty-discovered");
|
||||
c.runOnClient(m->{cycle(m.gui.screen()).onPress(enter());edit(m.gui.screen()).setValue("absent-story");});shot(c,"empty-search");
|
||||
c.runOnClient(m->edit(m.gui.screen()).setValue("100"));shot(c,"search-100");
|
||||
}
|
||||
c.runOnClient(m->{var story=(StoryScreen)m.gui.screen();var pause=story.parent;story.keyPressed(new KeyEvent(InputConstants.KEY_ESCAPE,0,0));check(m.gui.screen()==pause,"Escape returns directly to original Pause");press(pause,"sanctuary.story.title");press(m.gui.screen(),"gui.done");check(m.gui.screen()==pause,"Done returns directly to original Pause");m.gui.setScreen(null);});
|
||||
c.waitTicks(2);
|
||||
}
|
||||
server.runOnServer(s->check(before.equals(s.getPlayerList().getPlayers().getFirst().getAttached(ProgressionService.RECORD)),"Reading and searching Story leave authoritative progression unchanged"));
|
||||
}
|
||||
System.out.println("STORY234_PASS: Vulkan FR/EN GUI 2/3/4, Atlas gating, 100 disabled entries, live search, three filters, distinct empty states, typing focus, wheel, resize, Escape/Done, unchanged progression");
|
||||
c.runOnClient(m->m.gui.setScreen(null));
|
||||
}finally{intro.set(false);}
|
||||
}
|
||||
|
||||
private static List<Button> entries(Screen s){return widgets(s).filter(Button.class::isInstance).map(Button.class::cast).filter(b->key(b).equals("sanctuary.story.unknown_entry")).toList();}
|
||||
private static String key(AbstractWidget w){return w.getMessage().getContents() instanceof TranslatableContents t?t.getKey():"";}
|
||||
private static CycleButton<?> cycle(Screen s){return widgets(s).filter(CycleButton.class::isInstance).map(CycleButton.class::cast).findFirst().orElseThrow();}
|
||||
private static AbstractScrollArea area(Screen s){return widgets(s).filter(AbstractScrollArea.class::isInstance).map(AbstractScrollArea.class::cast).findFirst().orElseThrow();}
|
||||
private static void assertEntries(Screen s,int expected){var rows=entries(s);check(rows.size()==expected,"Expected "+expected+" entries, got "+rows.size());check(rows.stream().allMatch(b->!b.active),"Unknown entries stay disabled");if(expected==100)for(int i=0;i<100;i++)check(rows.get(i).getMessage().getString().equals(StoryScreen.label("unknown_entry",i+1).getString()),"Numbers stay ordered without gaps or duplicates");}
|
||||
private static void layout(Screen s){var search=edit(s);var filter=cycle(s);var list=area(s);check(search.getBottom()<filter.getY()&&filter.getBottom()<list.getY(),"Search, filter and list do not overlap");for(var child:s.children())if(child instanceof AbstractWidget w)check(w.getX()>=0&&w.getRight()<=s.width&&w.getY()>=0&&w.getBottom()<=s.height,"Top-level widget stays inside the screen");var done=widgets(s).filter(Button.class::isInstance).map(Button.class::cast).filter(b->key(b).equals("gui.done")).findFirst().orElseThrow();check(list.getBottom()<done.getY(),"List leaves room for native footer");}
|
||||
private static void shot(ClientGameTestContext c,String name){var source=c.takeScreenshot("story234-"+name);try{var dir=FabricLoader.getInstance().getGameDir().resolve("../../../../../build/story234-screenshots").normalize();Files.createDirectories(dir);Files.copy(source,dir.resolve(name+".png"),StandardCopyOption.REPLACE_EXISTING);}catch(Exception e){throw new AssertionError(e);}}
|
||||
}
|
||||
@@ -17,7 +17,11 @@ abstract class BlocodexFrame extends Screen {
|
||||
private HeaderAndFooterLayout frame;
|
||||
|
||||
protected BlocodexFrame(Screen parent) {
|
||||
super(ProgressionService.label("title"));
|
||||
this(parent,ProgressionService.label("title"));
|
||||
}
|
||||
|
||||
protected BlocodexFrame(Screen parent,net.minecraft.network.chat.Component pageTitle) {
|
||||
super(pageTitle);
|
||||
this.parent=parent;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,35 +23,45 @@ public final class CommunityPausePanels implements AutoCloseable {
|
||||
private boolean closed;
|
||||
public CommunityPausePanels(Screen screen,Consumer<AbstractWidget> add,List<Button> nativeButtons) {
|
||||
this.screen=screen;
|
||||
int margin=8,gap=10,top=50,bottom=screen.height-39;
|
||||
int left=Math.min(132,Math.max(76,screen.width/5)),right=Math.min(244,Math.max(104,screen.width*31/100));
|
||||
int centerX=margin+left+gap,rightX=screen.width-margin-right,centerWidth=rightX-gap-centerX;
|
||||
boolean compact=screen.height<280;
|
||||
boolean unlocked=ProgressionClient.progress.atlas();
|
||||
int menuWidth=unlocked?left:Math.max(left,Math.min(200,2*(rightX-gap)-screen.width));
|
||||
int margin=8,gap=10,top=compact?46:50,bottom=screen.height-39;
|
||||
String[] sections={"blocks","identity","progress","faction","story","battle"};
|
||||
int columnGap=compact?4:8;
|
||||
int minimumWidth=Arrays.stream(sections).map(CommunityPausePanels::sectionLabel).mapToInt(Minecraft.getInstance().font::width).max().orElse(0)+8;
|
||||
int columnWidth=Math.max(minimumWidth,(Math.min(264,screen.width/3)-columnGap)/2);
|
||||
int menuWidth=unlocked?Math.max(minimumWidth,Math.min(132,Math.max(76,screen.width/5))):2*columnWidth+columnGap;
|
||||
int right=unlocked?Math.min(244,Math.max(104,screen.width*31/100))
|
||||
:Math.min(244,(screen.width-2*margin-menuWidth-2*gap)/2);
|
||||
int centerX=margin+menuWidth+gap,rightX=screen.width-margin-right,centerWidth=rightX-gap-centerX;
|
||||
int menuX=unlocked?margin:(screen.width-menuWidth)/2;
|
||||
int optionHeight=screen.height<280?16:20,navGap=screen.height<280?1:4,groupGap=screen.height<280?3:12,titleHeight=screen.height<280?10:12;
|
||||
int optionsTop=bottom-(3*optionHeight+8);
|
||||
int navHeight=Math.min(20,Math.max(11,(optionsTop-top-8-4*navGap-groupGap)/5));
|
||||
// Leave room for the upper group and its headings above the centered pair.
|
||||
navHeight=Math.min(navHeight,Math.max(11,(screen.height-2*top-4*titleHeight-5*navGap-2*groupGap)/8));
|
||||
int communityX=unlocked?menuX:menuX+columnWidth+columnGap;
|
||||
int navGap=compact?(unlocked?1:2):4,groupGap=compact?(unlocked?2:8):12,titleHeight=compact&&unlocked?10:12;
|
||||
int groupCount=unlocked?3:2;
|
||||
int navHeight=Math.min(compact?14:20,Math.max(11,(bottom-top-16-groupCount*titleHeight-(groupCount-1)*groupGap-2*groupCount*navGap)/(3*groupCount)));
|
||||
int step=navHeight+navGap;
|
||||
int upperTop=top+titleHeight;
|
||||
int pairSize=2*navHeight+navGap;
|
||||
int pairTop=Math.max(upperTop+2*step+navHeight+groupGap+titleHeight,Math.min((screen.height-pairSize)/2,optionsTop-titleHeight-pairSize-groupGap));
|
||||
int sectionHeight=titleHeight+3*navHeight+2*navGap;
|
||||
int menuHeight=groupCount*sectionHeight+(groupCount-1)*groupGap;
|
||||
// Use a narrow stack beside the map, or parallel groups before Atlas.
|
||||
int menuTop=top+Math.max(0,(bottom-top-menuHeight)/2);
|
||||
int adventureTop=unlocked?menuTop+sectionHeight+groupGap:menuTop;
|
||||
int communityTop=menuTop;
|
||||
int gameTop=menuTop+(groupCount-1)*(sectionHeight+groupGap);
|
||||
int optionsTop=gameTop+titleHeight;
|
||||
add.accept(new Frame(screen.width,screen.height));
|
||||
date=new Calendar(margin,3,left);
|
||||
date=new Calendar(margin,3,Math.min(132,Math.max(76,screen.width/5)));
|
||||
add.accept(date);
|
||||
bulletin=new PauseTextButton(centerX,3,screen.width-margin-centerX,38,text("bulletin"),()->open(screen,"bulletin",items("bulletins").isEmpty()?"":str(items("bulletins").get(0).getAsJsonObject(),"id"),false));
|
||||
add.accept(bulletin);
|
||||
add.accept(new StringWidget(menuX,top,menuWidth,9,text("pause_group_community").withStyle(net.minecraft.ChatFormatting.GRAY),Minecraft.getInstance().font));
|
||||
add.accept(new StringWidget(menuX,pairTop-titleHeight,menuWidth,9,text("pause_group_adventure").withStyle(net.minecraft.ChatFormatting.GRAY),Minecraft.getInstance().font));
|
||||
add.accept(new StringWidget(menuX,optionsTop-titleHeight,menuWidth,9,text("pause_group_game").withStyle(net.minecraft.ChatFormatting.GRAY),Minecraft.getInstance().font));
|
||||
String[] sections={"identity","faction","battle","blocks","progress"};
|
||||
add.accept(new StringWidget(menuX,adventureTop,unlocked?menuWidth:columnWidth,9,text("pause_group_adventure").withStyle(net.minecraft.ChatFormatting.GRAY),Minecraft.getInstance().font));
|
||||
add.accept(new StringWidget(communityX,communityTop,unlocked?menuWidth:columnWidth,9,text("pause_group_community").withStyle(net.minecraft.ChatFormatting.GRAY),Minecraft.getInstance().font));
|
||||
add.accept(new StringWidget(menuX,gameTop,menuWidth,9,text("pause_group_game").withStyle(net.minecraft.ChatFormatting.GRAY),Minecraft.getInstance().font));
|
||||
if(unlocked)sections=new String[]{"identity","faction","battle","blocks","progress","story"};
|
||||
for(String target:sections) {
|
||||
int y=switch(target){case "blocks"->pairTop;case "progress"->pairTop+step;case "identity"->upperTop;case "faction"->upperTop+step;default->upperTop+2*step;};
|
||||
Component label=switch(target){case "faction"->fr.koka.sanctuary.cycle.CycleService.label("faction");case "battle"->fr.koka.sanctuary.arena.Arenas.text("title");default->fr.koka.sanctuary.progression.ProgressionService.label(target);};
|
||||
add.accept(Button.builder(label,b->{switch(target){case "faction"->CycleClient.open(screen,"faction");case "battle"->ArenaClient.open(screen);default->ProgressionClient.open(screen,target);}})
|
||||
.bounds(menuX,y,menuWidth,navHeight).build());
|
||||
boolean adventure=target.equals("blocks")||target.equals("progress")||target.equals("story");
|
||||
int y=(adventure?adventureTop:communityTop)+titleHeight+step*switch(target){case "blocks","identity"->0;case "progress","faction"->1;default->2;};
|
||||
add.accept(Button.builder(sectionLabel(target),b->{switch(target){case "faction"->CycleClient.open(screen,"faction");case "battle"->ArenaClient.open(screen);default->ProgressionClient.open(screen,target);}})
|
||||
.bounds(adventure?menuX:communityX,y,unlocked?menuWidth:columnWidth,navHeight).build());
|
||||
}
|
||||
map=unlocked?new PauseAtlasWidget(screen,centerX,top,centerWidth,Math.max(30,bottom-top)):null;
|
||||
if(map!=null)add.accept(map);
|
||||
@@ -60,13 +70,16 @@ public final class CommunityPausePanels implements AutoCloseable {
|
||||
int feedHeight=(bottom-top-gap)/2;
|
||||
articles=new PauseCommunityFeed(screen,add,unlocked?rightX:margin,top,right,unlocked?feedHeight:bottom-top,"article");
|
||||
notices=new PauseCommunityFeed(screen,add,rightX,unlocked?top+feedHeight+gap:top,right,unlocked?bottom-top-feedHeight-gap:bottom-top,"notice");
|
||||
place(nativeButtons,add,"menu.options",menuX,optionsTop,menuWidth,optionHeight);
|
||||
place(nativeButtons,add,"options.worldOptions.button",menuX,optionsTop+optionHeight+4,menuWidth,optionHeight);
|
||||
place(nativeButtons,add,"menu.options",menuX,optionsTop,menuWidth,navHeight);
|
||||
place(nativeButtons,add,"options.worldOptions.button",menuX,optionsTop+step,menuWidth,navHeight);
|
||||
for(var button:nativeButtons) if(key(button).equals("menu.returnToMenu") || key(button).equals("menu.disconnect")) {
|
||||
button.setRectangle(menuWidth,optionHeight,menuX,optionsTop+2*(optionHeight+4));button.setTooltip(Tooltip.create(button.getMessage()));add.accept(button);
|
||||
button.setRectangle(menuWidth,navHeight,menuX,optionsTop+2*step);button.setTooltip(Tooltip.create(button.getMessage()));add.accept(button);
|
||||
}
|
||||
updateHeader();
|
||||
}
|
||||
private static Component sectionLabel(String target) {
|
||||
return switch(target){case "story"->StoryScreen.label("title");case "faction"->fr.koka.sanctuary.cycle.CycleService.label("faction");case "battle"->fr.koka.sanctuary.arena.Arenas.text("title");default->fr.koka.sanctuary.progression.ProgressionService.label(target);};
|
||||
}
|
||||
public static String key(Button button) { return button.getMessage().getContents() instanceof TranslatableContents t?t.getKey():""; }
|
||||
private static void place(List<Button> buttons,Consumer<AbstractWidget> add,String key,int x,int y,int width) {
|
||||
place(buttons,add,key,x,y,width,20);
|
||||
|
||||
@@ -30,6 +30,7 @@ public final class ProgressionClient {
|
||||
public static void open(Screen parent,String section) {
|
||||
var gui=Minecraft.getInstance().gui;
|
||||
if (section.equals("blocks")) gui.setScreen(new SanctuaryBlocodexScreen(parent));
|
||||
else if (section.equals("story") && active()) gui.setScreen(new StoryScreen(parent));
|
||||
else if (active()) gui.setScreen(section.equals("history") ? new SanctuaryAtlasScreen(parent) : new ProgressionScreen(parent,section));
|
||||
}
|
||||
public static void register() {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import java.util.Locale;
|
||||
import net.minecraft.client.gui.components.*;
|
||||
import net.minecraft.client.gui.layouts.LinearLayout;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
/** The first Story catalogue reserves 100 unknown stories without inventing discoveries. */
|
||||
public final class StoryScreen extends BlocodexFrame {
|
||||
private enum Filter {
|
||||
ALL, DISCOVERED, UNKNOWN;
|
||||
Component label() { return StoryScreen.label("filter_"+name().toLowerCase(Locale.ROOT)); }
|
||||
}
|
||||
|
||||
private String query="";
|
||||
private Filter filter=Filter.ALL;
|
||||
private ScrollableLayout scroll;
|
||||
|
||||
public StoryScreen(Screen parent) { super(parent,label("title")); }
|
||||
|
||||
public static Component label(String key,Object... args) {
|
||||
return Component.translatable("sanctuary.story."+key,args);
|
||||
}
|
||||
|
||||
@Override protected void init() {
|
||||
initFrame("story",label("title"));
|
||||
var search=addRenderableWidget(new EditBox(font,contentX,contentTop,contentWidth,20,label("search")));
|
||||
search.setMaxLength(128);
|
||||
search.setValue(query);
|
||||
// Native hints disappear on focus; a suggestion keeps the empty search identifiable.
|
||||
search.setSuggestion(query.isEmpty()?label("search").getString():null);
|
||||
search.setResponder(value->{query=value;search.setSuggestion(value.isEmpty()?label("search").getString():null);refreshList(0);});
|
||||
addRenderableWidget(CycleButton.builder(Filter::label,filter).withValues(Filter.values())
|
||||
.create(contentX,contentTop+24,contentWidth,20,label("filter"),(button,value)->{
|
||||
filter=value;refreshList(0);
|
||||
}));
|
||||
contentTop+=50;
|
||||
refreshList(scroll==null?0:scroll.getScrollAmount());
|
||||
setInitialFocus(search);
|
||||
}
|
||||
|
||||
/** Replace only list widgets: search/cursor and keyboard focus remain untouched. */
|
||||
private void refreshList(double offset) {
|
||||
if(scroll!=null)scroll.visitWidgets(this::removeWidget);
|
||||
var contents=LinearLayout.vertical().spacing(4);
|
||||
String needle=query.strip().toLowerCase(Locale.ROOT);
|
||||
int count=0;
|
||||
if(filter!=Filter.DISCOVERED)for(int number=1;number<=100;number++) {
|
||||
Component name=label("unknown_entry",number);
|
||||
if(!name.getString().toLowerCase(Locale.ROOT).contains(needle))continue;
|
||||
var entry=contents.addChild(Button.builder(name,button->{}).width(contentWidth-18).build());
|
||||
entry.active=false;
|
||||
count++;
|
||||
}
|
||||
if(count==0)contents.addChild(new MultiLineTextWidget(label(filter==Filter.DISCOVERED?"no_discoveries":"no_results"),font)
|
||||
.setMaxWidth(contentWidth-18));
|
||||
int height=Math.max(20,contentBottom-contentTop);
|
||||
scroll=new ScrollableLayout(minecraft,contents,height,ScrollableLayout.ReserveStrategy.RIGHT);
|
||||
scroll.setMinHeight(height);
|
||||
scroll.arrangeElements();scroll.setX(contentX);scroll.setY(contentTop);
|
||||
scroll.visitWidgets(widget->{addRenderableWidget(widget);if(widget instanceof AbstractScrollArea area)area.setScrollAmount(offset);});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,13 @@
|
||||
{
|
||||
"sanctuary.story.title": "Story",
|
||||
"sanctuary.story.search": "Search stories…",
|
||||
"sanctuary.story.filter": "Discovery",
|
||||
"sanctuary.story.filter_all": "All",
|
||||
"sanctuary.story.filter_discovered": "Discovered",
|
||||
"sanctuary.story.filter_unknown": "Unknown",
|
||||
"sanctuary.story.unknown_entry": "Story %s — Unknown",
|
||||
"sanctuary.story.no_discoveries": "No stories discovered yet.",
|
||||
"sanctuary.story.no_results": "No stories match the search.",
|
||||
"biome.sanctuary.diagonal_lush227": "Mangrove Lush Caves",
|
||||
"biome.sanctuary.diagonal_dripstone227": "Mangrove Dripstone Caves",
|
||||
"biome.sanctuary.diagonal_swamp226": "Cool Swamp",
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
{
|
||||
"sanctuary.story.title": "Story",
|
||||
"sanctuary.story.search": "Rechercher une histoire…",
|
||||
"sanctuary.story.filter": "Découverte",
|
||||
"sanctuary.story.filter_all": "Toutes",
|
||||
"sanctuary.story.filter_discovered": "Découvertes",
|
||||
"sanctuary.story.filter_unknown": "Inconnues",
|
||||
"sanctuary.story.unknown_entry": "Histoire %s — Inconnue",
|
||||
"sanctuary.story.no_discoveries": "Aucune histoire découverte pour le moment.",
|
||||
"sanctuary.story.no_results": "Aucune histoire ne correspond à la recherche.",
|
||||
"biome.sanctuary.diagonal_lush227": "Grottes luxuriantes des mangroves",
|
||||
"biome.sanctuary.diagonal_dripstone227": "Grottes à concrétions des mangroves",
|
||||
"biome.sanctuary.diagonal_swamp226": "Marais frais",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
name = "Sanctuary"
|
||||
author = "KOKA99CAB"
|
||||
version = "beta.233"
|
||||
version = "beta.235"
|
||||
pack-format = "packwiz:1.1.0"
|
||||
|
||||
[index]
|
||||
|
||||
Reference in New Issue
Block a user