Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c55a37562 |
@@ -1,5 +1,12 @@
|
||||
# Sanctuary
|
||||
|
||||
## beta.162 — tableau en scène et conversation
|
||||
|
||||
Portrait de l’auteur tourné vers la souris, bulle aux couleurs de l’annonce,
|
||||
demande à gauche et réponses à droite. Retour/Actualiser restent en haut ;
|
||||
la conversation passe dessous sur les GUI étroits.
|
||||
[Résultat et vérifications](docs/notice-scene-beta162.md).
|
||||
|
||||
## beta.161 — correctifs de séance et site vivant
|
||||
|
||||
Liste des joueurs sur U, suivi des demandes expliqué, intendance compacte et
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# beta.162 — scène du tableau et conversation
|
||||
|
||||
Ticket issu du retour de séance du 24 septembre : conserver Retour/Actualiser en
|
||||
haut, montrer l’auteur avec son skin orienté vers la souris et sa demande dans
|
||||
une bulle colorée, puis les réponses à droite (dessous sur GUI étroit).
|
||||
|
||||
Modification client uniquement, sans migration, écriture dans les mondes ni
|
||||
changement des contrats fichier/MariaDB. Réponses, modération et suivi gardent
|
||||
leurs validations serveur. Le portrait est un rendu GUI, pas une entité.
|
||||
|
||||
## Présentation
|
||||
|
||||
- Retour et Actualiser restent hors de la zone défilante.
|
||||
- Le portrait utilise le skin de la connexion de l’auteur s’il est présent,
|
||||
sinon le cache de profils Minecraft asynchrone. Le skin natif de repli apparaît
|
||||
pendant le chargement ou si le profil ne peut pas être résolu (notamment les
|
||||
comptes fictifs/hors ligne). Aucun téléchargement synchrone dans le rendu.
|
||||
- Une bulle persistante encadrée montre catégorie/état, titre puis texte complet.
|
||||
Information : jaune clair sur fond sombre jaune ; les autres catégories gardent
|
||||
leurs couleurs. Les cartes de liste et du menu pause suivent aussi ce jaune.
|
||||
- Le portrait tourne doucement dans des angles bornés selon la position de la
|
||||
souris, sans capturer ses mouvements et sans clic requis.
|
||||
- Demande et discussion défilent séparément si l’espace le permet ; le champ de
|
||||
réponse et son bouton restent en bas de la discussion. Sur GUI étroit/bas,
|
||||
un défilement unique garde les réponses sous la scène.
|
||||
- Matériaux, lieu, récompense, suivi, visages des réponses et modération sont
|
||||
conservés. Les détails d’une demande vide ne sont plus affichés artificiellement.
|
||||
- Aucune animation de parole ni synchronisation vocale : « parle » désigne ici
|
||||
la mise en scène visuelle avec la bulle.
|
||||
|
||||
## Vérification
|
||||
|
||||
- Compilation Java 25 / Minecraft 26.3 réussie.
|
||||
- Parcours natif Vulkan : `NOTICE162_SCENE_PASS`, puis
|
||||
`SEARCH159_CLIENT_PASS mode=file`, build réussi en 1 min 59 s.
|
||||
- FR/EN, GUI 2/3/4 : scène, passage en colonne unique, boutons de retour au-dessus,
|
||||
réponse au-dessus du footer en deux colonnes, angles du portrait bornés et
|
||||
changement d’orientation avec la souris.
|
||||
- Brouillon conservé pendant les redimensionnements ; publication d’une réponse
|
||||
et lecture de son contenu après rechargement serveur vérifiées.
|
||||
- Régressions communautaires : photos, recherche, suivi/désabonnement, marqueur
|
||||
de carte et infobulle, restrictions de l’Intendance toujours validés.
|
||||
- Captures relues : `build/qa-beta162/0042_notice162-scene-fr_fr-2.png` et
|
||||
`0043_notice162-scene-fr_fr-3.png`. Conversation : `0048_notice162-conversation.png`.
|
||||
- `./gradlew check build assemblePack` lancé : 252 GameTests terminés, 229 réussis
|
||||
et 23 échecs, exactement les mêmes qu’en beta.161 (aucun ajouté/retiré).
|
||||
Liste comparée : `build/session162-server-failures.json`.
|
||||
- Contrôles restants et assemblage avec exclusion ponctuelle de `runGameTest` :
|
||||
réussis en 3 min 16 s (`check build assemblePack -x :sanctuary:runGameTest`).
|
||||
Aucune assertion désactivée dans les sources. Log : `build/beta162-assembly.log`.
|
||||
|
||||
Aucune modification de persistance : cette passe native utilise le mode fichier ;
|
||||
le laboratoire MariaDB partagé a été relancé avec la même interface, KokaLab
|
||||
connecté sur le monde existant `duo-flat-160`, Alice/Bob recréés, shader désactivé. Les figurants
|
||||
et profils hors ligne peuvent afficher un skin Minecraft de repli.
|
||||
|
||||
## Livraison locale
|
||||
|
||||
JAR : `mods/sanctuary/build/libs/sanctuary-beta.162.jar`.
|
||||
Pack assemblé : `build/packwiz`.
|
||||
SHA-256 : `35c5d82eba110ee2150f73baf6341efc90f87971965a3b4dd2bcd04add0a59ff`.
|
||||
|
||||
Branche `codex/notice-scene-beta162`, tag local `beta.162`. Anciens JAR conservés.
|
||||
Aucune publication distante ni synchronisation de Prism dans cette livraison.
|
||||
Les 23 échecs préexistants restent ouverts ; les essais graphiques ne démontrent
|
||||
pas la résolution réseau d’un skin officiel absent dans ce laboratoire hors ligne.
|
||||
+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.161
|
||||
pack_version=beta.161
|
||||
mod_version=beta.162
|
||||
pack_version=beta.162
|
||||
resource_pack_version=beta.121
|
||||
maven_group=fr.koka.sanctuary
|
||||
jei_version=30.32.0-sanctuary.3
|
||||
|
||||
+27
@@ -141,6 +141,8 @@ public final class Community158ClientChecks implements FabricClientGameTest {
|
||||
c.runOnClient(m->{press(m.gui.screen(),"sanctuary.community.save");press(m.gui.screen(),"sanctuary.community.audience.shared");press(m.gui.screen(),"sanctuary.community.reward.none");press(m.gui.screen(),"sanctuary.community.reward.items");edit(m.gui.screen()).setValue("Un repas au village");press(m.gui.screen(),"sanctuary.community.save");press(m.gui.screen(),"sanctuary.community.publish");});
|
||||
c.waitFor(m->has(m.gui.screen(),"sanctuary.community.follow")&&!CommunityClient.pending(),400);
|
||||
c.takeScreenshot("cards158-notice-details");
|
||||
noticeSceneChecks(c);
|
||||
|
||||
world.getServer().runOnServer(server->{var inventory=server.getPlayerList().getPlayers().getFirst().getInventory();check(inventory.getItem(6).getCount()==17&&inventory.getItem(6).is(net.minecraft.world.item.Items.OAK_LOG)&&inventory.getItem(7).getCount()==3&&inventory.getItem(7).is(net.minecraft.world.item.Items.DIAMOND),"Descriptive chest preserves real inventory");});
|
||||
c.runOnClient(m->{check(text(m.gui.screen(),"Un repas au village"),"Custom reward round trip");CommunityClient.open(new PauseScreen(true),"notice",noticeId,false);});
|
||||
c.waitFor(m->has(m.gui.screen(),"sanctuary.community.follow")&&!CommunityClient.pending(),300);c.waitTicks(25);
|
||||
@@ -204,6 +206,31 @@ public final class Community158ClientChecks implements FabricClientGameTest {
|
||||
System.out.println("SEARCH159_CLIENT_PASS mode="+(database?"database":"file"));
|
||||
} finally {intro.set(false);}
|
||||
}
|
||||
private static void noticeSceneChecks(ClientGameTestContext c) {
|
||||
for(String locale:List.of("fr_fr","en_us"))for(int scale:List.of(2,3,4)) {
|
||||
c.runOnClient(m->{m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.options.guiScale().set(scale);m.resizeGui();});
|
||||
c.waitTicks(3);
|
||||
c.runOnClient(m->{
|
||||
var s=m.gui.screen();var scene=widgets(s).filter(CommunityNoticeScene.class::isInstance).map(CommunityNoticeScene.class::cast).findFirst().orElseThrow();
|
||||
check(button(s,"back").getBottom()<=scene.getY(),"Back/refresh fixed above scene");
|
||||
if(s.width>=522) {
|
||||
check(body(s).getX()>=scene.getRight(),"Conversation to the right of author scene");
|
||||
check(button(s,"reply").getBottom()<=s.height-33,"Reply stays above footer");
|
||||
} else check(body(s).getY()>scene.getBottom(),"Narrow GUI stacks conversation below scene");
|
||||
body(s).setValue("Brouillon scène162");hover(m,0,0);
|
||||
});
|
||||
c.waitTicks(3);
|
||||
var left=new java.util.concurrent.atomic.AtomicReference<Float>();
|
||||
c.runOnClient(m->{var scene=widgets(m.gui.screen()).filter(CommunityNoticeScene.class::isInstance).map(CommunityNoticeScene.class::cast).findFirst().orElseThrow();left.set(scene.yaw());hover(m,m.gui.screen().width,m.gui.screen().height);});
|
||||
c.waitTicks(3);
|
||||
c.runOnClient(m->{var scene=widgets(m.gui.screen()).filter(CommunityNoticeScene.class::isInstance).map(CommunityNoticeScene.class::cast).findFirst().orElseThrow();check(scene.yaw()>left.get()&&Math.abs(scene.pitch())<50,"Portrait tracks pointer within bounded angles");});
|
||||
c.takeScreenshot("notice162-scene-"+locale+"-"+scale);
|
||||
}
|
||||
c.runOnClient(m->{m.options.guiScale().set(2);m.resizeGui();check(body(m.gui.screen()).getValue().equals("Brouillon scène162"),"Draft survives GUI resize");body(m.gui.screen()).setValue("Réponse scène162");press(m.gui.screen(),"sanctuary.community.reply");});
|
||||
c.waitFor(m->!CommunityClient.pending()&&widgets(m.gui.screen()).filter(CommunityBubble.class::isInstance).map(CommunityBubble.class::cast).anyMatch(w->w.getMessage().getString().equals("Réponse scène162")),300);
|
||||
c.takeScreenshot("notice162-conversation");
|
||||
System.out.println("NOTICE162_SCENE_PASS");
|
||||
}
|
||||
private static void hover(net.minecraft.client.Minecraft m,double x,double y) {
|
||||
var w=m.getWindow();double sx=(double)w.getScreenWidth()/w.getGuiScaledWidth(),sy=(double)w.getScreenHeight()/w.getGuiScaledHeight();
|
||||
m.mouseHandler.onMove(w.handle(),x*sx,y*sy,(x-m.mouseHandler.getScaledXPos(w))*sx,(y-m.mouseHandler.getScaledYPos(w))*sy);m.mouseHandler.handleAccumulatedMovement();
|
||||
|
||||
@@ -18,8 +18,8 @@ final class CommunityCard extends AbstractButton implements AutoCloseable {
|
||||
private final PlayerFaceWidget face;
|
||||
private final boolean compact,article;
|
||||
private CommunityPhotoWidget photo;
|
||||
static int color(String category) {return switch(category){case "event"->0xffba93dd;case "work"->0xffd4ac71;case "need"->0xff80c1a4;default->0xff80b2d2;};}
|
||||
private static int background(String category) {return switch(category){case "event"->0xff2e253a;case "work"->0xff322b20;case "need"->0xff20322c;default->0xff202d37;};}
|
||||
static int color(String category) {return switch(category){case "event"->0xffba93dd;case "work"->0xffd4ac71;case "need"->0xff80c1a4;default->0xffead47e;};}
|
||||
static int background(String category) {return switch(category){case "event"->0xff2e253a;case "work"->0xff322b20;case "need"->0xff20322c;default->0xff34301f;};}
|
||||
CommunityCard(int width,JsonObject entry,boolean compact,Runnable action) {
|
||||
super(0,0,width,compact?44:90,Component.literal(str(entry,"title")));this.entry=entry;this.compact=compact;this.action=action;article=str(entry,"kind").equals("article");
|
||||
face=new PlayerFaceWidget(compact?20:24,ResolvableProfile.createUnresolved(UUID.fromString(str(entry,"authorId"))));
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.client.gui.components.PlayerSkinWidget;
|
||||
import net.minecraft.client.gui.narration.NarrationElementOutput;
|
||||
import net.minecraft.client.input.MouseButtonEvent;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.util.FormattedCharSequence;
|
||||
import net.minecraft.world.entity.player.PlayerSkin;
|
||||
import net.minecraft.world.item.component.ResolvableProfile;
|
||||
import static fr.koka.sanctuary.client.CommunityClient.*;
|
||||
|
||||
/** A GUI-only author portrait and persistent speech bubble; no world entity is created. */
|
||||
final class CommunityNoticeScene extends AbstractWidget {
|
||||
private final JsonObject entry;
|
||||
private final List<FormattedCharSequence> heading,title,body;
|
||||
private final Portrait portrait;
|
||||
private final int portraitWidth,bubbleX;
|
||||
CommunityNoticeScene(int width,JsonObject entry) {
|
||||
super(0,0,width,160,Component.literal(str(entry,"title")).append("\n").append(str(entry,"body")));
|
||||
this.entry=entry;
|
||||
portraitWidth=Math.clamp(width/4,56,94);bubbleX=portraitWidth+10;
|
||||
var font=Minecraft.getInstance().font;int wrap=Math.max(36,width-bubbleX-18);
|
||||
heading=font.split(text("category."+str(entry,"category")).append(" · ").append(text(str(entry,"status"))),wrap);
|
||||
title=font.split(Component.literal(str(entry,"title")).withStyle(net.minecraft.ChatFormatting.BOLD),wrap);
|
||||
body=font.split(Component.literal(str(entry,"body")),wrap);
|
||||
height=Math.max(154,28+(heading.size()+title.size()+body.size())*11);
|
||||
portrait=new Portrait(portraitWidth,140,skin(entry));active=false;
|
||||
}
|
||||
private static Supplier<PlayerSkin> skin(JsonObject entry) {
|
||||
var m=Minecraft.getInstance();var uuid=UUID.fromString(str(entry,"authorId"));
|
||||
var fallback=m.playerSkinRenderCache().createLookup(ResolvableProfile.createUnresolved(uuid));
|
||||
return ()->{
|
||||
var connection=m.getConnection();var online=connection==null?null:connection.getPlayerInfo(uuid);
|
||||
return online!=null?online.getSkin():fallback.get().playerSkin();
|
||||
};
|
||||
}
|
||||
float yaw(){return portrait.yaw;}
|
||||
float pitch(){return portrait.pitch;}
|
||||
@Override protected void extractWidgetRenderState(GuiGraphicsExtractor g,int mx,int my,float delta) {
|
||||
int x=getX(),y=getY(),bx=x+bubbleX,accent=CommunityCard.color(str(entry,"category"));
|
||||
g.fill(x,y,getRight(),getBottom(),0xA013171C);
|
||||
portrait.setPosition(x,y+6);portrait.extractRenderState(g,mx,my,delta);
|
||||
g.fill(bx,y,getRight(),getBottom(),CommunityCard.background(str(entry,"category")));
|
||||
g.outline(bx,y,width-bubbleX,height,accent);
|
||||
for(int step=0;step<6;step++)g.fill(bx-step-1,y+39+step,bx-step,y+50-step,accent);
|
||||
int top=y+9;
|
||||
for(var line:heading){g.text(Minecraft.getInstance().font,line,bx+9,top,accent,false);top+=11;}
|
||||
top+=5;
|
||||
for(var line:title){g.text(Minecraft.getInstance().font,line,bx+9,top,0xfffaf5e6,false);top+=11;}
|
||||
top+=5;
|
||||
for(var line:body){g.text(Minecraft.getInstance().font,line,bx+9,top,0xffeeeae1,false);top+=11;}
|
||||
}
|
||||
@Override protected void updateWidgetNarration(NarrationElementOutput output){defaultButtonNarrationText(output);}
|
||||
|
||||
private static final class Portrait extends PlayerSkinWidget {
|
||||
private float pitch=-5,yaw=30;
|
||||
Portrait(int width,int height,Supplier<PlayerSkin> skin){super(width,height,Minecraft.getInstance().getEntityModels(),skin);active=false;}
|
||||
@Override protected void extractWidgetRenderState(GuiGraphicsExtractor g,int mx,int my,float delta) {
|
||||
float nextYaw=(float)Math.toDegrees(Math.atan((mx-getX()-getWidth()/2f)/160f))*0.55f;
|
||||
float nextPitch=-(float)Math.toDegrees(Math.atan((my-getY()-30)/180f))*0.4f;
|
||||
// Native 26.3 skin widget applies 2.5 degrees per drag unit; use bounded absolute targets.
|
||||
super.onDrag(null,(nextYaw-yaw)/2.5,(pitch-nextPitch)/2.5);
|
||||
yaw=nextYaw;pitch=nextPitch;
|
||||
super.extractWidgetRenderState(g,mx,my,delta);
|
||||
}
|
||||
@Override protected void onDrag(MouseButtonEvent event,double dx,double dy) { }
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,7 @@ public final class CommunityScreen extends BlocodexFrame {
|
||||
@Override protected void init() {
|
||||
searchBox=null;
|
||||
closePhotos();initFrame("history",text(kind)); contentX=(width-contentWidth)/2; int w = contentWidth - 28;
|
||||
if(!composing && kind.equals("notice") && data.has("thread")) {noticeDetail(data.getAsJsonObject("thread"),w);return;}
|
||||
var layout = LinearLayout.vertical().spacing(8);
|
||||
if (!message.isEmpty()) line(layout,w,text(message));
|
||||
if (!available()) line(layout,w,text("unavailable"));
|
||||
@@ -100,23 +101,57 @@ public final class CommunityScreen extends BlocodexFrame {
|
||||
if(kind.equals("notice"))taskDetails(layout,w,entry);
|
||||
}
|
||||
if (kind.equals("notice")) line(layout,w,text("category."+str(entry,"category")).append(" · ").append(text(str(entry,"status"))));
|
||||
if (!kind.equals("bulletin")) {
|
||||
line(layout,w,text("replies"));
|
||||
var replies = thread.getAsJsonObject("replies");
|
||||
for (var item : replies.getAsJsonArray("entries")) message(layout,w,item.getAsJsonObject());
|
||||
pages(layout,w,replies);
|
||||
if (str(entry,"status").equals("open")) {
|
||||
useDraft("reply:"+id);
|
||||
var replyDraft=draft;
|
||||
var box=layout.addChild(MultiLineEditBox.builder().setPlaceholder(text("reply_hint")).build(font,w,52,text("reply")));
|
||||
box.setCharacterLimit(1000); box.setValue(replyDraft.body); box.setValueListener(value -> replyDraft.body=value);
|
||||
button(layout,100,"reply",() -> request(this,"create",replyDraft.operation,id,"reply","info","",replyDraft.body,0,0,result -> {
|
||||
if (result.has("error")) { message=str(result,"error"); rebuildWidgets(); return; }
|
||||
DRAFTS.values().removeIf(value -> value==replyDraft); message="saved"; needsLoad=true; stale=true;
|
||||
}));
|
||||
}
|
||||
if (!kind.equals("bulletin")) { replies(layout,w,thread);replyComposer(layout,w,entry); }
|
||||
}
|
||||
private void replies(LinearLayout layout,int w,JsonObject thread) {
|
||||
line(layout,w,text("replies"));
|
||||
var replies=thread.getAsJsonObject("replies");
|
||||
for(var item:replies.getAsJsonArray("entries"))message(layout,w,item.getAsJsonObject());
|
||||
if(replies.getAsJsonArray("entries").isEmpty())line(layout,w,text("conversation_empty"));
|
||||
pages(layout,w,replies);
|
||||
}
|
||||
private void replyComposer(LinearLayout layout,int w,JsonObject entry) {
|
||||
if(!str(entry,"status").equals("open"))return;
|
||||
useDraft("reply:"+id);var replyDraft=draft;
|
||||
var box=layout.addChild(MultiLineEditBox.builder().setPlaceholder(text("reply_hint")).build(font,w,52,text("reply")));
|
||||
box.setCharacterLimit(1000);box.setValue(replyDraft.body);box.setValueListener(value->replyDraft.body=value);
|
||||
button(layout,Math.min(w,100),"reply",()->request(this,"create",replyDraft.operation,id,"reply","info","",replyDraft.body,0,0,result->{
|
||||
if(result.has("error")){message=str(result,"error");rebuildWidgets();return;}
|
||||
DRAFTS.values().removeIf(value->value==replyDraft);message="saved";needsLoad=true;stale=true;
|
||||
}));
|
||||
}
|
||||
private void noticeDetail(JsonObject thread,int w) {
|
||||
var entry=thread.getAsJsonObject("entry");
|
||||
var toolbar=LinearLayout.horizontal().spacing(5);
|
||||
button(toolbar,90,"back",()->{id="";page=0;data=new JsonObject();needsLoad=true;rebuildWidgets();});
|
||||
button(toolbar,90,"refresh",()->needsLoad=true);
|
||||
toolbar.setPosition(contentX+8,contentTop);toolbar.arrangeElements();toolbar.visitWidgets(this::addRenderableWidget);
|
||||
int top=contentTop+28,availableHeight=contentBottom-top;
|
||||
boolean columns=w>=470 && availableHeight>=180;
|
||||
int leftWidth=columns?(w-20)*3/5:w;
|
||||
var scene=LinearLayout.vertical().spacing(8);
|
||||
if(!message.isEmpty())line(scene,leftWidth,text(message));
|
||||
if(!available())line(scene,leftWidth,text("unavailable"));
|
||||
scene.addChild(new CommunityNoticeScene(leftWidth,entry));
|
||||
author(scene,leftWidth,entry,true);
|
||||
if(expandedId.equals(id))controls(scene,leftWidth,entry);
|
||||
taskDetails(scene,leftWidth,entry);
|
||||
if(columns) {
|
||||
scrollColumn(scene,leftWidth,contentX+8,top,availableHeight);
|
||||
int rightWidth=w-leftWidth-20,rightX=contentX+8+leftWidth+20;
|
||||
var conversation=LinearLayout.vertical().spacing(8);replies(conversation,rightWidth,thread);
|
||||
int composeHeight=str(entry,"status").equals("open")?88:0;
|
||||
scrollColumn(conversation,rightWidth,rightX,top,availableHeight-composeHeight);
|
||||
if(composeHeight>0){var compose=LinearLayout.vertical().spacing(8);replyComposer(compose,rightWidth,entry);compose.setPosition(rightX,contentBottom-80);compose.arrangeElements();compose.visitWidgets(this::addRenderableWidget);}
|
||||
} else {
|
||||
replies(scene,w,thread);replyComposer(scene,w,entry);
|
||||
scrollColumn(scene,w,contentX+8,top,availableHeight);
|
||||
}
|
||||
}
|
||||
private void scrollColumn(LinearLayout layout,int w,int x,int y,int h) {
|
||||
var scroll=new ScrollableLayout(minecraft,layout,Math.max(24,h),ScrollableLayout.ReserveStrategy.RIGHT);
|
||||
scroll.setMinWidth(w);scroll.setPosition(x,y);scroll.arrangeElements();scroll.visitWidgets(this::addRenderableWidget);
|
||||
}
|
||||
private boolean own(JsonObject entry) { return minecraft.player!=null && str(entry,"authorId").equals(minecraft.player.getStringUUID()); }
|
||||
private void articleAuthor(LinearLayout layout,int w,JsonObject entry) {
|
||||
var row=layout.addChild(LinearLayout.horizontal().spacing(6));
|
||||
@@ -209,6 +244,8 @@ public final class CommunityScreen extends BlocodexFrame {
|
||||
}
|
||||
private void taskDetails(LinearLayout layout,int w,JsonObject entry) {
|
||||
var task=readTask(entry);
|
||||
if(!task.equals(fr.koka.sanctuary.community.CommunityTask.EMPTY)) {
|
||||
line(layout,w,text("request_details"));
|
||||
if(task.location()!=null) {
|
||||
line(layout,w,text("location_value",task.location().dimension(),task.location().x(),task.location().y(),task.location().z()));
|
||||
var map=layout.addChild(Button.builder(text("map_show"),b->minecraft.gui.setScreen(new SanctuaryAtlasScreen(this,task.location()))).width(Math.min(w,220)).build());
|
||||
@@ -220,6 +257,7 @@ public final class CommunityScreen extends BlocodexFrame {
|
||||
if(task.reward().equals("items"))CommunityMaterialsScreen.showItems(layout,w,task.rewards());
|
||||
if(task.reward().equals("custom"))line(layout,w,Component.literal(task.customReward()));
|
||||
line(layout,w,text("exchange_help"));
|
||||
}
|
||||
int count=data.has("members")?data.getAsJsonArray("members").size():0;
|
||||
line(layout,w,text("followers",count));
|
||||
if(data.has("members"))for(var member:data.getAsJsonArray("members"))line(layout,w,Component.literal("• "+str(member.getAsJsonObject(),"playerName")));
|
||||
|
||||
@@ -2442,5 +2442,7 @@
|
||||
"sanctuary.community.notice_full": "This request has no available places.",
|
||||
"sanctuary.community.follow_map_active": "Following: a map marker appears when a location is provided.",
|
||||
"sanctuary.community.follow_map_help": "Replying does not subscribe you. Follow this request to display its map marker.",
|
||||
"sanctuary.community.notice_full_help": "The single place is taken. You can reply, but cannot follow this request."
|
||||
"sanctuary.community.notice_full_help": "The single place is taken. You can reply, but cannot follow this request.",
|
||||
"sanctuary.community.conversation_empty": "No replies yet.",
|
||||
"sanctuary.community.request_details": "The request"
|
||||
}
|
||||
|
||||
@@ -2442,5 +2442,7 @@
|
||||
"sanctuary.community.notice_full": "Cette demande n’a plus de place disponible.",
|
||||
"sanctuary.community.follow_map_active": "Demande suivie : son repère apparaît sur la carte si un lieu est indiqué.",
|
||||
"sanctuary.community.follow_map_help": "Répondre ne vous abonne pas. Suivez la demande pour afficher son repère sur la carte.",
|
||||
"sanctuary.community.notice_full_help": "La place unique est occupée. Vous pouvez répondre, mais pas suivre cette demande."
|
||||
"sanctuary.community.notice_full_help": "La place unique est occupée. Vous pouvez répondre, mais pas suivre cette demande.",
|
||||
"sanctuary.community.conversation_empty": "Aucune réponse pour le moment.",
|
||||
"sanctuary.community.request_details": "La demande"
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
name = "Sanctuary"
|
||||
author = "KOKA99CAB"
|
||||
version = "beta.161"
|
||||
version = "beta.162"
|
||||
pack-format = "packwiz:1.1.0"
|
||||
|
||||
[index]
|
||||
|
||||
Reference in New Issue
Block a user