27 Commits
Author SHA1 Message Date
Anaïs Saraiva 00e9ff003c docs : préciser le retrait de spatial-gui (YACL conservée pour PatPat) 2026-10-20 00:04:03 +02:00
Anaïs Saraiva e5b2fc5a79 nosingleplayer : README à jour (1.2.0, textures MC, piège frame/Vulkan) 2026-10-20 00:02:57 +02:00
Anaïs Saraiva 2bb5b30857 nosingleplayer 1.2.0 : textures créées par MC (fix menu noir Windows/NVIDIA)
Le menu titre rendait entièrement noir sur les PC Windows + NVIDIA
(2 postes) : le mod créait sa texture (DynamicTexture) et son sampler
pendant l'extraction du rendu, et blittait une texture hors atlas.

Correctifs :
- la texture est désormais enregistrée via le mécanisme officiel de MC
  (ReloadableTexture + TextureContents) : c'est MC qui crée la texture
  GPU ET le sampler, comme pour ses propres textures. Plus de sampler
  custom, plus d'upload manuel ;
- la création de la texture GPU est déplacée dans TitleScreen.init, donc
  HORS de l'enregistrement des commandes de la frame (le créer pendant
  l'extraction invalide le frame entier sur les backends à command
  encoder => écran noir) ;
- fond réduit de 3840x2400 à 1920x1200 (36 Mo -> 9 Mo de VRAM) ;
- suppression des 6 panorama_*.png embarqués : MC 26.3 n'expose pas les
  ressources des mods au ResourceManager, ils n'ont jamais été rendus
  (vérifié : le panorama vanilla'affiche le stock Mojang) ;
- jar réduit de 3,6 Mo à 871 Ko.

Vérifié par capture d'écran en jeu : menu OK en OpenGL (Mesa/AMD) et en
Vulkan (lavapipe).

atlas : distribution.json regénérée (nosingleplayer 1.2.0, MD5/URL à
jour) ; retrait de spatial-gui du pack (fait par le repo, documenté dans
server/mods/SOURCE.md).
2026-10-20 00:02:04 +02:00
Anaïs Saraiva 4fd8aa09b6 atlas : nosingleplayer 1.1.9 passe en optionalon (désactivable)
Le mod nosingleplayer (menu titre custom) reste activé par défaut mais
devient basculable dans Settings -> Mods -> Mods optionnels du launcher.
Les postes où le menu titre rend noir (écran noir Windows) peuvent
le désactiver sans réinstallation.

distribution.json regénéré : URL du jar passée de .../required/ à
.../optionalon/ (MD5 inchangé).
2026-10-19 23:41:00 +02:00
Anaïs Saraiva 177a765783 launcher 1.1.1 : release (AppImage + exe) — plus de console noire au lancement Windows
Rebuild complet du launcher avec le correctif windowsHide (la console
noire de java.exe ne s'ouvre plus par-dessus le jeu sous Windows).
Version bump 1.1.0 -> 1.1.1, entrée desktop repointée sur le nouvel
AppImage, artefacts dans releases/v1.1.1/.
2026-10-19 23:34:30 +02:00
Anaïs Saraiva 4c7da4cab5 nosingleplayer 1.1.9 : menu titre résilient (repli vanilla) + launcher : plus de console noire Windows
Écran noir au menu titre sur les PC Windows (2 postes) : dans
TitleScreen.extractRenderState, les widgets sont dessinés juste APRÈS
extractPanorama. Toute exception dans la redirection du panorama
interrompt donc le rendu du menu entier (écran noir, sans bouton).

Mod nosingleplayer 1.1.9 :
- try/catch autour du fond custom + repli automatique sur le panorama
  vanilla (avant : écran noir définitif si la texture est refusée par
  le GPU ou absente du jar) ;
- try/catch autour de la construction du menu, et les widgets vanilla ne
  sont retirés qu'APRÈS la création des widgets SurviePote (avant : une
  exception laissait un menu vidé) ;
- ping serveur déplacé sur un thread dédié : il était lancé depuis
  extractWidgetRenderState, donc sur le thread de rendu (un DNS lent
  suffisait à figer le menu) ;
- log GPU au démarrage du menu (nom/vendor/pilote/backend) ;
- option de diagnostic -Dsurviepote.customTitle=false (menu vanilla).

Launcher : java.exe est lancé avec detached:true sans windowsHide ->
sur Windows, une console noire et vide s'ouvre par-dessus le jeu.
Ajout de windowsHide:true.

atlas : distribution.json regénéré (MD5/URL du nouveau jar).
2026-10-19 23:31:49 +02:00
Anaïs Saraiva 1ff5f52ce8 desktop : cible l'AppImage 1.1.0 (release launcher v1.1.0)
TryExec/Exec pointent maintenant vers SurviePote-setup-1.1.0.AppImage.
Entrée validée par desktop-file-validate.
2026-10-19 22:30:04 +02:00
Anaïs Saraiva d02bb4b0ac launcher 1.1.0 : release (AppImage + exe compilés)
- Version du launcher 1.0.0 -> 1.1.0 (package.json).
- Artefacts de la release versionnés dans releases/v1.1.0/ :
  SurviePote-setup-1.1.0.AppImage (92 Mo) et SurviePote-setup-1.1.0.exe (98 Mo),
  construits avec electron-builder (npm run dist -- -l -w).
- Distribution : https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/distribution.json
2026-10-19 22:19:45 +02:00
Anaïs Saraiva 49e429ce1b atlas : nosingleplayer 1.1.8 + autoconnect désactivé
- Remplace nosingleplayer-1.1.4.jar par nosingleplayer-1.1.8.jar dans
  fabricmods/required/ (fond du menu titre, boutons icônes, statut serveur).
- servermeta.json : autoconnect false (le menu titre custom doit s'afficher,
  pas la connexion automatique).
- distribution.json régénéré (npm run faststart -- g distro) : nouvel id
  fr.surviepote:nosingleplayer:1.1.8@jar, taille et MD5 à jour.
2026-10-19 22:10:11 +02:00
Anaïs Saraiva 4f95e82812 nosingleplayer 1.1.8 : menu titre rework (boutons icônes, Discord, statut serveur)
Menu principal :
- Bouton "Join Discord" (logo Discord lissé + texte) sous "Rejoindre
  SurviePote", ouvre https://discord.gg/EpkgEPv5c.
- "Options..." et "Quit Game" remplacés par des boutons icônes sans texte
  (engrenage / symbole power) qui conservent l'action du bouton vanilla
  (ButtonAccessor -> onPress).
- Panneau de statut du serveur en haut à gauche : En ligne / Hors ligne,
  joueurs current/max et ping.
  Attention : dans ServerStatusPinger.pingServer les 2 Runnable ne sont pas
  des callbacks succès/échec (le 1er ne fire que si le favicon change, le
  2nd est le pong) -> on lit directement ServerData.status ("0/20" quand le
  statut est arrivé, vidé en cas d'échec) + timeout 8s.
- Suppression du texte "26.3 mc" (bas à gauche) : le mixin TitleScreenTextMixin
  qui le dessinait est supprimé, la redirection masque le texte de version.
- Suppression du splash, du logo Mojang, des boutons amis/icônes et du
  copyright (TitleScreenMixin : FriendsButton/SpriteIconButton/PlainTextButton
  sont des sous-classes de Button, ils étaient avalés par "instanceof Button").

Icônes : PNG lisses 64x64 (supersampling 8x8) au lieu du pixel art.
Chargement des textures du mod via ModTextures (les assets des mods ne sont
plus exposés au ResourceManager en MC 26.3).
2026-10-19 22:08:21 +02:00
Anaïs Saraiva 89e6b6f680 nosingleplayer 1.1.7 : menu titre nettoyé (splash, amis, copyright) + titre de fenêtre
- Titre de fenêtre : "Minecraft* 26.3" -> "SurviePote [26.3]"
  (mixin sur Minecraft.createTitle(), la classe Window a changé de package
  en 26.3 : com.mojang.blaze3d.platform.Window).
- Suppression du texte de splash (message rotatif) du menu principal.
- Bouton "amis" (FriendsButton) réellement supprimé : le if/else if de
  TitleScreenMixin testait "instanceof Button" en premier, et FriendsButton /
  SpriteIconButton / PlainTextButton en héritent -> ils tombaient dans cette
  branche et n'étaient jamais retirés. Ordre des tests corrigé (icônes et
  texte cliquable testés avant Button).
2026-10-19 21:31:41 +02:00
Anaïs Saraiva fc80845f5c nosingleplayer 1.1.6 : fond du menu titre enfin affiché
Trois bugs cumulés, tous vérifiés contre le jar client 26.3 :

1. blit() : l'overload 11 args réutilise w/h comme région source
   (u0 = u/tw, u1 = (u+w)/tw) -> on n'affichait que le coin haut-gauche
   de l'image (noir). On utilise l'overload 13 args avec une région
   source explicite + recadrage "cover" (pas de déformation).

2. Texture introuvable : en MC 26.3 le ResourceManager ne lit plus les
   packs depuis les jars du classpath (seulement des dossiers), donc les
   assets d'un mod ne sont plus exposés -> placeholder magenta.
   Vérifié : "Reloading ResourceManager: vanilla, vanilla" et
   namespaces=[minecraft, realms]. Le PNG est donc lu dans le jar du
   mod (getResourceAsStream) et uploadé en DynamicTexture, enregistrée
   dans le TextureManager.

3. Filtrage : sampler NEAREST par défaut (aliasing visible) -> sampler
   linéaire + clamp-to-edge.

Asset : PNG RGBA 3840x2400.
2026-10-19 21:23:41 +02:00
Anaïs Saraiva 8053411565 nosingleplayer 1.1.5: fond en vrai PNG (MC rejette le JPEG) + launcher: autoconnect désactivé 2026-10-19 20:48:49 +02:00
Anaïs Saraiva c92059db26 server: config PM2 (lancement du serveur dans le nix-shell) 2026-10-19 20:40:38 +02:00
Anaïs Saraiva fdc7d867ec server: écouter uniquement sur l'interface 10.7.0.2 (server-ip) 2026-10-19 20:16:39 +02:00
Anaïs Saraiva b8a283a177 server: start.sh/setup.sh relancent automatiquement via nix-shell si Java 25 absent
Sur NixOS, sans nix-shell le java systeme manque souvent. Le script detecte
l'absence de Java 25 et se re-execute dans le shell Nix du repo (shell.nix),
avec garde SURVIEPOTE_NIX pour eviter toute boucle.
2026-10-19 20:11:09 +02:00
Anaïs Saraiva 3a2b403acc scripts: retirer shebang #!/bin/bash (incompatible NixOS, /bin/bash absent)
start.sh/setup.sh/sync-mods.sh s'executent via le shebang du shell appelant
(./start.sh depuis bash fonctionne).
2026-10-19 20:06:43 +02:00
Anaïs Saraiva 37fcdfb277 server: installation auto du runtime Fabric (setup.sh/.bat)
Le runtime (jars vanilla/Fabric, libraries/, versions/) n'est pas versionne.
- setup.sh / setup.bat : telecharge fabric-installer 1.1.2 et installe
  le serveur Fabric (MC 26.3 / Loader 0.20.0) + server.jar vanilla
- start.sh / start.bat : lance setup automatiquement si le runtime manque
- README : premier lancement mis a jour
- teste en dossier vierge : installation OK
2026-10-19 20:03:37 +02:00
Anaïs Saraiva 20330310fe gitignore: ignorer .direnv/ (racine + mods) 2026-10-19 19:55:48 +02:00
Anaïs Saraiva f4372ab8a4 gitignore: logs des mods (mods/*/logs/) + untrack mods/pala/logs 2026-10-19 19:54:44 +02:00
Anaïs Saraiva 7a1a4306b1 gitignore: ignorer server/config/ (configs runtime) + dedup logs; suppr ADD_THIS
- server/config/ sorti du versionnement (configs regenerees au boot)
- bloc runtime duplique (world/logs/crash-reports/*.log) consolide
- server/config/**/update/ devenu redondant, retire
2026-10-19 19:53:11 +02:00
Anaïs Saraiva 058c18c4cb nosingleplayer 1.1.4: retirer completement le bouton Solo (au lieu de le griser) 2026-10-19 19:51:36 +02:00
Anaïs Saraiva 3f389f65f2 mods: +15 communs (serveur+atlas) et +2 client-only (atlas)
Ajoutés depuis ADD_THIS/ :
- communs (env *) : advancednetherite, betterarcheology, better-nether,
  constructionwand, elevatorid, ImprovedPillagerOutpost, mythicupgrades,
  NoChatReports, simple-warp-tpa-home-back, travelersbackpack,
  universalenchants, yyzsbackpack
- libs communes : ResourcefulConfig, ForgeConfigAPIPort, puzzleslib (Modrinth)
- client-only (atlas) : spatial-gui, yet_another_config_lib_v3
- doublons ignorés : cloth-config, placeholder-api (déjà présents)
- distribution.json régénéré ; doc serveur mise à jour
- boot-test serveur validé : Done (1.202s), 140 mods, 0 erreur
2026-10-19 19:50:26 +02:00
Anaïs Saraiva 19f0e05c8e atlas+serveur: retirer ImmersiveOres (mod cassé)
- retiré de atlas/.../fabricmods/required/ (clients) et de server/mods-client-only/
- retiré les configs atlas/.../files/config/immersiveores/*.toml
- sync-mods.sh: ImmersiveOres ajouté à ATLAS_EXCLUDE + cleanup serveur/atlas
- docs (SOURCE.md, quarantine/README.md) mises à jour
- distribution.json régénéré (retrait du mod + de ses 3 configs)
2026-10-19 19:27:50 +02:00
Anaïs Saraiva 722802c001 launcher: purge auth Mojang (depreciee), Microsoft uniquement
- bootstrap: validateSelected() ne tente plus de sign-in Mojang
- suppression formulaire login.ejs / login.js et de la vue login
- authmanager: retrait addMojangAccount/removeMojangAccount/mojangErrorDisplayable
- configmanager: retrait clientToken et helpers comptes Mojang
- settings/processbuilder: flux Microsoft uniquement (user_type=msa)
- landing: retrait du refresh de statut Mojang (fonction + UI + CSS)
2026-10-19 18:57:41 +02:00
Anaïs Saraiva 4a48d2c721 nosingleplayer 1.1.3: fix connexion + bouton Quitter SurviePote
- TitleScreenMixin : TransferState null sur startConnecting -> connexion
  normale, corrige "This server does not accept transfers".
- PauseScreenMixin (nouveau) : renomme le bouton de deconnexion du menu
  Echap en "Quitter SurviePote" et redirige vers l'ecran d'accueil
  (TitleScreen) au lieu de la liste des serveurs.
- build.gradle : tache runClient (dev sans loom) reutilisant
  ~/.surviepote/common.
- Jar 1.1.3 copie dans l'atlas + distribution.json regenere.
2026-10-19 18:41:56 +02:00
Anaïs Saraiva 159a594b2c launcher: exiger Java 25 (servermeta javaOptions + distribution.json)
La config serveur declare desormais javaOptions (TEMURIN, supported
>=25.x, suggestedMajor 25) au lieu du defaut Helios 21 pour MC >= 1.20.5.
Le launcher exigera/detectera Java 25 et telechargera Temurin 25.x depuis
Adoptium si absent. Genere via `npm run faststart -- generate distro`.
2026-10-19 18:36:40 +02:00
132 changed files with 1389 additions and 2633 deletions
+3 -8
View File
@@ -22,16 +22,11 @@ server/crash-reports/
server/versions/ server/versions/
server/*.log server/*.log
server/*.json server/*.json
server/config/
server/fabricloader.log server/fabricloader.log
server/fabric-server-launcher.properties server/fabric-server-launcher.properties
quarantine/*.jar quarantine/*.jar
# server runtime
server/world/
server/logs/
server/crash-reports/
server/*.log
# OS / IDE # OS / IDE
.DS_Store .DS_Store
Thumbs.db Thumbs.db
@@ -43,10 +38,10 @@ Thumbs.db
.env .env
atlas/.env atlas/.env
server/.fabric/ server/.fabric/
server/config/**/update/
mods/*/.gradle/ mods/*/.gradle/
mods/*/build/ mods/*/build/
mods/*/logs/
.gradle/ .gradle/
mods/pala/.direnv/ .direnv/
mods/pala/.classpath mods/pala/.classpath
mods/pala/.project mods/pala/.project
+2 -2
View File
@@ -4,8 +4,8 @@ Type=Application
Name=SurviePote Name=SurviePote
GenericName=Minecraft Launcher GenericName=Minecraft Launcher
Comment=Launcher officiel SurviePote — rejoins mcpv.ihorizon.org Comment=Launcher officiel SurviePote — rejoins mcpv.ihorizon.org
TryExec=/home/kisakay/Applications/SurviePote-setup-1.0.0.AppImage TryExec=/home/kisakay/Applications/SurviePote-setup-1.1.1.AppImage
Exec=/home/kisakay/Applications/SurviePote-setup-1.0.0.AppImage --no-sandbox %U Exec=/home/kisakay/Applications/SurviePote-setup-1.1.1.AppImage --no-sandbox %U
Icon=surviepote Icon=surviepote
Terminal=false Terminal=false
Categories=Game; Categories=Game;
+168 -43
View File
@@ -10,7 +10,12 @@
"address": "mcpv.ihorizon.org:25565", "address": "mcpv.ihorizon.org:25565",
"minecraftVersion": "26.3", "minecraftVersion": "26.3",
"mainServer": true, "mainServer": true,
"autoconnect": true, "autoconnect": false,
"javaOptions": {
"distribution": "TEMURIN",
"supported": ">=25.x",
"suggestedMajor": 25
},
"modules": [ "modules": [
{ {
"id": "net.fabricmc:fabric-loader:0.20.0", "id": "net.fabricmc:fabric-loader:0.20.0",
@@ -94,6 +99,16 @@
} }
] ]
}, },
{
"id": "com.autovw:advancednetherite:2.4.4-26.3@jar",
"name": "Advanced Netherite",
"type": "FabricMod",
"artifact": {
"size": 568803,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/advancednetherite-fabric-2.4.4-26.3.jar",
"MD5": "ed5a33f1c9d190369eaaa9a757ad0678"
}
},
{ {
"id": "squeek.appleskin:appleskin:3.0.10+mc26.3@jar", "id": "squeek.appleskin:appleskin:3.0.10+mc26.3@jar",
"name": "AppleSkin", "name": "AppleSkin",
@@ -147,6 +162,26 @@
"MD5": "522b9df994d1a6111aa3d716d364073b" "MD5": "522b9df994d1a6111aa3d716d364073b"
} }
}, },
{
"id": "org.betterx:betternether:26.301.2@jar",
"name": "Better Nether",
"type": "FabricMod",
"artifact": {
"size": 27986160,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/better-nether-26.301.2.jar",
"MD5": "dae1d6ca17260a928bd2a685b169adde"
}
},
{
"id": "net.Pandarix:betterarcheology:26.3-1.3.9@jar",
"name": "Better Archeology",
"type": "FabricMod",
"artifact": {
"size": 3113287,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/betterarcheology-fabric-26.3-1.3.9.jar",
"MD5": "b80841a76199f6df1facc8e6a7df7a64"
}
},
{ {
"id": "net.bettercombat:bettercombat:3.2.2@jar", "id": "net.bettercombat:bettercombat:3.2.2@jar",
"name": "Better Combat", "name": "Better Combat",
@@ -223,6 +258,16 @@
"MD5": "19d0bb757fbc910514254261bb022e91" "MD5": "19d0bb757fbc910514254261bb022e91"
} }
}, },
{
"id": "dev.smto:constructionwand:2.1.3@jar",
"name": "Construction Wand (Fabric)",
"type": "FabricMod",
"artifact": {
"size": 306717,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/constructionwand-2.1.3+26.3.jar",
"MD5": "e7df688426cc24b62474386086be5dd7"
}
},
{ {
"id": "me.pepperbell:continuity:3.0.1+26.3@jar", "id": "me.pepperbell:continuity:3.0.1+26.3@jar",
"name": "Continuity", "name": "Continuity",
@@ -259,6 +304,16 @@
"MD5": "b59adc60f71cb3bf517b876b64689c91" "MD5": "b59adc60f71cb3bf517b876b64689c91"
} }
}, },
{
"id": "com.vsngarcia:elevatorid:26.3-1.16.6@jar",
"name": "ElevatorMod",
"type": "FabricMod",
"artifact": {
"size": 780593,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/elevatorid-fabric-26.3-1.16.6.jar",
"MD5": "8046d4dfc93e80121d47db42292fea5b"
}
},
{ {
"id": "generated.fabricmod:fabric-api:0.163.0+26.3@jar", "id": "generated.fabricmod:fabric-api:0.163.0+26.3@jar",
"name": "Fabric API", "name": "Fabric API",
@@ -292,6 +347,16 @@
"value": false "value": false
} }
}, },
{
"id": "fuzs.forgeconfigapiport.fabric.impl:forgeconfigapiport:26.3.1@jar",
"name": "Forge Config API Port",
"type": "FabricMod",
"artifact": {
"size": 609574,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/ForgeConfigAPIPort-v26.3.1-mc26.3.x-Fabric.jar",
"MD5": "a16f7b8b0f57d3f2fb2fb9d4adab070c"
}
},
{ {
"id": "glitchcore.fabric.core:glitchcore:26.3.0.0.5@jar", "id": "glitchcore.fabric.core:glitchcore:26.3.0.0.5@jar",
"name": "GlitchCore", "name": "GlitchCore",
@@ -303,13 +368,13 @@
} }
}, },
{ {
"id": "com.blocklegend001:immersiveores:26.3-0.1@jar", "id": "generated.fabricmod:ipo:7@jar",
"name": "ImmersiveOres", "name": "ImprovedPillagerOutpost",
"type": "FabricMod", "type": "FabricMod",
"artifact": { "artifact": {
"size": 543252, "size": 614666,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/ImmersiveOres-26.3-0.1.jar", "url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/ImprovedPillagerOutpost-v7.jar",
"MD5": "840a11c59c29eadd909cba3ce6f569bb" "MD5": "5806e860495c8a4a7294a5249b41f968"
} }
}, },
{ {
@@ -391,6 +456,16 @@
"value": false "value": false
} }
}, },
{
"id": "net.trique:mythicupgrades:5.1.1@jar",
"name": "Mythic Upgrades",
"type": "FabricMod",
"artifact": {
"size": 2375834,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/mythicupgrades-fabric-26.3-5.1.1.jar",
"MD5": "09e9e8eaf95febf0835780c073220f28"
}
},
{ {
"id": "com.chaosthedude:naturescompass:26.3-2.5.1-fabric@jar", "id": "com.chaosthedude:naturescompass:26.3-2.5.1-fabric@jar",
"name": "Nature's Compass", "name": "Nature's Compass",
@@ -402,13 +477,26 @@
} }
}, },
{ {
"id": "fr.surviepote:nosingleplayer:1.1.2@jar", "id": "com.aizistral:nochatreports:26.3-v2.21.0@jar",
"name": "No Chat Reports",
"type": "FabricMod",
"artifact": {
"size": 239561,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/NoChatReports-FABRIC-26.3-v2.21.0.jar",
"MD5": "8434b3b01bbad6e6deb5309d3e2c5800"
}
},
{
"id": "fr.surviepote:nosingleplayer:1.2.0@jar",
"name": "NoSingleplayer (SurviePote)", "name": "NoSingleplayer (SurviePote)",
"type": "FabricMod", "type": "FabricMod",
"artifact": { "artifact": {
"size": 2292471, "size": 870981,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/nosingleplayer-1.1.2.jar", "url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/optionalon/nosingleplayer-1.2.0.jar",
"MD5": "2ba50ec99adb670cdb34fe41b0e3d7dd" "MD5": "107e3b749568d916e556479efbbda3c6"
},
"required": {
"value": false
} }
}, },
{ {
@@ -461,6 +549,26 @@
"MD5": "e176ae70db08a26839282e6d373b5d97" "MD5": "e176ae70db08a26839282e6d373b5d97"
} }
}, },
{
"id": "fuzs.puzzleslib.fabric.impl:puzzleslib:26.3.15@jar",
"name": "Puzzles Lib",
"type": "FabricMod",
"artifact": {
"size": 1093312,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/puzzleslib-v26.3.15-mc26.3.x+fabric.jar",
"MD5": "51ceba62abb73e69212caa0f923eacff"
}
},
{
"id": "com.teamresourceful.resourcefulconfig.web.server:resourcefulconfig:6.0.2@jar",
"name": "Resourcefulconfig",
"type": "FabricMod",
"artifact": {
"size": 444092,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/ResourcefulConfig-6.0.2.jar",
"MD5": "f9817fe0bf73467af9c00eeaee420550"
}
},
{ {
"id": "net.blay09.mods:shogi:26.3.0.3@jar", "id": "net.blay09.mods:shogi:26.3.0.3@jar",
"name": "Shogi", "name": "Shogi",
@@ -471,6 +579,16 @@
"MD5": "7d4488104a6d0714838b5c801f50407d" "MD5": "7d4488104a6d0714838b5c801f50407d"
} }
}, },
{
"id": "zy.swthb:simple-warp-tpa-home-back:26.3-1.3@jar",
"name": "Simple-warp-tpa-home-back",
"type": "FabricMod",
"artifact": {
"size": 67653,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/simple-warp-tpa-home-back-26.3-1.3.jar",
"MD5": "8e6235409b851c40691f65d729130e82"
}
},
{ {
"id": "dev.tr7zw.skinlayers:skinlayers3d:1.11.3@jar", "id": "dev.tr7zw.skinlayers:skinlayers3d:1.11.3@jar",
"name": "3d-Skin-Layers", "name": "3d-Skin-Layers",
@@ -517,6 +635,16 @@
"MD5": "310a79696ae06b2049c29f1689c86db1" "MD5": "310a79696ae06b2049c29f1689c86db1"
} }
}, },
{
"id": "com.tiviacz:travelersbackpack:11.4.1@jar",
"name": "Traveler's Backpack",
"type": "FabricMod",
"artifact": {
"size": 1511186,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/travelersbackpack-fabric-26.3-11.4.1.jar",
"MD5": "a18139ae9428e3072db547749642cd5d"
}
},
{ {
"id": "com.serilum:treeharvester:10.3@jar", "id": "com.serilum:treeharvester:10.3@jar",
"name": "Tree Harvester", "name": "Tree Harvester",
@@ -527,6 +655,16 @@
"MD5": "63fe436df405aa30d6adf50ebf65737f" "MD5": "63fe436df405aa30d6adf50ebf65737f"
} }
}, },
{
"id": "fuzs.universalenchants:universalenchants:26.3.1@jar",
"name": "Universal Enchants",
"type": "FabricMod",
"artifact": {
"size": 198420,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/universalenchants-v26.3.1-mc26.3.x+fabric.jar",
"MD5": "d8ed00b6e2d334da7e469686f6b64b59"
}
},
{ {
"id": "net.execheinz:upgrader:1.2.1@jar", "id": "net.execheinz:upgrader:1.2.1@jar",
"name": "Upgrader", "name": "Upgrader",
@@ -619,6 +757,26 @@
"value": false "value": false
} }
}, },
{
"id": "dev.isxander.yacl3.platform:yet_another_config_lib_v3:3.9.7+26.3-fabric@jar",
"name": "YetAnotherConfigLib",
"type": "FabricMod",
"artifact": {
"size": 1125377,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/yet_another_config_lib_v3-3.9.7+26.3-fabric.jar",
"MD5": "967207b5cf360d9481fb318abacfe8eb"
}
},
{
"id": "com.yyz:yyzsbackpack:0.0.11-reborn+26.3@jar",
"name": "Yyz's Backpack",
"type": "FabricMod",
"artifact": {
"size": 357667,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/yyzsbackpack-0.0.11-reborn+26.3.jar",
"MD5": "67db85ac45cf311d9994349f51d00942"
}
},
{ {
"id": "settings.json", "id": "settings.json",
"name": "settings.json", "name": "settings.json",
@@ -773,39 +931,6 @@
"path": "config/easy_homes.json" "path": "config/easy_homes.json"
} }
}, },
{
"id": "enderium-common.toml",
"name": "enderium-common.toml",
"type": "File",
"artifact": {
"size": 3930,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/files/config/immersiveores/enderium-common.toml",
"MD5": "29c0e8dc797b264ff04944f44756ff16",
"path": "config/immersiveores/enderium-common.toml"
}
},
{
"id": "vibranium-common.toml",
"name": "vibranium-common.toml",
"type": "File",
"artifact": {
"size": 3531,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/files/config/immersiveores/vibranium-common.toml",
"MD5": "425488f8ca763dc9c5f1c406f79a7f53",
"path": "config/immersiveores/vibranium-common.toml"
}
},
{
"id": "vulpus-common.toml",
"name": "vulpus-common.toml",
"type": "File",
"artifact": {
"size": 3466,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/files/config/immersiveores/vulpus-common.toml",
"MD5": "05cc5da3fcd2dab7314b62636043342f",
"path": "config/immersiveores/vulpus-common.toml"
}
},
{ {
"id": "jade.json", "id": "jade.json",
"name": "jade.json", "name": "jade.json",
@@ -1,68 +0,0 @@
# --- Enderium Armor Protection and Resistance ---
protectionValueEnderiumBoots=12 #Protection value of Enderium Boots | default: 12
protectionValueEnderiumLeggings=16 #Protection value of Enderium Leggings | default: 16
protectionValueEnderiumChestplate=20 #Protection value of Enderium Chestplate | default: 20
protectionValueEnderiumHelmet=12 #Protection value of Enderium Helmet | default: 12
toughnessValueEnderiumArmor=10 #Toughness value of Enderium Armor | default: 10
knockbackResistanceValueEnderiumArmor=0.4 #Knockback resistance of Enderium Armor | default: 0.4
enchantmentValueEnderiumArmor=65 #Enchantment value of Enderium Armor | default: 65
# --- Enderium Armor Special Effects and Abilities ---
speedIIIEnderiumArmor=true #Speed III effect for Enderium Armor | default: true
jumpIIIEnderiumArmor=true #Jump III effect for Enderium Armor | default: true
nightVisionEnderiumArmor=true #Night Vision effect for Enderium Armor | default: true
immuneToFallDamageEnderiumArmor=true #Immunity to fall damage for Enderium Armor | default: true
fireResistanceEnderiumArmor=true #Fire resistance for Enderium Armor | default: true
neverLoseHungerEnderiumArmor=true #Never lose hunger with Enderium Armor | default: true
canFlyEnderiumArmor=true #Ability to fly with Enderium Armor | default: true
canWalkOnPowderedSnowEnderium=true #Can walk on powdered snow with Enderium Armor | default: true
makesPiglinsNeutralEnderium=true #Makes Piglins neutral with Enderium Armor | default: true
endermanWillNotBeAngryWithYouEnderium=true #Endermen won't be angry with you with Enderium Armor | default: true
# --- Enderium Bow Settings ---
enchantmentValueEnderiumBow=5 #Enchantment value of Enderium Bow | default: 5
damageEnderiumBow=14 #Damage value of Enderium Bow | default: 14
arrowCountEnderiumBow=4 #Arrow count for Enderium Bow | default: 4
# --- Enderium Tool Tier Base Properties ---
speedEnderiumTier=50 #Speed value of Enderium tier | default: 50
enchantmentValueEnderiumTier=65 #Enchantment value of Enderium tier | default: 65
attackDamageBonusEnderiumTier=3 #Attack damage bonus of Enderium tier | default: 3
durabilityEnderium=5139 #Durability of Enderium tools | default: 5139
unbreakableEnderium=false #Whether Enderium tools are unbreakable | default: false
# --- Enderium Pickaxe Stats ---
attackDamageEnderiumPickaxe=15 #Attack damage of Enderium Pickaxe | default: 15
attackSpeedEnderiumPickaxe=1.0 #Attack speed of Enderium Pickaxe | default: 1.0
# --- Enderium Axe Stats ---
attackDamageEnderiumAxe=18 #Attack damage of Enderium Axe | default: 18
attackSpeedEnderiumAxe=1.0 #Attack speed of Enderium Axe | default: 1.0
# --- Enderium Shovel Stats ---
attackDamageEnderiumShovel=15 #Attack damage of Enderium Shovel | default: 15
attackSpeedEnderiumShovel=1.0 #Attack speed of Enderium Shovel | default: 1.0
# --- Enderium Sword Stats ---
attackDamageEnderiumSword=22 #Attack damage of Enderium Sword | default: 22
attackSpeedEnderiumSword=1.0 #Attack speed of Enderium Sword | default: 1.0
# --- Enderium Hoe Stats ---
attackDamageEnderiumHoe=15 #Attack damage of Enderium Hoe | default: 15
attackSpeedEnderiumHoe=1.0 #Attack speed of Enderium Hoe | default: 1.0
# --- Enderium Paxel Stats ---
attackDamageEnderiumPaxel=18 #Attack damage of Enderium Paxel | default: 18
attackSpeedEnderiumPaxel=1.0 #Attack speed of Enderium Paxel | default: 1.0
# --- Enderium Hammer Stats ---
attackDamageEnderiumHammer=16 #Attack damage of Enderium Hammer | default: 16
attackSpeedEnderiumHammer=1.0 #Attack speed of Enderium Hammer | default: 1.0
radiusEnderiumHammer=3 #Hammering radius for Enderium Hammer | default: 3
# --- Enderium Excavator Stats ---
attackDamageEnderiumExcavator=16 #Attack damage of Enderium Excavator | default: 16
attackSpeedEnderiumExcavator=1.0 #Attack speed of Enderium Excavator | default: 1.0
radiusEnderiumExcavator=3 #Excavation radius for Enderium Excavator | default: 3
@@ -1,62 +0,0 @@
# --- --- Vibranium Armor Protection and Resistance --- ---
protectionValueVibraniumBoots=6 #Protection value of Vibranium Boots | default: 6
protectionValueVibraniumLeggings=9 #Protection value of Vibranium Leggings | default: 9
protectionValueVibraniumChestplate=12 #Protection value of Vibranium Chestplate | default: 12
protectionValueVibraniumHelmet=6 #Protection value of Vibranium Helmet | default: 6
toughnessValueVibraniumArmor=3 #Toughness value of Vibranium Armor | default: 3
knockbackResistanceValueVibraniumArmor=0.2 #Knockback resistance of Vibranium Armor | default: 0.2
enchantmentValueVibraniumArmor=55 #Enchantment value of Vibranium Armor | default: 55
# --- --- Vibranium Armor Special Effects and Abilities --- ---
speedIVibraniumArmor=true #Speed I effect for Vibranium Armor | default: true
jumpIVibraniumArmor=true #Jump I effect for Vibranium Armor | default: true
nightVisionVibraniumArmor=true #Night Vision effect for Vibranium Armor | default: true
canWalkOnPowderedSnowVibranium=true #Can walk on powdered snow with Vibranium Armor | default: true
# --- --- Vibranium Bow Settings --- ---
enchantmentValueVibraniumBow=5 #Enchantment value of Vibranium Bow | default: 5
damageVibraniumBow=5 #Damage value of Vibranium Bow | default: 5
arrowCountVibraniumBow=2 #Arrow count for Vibranium Bow | default: 2
# --- --- Vibranium Tool Tier Base Properties --- ---
speedVibraniumTier=30 #Speed value of Vibranium tier | default: 30
enchantmentValueVibraniumTier=55 #Enchantment value of Vibranium tier | default: 55
attackDamageBonusVibraniumTier=1 #Attack damage bonus of Vibranium tier | default: 1
durabilityVibranium=2562 #Durability of Vibranium tools | default: 2562
unbreakableVibranium=false #Whether Vibranium tools are unbreakable | default: false
# --- --- Vibranium Pickaxe Stats --- ---
attackDamageVibraniumPickaxe=7 #Attack damage of Vibranium Pickaxe | default: 7
attackSpeedVibraniumPickaxe=0.5 #Attack speed of Vibranium Pickaxe | default: 0.5
# --- --- Vibranium Axe Stats --- ---
attackDamageVibraniumAxe=12 #Attack damage of Vibranium Axe | default: 12
attackSpeedVibraniumAxe=0.5 #Attack speed of Vibranium Axe | default: 0.5
# --- --- Vibranium Shovel Stats --- ---
attackDamageVibraniumShovel=7 #Attack damage of Vibranium Shovel | default: 7
attackSpeedVibraniumShovel=0.5 #Attack speed of Vibranium Shovel | default: 0.5
# --- --- Vibranium Sword Stats --- ---
attackDamageVibraniumSword=10 #Attack damage of Vibranium Sword | default: 10
attackSpeedVibraniumSword=0.5 #Attack speed of Vibranium Sword | default: 0.5
# --- --- Vibranium Hoe Stats --- ---
attackDamageVibraniumHoe=7 #Attack damage of Vibranium Hoe | default: 7
attackSpeedVibraniumHoe=0.5 #Attack speed of Vibranium Hoe | default: 0.5
# --- --- Vibranium Paxel Stats --- ---
attackDamageVibraniumPaxel=11 #Attack damage of Vibranium Paxel | default: 11
attackSpeedVibraniumPaxel=0.5 #Attack speed of Vibranium Paxel | default: 0.5
# --- --- Vibranium Hammer Stats --- ---
attackDamageVibraniumHammer=7 #Attack damage of Vibranium Hammer | default: 7
attackSpeedVibraniumHammer=0.5 #Attack speed of Vibranium Hammer | default: 0.5
radiusVibraniumHammer=1 #Hammering radius for Vibranium Hammer | default: 1
# --- --- Vibranium Excavator Stats --- ---
attackDamageVibraniumExcavator=7 #Attack damage of Vibranium Excavator | default: 7
attackSpeedVibraniumExcavator=0.5 #Attack speed of Vibranium Excavator | default: 0.5
radiusVibraniumExcavator=1 #Excavation radius for Vibranium Excavator | default: 1
@@ -1,66 +0,0 @@
# --- Vulpus Armor Protection and Resistance ---
protectionValueVulpusBoots=9 #Protection value of Vulpus Boots | default: 9
protectionValueVulpusLeggings=12 #Protection value of Vulpus Leggings | default: 12
protectionValueVulpusChestplate=16 #Protection value of Vulpus Chestplate | default: 16
protectionValueVulpusHelmet=9 #Protection value of Vulpus Helmet | default: 9
toughnessValueVulpusArmor=7 #Toughness value of Vulpus Armor | default: 7
enchantmentValueVulpusArmor=65 #Enchantment value of Vulpus Armor | default: 65
knockbackResistanceValueVulpusArmor=0.3 #Knockback resistance of Vulpus Armor | default: 0.3
# --- Vulpus Armor Special Effects and Abilities ---
speedIIVulpusArmor=true #Speed II effect for Vulpus Armor | default: true
jumpIIVulpusArmor=true #Jump II effect for Vulpus Armor | default: true
nightVisionVulpusArmor=true #Night Vision for Vulpus Armor | default: true
fireResistanceVulpusArmor=true #Fire Resistance for Vulpus Armor | default: true
immuneToFallDamageVulpusArmor=true #Fall Damage Immunity for Vulpus Armor | default: true
neverLoseHungerVulpusArmor=true #Prevention of Hunger Loss for Vulpus Armor | default: true
canWalkOnPowderedSnowVulpus=true #Walking on Powdered Snow with Vulpus Armor | default: true
makesPiglinsNeutralVulpus=true #Piglins become Neutral with Vulpus Armor | default: true
# --- Vulpus Bow ---
enchantmentValueVulpusBow=5 #Enchantment value of Vulpus Bow | default: 5
damageVulpusBow=10 #Damage of Vulpus Bow | default: 10
arrowCountVulpusBow=3 #Arrow count for Vulpus Bow | default: 3
# --- Vulpus Tool Tier Base ---
speedVulpusTier=40 #Mining speed of Vulpus tier | default: 40
enchantmentValueVulpusTier=65 #Enchantment value of Vulpus tier | default: 65
attackDamageBonusVulpusTier=3 #Attack damage bonus of Vulpus tier | default: 3
durabilityVulpus=3093 #Durability of Vulpus tools | default: 3093
unbreakableVulpus=false #Whether Vulpus tools are unbreakable | default: false
# --- Vulpus Pickaxe ---
attackDamageVulpusPickaxe=10 #Attack damage of Vulpus Pickaxe | default: 10
attackSpeedVulpusPickaxe=0.5 #Attack speed of Vulpus Pickaxe | default: 0.5
# --- Vulpus Axe ---
attackDamageVulpusAxe=15 #Attack damage of Vulpus Axe | default: 15
attackSpeedVulpusAxe=0.5 #Attack speed of Vulpus Axe | default: 0.5
# --- Vulpus Shovel ---
attackDamageVulpusShovel=10 #Attack damage of Vulpus Shovel | default: 10
attackSpeedVulpusShovel=0.5 #Attack speed of Vulpus Shovel | default: 0.5
# --- Vulpus Sword ---
attackDamageVulpusSword=14 #Attack damage of Vulpus Sword | default: 14
attackSpeedVulpusSword=0.5 #Attack speed of Vulpus Sword | default: 0.5
# --- Vulpus Hoe ---
attackDamageVulpusHoe=10 #Attack damage of Vulpus Hoe | default: 10
attackSpeedVulpusHoe=0.5 #Attack speed of Vulpus Hoe | default: 0.5
# --- Vulpus Paxel ---
attackDamageVulpusPaxel=15 #Attack damage of Vulpus Paxel | default: 15
attackSpeedVulpusPaxel=0.5 #Attack speed of Vulpus Paxel | default: 0.5
# --- Vulpus Hammer ---
attackDamageVulpusHammer=11 #Attack damage of Vulpus Hammer | default: 11
attackSpeedVulpusHammer=0.5 #Attack speed of Vulpus Hammer | default: 0.5
radiusVulpusHammer=2 #Hammering radius for Vulpus Hammer | default: 2
# --- Vulpus Excavator ---
attackDamageVulpusExcavator=11 #Attack damage of Vulpus Excavator | default: 11
attackSpeedVulpusExcavator=0.5 #Attack speed of Vulpus Excavator | default: 0.5
radiusVulpusExcavator=2 #Excavation radius for Vulpus Excavator | default: 2
@@ -7,7 +7,12 @@
"icon": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/SurviePote-26.3.png", "icon": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/SurviePote-26.3.png",
"address": "mcpv.ihorizon.org:25565", "address": "mcpv.ihorizon.org:25565",
"mainServer": true, "mainServer": true,
"autoconnect": true "autoconnect": false,
"javaOptions": {
"distribution": "TEMURIN",
"supported": ">=25.x",
"suggestedMajor": 25
}
}, },
"fabric": { "fabric": {
"version": "0.20.0" "version": "0.20.0"
-1
View File
@@ -30,7 +30,6 @@
<%- include('frame') %> <%- include('frame') %>
<div id="main"> <div id="main">
<%- include('welcome') %> <%- include('welcome') %>
<%- include('login') %>
<%- include('waiting') %> <%- include('waiting') %>
<%- include('loginOptions') %> <%- include('loginOptions') %>
<%- include('settings') %> <%- include('settings') %>
-103
View File
@@ -3208,109 +3208,6 @@ input:checked + .toggleSwitchSlider:before {
margin-left: 10px; margin-left: 10px;
} }
/* Wrapper container for the mojang status bar. */
#mojangStatusWrapper {
position: relative;
display: flex;
cursor: pointer;
}
/* Icon which displays the status of the mojang services. */
#mojang_status_icon {
font-size: 30px;
color: #848484;
margin-left: 15px;
font-family: 'sans-serif';
}
/* Tooltip which displays more details about the mojang statuses. */
#mojangStatusTooltip {
position: absolute;
visibility: hidden;
opacity: 0;
width: 145px;
min-height: 150px;
background-color: rgba(0, 0, 0, 0.75);
color: #fff;
border-radius: 4px;
padding: 5px 10px;
z-index: 1;
font-family: 'Avenir Medium';
font-size: 12px;
transition: visibility 0s linear 0.25s, opacity 0.25s ease;
bottom: calc(100% + 15px);
transform: translateX(-50%);
margin-left: 50%;
box-shadow: 0px 0px 20px rgb(0, 0, 0);
cursor: default;
}
#mojangStatusTooltip:after {
content: " ";
position: absolute;
left: 50%;
top: 100%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: rgba(0, 0, 0, 0.75) transparent transparent transparent;
}
#mojangStatusWrapper:hover #mojangStatusTooltip {
visibility: visible;
opacity: 1;
transition-delay: 0s;
}
/* Tooltip title for the mojang statuses. */
#mojangStatusTooltipTitle {
width: 100%;
text-align: center;
margin-bottom: 5px;
letter-spacing: 1px;
}
/* Wrapper container for the non essential services title. */
#mojangStatusNEContainer {
display: flex;
align-items: center;
margin: 10px 0px;
}
/* White bar which surrounds the non essential service title. */
.mojangStatusNEBar {
height: 1px;
width: 100%;
background: white;
}
/* Non essential service title text. */
#mojangStatusNETitle {
font-size: 10px;
padding: 0px 3px;
text-align: center;
letter-spacing: 1px;
}
/* Wrapper container for mojang service information. */
.mojangStatusContainer {
display: flex;
}
/* Displays the name of the mojang service. */
.mojangStatusName {
width: 100%;
font-size: 10px;
letter-spacing: 1px;
line-height: 12px;
padding: 6px 0px;
}
/* Displays the status of the mojang service. */
.mojangStatusIcon {
margin-right: 10px;
font-size: 18.5px;
color: #848484;
}
/* * * /* * *
* Landing View (Bottom Styles) | Center Content * Landing View (Bottom Styles) | Center Content
* * */ * * */
+12 -184
View File
@@ -1,10 +1,10 @@
/** /**
* AuthManager * AuthManager
* *
* This module aims to abstract login procedures. Results from Mojang's REST api * This module aims to abstract login procedures. Microsoft auth results
* are retrieved through our Mojang module. These results are processed and stored, * are retrieved through our Microsoft module. These results are processed and
* if applicable, in the config using the ConfigManager. All login procedures should * stored, if applicable, in the config using the ConfigManager. All login
* be made through this module. * procedures should be made through this module.
* *
* @module authmanager * @module authmanager
*/ */
@@ -12,7 +12,6 @@
const ConfigManager = require('./configmanager') const ConfigManager = require('./configmanager')
const { LoggerUtil } = require('helios-core') const { LoggerUtil } = require('helios-core')
const { RestResponseStatus } = require('helios-core/common') const { RestResponseStatus } = require('helios-core/common')
const { MojangRestAPI, MojangErrorCode } = require('helios-core/mojang')
const { MicrosoftAuth, MicrosoftErrorCode } = require('helios-core/microsoft') const { MicrosoftAuth, MicrosoftErrorCode } = require('helios-core/microsoft')
const { AZURE_CLIENT_ID } = require('./ipcconstants') const { AZURE_CLIENT_ID } = require('./ipcconstants')
const Lang = require('./langloader') const Lang = require('./langloader')
@@ -51,122 +50,8 @@ function microsoftErrorDisplayable(errorCode) {
} }
} }
function mojangErrorDisplayable(errorCode) {
switch(errorCode) {
case MojangErrorCode.ERROR_METHOD_NOT_ALLOWED:
return {
title: Lang.queryJS('auth.mojang.error.methodNotAllowedTitle'),
desc: Lang.queryJS('auth.mojang.error.methodNotAllowedDesc')
}
case MojangErrorCode.ERROR_NOT_FOUND:
return {
title: Lang.queryJS('auth.mojang.error.notFoundTitle'),
desc: Lang.queryJS('auth.mojang.error.notFoundDesc')
}
case MojangErrorCode.ERROR_USER_MIGRATED:
return {
title: Lang.queryJS('auth.mojang.error.accountMigratedTitle'),
desc: Lang.queryJS('auth.mojang.error.accountMigratedDesc')
}
case MojangErrorCode.ERROR_INVALID_CREDENTIALS:
return {
title: Lang.queryJS('auth.mojang.error.invalidCredentialsTitle'),
desc: Lang.queryJS('auth.mojang.error.invalidCredentialsDesc')
}
case MojangErrorCode.ERROR_RATELIMIT:
return {
title: Lang.queryJS('auth.mojang.error.tooManyAttemptsTitle'),
desc: Lang.queryJS('auth.mojang.error.tooManyAttemptsDesc')
}
case MojangErrorCode.ERROR_INVALID_TOKEN:
return {
title: Lang.queryJS('auth.mojang.error.invalidTokenTitle'),
desc: Lang.queryJS('auth.mojang.error.invalidTokenDesc')
}
case MojangErrorCode.ERROR_ACCESS_TOKEN_HAS_PROFILE:
return {
title: Lang.queryJS('auth.mojang.error.tokenHasProfileTitle'),
desc: Lang.queryJS('auth.mojang.error.tokenHasProfileDesc')
}
case MojangErrorCode.ERROR_CREDENTIALS_MISSING:
return {
title: Lang.queryJS('auth.mojang.error.credentialsMissingTitle'),
desc: Lang.queryJS('auth.mojang.error.credentialsMissingDesc')
}
case MojangErrorCode.ERROR_INVALID_SALT_VERSION:
return {
title: Lang.queryJS('auth.mojang.error.invalidSaltVersionTitle'),
desc: Lang.queryJS('auth.mojang.error.invalidSaltVersionDesc')
}
case MojangErrorCode.ERROR_UNSUPPORTED_MEDIA_TYPE:
return {
title: Lang.queryJS('auth.mojang.error.unsupportedMediaTypeTitle'),
desc: Lang.queryJS('auth.mojang.error.unsupportedMediaTypeDesc')
}
case MojangErrorCode.ERROR_GONE:
return {
title: Lang.queryJS('auth.mojang.error.accountGoneTitle'),
desc: Lang.queryJS('auth.mojang.error.accountGoneDesc')
}
case MojangErrorCode.ERROR_UNREACHABLE:
return {
title: Lang.queryJS('auth.mojang.error.unreachableTitle'),
desc: Lang.queryJS('auth.mojang.error.unreachableDesc')
}
case MojangErrorCode.ERROR_NOT_PAID:
return {
title: Lang.queryJS('auth.mojang.error.gameNotPurchasedTitle'),
desc: Lang.queryJS('auth.mojang.error.gameNotPurchasedDesc')
}
case MojangErrorCode.UNKNOWN:
return {
title: Lang.queryJS('auth.mojang.error.unknownErrorTitle'),
desc: Lang.queryJS('auth.mojang.error.unknownErrorDesc')
}
default:
throw new Error(`Unknown error code: ${errorCode}`)
}
}
// Functions // Functions
/**
* Add a Mojang account. This will authenticate the given credentials with Mojang's
* authserver. The resultant data will be stored as an auth account in the
* configuration database.
*
* @param {string} username The account username (email if migrated).
* @param {string} password The account password.
* @returns {Promise.<Object>} Promise which resolves the resolved authenticated account object.
*/
exports.addMojangAccount = async function(username, password) {
try {
const response = await MojangRestAPI.authenticate(username, password, ConfigManager.getClientToken())
console.log(response)
if(response.responseStatus === RestResponseStatus.SUCCESS) {
const session = response.data
if(session.selectedProfile != null){
const ret = ConfigManager.addMojangAuthAccount(session.selectedProfile.id, session.accessToken, username, session.selectedProfile.name)
if(ConfigManager.getClientToken() == null){
ConfigManager.setClientToken(session.clientToken)
}
ConfigManager.save()
return ret
} else {
return Promise.reject(mojangErrorDisplayable(MojangErrorCode.ERROR_NOT_PAID))
}
} else {
return Promise.reject(mojangErrorDisplayable(response.mojangErrorCode))
}
} catch (err){
log.error(err)
return Promise.reject(mojangErrorDisplayable(MojangErrorCode.UNKNOWN))
}
}
const AUTH_MODE = { FULL: 0, MS_REFRESH: 1, MC_REFRESH: 2 } const AUTH_MODE = { FULL: 0, MS_REFRESH: 1, MC_REFRESH: 2 }
/** /**
@@ -239,7 +124,7 @@ function calculateExpiryDate(nowMs, epiresInS) {
} }
/** /**
* Add a Microsoft account. This will pass the provided auth code to Mojang's OAuth2.0 flow. * Add a Microsoft account. This will pass the provided auth code to Microsoft's OAuth2.0 flow.
* The resultant data will be stored as an auth account in the configuration database. * The resultant data will be stored as an auth account in the configuration database.
* *
* @param {string} authCode The authCode obtained from microsoft. * @param {string} authCode The authCode obtained from microsoft.
@@ -266,31 +151,6 @@ exports.addMicrosoftAccount = async function(authCode) {
return ret return ret
} }
/**
* Remove a Mojang account. This will invalidate the access token associated
* with the account and then remove it from the database.
*
* @param {string} uuid The UUID of the account to be removed.
* @returns {Promise.<void>} Promise which resolves to void when the action is complete.
*/
exports.removeMojangAccount = async function(uuid){
try {
const authAcc = ConfigManager.getAuthAccount(uuid)
const response = await MojangRestAPI.invalidate(authAcc.accessToken, ConfigManager.getClientToken())
if(response.responseStatus === RestResponseStatus.SUCCESS) {
ConfigManager.removeAuthAccount(uuid)
ConfigManager.save()
return Promise.resolve()
} else {
log.error('Error while removing account', response.error)
return Promise.reject(response.error)
}
} catch (err){
log.error('Error while removing account', err)
return Promise.reject(err)
}
}
/** /**
* Remove a Microsoft account. It is expected that the caller will invoke the OAuth logout * Remove a Microsoft account. It is expected that the caller will invoke the OAuth logout
* through the ipc renderer. * through the ipc renderer.
@@ -309,41 +169,6 @@ exports.removeMicrosoftAccount = async function(uuid){
} }
} }
/**
* Validate the selected account with Mojang's authserver. If the account is not valid,
* we will attempt to refresh the access token and update that value. If that fails, a
* new login will be required.
*
* @returns {Promise.<boolean>} Promise which resolves to true if the access token is valid,
* otherwise false.
*/
async function validateSelectedMojangAccount(){
const current = ConfigManager.getSelectedAccount()
const response = await MojangRestAPI.validate(current.accessToken, ConfigManager.getClientToken())
if(response.responseStatus === RestResponseStatus.SUCCESS) {
const isValid = response.data
if(!isValid){
const refreshResponse = await MojangRestAPI.refresh(current.accessToken, ConfigManager.getClientToken())
if(refreshResponse.responseStatus === RestResponseStatus.SUCCESS) {
const session = refreshResponse.data
ConfigManager.updateMojangAuthAccount(current.uuid, session.accessToken)
ConfigManager.save()
} else {
log.error('Error while validating selected profile:', refreshResponse.error)
log.info('Account access token is invalid.')
return false
}
log.info('Account access token validated.')
return true
} else {
log.info('Account access token validated.')
return true
}
}
}
/** /**
* Validate the selected account with Microsoft's authserver. If the account is not valid, * Validate the selected account with Microsoft's authserver. If the account is not valid,
* we will attempt to refresh the access token and update that value. If that fails, a * we will attempt to refresh the access token and update that value. If that fails, a
@@ -416,10 +241,13 @@ async function validateSelectedMicrosoftAccount(){
exports.validateSelected = async function(){ exports.validateSelected = async function(){
const current = ConfigManager.getSelectedAccount() const current = ConfigManager.getSelectedAccount()
if(current.type === 'microsoft') { // Auth Mojang supprimee (depreciee) : plus de sign-in Mojang au bootstrap.
return await validateSelectedMicrosoftAccount() // Seuls les comptes Microsoft sont valides ; un ancien compte Mojang est
} else { // considere invalide sans appel reseau (l'authserver Mojang est ferme).
return await validateSelectedMojangAccount() if(current.type !== 'microsoft') {
return false
} }
return await validateSelectedMicrosoftAccount()
} }
+4 -61
View File
@@ -81,7 +81,7 @@ const DEFAULT_CONFIG = {
resWidth: 1280, resWidth: 1280,
resHeight: 720, resHeight: 720,
fullscreen: false, fullscreen: false,
autoConnect: true, autoConnect: false,
launchDetached: true launchDetached: true
}, },
launcher: { launcher: {
@@ -94,7 +94,6 @@ const DEFAULT_CONFIG = {
content: null, content: null,
dismissed: false dismissed: false
}, },
clientToken: null,
selectedServer: null, // Resolved selectedServer: null, // Resolved
selectedAccount: null, selectedAccount: null,
authenticationDatabase: {}, authenticationDatabase: {},
@@ -255,25 +254,6 @@ exports.getInstanceDirectory = function(){
return path.join(exports.getDataDirectory(), 'instances') return path.join(exports.getDataDirectory(), 'instances')
} }
/**
* Retrieve the launcher's Client Token.
* There is no default client token.
*
* @returns {string} The launcher's Client Token.
*/
exports.getClientToken = function(){
return config.clientToken
}
/**
* Set the launcher's Client Token.
*
* @param {string} clientToken The launcher's new Client Token.
*/
exports.setClientToken = function(clientToken){
config.clientToken = clientToken
}
/** /**
* Retrieve the ID of the selected serverpack. * Retrieve the ID of the selected serverpack.
* *
@@ -281,7 +261,7 @@ exports.setClientToken = function(clientToken){
* @returns {string} The ID of the selected serverpack. * @returns {string} The ID of the selected serverpack.
*/ */
exports.getSelectedServer = function(def = false){ exports.getSelectedServer = function(def = false){
return !def ? config.selectedServer : DEFAULT_CONFIG.clientToken return !def ? config.selectedServer : DEFAULT_CONFIG.selectedServer
} }
/** /**
@@ -313,42 +293,6 @@ exports.getAuthAccount = function(uuid){
return config.authenticationDatabase[uuid] return config.authenticationDatabase[uuid]
} }
/**
* Update the access token of an authenticated mojang account.
*
* @param {string} uuid The uuid of the authenticated account.
* @param {string} accessToken The new Access Token.
*
* @returns {Object} The authenticated account object created by this action.
*/
exports.updateMojangAuthAccount = function(uuid, accessToken){
config.authenticationDatabase[uuid].accessToken = accessToken
config.authenticationDatabase[uuid].type = 'mojang' // For gradual conversion.
return config.authenticationDatabase[uuid]
}
/**
* Adds an authenticated mojang account to the database to be stored.
*
* @param {string} uuid The uuid of the authenticated account.
* @param {string} accessToken The accessToken of the authenticated account.
* @param {string} username The username (usually email) of the authenticated account.
* @param {string} displayName The in game name of the authenticated account.
*
* @returns {Object} The authenticated account object created by this action.
*/
exports.addMojangAuthAccount = function(uuid, accessToken, username, displayName){
config.selectedAccount = uuid
config.authenticationDatabase[uuid] = {
type: 'mojang',
accessToken,
username: username.trim(),
uuid: uuid.trim(),
displayName: displayName.trim()
}
return config.authenticationDatabase[uuid]
}
/** /**
* Update the tokens of an authenticated microsoft account. * Update the tokens of an authenticated microsoft account.
* *
@@ -357,7 +301,7 @@ exports.addMojangAuthAccount = function(uuid, accessToken, username, displayName
* @param {string} msAccessToken The new Microsoft Access Token * @param {string} msAccessToken The new Microsoft Access Token
* @param {string} msRefreshToken The new Microsoft Refresh Token * @param {string} msRefreshToken The new Microsoft Refresh Token
* @param {date} msExpires The date when the microsoft access token expires * @param {date} msExpires The date when the microsoft access token expires
* @param {date} mcExpires The date when the mojang access token expires * @param {date} mcExpires The date when the Minecraft access token expires
* *
* @returns {Object} The authenticated account object created by this action. * @returns {Object} The authenticated account object created by this action.
*/ */
@@ -376,7 +320,7 @@ exports.updateMicrosoftAuthAccount = function(uuid, accessToken, msAccessToken,
* @param {string} uuid The uuid of the authenticated account. * @param {string} uuid The uuid of the authenticated account.
* @param {string} accessToken The accessToken of the authenticated account. * @param {string} accessToken The accessToken of the authenticated account.
* @param {string} name The in game name of the authenticated account. * @param {string} name The in game name of the authenticated account.
* @param {date} mcExpires The date when the mojang access token expires * @param {date} mcExpires The date when the Minecraft access token expires
* @param {string} msAccessToken The microsoft access token * @param {string} msAccessToken The microsoft access token
* @param {string} msRefreshToken The microsoft refresh token * @param {string} msRefreshToken The microsoft refresh token
* @param {date} msExpires The date when the microsoft access token expires * @param {date} msExpires The date when the microsoft access token expires
@@ -419,7 +363,6 @@ exports.removeAuthAccount = function(uuid){
config.selectedAccount = keys[0] config.selectedAccount = keys[0]
} else { } else {
config.selectedAccount = null config.selectedAccount = null
config.clientToken = null
} }
} }
return true return true
+6 -23
View File
@@ -185,6 +185,10 @@ class ProcessBuilder {
const child = child_process.spawn(ConfigManager.getJavaExecutable(this.server.rawServer.id), args, { const child = child_process.spawn(ConfigManager.getJavaExecutable(this.server.rawServer.id), args, {
cwd: this.gameDir, cwd: this.gameDir,
detached: ConfigManager.getLaunchDetached(), detached: ConfigManager.getLaunchDetached(),
// Windows : sans ça, java.exe ouvre sa propre console (detached => nouvelle
// console) : une fenêtre noire, vide (stdout est pipe vers le launcher),
// qui traîne par-dessus la fenêtre du jeu pendant toute la session.
windowsHide: true,
env: gameEnv env: gameEnv
}) })
@@ -431,20 +435,6 @@ class ProcessBuilder {
} }
_processAutoConnectArg(args){
if(ConfigManager.getAutoConnect() && this.server.rawServer.autoconnect){
if(mcVersionAtLeast('1.20', this.server.rawServer.minecraftVersion)){
args.push('--quickPlayMultiplayer')
args.push(`${this.server.hostname}:${this.server.port}`)
} else {
args.push('--server')
args.push(this.server.hostname)
args.push('--port')
args.push(this.server.port)
}
}
}
/** /**
* Construct the argument array that will be passed to the JVM process. * Construct the argument array that will be passed to the JVM process.
* *
@@ -616,7 +606,7 @@ class ProcessBuilder {
val = this.authUser.accessToken val = this.authUser.accessToken
break break
case 'user_type': case 'user_type':
val = this.authUser.type === 'microsoft' ? 'msa' : 'mojang' val = 'msa'
break break
case 'version_type': case 'version_type':
val = this.vanillaManifest.type val = this.vanillaManifest.type
@@ -647,10 +637,6 @@ class ProcessBuilder {
} }
} }
// Autoconnect
this._processAutoConnectArg(args)
// Forge Specific Arguments // Forge Specific Arguments
args = args.concat(this.modManifest.arguments.game) args = args.concat(this.modManifest.arguments.game)
@@ -700,7 +686,7 @@ class ProcessBuilder {
val = this.authUser.accessToken val = this.authUser.accessToken
break break
case 'user_type': case 'user_type':
val = this.authUser.type === 'microsoft' ? 'msa' : 'mojang' val = 'msa'
break break
case 'user_properties': // 1.8.9 and below. case 'user_properties': // 1.8.9 and below.
val = '{}' val = '{}'
@@ -715,9 +701,6 @@ class ProcessBuilder {
} }
} }
// Autoconnect to the selected server.
this._processAutoConnectArg(mcArgs)
// Prepare game resolution // Prepare game resolution
if(ConfigManager.getFullscreen()){ if(ConfigManager.getFullscreen()){
mcArgs.push('--fullscreen') mcArgs.push('--fullscreen')
-63
View File
@@ -4,7 +4,6 @@
// Requirements // Requirements
const { URL } = require('url') const { URL } = require('url')
const { const {
MojangRestAPI,
getServerStatus getServerStatus
} = require('helios-core/mojang') } = require('helios-core/mojang')
const { const {
@@ -176,65 +175,6 @@ server_selection_button.onclick = async e => {
await toggleServerSelection(true) await toggleServerSelection(true)
} }
// Update Mojang Status Color
const refreshMojangStatuses = async function(){
loggerLanding.info('Refreshing Mojang Statuses..')
let status = 'grey'
let tooltipEssentialHTML = ''
let tooltipNonEssentialHTML = ''
const response = await MojangRestAPI.status()
let statuses
if(response.responseStatus === RestResponseStatus.SUCCESS) {
statuses = response.data
} else {
loggerLanding.warn('Unable to refresh Mojang service status.')
statuses = MojangRestAPI.getDefaultStatuses()
}
greenCount = 0
greyCount = 0
for(let i=0; i<statuses.length; i++){
const service = statuses[i]
const tooltipHTML = `<div class="mojangStatusContainer">
<span class="mojangStatusIcon" style="color: ${MojangRestAPI.statusToHex(service.status)};">&#8226;</span>
<span class="mojangStatusName">${service.name}</span>
</div>`
if(service.essential){
tooltipEssentialHTML += tooltipHTML
} else {
tooltipNonEssentialHTML += tooltipHTML
}
if(service.status === 'yellow' && status !== 'red'){
status = 'yellow'
} else if(service.status === 'red'){
status = 'red'
} else {
if(service.status === 'grey'){
++greyCount
}
++greenCount
}
}
if(greenCount === statuses.length){
if(greyCount === statuses.length){
status = 'grey'
} else {
status = 'green'
}
}
document.getElementById('mojangStatusEssentialContainer').innerHTML = tooltipEssentialHTML
document.getElementById('mojangStatusNonEssentialContainer').innerHTML = tooltipNonEssentialHTML
document.getElementById('mojang_status_icon').style.color = MojangRestAPI.statusToHex(status)
}
const refreshServerStatus = async (fade = false) => { const refreshServerStatus = async (fade = false) => {
loggerLanding.info('Refreshing Server Status') loggerLanding.info('Refreshing Server Status')
const serv = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer()) const serv = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer())
@@ -266,11 +206,8 @@ const refreshServerStatus = async (fade = false) => {
} }
refreshMojangStatuses()
// Server Status is refreshed in uibinder.js on distributionIndexDone. // Server Status is refreshed in uibinder.js on distributionIndexDone.
// Refresh statuses every hour. The status page itself refreshes every day so...
let mojangStatusListener = setInterval(() => refreshMojangStatuses(true), 60*60*1000)
// Set refresh rate to once every 5 minutes. // Set refresh rate to once every 5 minutes.
let serverStatusListener = setInterval(() => refreshServerStatus(true), 300000) let serverStatusListener = setInterval(() => refreshServerStatus(true), 300000)
-234
View File
@@ -1,234 +0,0 @@
/**
* Script for login.ejs
*/
// Validation Regexes.
const validUsername = /^[a-zA-Z0-9_]{1,16}$/
const basicEmail = /^\S+@\S+\.\S+$/
//const validEmail = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i
// Login Elements
const loginCancelContainer = document.getElementById('loginCancelContainer')
const loginCancelButton = document.getElementById('loginCancelButton')
const loginEmailError = document.getElementById('loginEmailError')
const loginUsername = document.getElementById('loginUsername')
const loginPasswordError = document.getElementById('loginPasswordError')
const loginPassword = document.getElementById('loginPassword')
const checkmarkContainer = document.getElementById('checkmarkContainer')
const loginRememberOption = document.getElementById('loginRememberOption')
const loginButton = document.getElementById('loginButton')
const loginForm = document.getElementById('loginForm')
// Control variables.
let lu = false, lp = false
/**
* Show a login error.
*
* @param {HTMLElement} element The element on which to display the error.
* @param {string} value The error text.
*/
function showError(element, value){
element.innerHTML = value
element.style.opacity = 1
}
/**
* Shake a login error to add emphasis.
*
* @param {HTMLElement} element The element to shake.
*/
function shakeError(element){
if(element.style.opacity == 1){
element.classList.remove('shake')
void element.offsetWidth
element.classList.add('shake')
}
}
/**
* Validate that an email field is neither empty nor invalid.
*
* @param {string} value The email value.
*/
function validateEmail(value){
if(value){
if(!basicEmail.test(value) && !validUsername.test(value)){
showError(loginEmailError, Lang.queryJS('login.error.invalidValue'))
loginDisabled(true)
lu = false
} else {
loginEmailError.style.opacity = 0
lu = true
if(lp){
loginDisabled(false)
}
}
} else {
lu = false
showError(loginEmailError, Lang.queryJS('login.error.requiredValue'))
loginDisabled(true)
}
}
/**
* Validate that the password field is not empty.
*
* @param {string} value The password value.
*/
function validatePassword(value){
if(value){
loginPasswordError.style.opacity = 0
lp = true
if(lu){
loginDisabled(false)
}
} else {
lp = false
showError(loginPasswordError, Lang.queryJS('login.error.invalidValue'))
loginDisabled(true)
}
}
// Emphasize errors with shake when focus is lost.
loginUsername.addEventListener('focusout', (e) => {
validateEmail(e.target.value)
shakeError(loginEmailError)
})
loginPassword.addEventListener('focusout', (e) => {
validatePassword(e.target.value)
shakeError(loginPasswordError)
})
// Validate input for each field.
loginUsername.addEventListener('input', (e) => {
validateEmail(e.target.value)
})
loginPassword.addEventListener('input', (e) => {
validatePassword(e.target.value)
})
/**
* Enable or disable the login button.
*
* @param {boolean} v True to enable, false to disable.
*/
function loginDisabled(v){
if(loginButton.disabled !== v){
loginButton.disabled = v
}
}
/**
* Enable or disable loading elements.
*
* @param {boolean} v True to enable, false to disable.
*/
function loginLoading(v){
if(v){
loginButton.setAttribute('loading', v)
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.login'), Lang.queryJS('login.loggingIn'))
} else {
loginButton.removeAttribute('loading')
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.loggingIn'), Lang.queryJS('login.login'))
}
}
/**
* Enable or disable login form.
*
* @param {boolean} v True to enable, false to disable.
*/
function formDisabled(v){
loginDisabled(v)
loginCancelButton.disabled = v
loginUsername.disabled = v
loginPassword.disabled = v
if(v){
checkmarkContainer.setAttribute('disabled', v)
} else {
checkmarkContainer.removeAttribute('disabled')
}
loginRememberOption.disabled = v
}
let loginViewOnSuccess = VIEWS.landing
let loginViewOnCancel = VIEWS.settings
let loginViewCancelHandler
function loginCancelEnabled(val){
if(val){
$(loginCancelContainer).show()
} else {
$(loginCancelContainer).hide()
}
}
loginCancelButton.onclick = (e) => {
switchView(getCurrentView(), loginViewOnCancel, 500, 500, () => {
loginUsername.value = ''
loginPassword.value = ''
loginCancelEnabled(false)
if(loginViewCancelHandler != null){
loginViewCancelHandler()
loginViewCancelHandler = null
}
})
}
// Disable default form behavior.
loginForm.onsubmit = () => { return false }
// Bind login button behavior.
loginButton.addEventListener('click', () => {
// Disable form.
formDisabled(true)
// Show loading stuff.
loginLoading(true)
AuthManager.addMojangAccount(loginUsername.value, loginPassword.value).then((value) => {
updateSelectedAccount(value)
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.loggingIn'), Lang.queryJS('login.success'))
$('.circle-loader').toggleClass('load-complete')
$('.checkmark').toggle()
setTimeout(() => {
switchView(VIEWS.login, loginViewOnSuccess, 500, 500, async () => {
// Temporary workaround
if(loginViewOnSuccess === VIEWS.settings){
await prepareSettings()
}
loginViewOnSuccess = VIEWS.landing // Reset this for good measure.
loginCancelEnabled(false) // Reset this for good measure.
loginViewCancelHandler = null // Reset this for good measure.
loginUsername.value = ''
loginPassword.value = ''
$('.circle-loader').toggleClass('load-complete')
$('.checkmark').toggle()
loginLoading(false)
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.success'), Lang.queryJS('login.login'))
formDisabled(false)
})
}, 1000)
}).catch((displayableError) => {
loginLoading(false)
let actualDisplayableError
if(isDisplayableError(displayableError)) {
msftLoginLogger.error('Error while logging in.', displayableError)
actualDisplayableError = displayableError
} else {
// Uh oh.
msftLoginLogger.error('Unhandled error during login.', displayableError)
actualDisplayableError = Lang.queryJS('login.error.unknown')
}
setOverlayContent(actualDisplayableError.title, actualDisplayableError.desc, Lang.queryJS('login.tryAgain'))
setOverlayHandler(() => {
formDisabled(false)
toggleOverlay(false)
})
toggleOverlay(true)
})
})
@@ -502,32 +502,10 @@ let msAccDomElementCache
function processLogOut(val, isLastAccount){ function processLogOut(val, isLastAccount){
const parent = val.closest('.settingsAuthAccount') const parent = val.closest('.settingsAuthAccount')
const uuid = parent.getAttribute('uuid') const uuid = parent.getAttribute('uuid')
const prevSelAcc = ConfigManager.getSelectedAccount()
const targetAcc = ConfigManager.getAuthAccount(uuid)
if(targetAcc.type === 'microsoft') {
msAccDomElementCache = parent msAccDomElementCache = parent
switchView(getCurrentView(), VIEWS.waiting, 500, 500, () => { switchView(getCurrentView(), VIEWS.waiting, 500, 500, () => {
ipcRenderer.send(MSFT_OPCODE.OPEN_LOGOUT, uuid, isLastAccount) ipcRenderer.send(MSFT_OPCODE.OPEN_LOGOUT, uuid, isLastAccount)
}) })
} else {
AuthManager.removeMojangAccount(uuid).then(() => {
if(!isLastAccount && uuid === prevSelAcc.uuid){
const selAcc = ConfigManager.getSelectedAccount()
refreshAuthAccountSelected(selAcc.uuid)
updateSelectedAccount(selAcc)
validateSelectedAccount()
}
if(isLastAccount) {
loginOptionsCancelEnabled(false)
loginOptionsViewOnLoginSuccess = VIEWS.settings
loginOptionsViewOnLoginCancel = VIEWS.loginOptions
switchView(getCurrentView(), VIEWS.loginOptions)
}
})
$(parent).fadeOut(250, () => {
parent.remove()
})
}
} }
// Bind reply for Microsoft Logout. // Bind reply for Microsoft Logout.
@@ -17,7 +17,6 @@ let fatalStartupError = false
const VIEWS = { const VIEWS = {
landing: '#landingContainer', landing: '#landingContainer',
loginOptions: '#loginOptionsContainer', loginOptions: '#loginOptionsContainer',
login: '#loginContainer',
settings: '#settingsContainer', settings: '#settingsContainer',
welcome: '#welcomeContainer', welcome: '#welcomeContainer',
waiting: '#waitingContainer' waiting: '#waitingContainer'
@@ -336,24 +335,12 @@ async function validateSelectedAccount(){
) )
setOverlayHandler(() => { setOverlayHandler(() => {
const isMicrosoft = selectedAcc.type === 'microsoft'
if(isMicrosoft) {
// Empty for now
} else {
// Mojang
// For convenience, pre-populate the username of the account.
document.getElementById('loginUsername').value = selectedAcc.username
validateEmail(selectedAcc.username)
}
loginOptionsViewOnLoginSuccess = getCurrentView() loginOptionsViewOnLoginSuccess = getCurrentView()
loginOptionsViewOnLoginCancel = VIEWS.loginOptions loginOptionsViewOnLoginCancel = VIEWS.loginOptions
if(accLen > 0) { if(accLen > 0) {
loginOptionsViewOnCancel = getCurrentView() loginOptionsViewOnCancel = getCurrentView()
loginOptionsViewCancelHandler = () => { loginOptionsViewCancelHandler = () => {
if(isMicrosoft) {
ConfigManager.addMicrosoftAuthAccount( ConfigManager.addMicrosoftAuthAccount(
selectedAcc.uuid, selectedAcc.uuid,
selectedAcc.accessToken, selectedAcc.accessToken,
@@ -363,9 +350,6 @@ async function validateSelectedAccount(){
selectedAcc.microsoft.refresh_token, selectedAcc.microsoft.refresh_token,
selectedAcc.microsoft.expires_at selectedAcc.microsoft.expires_at
) )
} else {
ConfigManager.addMojangAuthAccount(selectedAcc.uuid, selectedAcc.accessToken, selectedAcc.username, selectedAcc.displayName)
}
ConfigManager.save() ConfigManager.save()
validateSelectedAccount() validateSelectedAccount()
} }
-19
View File
@@ -39,25 +39,6 @@
<span class="bot_label" id="landingPlayerLabel"><%- lang('landing.serverStatus') %></span> <span class="bot_label" id="landingPlayerLabel"><%- lang('landing.serverStatus') %></span>
<span id="player_count"><%- lang('landing.serverStatusPlaceholder') %></span> <span id="player_count"><%- lang('landing.serverStatusPlaceholder') %></span>
</div> </div>
<div class="bot_divider"></div>
<div id="mojangStatusWrapper">
<span class="bot_label"><%- lang('landing.mojangStatus') %></span>
<span id="mojang_status_icon">&#8226;</span>
<div id="mojangStatusTooltip">
<div id="mojangStatusTooltipTitle"><%- lang('landing.mojangStatusTooltipTitle') %></div>
<div id="mojangStatusEssentialContainer">
<!-- Essential Mojang services are populated here. -->
</div>
<div id="mojangStatusNEContainer">
<div class="mojangStatusNEBar"></div>
<div id="mojangStatusNETitle"><%- lang('landing.mojangStatusNETitle') %></div>
<div class="mojangStatusNEBar"></div>
</div>
<div id="mojangStatusNonEssentialContainer">
<!-- Non Essential Mojang services are populated here. -->
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
-65
View File
@@ -1,65 +0,0 @@
<div id="loginContainer" style="display: none;">
<div id="loginCancelContainer" style="display: none;">
<button id="loginCancelButton">
<div id="loginCancelIcon">X</div>
<span id="loginCancelText"><%- lang('login.loginCancelText') %></span>
</button>
</div>
<div id="loginContent">
<form id="loginForm">
<img id="loginImageSeal" src="assets/images/SealCircle.png"/>
<span id="loginSubheader"><%- lang('login.loginSubheader') %></span>
<div class="loginFieldContainer">
<svg id="profileSVG" class="loginSVG" viewBox="40 37 65.36 61.43">
<g>
<path d="M86.77,58.12A13.79,13.79,0,1,0,73,71.91,13.79,13.79,0,0,0,86.77,58.12M97,103.67a3.41,3.41,0,0,0,3.39-3.84,27.57,27.57,0,0,0-54.61,0,3.41,3.41,0,0,0,3.39,3.84Z"/>
</g>
</svg>
<span class="loginErrorSpan" id="loginEmailError"><%- lang('login.loginEmailError') %></span>
<input id="loginUsername" class="loginField" type="text" placeholder="<%- lang('login.loginEmailPlaceholder') %>"/>
</div>
<div class="loginFieldContainer">
<svg id="lockSVG" class="loginSVG" viewBox="40 32 60.36 70.43">
<g>
<path d="M86.16,54a16.38,16.38,0,1,0-32,0H44V102.7H96V54Zm-25.9-3.39a9.89,9.89,0,1,1,19.77,0A9.78,9.78,0,0,1,79.39,54H60.89A9.78,9.78,0,0,1,60.26,50.59ZM70,96.2a6.5,6.5,0,0,1-6.5-6.5,6.39,6.39,0,0,1,3.1-5.4V67h6.5V84.11a6.42,6.42,0,0,1,3.39,5.6A6.5,6.5,0,0,1,70,96.2Z"/>
</g>
</svg>
<span class="loginErrorSpan" id="loginPasswordError"><%- lang('login.loginPasswordError') %></span>
<input id="loginPassword" class="loginField" type="password" placeholder="<%- lang('login.loginPasswordPlaceholder') %>"/>
</div>
<div id="loginOptions">
<span class="loginSpanDim">
<a href="<%- lang('login.loginForgotPasswordLink') %>"><%- lang('login.loginForgotPasswordText') %></a>
</span>
<label id="checkmarkContainer">
<input id="loginRememberOption" type="checkbox" checked>
<span id="loginRememberText" class="loginSpanDim"><%- lang('login.loginRememberMeText') %></span>
<span class="loginCheckmark"></span>
</label>
</div>
<button id="loginButton" disabled>
<div id="loginButtonContent">
<%- lang('login.loginButtonText') %>
<svg id="loginSVG" viewBox="0 0 24.87 13.97">
<defs>
<style>.arrowLine{fill:none;stroke:#FFF;stroke-width:2px;transition: 0.25s ease;}</style>
</defs>
<polyline class="arrowLine" points="0.71 13.26 12.56 1.41 24.16 13.02"/>
</svg>
<div class="circle-loader">
<div class="checkmark draw"></div>
</div>
<!--<div class="spinningCircle" id="loginSpinner"></div>-->
</div>
</button>
<div id="loginDisclaimer">
<span class="loginSpanDim" id="loginRegisterSpan">
<a href="<%- lang('login.loginNeedAccountLink') %>"><%- lang('login.loginNeedAccountText') %></a>
</span>
<p class="loginDisclaimerText"><%- lang('login.loginPasswordDisclaimer1') %></p>
<p class="loginDisclaimerText"><%- lang('login.loginPasswordDisclaimer2', { appName: lang('app.title') }) %></p>
</div>
</form>
</div>
<script src="./assets/js/scripts/login.js"></script>
</div>
-11
View File
@@ -73,17 +73,6 @@
</label> </label>
</div> </div>
</div> </div>
<div class="settingsFieldContainer">
<div class="settingsFieldLeft">
<span class="settingsFieldTitle"><%- lang('settings.autoConnectTitle') %></span>
</div>
<div class="settingsFieldRight">
<label class="toggleSwitch">
<input type="checkbox" cValue="AutoConnect">
<span class="toggleSwitchSlider"></span>
</label>
</div>
</div>
<div class="settingsFieldContainer"> <div class="settingsFieldContainer">
<div class="settingsFieldLeft"> <div class="settingsFieldLeft">
<span class="settingsFieldTitle"><%- lang('settings.launchDetachedTitle') %></span> <span class="settingsFieldTitle"><%- lang('settings.launchDetachedTitle') %></span>
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "surviepote-launcher", "name": "surviepote-launcher",
"version": "1.0.0", "version": "1.1.1",
"productName": "SurviePote", "productName": "SurviePote",
"description": "Launcher officiel SurviePote - Rejoins mcpv.ihorizon.org", "description": "Launcher officiel SurviePote - Rejoins mcpv.ihorizon.org",
"author": "SurviePote", "author": "SurviePote",
+30 -2
View File
@@ -1,6 +1,11 @@
# nosingleplayer (mod maison SurviePote) # nosingleplayer (mod maison SurviePote)
Désactive le bouton **Solo** du menu principal (Mixin sur `TitleScreen.init`). Désactive le bouton **Solo** du menu principal (Mixin sur `TitleScreen.init`) et adapte l'UI
au serveur unique :
- Menu titre : bouton **Rejoindre SurviePote** (connexion directe à `mcpv.ihorizon.org:25565`,
sans transfert) ; Solo, Multi, Realms, réseau social et icônes bas retirés.
- Menu Échap : le bouton de déconnexion devient **Quitter SurviePote** et renvoie à l'écran
d'accueil du jeu (et non la liste des serveurs).
- MC 26.3, Fabric Loader 0.20.0, Java 25, `environment: client`. - MC 26.3, Fabric Loader 0.20.0, Java 25, `environment: client`.
- Particularité 26.x : Mojang ne publie plus de mappings (ni Yarn ni `client_mappings`) et le - Particularité 26.x : Mojang ne publie plus de mappings (ni Yarn ni `client_mappings`) et le
jeu n'est plus obfusqué → **pas de Loom**, compilation directe contre le jar client Mojang jeu n'est plus obfusqué → **pas de Loom**, compilation directe contre le jar client Mojang
@@ -8,4 +13,27 @@ Désactive le bouton **Solo** du menu principal (Mixin sur `TitleScreen.init`).
pas de refmap nécessaire. pas de refmap nécessaire.
Build : `cd mods/nosingleplayer && ./gradlew build` (dans nix-shell racine pour Java 25). Build : `cd mods/nosingleplayer && ./gradlew build` (dans nix-shell racine pour Java 25).
Jar : `build/libs/nosingleplayer-1.0.0.jar` → copié dans `atlas/.../fabricmods/required/`. Jar : `build/libs/nosingleplayer-1.2.0.jar` → copié dans `atlas/.../fabricmods/optionalon/`.
## Textures
MC 26.3 n'expose pas les ressources des mods au ResourceManager (vérifié : le panorama
vanilla affiche le stock Mojang, pas les `panorama_*.png` du mod). Les PNG sont donc
lus **dans le jar** et enregistrés via le mécanisme officiel de MC
(`ReloadableTexture` + `TextureContents`) : c'est MC qui crée la texture GPU et le
sampler, exactement comme pour ses propres textures.
⚠️ La texture GPU doit être créée **hors de l'extraction du rendu** (ici dans
`TitleScreen.init`) : la créer pendant l'enregistrement des commandes d'une frame
invalide le frame entier sur les backends à command encoder (Vulkan) → menu titre
**entièrement noir** (constaté sur Windows + NVIDIA).
## Diagnostic
- `-Dsurviepote.customTitle=false` (options JVM du launcher) : repasser au menu **vanilla**
(panorama vanilla au lieu du fond custom). Sert à isoler un souci de rendu du fond custom.
- Le mod log au démarrage du menu : `[SurviePote] Texture enregistrée : … (1920x1200)` et
`[SurviePote] GPU: <nom> | <vendor> | pilote <driver> | backend <OpenGL|Vulkan>`.
- Si le fond custom échoue (texture refusée par le GPU, jar corrompu…), le mod se repli
automatiquement sur le panorama vanilla au lieu de laisser un écran noir.
+97
View File
@@ -42,6 +42,9 @@ tasks.register('downloadMcLibs') {
new File(outDir, 'slf4j-api.jar')) new File(outDir, 'slf4j-api.jar'))
dl('https://libraries.minecraft.net/com/mojang/brigadier/1.3.11/brigadier-1.3.11.jar', dl('https://libraries.minecraft.net/com/mojang/brigadier/1.3.11/brigadier-1.3.11.jar',
new File(outDir, 'brigadier.jar')) new File(outDir, 'brigadier.jar'))
// Annotations JSpecify : referencées par les signatures de MC 26.3 (@Nullable...).
dl('https://repo1.maven.org/maven2/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar',
new File(outDir, 'jspecify.jar'))
} }
} }
@@ -64,3 +67,97 @@ processResources {
java { java {
withSourcesJar() withSourcesJar()
} }
// ---------------------------------------------------------------------------
// Dev : lance le vrai client MC 26.3 + nosingleplayer (build sans loom).
// Réutilise l'installation commune du launcher SurviePote (~/.surviepote/common :
// versions, libraries, assets — remplie au premier lancement). Le mod est copié
// dans runClient/game/mods/ (Knot le découvre comme un mod Fabric classique).
// Usage : ./gradlew runClient
// ---------------------------------------------------------------------------
def survieCommon = new File(System.getProperty('user.home'), '.surviepote/common')
def mcVersion = project.minecraft_version
def loaderVersion = project.loader_version
tasks.register('extractClientNatives', Copy) {
def versionJson = new groovy.json.JsonSlurper().parse(
new File(survieCommon, "versions/${mcVersion}/${mcVersion}.json"))
from(versionJson.libraries.findAll { lib ->
lib.name.contains(':natives-linux') && (lib.rules == null || lib.rules.any { it.action == 'allow' && (it.os == null || it.os.name == 'linux') })
}.collect { lib ->
zipTree(new File(survieCommon, "libraries/${lib.downloads.artifact.path}"))
})
into(layout.buildDirectory.dir('runClient/natives'))
include('**/*.so')
eachFile { f -> f.path = f.name }
}
tasks.register('runClient', JavaExec) {
dependsOn('extractClientNatives', 'jar')
group = 'fabric'
description = 'Lance le client Minecraft avec le mod (dev visuel, sans loom).'
def versionJson = new groovy.json.JsonSlurper().parse(
new File(survieCommon, "versions/${mcVersion}/${mcVersion}.json"))
def fabricJson = new groovy.json.JsonSlurper().parse(
new File(survieCommon, "versions/${mcVersion}-fabric-${loaderVersion}/${mcVersion}-fabric-${loaderVersion}.json"))
def mavenToPath = { String coord ->
def parts = coord.split(':')
return "${parts[0].replace('.', '/')}/${parts[1]}/${parts[2]}/${parts[1]}-${parts[2]}.jar"
}
doFirst {
def modsDir = layout.buildDirectory.dir('runClient/game/mods').get().asFile
delete(modsDir)
modsDir.mkdirs()
copy {
from(tasks.named('jar'))
into(modsDir)
}
}
// NixOS : dlopen ne voit ni libGL ni libvulkan sans LD_LIBRARY_PATH (cf launcher).
def nixStore = new File('/nix/store')
def glDirs = []
['/run/opengl-driver/lib', '/run/current-system/sw/lib'].each { d ->
if (new File(d).exists()) glDirs << d
}
if (nixStore.exists()) {
nixStore.eachDir { e ->
def n = e.name
if (n.contains('libglvnd') || n.contains('vulkan-loader') || n.contains('alsa-lib')
|| n.contains('pulseaudio') || (n.contains('pipewire') && !n.contains('python'))) {
def lib = new File(e, 'lib')
if (lib.exists()) glDirs << lib.absolutePath
}
}
}
def prevLd = System.getenv('LD_LIBRARY_PATH')
environment('LD_LIBRARY_PATH', (prevLd ? prevLd + ':' : '') + glDirs.join(':'))
if (!System.getenv('SDL_VIDEODRIVER') && (System.getenv('WAYLAND_DISPLAY') || new File('/run/user/1000/wayland-1').exists())) {
environment('SDL_VIDEODRIVER', 'wayland')
}
javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(25) }
classpath = files(
new File(survieCommon, "versions/${mcVersion}/${mcVersion}.jar"),
versionJson.libraries.findAll { lib ->
!(lib.name.contains(':natives-')) && (lib.rules == null || lib.rules.any { it.action == 'allow' && (it.os == null || it.os.name == 'linux') })
}.collect { new File(survieCommon, "libraries/${it.downloads.artifact.path}") },
fabricJson.libraries.collect { new File(survieCommon, "libraries/${mavenToPath(it.name)}") }
)
mainClass = 'net.fabricmc.loader.impl.launch.knot.KnotClient'
args = [
'--gameDir', layout.buildDirectory.dir('runClient/game').get().asFile.absolutePath,
'--assetsDir', new File(survieCommon, 'assets').absolutePath,
'--assetIndex', versionJson.assetIndex.id,
'--version', "${project.archives_base_name}-dev",
'--username', 'Dev', '--uuid', '00000000-0000-0000-0000-000000000000',
'--accessToken', '0', '--versionType', 'release'
]
jvmArgs = [
"-Djava.library.path=${layout.buildDirectory.dir('runClient/natives').get().asFile.absolutePath}",
'-Xmx4G'
]
systemProperties['org.lwjgl.util.Debug'] = 'false'
}
+1 -1
View File
@@ -5,7 +5,7 @@ loader_version=0.20.0
loom_version=1.19.1 loom_version=1.19.1
# Mod Properties # Mod Properties
mod_version=1.1.2 mod_version=1.2.0
maven_group=fr.surviepote maven_group=fr.surviepote
archives_base_name=nosingleplayer archives_base_name=nosingleplayer
@@ -0,0 +1,74 @@
package fr.surviepote.nosingleplayer;
import java.util.HashMap;
import java.util.Map;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureContents;
import net.minecraft.resources.Identifier;
/**
* Enregistrement des textures du mod dans le TextureManager de MC.
*
* <p>IMPORTANT : l'enregistrement doit être fait sur le thread de rendu mais
* <b>en dehors</b> de l'extraction du rendu (ex. dans {@code TitleScreen.init}).
* Créer une ressource GPU pendant l'enregistrement des commandes d'une frame
* invalide le frame entier sur les backends à command encoder (Vulkan/NVIDIA) :
* résultat, un menu titre entièrement noir. D'où le try/catch + le repli sur le
* panorama vanilla dans TitleScreenRenderMixin.
*/
public final class ModTextures {
private static final Map<Identifier, SurviePoteTexture> CACHE = new HashMap<>();
private ModTextures() {
}
/**
* Enregistre une texture lue dans le jar du mod.
*
* @param id identifiant sous lequel la texture est enregistrée
* @param resourcePath chemin dans le jar, ex {@code /assets/nosingleplayer/...}
* @param blur true = filtrage linéaire, false = nearest (pixel art)
* @return true si la texture est utilisable
*/
public static boolean register(Identifier id, String resourcePath, boolean blur) {
Minecraft minecraft = Minecraft.getInstance();
SurviePoteTexture cached = CACHE.get(id);
if (cached != null && minecraft.getTextureManager().getTexture(id) == cached) {
return true; // déjà enregistrée et toujours en place
}
try {
SurviePoteTexture texture = new SurviePoteTexture(id, resourcePath, blur);
TextureContents contents = texture.loadContents(minecraft.getResourceManager());
texture.apply(contents);
minecraft.getTextureManager().register(id, texture);
CACHE.put(id, texture);
NoSingleplayer.LOGGER.info("[SurviePote] Texture enregistrée : {} ({}x{})",
id, texture.getWidth(), texture.getHeight());
return true;
} catch (Throwable t) {
NoSingleplayer.LOGGER.error("[SurviePote] Échec d'enregistrement de la texture " + resourcePath, t);
CACHE.remove(id);
return false;
}
}
/** True si la texture est enregistrée ET encore présente dans le TextureManager. */
public static boolean isRegistered(Identifier id) {
SurviePoteTexture cached = CACHE.get(id);
return cached != null
&& Minecraft.getInstance().getTextureManager().getTexture(id) == cached;
}
/** Largeur de la texture enregistrée (0 si inconnue). */
public static int getWidth(Identifier id) {
SurviePoteTexture cached = CACHE.get(id);
return cached != null ? cached.getWidth() : 0;
}
/** Hauteur de la texture enregistrée (0 si inconnue). */
public static int getHeight(Identifier id) {
SurviePoteTexture cached = CACHE.get(id);
return cached != null ? cached.getHeight() : 0;
}
}
@@ -0,0 +1,62 @@
package fr.surviepote.nosingleplayer;
import com.mojang.blaze3d.platform.NativeImage;
import java.io.IOException;
import java.io.InputStream;
import net.minecraft.client.renderer.texture.MipmapStrategy;
import net.minecraft.client.renderer.texture.ReloadableTexture;
import net.minecraft.client.renderer.texture.TextureContents;
import net.minecraft.client.resources.metadata.texture.TextureMetadataSection;
import net.minecraft.resources.Identifier;
import net.minecraft.server.packs.resources.ResourceManager;
/**
* Texture du mod (PNG lu dans le jar) gérée exactement comme une texture vanilla.
*
* <p>MC 26.3 n'expose plus les ressources des mods au ResourceManager : les packs ne
* sont lus que depuis des dossiers, pas depuis les jars du classpath. Une texture
* référencée par son Identifier est donc introuvable (placeholder magenta). On lit
* donc le PNG directement dans le jar du mod et on l'enregistre via le mécanisme
* officiel {@link ReloadableTexture} : c'est MC qui crée la texture GPU ET le
* sampler, exactement comme pour ses propres textures. Aucun sampler custom, aucun
* upload manuel => compatible avec tous les backends (OpenGL, Vulkan) et tous les
* pilotes (Mesa, NVIDIA, AMD, Intel).
*/
public class SurviePoteTexture extends ReloadableTexture {
private final String resourcePath;
private final boolean blur;
private int width;
private int height;
public SurviePoteTexture(Identifier id, String resourcePath, boolean blur) {
super(id);
this.resourcePath = resourcePath;
this.blur = blur;
}
@Override
public TextureContents loadContents(ResourceManager resourceManager) throws IOException {
try (InputStream in = SurviePoteTexture.class.getResourceAsStream(this.resourcePath)) {
if (in == null) {
throw new IOException("Texture introuvable dans le jar : " + this.resourcePath);
}
NativeImage image = NativeImage.read(in);
this.width = image.getWidth();
this.height = image.getHeight();
// blur=true => filtrage linéaire (image agrandie/réduite),
// clamp=true => pas de répétition sur les bords.
return new TextureContents(image,
new TextureMetadataSection(this.blur, true, MipmapStrategy.AUTO, 0.0f));
}
}
public int getWidth() {
return this.width;
}
public int getHeight() {
return this.height;
}
}
@@ -0,0 +1,65 @@
package fr.surviepote.nosingleplayer.gui;
import fr.surviepote.nosingleplayer.ModTextures;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.narration.NarrationElementOutput;
import net.minecraft.client.gui.components.AbstractButton;
import net.minecraft.client.input.InputWithModifiers;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
/**
* Bouton "Join Discord" : fond vanilla, logo Discord en pixel art et libellé.
* Ouvre l'invite Discord au clic.
*/
public class DiscordButton extends AbstractButton {
public static final Identifier TEXTURE = Identifier.fromNamespaceAndPath(
"nosingleplayer", "textures/gui/discord.png");
private static final String TEXTURE_PATH = "/assets/nosingleplayer/textures/gui/discord.png";
private static final int ICON_SIZE = 16;
private static final int ICON_GAP = 5;
private static final int TEXT_COLOR = 0xFFFFFFFF;
private static final int TEXT_HEIGHT = 9;
private final Runnable action;
public DiscordButton(int x, int y, int width, int height, String label, Runnable action) {
super(x, y, width, height, Component.literal(label));
this.action = action;
// La texture doit être enregistrée avant le premier rendu.
ModTextures.register(TEXTURE, TEXTURE_PATH, false);
}
@Override
protected void extractContents(GuiGraphicsExtractor extractor, int mouseX, int mouseY, float partialTick) {
extractDefaultSprite(extractor); // fond standard (survol/clic inclus)
Font font = Minecraft.getInstance().font;
String label = getMessage().getString();
int textWidth = font.width(label);
int groupWidth = ICON_SIZE + ICON_GAP + textWidth;
int startX = getX() + (getWidth() - groupWidth) / 2;
int iconY = getY() + (getHeight() - ICON_SIZE) / 2;
int textY = getY() + (getHeight() - TEXT_HEIGHT) / 2;
extractor.blit(RenderPipelines.GUI_TEXTURED, TEXTURE,
startX, iconY, 0.0F, 0.0F, ICON_SIZE, ICON_SIZE, ICON_SIZE, ICON_SIZE, -1);
extractor.text(font, label, startX + ICON_SIZE + ICON_GAP, textY, TEXT_COLOR, true);
}
@Override
public void onPress(InputWithModifiers input) {
this.action.run();
}
@Override
protected void updateWidgetNarration(NarrationElementOutput output) {
defaultButtonNarrationText(output);
}
}
@@ -0,0 +1,54 @@
package fr.surviepote.nosingleplayer.gui;
import fr.surviepote.nosingleplayer.ModTextures;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.narration.NarrationElementOutput;
import net.minecraft.client.gui.components.AbstractButton;
import net.minecraft.client.input.InputWithModifiers;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
/**
* Bouton icône vanilla (fond standard) avec une texture du mod, sans texte.
*/
public class IconButton extends AbstractButton {
public static final Identifier OPTIONS_TEXTURE = Identifier.fromNamespaceAndPath(
"nosingleplayer", "textures/gui/options.png");
public static final Identifier QUIT_TEXTURE = Identifier.fromNamespaceAndPath(
"nosingleplayer", "textures/gui/quit.png");
private static final int ICON_SIZE = 16;
private final Identifier texture;
private final Runnable action;
public IconButton(int x, int y, int width, int height, Component message,
Identifier texture, String resourcePath, Runnable action) {
super(x, y, width, height, message);
this.texture = texture;
this.action = action;
// La texture doit être enregistrée avant le premier rendu.
ModTextures.register(texture, resourcePath, false);
}
@Override
protected void extractContents(GuiGraphicsExtractor extractor, int mouseX, int mouseY, float partialTick) {
extractDefaultSprite(extractor); // fond standard (survol/clic inclus)
int iconX = getX() + (getWidth() - ICON_SIZE) / 2;
int iconY = getY() + (getHeight() - ICON_SIZE) / 2;
extractor.blit(RenderPipelines.GUI_TEXTURED, this.texture,
iconX, iconY, 0.0F, 0.0F, ICON_SIZE, ICON_SIZE, ICON_SIZE, ICON_SIZE, -1);
}
@Override
public void onPress(InputWithModifiers input) {
this.action.run();
}
@Override
protected void updateWidgetNarration(NarrationElementOutput output) {
defaultButtonNarrationText(output);
}
}
@@ -0,0 +1,148 @@
package fr.surviepote.nosingleplayer.gui;
import fr.surviepote.nosingleplayer.NoSingleplayer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.narration.NarrationElementOutput;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.multiplayer.ServerStatusPinger;
import net.minecraft.network.chat.Component;
import net.minecraft.server.network.EventLoopGroupHolder;
/**
* Panneau "statut du serveur" affiché en haut à gauche du menu principal :
* état en ligne / hors ligne, nombre de joueurs et ping.
*
* <p>Note : dans ServerStatusPinger.pingServer, les deux Runnable passés ne sont pas
* des callbacks de succès/échec (le 1er ne fire que si le favicon change, le 2nd est
* le pong). On lit donc directement l'état dans ServerData : `status` contient le
* compteur de joueurs ("0/20") quand le statut est arrivé, et est vidé en cas d'échec.
*/
public class ServerStatusWidget extends AbstractWidget {
private static final String HOST = "mcpv.ihorizon.org:25565";
private static final long REFRESH_MS = 15000L;
private static final long TIMEOUT_MS = 8000L;
/** Thread dédié : le ping ne doit jamais bloquer le thread de rendu. */
private static final java.util.concurrent.ExecutorService PING_EXECUTOR =
java.util.concurrent.Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "SurviePote-ServerStatus");
t.setDaemon(true);
return t;
});
private static final int BG_COLOR = 0xB0000000;
private static final int ONLINE_COLOR = 0xFF4CD964;
private static final int OFFLINE_COLOR = 0xFFE04B4B;
private static final int PENDING_COLOR = 0xFFAAAAAA;
private static final int TEXT_COLOR = 0xFFFFFFFF;
private final ServerStatusPinger pinger = new ServerStatusPinger();
private final ServerData data = new ServerData("SurviePote", HOST, ServerData.Type.OTHER);
private volatile boolean resolved;
private volatile boolean online;
private volatile String playersText = "";
private volatile long ping = -1L;
private long nextRefresh;
private long pingStartedAt;
private boolean pending;
public ServerStatusWidget(int x, int y, int width, int height) {
super(x, y, width, height, Component.literal("Statut du serveur SurviePote"));
}
@Override
protected void extractWidgetRenderState(GuiGraphicsExtractor extractor, int mouseX, int mouseY, float partialTick) {
long now = System.currentTimeMillis();
if (now >= this.nextRefresh) {
this.nextRefresh = now + REFRESH_MS;
refresh();
}
if (this.pending && now - this.pingStartedAt > TIMEOUT_MS) {
this.pending = false; // aucune réponse du serveur
}
this.pinger.tick();
readStatus();
Font font = Minecraft.getInstance().font;
boolean online = this.online;
boolean resolved = this.resolved;
String statusText = !resolved ? "Connexion..." : (online ? "En ligne" : "Hors ligne");
int statusColor = !resolved ? PENDING_COLOR : (online ? ONLINE_COLOR : OFFLINE_COLOR);
String playersText = online
? (this.playersText.matches("\\d+/\\d+") ? this.playersText + " joueurs" : this.playersText)
: (resolved ? "Joueurs : -" : "");
String pingText = online && this.ping > 0 ? this.ping + " ms" : "";
// Fond du panneau.
extractor.fill(getX(), getY(), getX() + getWidth(), getY() + getHeight(), BG_COLOR);
// Pastille d'état.
extractor.fill(getX() + 4, getY() + 4, getX() + 9, getY() + 9, statusColor);
// Textes.
extractor.text(font, statusText, getX() + 12, getY() + 3, statusColor, true);
if (!playersText.isEmpty()) {
extractor.text(font, playersText, getX() + 12, getY() + 13, TEXT_COLOR, true);
}
if (!pingText.isEmpty()) {
extractor.text(font, pingText,
getX() + getWidth() - font.width(pingText) - 4, getY() + 3, PENDING_COLOR, true);
}
}
/** Lit l'état réel du ping dans ServerData (mis à jour par le thread réseau). */
private void readStatus() {
Component status = this.data.status;
String text = status != null ? status.getString() : "";
if (!text.isEmpty()) {
this.online = true;
this.resolved = true;
this.playersText = text; // "0/20"
} else if (this.resolved) {
this.online = false; // le status a été vidé => échec du ping
} else if (!this.pending) {
this.resolved = true; // timeout : serveur injoignable
}
if (this.data.ping > 0) {
this.ping = this.data.ping;
}
}
private void refresh() {
// ATTENTION : pingServer() fait une résolution DNS + ouvre une connexion.
// Appelé depuis extractWidgetRenderState (thread de rendu), ça BLOQUE le
// rendu du menu (écran figé/noir) le temps du timeout DNS. On le lance
// donc sur un thread dédié ; l'état lu par le rendu est "volatile".
try {
this.pingStartedAt = System.currentTimeMillis();
this.pending = true;
PING_EXECUTOR.execute(() -> {
try {
this.pinger.pingServer(this.data, () -> {
}, () -> {
}, EventLoopGroupHolder.remote(Minecraft.getInstance().options.useNativeTransport()));
} catch (Exception e) {
NoSingleplayer.LOGGER.warn("[SurviePote] Ping serveur impossible : {}", e.toString());
this.pending = false;
this.resolved = true;
this.online = false;
}
});
} catch (Exception e) {
NoSingleplayer.LOGGER.warn("[SurviePote] Ping serveur impossible : {}", e.toString());
this.pending = false;
this.resolved = true;
this.online = false;
}
}
@Override
protected void updateWidgetNarration(NarrationElementOutput output) {
defaultButtonNarrationText(output);
}
}
@@ -0,0 +1,13 @@
package fr.surviepote.nosingleplayer.mixin;
import net.minecraft.client.gui.components.Button;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
/** Permet de récupérer l'action d'un bouton vanilla (pour la conserver). */
@Mixin(Button.class)
public interface ButtonAccessor {
@Accessor("onPress")
Button.OnPress surviepote$getOnPress();
}
@@ -0,0 +1,32 @@
package fr.surviepote.nosingleplayer.mixin;
import fr.surviepote.nosingleplayer.NoSingleplayer;
import net.minecraft.SharedConstants;
import net.minecraft.client.Minecraft;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
/**
* Titre de la fenêtre : "Minecraft* 26.3" -> "SurviePote [26.3]".
* Minecraft.createTitle() est privé, appelé au démarrage (création de la fenêtre)
* et par updateTitle() (changement de monde, focus, resize...).
*/
@Mixin(Minecraft.class)
public abstract class MinecraftTitleMixin {
@Unique
private static boolean surviepote$titleLogged;
@Inject(method = "createTitle", at = @At("HEAD"), cancellable = true)
private void surviepote$customWindowTitle(CallbackInfoReturnable<String> cir) {
String title = "SurviePote [" + SharedConstants.getCurrentVersion().name() + "]";
cir.setReturnValue(title);
if (!surviepote$titleLogged) {
surviepote$titleLogged = true;
NoSingleplayer.LOGGER.info("[SurviePote] Titre de fenêtre : {}", title);
}
}
}
@@ -0,0 +1,45 @@
package fr.surviepote.nosingleplayer.mixin;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.screens.PauseScreen;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(PauseScreen.class)
public abstract class PauseScreenMixin {
@Unique
private static final Component SURVIEPOTE_QUIT = Component.literal("Quitter SurviePote");
// Quitte le monde comme le bouton vanilla, mais renvoie vers l'écran d'accueil
// (au lieu de la liste des serveurs pour un serveur distant).
@Unique
private static void surviepote$quitToTitle(Button button) {
button.active = false;
Minecraft minecraft = Minecraft.getInstance();
// Conserve la gestion vanilla du brouillon de signalement, puis redirige.
minecraft.getReportingContext().draftReportHandled(minecraft, minecraft.gui.screen(), () -> {
minecraft.disconnectFromWorld(ClientLevel.DEFAULT_QUIT_MESSAGE);
minecraft.setScreenAndShow(new TitleScreen());
}, true);
}
@Redirect(method = "createPauseMenu",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/gui/components/Button;builder(Lnet/minecraft/network/chat/Component;Lnet/minecraft/client/gui/components/Button$OnPress;)Lnet/minecraft/client/gui/components/Button$Builder;"))
private static Button.Builder surviepote$relabelDisconnect(Component message, Button.OnPress onPress) {
// createPauseMenu ne crée que deux boutons via Button.builder : "Retour au jeu"
// et "Déconnexion". On ne remplace que ce dernier.
if (message.getString().equals(CommonComponents.GUI_DISCONNECT.getString())) {
return Button.builder(SURVIEPOTE_QUIT, PauseScreenMixin::surviepote$quitToTitle);
}
return Button.builder(message, onPress);
}
}
@@ -1,14 +1,24 @@
package fr.surviepote.nosingleplayer.mixin; package fr.surviepote.nosingleplayer.mixin;
import java.util.List; import java.util.List;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.Renderable; import net.minecraft.client.gui.components.Renderable;
import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.Screen;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(Screen.class) @Mixin(Screen.class)
public interface ScreenAccessor { public interface ScreenAccessor {
@Accessor("renderables") @Accessor("renderables")
List<Renderable> surviepote$getRenderables(); List<Renderable> surviepote$getRenderables();
/**
* Panorama vanilla (déclaré dans Screen, appelé par TitleScreen).
* Utilisé en repli quand le fond custom de SurviePote n'a pas pu être chargé :
* sans ça, la redirection de l'appel laisse l'écran entièrement noir.
*/
@Invoker("extractPanorama")
void surviepote$extractVanillaPanorama(GuiGraphicsExtractor extractor, float partialTick);
} }
@@ -1,8 +1,12 @@
package fr.surviepote.nosingleplayer.mixin; package fr.surviepote.nosingleplayer.mixin;
import com.mojang.blaze3d.Blaze3D;
import fr.surviepote.nosingleplayer.gui.DiscordButton;
import fr.surviepote.nosingleplayer.gui.IconButton;
import fr.surviepote.nosingleplayer.gui.ServerStatusWidget;
import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.AbstractWidget; import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Button;
@@ -13,7 +17,6 @@ import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.screens.ConnectScreen; import net.minecraft.client.gui.screens.ConnectScreen;
import net.minecraft.client.gui.screens.TitleScreen; import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraft.client.multiplayer.ServerData; import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.multiplayer.TransferState;
import net.minecraft.client.multiplayer.resolver.ServerAddress; import net.minecraft.client.multiplayer.resolver.ServerAddress;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import fr.surviepote.nosingleplayer.NoSingleplayer; import fr.surviepote.nosingleplayer.NoSingleplayer;
@@ -28,6 +31,12 @@ public abstract class TitleScreenMixin {
private static final String SERVER_HOST = "mcpv.ihorizon.org:25565"; private static final String SERVER_HOST = "mcpv.ihorizon.org:25565";
private static final String DISCORD_URL = "https://discord.gg/EpkgEPv5c";
private static final int ICON_SIZE = 20;
private static final String OPTIONS_TEXTURE_PATH = "/assets/nosingleplayer/textures/gui/options.png";
private static final String QUIT_TEXTURE_PATH = "/assets/nosingleplayer/textures/gui/quit.png";
@Unique @Unique
private static void surviepote$removeWidget(TitleScreen self, GuiEventListener widget) { private static void surviepote$removeWidget(TitleScreen self, GuiEventListener widget) {
self.children().remove(widget); self.children().remove(widget);
@@ -36,41 +45,39 @@ public abstract class TitleScreenMixin {
@Inject(method = "init", at = @At("TAIL")) @Inject(method = "init", at = @At("TAIL"))
private void surviepote$customizeMenu(CallbackInfo ci) { private void surviepote$customizeMenu(CallbackInfo ci) {
TitleScreen self = (TitleScreen) (Object) this; // Garde-fou : si la personnalisation échoue, on NE DOIT PAS avoir retiré les
// boutons vanilla (menu vide + fond noir = "écran cassé"). Les retraits ne
// sont appliqués qu'après la création des widgets, en fin de méthode.
try {
surviepote$buildMenu((TitleScreen) (Object) this);
} catch (Throwable t) {
NoSingleplayer.LOGGER.error("[SurviePote] Personnalisation du menu échouée, menu vanilla conservé.", t);
}
}
@Unique
private void surviepote$buildMenu(TitleScreen self) {
Minecraft minecraft = Minecraft.getInstance(); Minecraft minecraft = Minecraft.getInstance();
String solo = Component.translatable("menu.singleplayer").getString(); String solo = Component.translatable("menu.singleplayer").getString();
String multi = Component.translatable("menu.multiplayer").getString(); String multi = Component.translatable("menu.multiplayer").getString();
String realms = Component.translatable("menu.online").getString(); String realms = Component.translatable("menu.online").getString();
String options = Component.translatable("menu.options").getString();
String quit = Component.translatable("menu.quit").getString();
int joinX = minecraft.getWindow().getGuiScaledWidth() / 2 - 100; int joinX = minecraft.getWindow().getGuiScaledWidth() / 2 - 100;
int joinY = minecraft.getWindow().getGuiScaledHeight() / 4 + 72; int joinY = minecraft.getWindow().getGuiScaledHeight() / 4 + 72;
boolean foundMulti = false; boolean foundMulti = false;
List<GuiEventListener> toRemove = new ArrayList<>(); List<GuiEventListener> toRemove = new ArrayList<>();
// TODO DEBUG temporaire : lister les widgets du menu titre. Button optionsButton = null;
NoSingleplayer.LOGGER.info("[SurviePote] init TAIL, {} widgets:", self.children().size()); Button quitButton = null;
for (GuiEventListener c : self.children()) {
String desc = c.getClass().getSimpleName();
if (c instanceof Button b) {
desc += " msg=[" + b.getMessage().getString() + "]";
}
NoSingleplayer.LOGGER.info("[SurviePote] - {}", desc);
}
for (GuiEventListener child : new ArrayList<>(self.children())) { for (GuiEventListener child : new ArrayList<>(self.children())) {
if (child instanceof Button button) { // ATTENTION : FriendsButton/SpriteIconButton/PlainTextButton héritent tous
String msg = button.getMessage().getString(); // de Button. Il faut donc les tester AVANT "instanceof Button", sinon la
if (msg.equals(solo)) { // première branche les avale et ils ne sont jamais retirés.
button.active = false; if (child instanceof FriendsButton) {
} else if (msg.equals(multi)) { // Bouton "amis" (interactions sociales).
joinX = button.getX();
joinY = button.getY();
foundMulti = true;
toRemove.add(button);
} else if (msg.equals(realms)) {
toRemove.add(button);
}
} else if (child instanceof FriendsButton) {
toRemove.add(child); toRemove.add(child);
} else if (child instanceof SpriteIconButton) { } else if (child instanceof SpriteIconButton) {
// Bas-gauche : langue, accessibilité, etc. // Bas-gauche : langue, accessibilité, etc.
@@ -78,25 +85,97 @@ public abstract class TitleScreenMixin {
} else if (child instanceof PlainTextButton) { } else if (child instanceof PlainTextButton) {
// Copyright Mojang cliquable en bas. // Copyright Mojang cliquable en bas.
toRemove.add(child); toRemove.add(child);
} else if (child instanceof Button button) {
String msg = button.getMessage().getString();
if (msg.equals(solo)) {
// Solo retiré complètement (plutôt que grisé).
toRemove.add(button);
} else if (msg.equals(multi)) {
joinX = button.getX();
joinY = button.getY();
foundMulti = true;
toRemove.add(button);
} else if (msg.equals(realms)) {
toRemove.add(button);
} else if (msg.equals(options)) {
// Remplacé plus bas par un bouton icône (engrenage).
optionsButton = button;
toRemove.add(button);
} else if (msg.equals(quit)) {
// Remplacé plus bas par un bouton icône (power).
quitButton = button;
toRemove.add(button);
} }
} }
for (GuiEventListener widget : toRemove) {
surviepote$removeWidget(self, widget);
} }
// ATTENTION : les retraits des widgets vanilla ne sont appliqués qu'APRÈS la
// création des widgets SurviePote (fin de méthode). Si une exception survient
// entre-temps, le menu vanilla est conservé au lieu d'être vidé.
Button join = Button.builder(Component.literal("Rejoindre SurviePote"), pressed -> { Button join = Button.builder(Component.literal("Rejoindre SurviePote"), pressed -> {
ServerData data = new ServerData("SurviePote", SERVER_HOST, ServerData.Type.OTHER); ServerData data = new ServerData("SurviePote", SERVER_HOST, ServerData.Type.OTHER);
ConnectScreen.startConnecting(self, minecraft, ServerAddress.parseString(SERVER_HOST), data, false, // TransferState null = connexion normale. En passer un déclenche une connexion
new TransferState(Map.of(), Map.of(), false)); // de type "transfert", que le serveur refuse ("This server does not accept transfers").
ConnectScreen.startConnecting(self, minecraft, ServerAddress.parseString(SERVER_HOST), data, false, null);
}).bounds(joinX, joinY, 200, 20).build(); }).bounds(joinX, joinY, 200, 20).build();
((ScreenAccessor) self).surviepote$getRenderables().add(join); ((ScreenAccessor) self).surviepote$getRenderables().add(join);
@SuppressWarnings({ "unchecked", "rawtypes" }) @SuppressWarnings({ "unchecked", "rawtypes" })
List rawChildren = (List) self.children(); List rawChildren = (List) self.children();
rawChildren.add(join); rawChildren.add(join);
// Bouton Discord (logo pixel art + texte) juste sous "Rejoindre SurviePote".
DiscordButton discord = new DiscordButton(joinX, joinY + 24, 200, 20, "Join Discord", () ->
Blaze3D.openUri(URI.create(DISCORD_URL)));
((ScreenAccessor) self).surviepote$getRenderables().add(discord);
rawChildren.add(discord);
// Options / Quit Game : boutons icônes (engrenage / power) sans texte.
// On garde l'action du bouton vanilla d'origine.
if (optionsButton != null) {
final Button vanillaOptions = optionsButton;
Button.OnPress press = ((ButtonAccessor) vanillaOptions).surviepote$getOnPress();
IconButton icon = new IconButton(
optionsButton.getX() + optionsButton.getWidth() / 2 - ICON_SIZE / 2,
optionsButton.getY(), ICON_SIZE, ICON_SIZE,
Component.literal("Options"),
IconButton.OPTIONS_TEXTURE, OPTIONS_TEXTURE_PATH,
() -> press.onPress(vanillaOptions));
((ScreenAccessor) self).surviepote$getRenderables().add(icon);
rawChildren.add(icon);
}
if (quitButton != null) {
final Button vanillaQuit = quitButton;
Button.OnPress press = ((ButtonAccessor) vanillaQuit).surviepote$getOnPress();
IconButton icon = new IconButton(
quitButton.getX() + quitButton.getWidth() / 2 - ICON_SIZE / 2,
quitButton.getY(), ICON_SIZE, ICON_SIZE,
Component.literal("Quit Game"),
IconButton.QUIT_TEXTURE, QUIT_TEXTURE_PATH,
() -> press.onPress(vanillaQuit));
((ScreenAccessor) self).surviepote$getRenderables().add(icon);
rawChildren.add(icon);
}
// Panneau de statut du serveur en haut à gauche (en ligne / joueurs / ping).
ServerStatusWidget status = new ServerStatusWidget(4, 4, 120, 24);
((ScreenAccessor) self).surviepote$getRenderables().add(status);
rawChildren.add(status);
if (join instanceof AbstractWidget w) { if (join instanceof AbstractWidget w) {
w.visible = true; w.visible = true;
w.active = true; w.active = true;
} }
if (discord instanceof AbstractWidget w) {
w.visible = true;
w.active = true;
}
// Tout est prêt : on retire enfin les widgets vanilla (Solo, Multi, Realms,
// réseau social, icônes bas) remplacés ci-dessus.
for (GuiEventListener widget : toRemove) {
surviepote$removeWidget(self, widget);
}
if (!foundMulti) { if (!foundMulti) {
NoSingleplayer.LOGGER.warn("[SurviePote] Bouton Multi introuvable, Join placé par défaut."); NoSingleplayer.LOGGER.warn("[SurviePote] Bouton Multi introuvable, Join placé par défaut.");
} }
@@ -1,16 +1,23 @@
package fr.surviepote.nosingleplayer.mixin; package fr.surviepote.nosingleplayer.mixin;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.realmsclient.gui.screens.RealmsNotificationsScreen; import com.mojang.realmsclient.gui.screens.RealmsNotificationsScreen;
import fr.surviepote.nosingleplayer.ModTextures;
import fr.surviepote.nosingleplayer.NoSingleplayer;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.components.LogoRenderer; import net.minecraft.client.gui.components.LogoRenderer;
import net.minecraft.client.gui.components.SplashRenderer;
import net.minecraft.client.gui.screens.TitleScreen; import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.resources.Identifier; import net.minecraft.resources.Identifier;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(TitleScreen.class) @Mixin(TitleScreen.class)
public abstract class TitleScreenRenderMixin { public abstract class TitleScreenRenderMixin {
@@ -19,23 +26,144 @@ public abstract class TitleScreenRenderMixin {
private static final Identifier SURVIEPOTE_BACKGROUND = Identifier.fromNamespaceAndPath( private static final Identifier SURVIEPOTE_BACKGROUND = Identifier.fromNamespaceAndPath(
"nosingleplayer", "textures/gui/background.png"); "nosingleplayer", "textures/gui/background.png");
// Taille réelle de background.png (fond plein écran étiré). // Le PNG est lu directement dans le jar du mod (voir ModTextures).
@Unique @Unique
private static final int BACKGROUND_TEX_W = 3840; private static final String SURVIEPOTE_BACKGROUND_RESOURCE =
"/assets/nosingleplayer/textures/gui/background.png";
/**
* Option de diagnostic : lancer le jeu avec
* {@code -Dsurviepote.customTitle=false} pour repasser au menu vanilla
* (utilisable dans les options JVM du launcher). Sert à isoler un éventuel
* souci de rendu du fond custom sur un poste donné.
*/
@Unique @Unique
private static final int BACKGROUND_TEX_H = 2400; private static final String DISABLE_CUSTOM_TITLE_PROPERTY = "surviepote.customTitle";
@Unique
private static boolean surviepote$gpuLogged;
/**
* Enregistre le fond custom AVANT le premier rendu du menu.
*
* <p>La texture GPU doit être créée hors de l'extraction du rendu : la créer
* pendant l'enregistrement des commandes de la frame invalide le frame entier
* sur les backends à command encoder (Vulkan) => menu titre entièrement noir
* (le PC affected est sous Windows + NVIDIA, qui utilise le backend Vulkan).
*/
@Inject(method = "init", at = @At("HEAD"))
private void surviepote$registerBackground(CallbackInfo ci) {
try {
ModTextures.register(SURVIEPOTE_BACKGROUND, SURVIEPOTE_BACKGROUND_RESOURCE, true);
} catch (Throwable t) {
NoSingleplayer.LOGGER.error("[SurviePote] Enregistrement du fond custom impossible.", t);
}
}
// Pas de panorama dynamique : fond statique custom à la place. // Pas de panorama dynamique : fond statique custom à la place.
// Note : extractPanorama est déclarée dans Screen (héritée), on redirige donc // Note : extractPanorama est déclarée dans Screen (héritée), on redirige donc
// l'APPEL dans TitleScreen plutôt que la méthode elle-même. // l'APPEL dans TitleScreen plutôt que la méthode elle-même.
// Le fond couvre l'écran sans déformation : recadrage centré type "cover".
@Redirect(method = "extractRenderState", @Redirect(method = "extractRenderState",
at = @At(value = "INVOKE", at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/gui/screens/TitleScreen;extractPanorama(Lnet/minecraft/client/gui/GuiGraphicsExtractor;F)V")) target = "Lnet/minecraft/client/gui/screens/TitleScreen;extractPanorama(Lnet/minecraft/client/gui/GuiGraphicsExtractor;F)V"))
private void surviepote$customBackground(TitleScreen self, GuiGraphicsExtractor extractor, float partialTick) { private void surviepote$customBackground(TitleScreen self, GuiGraphicsExtractor extractor, float partialTick) {
// Les widgets du menu sont dessinés par super.extractRenderState(), juste APRÈS
// cet appel. Toute exception levée ici interromp le rendu du menu entier =>
// écran 100 % noir (sans boutons). D'où les garde-fous ci-dessous.
surviepote$logGpuOnce();
// Menu vanilla si l'utilisateur a forcé -Dsurviepote.customTitle=false,
// ou si le fond custom n'est pas (ou plus) enregistré.
final boolean customEnabled = !"false".equalsIgnoreCase(
System.getProperty(DISABLE_CUSTOM_TITLE_PROPERTY, "true"));
if (!customEnabled || !ModTextures.isRegistered(SURVIEPOTE_BACKGROUND)) {
surviepote$vanillaPanorama(self, extractor, partialTick);
return;
}
try {
int w = Minecraft.getInstance().getWindow().getGuiScaledWidth(); int w = Minecraft.getInstance().getWindow().getGuiScaledWidth();
int h = Minecraft.getInstance().getWindow().getGuiScaledHeight(); int h = Minecraft.getInstance().getWindow().getGuiScaledHeight();
extractor.blit(RenderPipelines.GUI_TEXTURED, SURVIEPOTE_BACKGROUND, 0, 0, 0.0F, 0.0F, int texW = ModTextures.getWidth(SURVIEPOTE_BACKGROUND);
w, h, BACKGROUND_TEX_W, BACKGROUND_TEX_H, -1); int texH = ModTextures.getHeight(SURVIEPOTE_BACKGROUND);
if (texW <= 0 || texH <= 0 || w <= 0 || h <= 0) {
surviepote$vanillaPanorama(self, extractor, partialTick);
return;
}
float screenAspect = (float) w / (float) h;
float texAspect = (float) texW / (float) texH;
int u, v, srcW, srcH;
if (screenAspect > texAspect) {
// Écran plus large que l'image : on rogne en haut/bas.
srcW = texW;
srcH = Math.round(texW / screenAspect);
u = 0;
v = (texH - srcH) / 2;
} else {
// Écran plus haut que l'image : on rogne à gauche/droite.
srcH = texH;
srcW = Math.round(texH * screenAspect);
u = (texW - srcW) / 2;
v = 0;
}
// Surcharge complète : (u, v) = origine dans la texture, (w, h) = taille à
// l'écran, (srcW, srcH) = taille de la région source dans la texture.
extractor.blit(RenderPipelines.GUI_TEXTURED, SURVIEPOTE_BACKGROUND,
0, 0, (float) u, (float) v,
w, h, srcW, srcH,
texW, texH, -1);
} catch (Throwable t) {
// Repli : on bascule sur le panorama vanilla pour ne pas laisser un écran noir.
NoSingleplayer.LOGGER.error("[SurviePote] Échec du fond custom, repli sur le panorama vanilla.", t);
surviepote$vanillaPanorama(self, extractor, partialTick);
}
}
/**
* Redessine le panorama vanilla (appelé dans Screen, pas dans TitleScreen :
* la redirection porte sur l'appel, donc pas de récursion).
*/
@Unique
private void surviepote$vanillaPanorama(TitleScreen self, GuiGraphicsExtractor extractor, float partialTick) {
try {
((ScreenAccessor) self).surviepote$extractVanillaPanorama(extractor, partialTick);
} catch (Throwable t) {
NoSingleplayer.LOGGER.error("[SurviePote] Échec du panorama vanilla.", t);
}
}
/** Log une fois les infos GPU : permet de comparer un poste qui plante d'un poste qui marche. */
@Unique
private static void surviepote$logGpuOnce() {
if (surviepote$gpuLogged) {
return;
}
surviepote$gpuLogged = true;
try {
var info = RenderSystem.getDevice().getDeviceInfo();
NoSingleplayer.LOGGER.info("[SurviePote] GPU: {} | {} | pilote {} | backend {}",
info.name(), info.vendorName(), info.driverInfo(), info.backendName());
} catch (Throwable t) {
NoSingleplayer.LOGGER.warn("[SurviePote] Infos GPU indisponibles : {}", t.toString());
}
}
// Pas de texte de version ("26.3 mc") en bas à gauche.
@Redirect(method = "extractRenderState",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/gui/GuiGraphicsExtractor;text(Lnet/minecraft/client/gui/Font;Ljava/lang/String;III)V"))
private void surviepote$skipVersionText(GuiGraphicsExtractor extractor, Font font, String text, int x, int y, int color) {
}
// Pas de texte de splash (message rotatif) sur le menu principal.
@Redirect(method = "extractRenderState",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/gui/components/SplashRenderer;extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;ILnet/minecraft/client/gui/Font;F)V"))
private void surviepote$skipSplash(SplashRenderer splash, GuiGraphicsExtractor extractor, int width, Font font, float fade) {
} }
// Pas de logo Minecraft. // Pas de logo Minecraft.
@@ -1,26 +0,0 @@
package fr.surviepote.nosingleplayer.mixin;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.screens.TitleScreen;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(TitleScreen.class)
public class TitleScreenTextMixin {
@Redirect(
method = "extractRenderState",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/gui/GuiGraphicsExtractor;text(Lnet/minecraft/client/gui/Font;Ljava/lang/String;III)V"
)
)
private void surviepote$versionLine(GuiGraphicsExtractor extractor, Font font, String text, int x, int y, int color) {
if (text.contains("Copyright") || text.contains("Mojang")) {
return;
}
extractor.text(font, "26.3 mc", x, y, color);
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 629 KiB

After

Width:  |  Height:  |  Size: 826 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 904 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

@@ -5,8 +5,10 @@
"client": [ "client": [
"TitleScreenMixin", "TitleScreenMixin",
"TitleScreenRenderMixin", "TitleScreenRenderMixin",
"TitleScreenTextMixin", "MinecraftTitleMixin",
"ButtonAccessor",
"RealmsNotificationsMixin", "RealmsNotificationsMixin",
"PauseScreenMixin",
"ScreenAccessor" "ScreenAccessor"
], ],
"injectors": { "injectors": {
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
+6 -4
View File
@@ -9,7 +9,9 @@
- À faire : le retirer aussi de Prism (`~/.local/share/PrismLauncher/instances/26.3/minecraft/mods/`), - À faire : le retirer aussi de Prism (`~/.local/share/PrismLauncher/instances/26.3/minecraft/mods/`),
sinon le client crashera pareil. Réintégrer quand une 5.2.2+ sortira. sinon le client crashera pareil. Réintégrer quand une 5.2.2+ sortira.
## ImmersiveOres — voir server/mods-client-only/ ## ImmersiveOres — RETIRÉ (mod cassé, 19/10/2026)
- `ImmersiveOres-26.3-0.1.jar` : mixin client (`ToolOverlayRenderer` → `LevelRenderer`) dans la - `ImmersiveOres-26.3-0.1.jar` : `environment: *` mais mixin client (`ToolOverlayRenderer` → `LevelRenderer`)
config commune → crash serveur dédié. Gardé côté CLIENT (atlas required) pour les blocs/items, dans la config commune → crash serveur dédié. Mod jugé cassé → retiré partout : serveur,
exclu du serveur. À signaler à l'auteur. `atlas/.../fabricmods/required/` (clients) et config `config/immersiveores/`.
- À faire : le retirer aussi de Prism (`~/.local/share/PrismLauncher/instances/26.3/minecraft/mods/`),
sinon le client plantera pareil. À signaler à l'auteur.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+49 -9
View File
@@ -12,11 +12,20 @@ Serveur **Minecraft 26.3 + Fabric Loader 0.20.0**, pré-configuré en « défaut
```bash ```bash
cd server cd server
echo "eula=true" > eula.txt echo "eula=true" > eula.txt # déjà présent (eula=false) : à passer à true
./start.sh ./start.sh # installe le runtime automatiquement si absent, puis démarre
``` ```
(Sous Windows : `start.bat`.) (Sous Windows : `start.bat`, même comportement.)
Le runtime Fabric (jars vanilla/Fabric, `libraries/`, `versions/`, `.fabric/`) n'est **pas
versionné** (voir `.gitignore`) : il est (re)généré par `setup.sh`, appelé automatiquement par
`start.sh` s'il manque, ou manuellement :
```bash
cd server
nix-shell .. --run 'bash server/setup.sh' # ou : ./setup.sh (avec Java dans le PATH)
```
Au premier démarrage, le serveur génère `world/`, `config/`, `logs/`, etc. Au premier démarrage, le serveur génère `world/`, `config/`, `logs/`, etc.
Pour régénérer le monde (ex. changer de `level-type`), stoppe le serveur et supprime `world/`. Pour régénérer le monde (ex. changer de `level-type`), stoppe le serveur et supprime `world/`.
@@ -25,18 +34,20 @@ Pour régénérer le monde (ex. changer de `level-type`), stoppe le serveur et s
| Fichier | Rôle | | Fichier | Rôle |
| --- | --- | | --- | --- |
| `fabric-server-launch.jar` | Lanceur Fabric (généré par fabric-installer 1.1.2) | | `setup.sh` / `setup.bat` | Installe/repare le runtime Fabric (télécharge vanilla + lanceur Fabric) |
| `server.jar` | Serveur Minecraft 26.3 vanilla (téléchargé par l'installeur) | | `fabric-server-launch.jar` | Lanceur Fabric (généré par fabric-installer 1.1.2 — non versionné) |
| `fabric-installer.jar` | Installeur (pour réinstaller/réparer) | | `server.jar` | Serveur Minecraft 26.3 vanilla (téléchargé par l'installeur — non versionné) |
| `mods/` | 31 mods serveur (copie physique) — voir `mods/SOURCE.md` | | `fabric-installer.jar` | Installeur (pour réinstaller/réparer — non versionné) |
| `config/` | Configs générées au premier lancement (+ customs à déposer ici) | | `mods/` | 46 mods serveur (copie physique) — voir `mods/SOURCE.md` |
| `config/` | Configs générées au premier lancement (non versionnées) |
| `server.properties` | Config plug-and-play (survie, normal, 20 joueurs) | | `server.properties` | Config plug-and-play (survie, normal, 20 joueurs) |
| `eula.txt` | Passer à `eula=true` avant de lancer | | `eula.txt` | Passer à `eula=true` avant de lancer |
| `start.sh` / `start.bat` | Scripts de lancement (4 Go RAM, `nogui`) | | `start.sh` / `start.bat` | Scripts de lancement (4 Go RAM, `nogui`) |
| `ecosystem.config.cjs` | Config PM2 (démarre le serveur dans le nix-shell, Java 25) |
## Mods ## Mods
- **Inclus (31)** : gameplay/contenu (Better Combat, BOP, BCLib/Better End, Waystones, Backpacks, Drawers, VeinMiner, Tree Harvester, Jade, JEI, etc.). Détail dans `mods/SOURCE.md`. - **Inclus (46)** : gameplay/contenu (Better Combat, BOP, BCLib/Better End, Waystones, Backpacks, Drawers, VeinMiner, Tree Harvester, Jade, JEI, etc.). Détail dans `mods/SOURCE.md`.
- **Exclus (15, client-only)** : sodium, iris, continuity, dynamic-fps, xaerominimap, xaeroworldmap, skinlayers3d, waveycapes, capes, chat_heads, morechathistory, fast-ip-ping, modmenu, appleskin, vanillarice. À garder côté **client** uniquement. - **Exclus (15, client-only)** : sodium, iris, continuity, dynamic-fps, xaerominimap, xaeroworldmap, skinlayers3d, waveycapes, capes, chat_heads, morechathistory, fast-ip-ping, modmenu, appleskin, vanillarice. À garder côté **client** uniquement.
- Resourcepacks/shaders : jamais côté serveur. - Resourcepacks/shaders : jamais côté serveur.
@@ -49,4 +60,33 @@ Pour régénérer le monde (ex. changer de `level-type`), stoppe le serveur et s
## Réseau ## Réseau
- `server-port=25565`, `online-mode=true`, `white-list=false`. - `server-port=25565`, `online-mode=true`, `white-list=false`.
- `server-ip=10.7.0.2` : le serveur n'écoute **que** sur l'interface portant cette IP (VPN).
Laisser vide = écouter sur toutes les interfaces (`0.0.0.0`).
- Ouvre le port 25565/TCP (box/pare-feu) et pointe `mcpv.ihorizon.org` vers l'IP publique. - Ouvre le port 25565/TCP (box/pare-feu) et pointe `mcpv.ihorizon.org` vers l'IP publique.
## PM2 (daemon)
`ecosystem.config.cjs` lance le serveur **dans le nix-shell du repo** (`shell.nix` → `jdk25`),
sans dépendre du Java du système. Les chemins sont résolus depuis le fichier (`__dirname`) :
`server/` et la racine du repo.
```bash
cd /srv/mc/server
pm2 start ecosystem.config.cjs
pm2 logs surviepote-mc
pm2 restart surviepote-mc
pm2 stop surviepote-mc
pm2 save # mémorise la liste des process
sudo pm2 startup systemd -u anais --hp /home/anais # relance au boot
```
Équivalent manuel de la commande lancée par PM2 :
```bash
cd /srv/mc/server
nix-shell /srv/mc --run "bash /srv/mc/server/start.sh"
```
> PM2 ne relaie pas la console interactive : utilise `pm2 logs` pour la sortie et
> `pm2 sendSignal`/`pm2 stop` (SIGINT → sauvegarde propre via le shutdown hook Java).
-1
View File
@@ -1 +0,0 @@
[]
-24
View File
@@ -1,24 +0,0 @@
[
{
"name": "Ores",
"blocks": [
"#c:ores"
],
"tools": [
"#minecraft:pickaxes"
],
"override": {
"cooldown": null,
"mustSneak": null,
"delay": null,
"maxChain": null,
"needCorrectTool": null,
"searchRadius": null,
"permissionRestricted": null,
"decreaseDurability": null,
"hungerPerBlock": null,
"miningSpeedModifier": null,
"separateGroupMining": null
}
}
]
-36
View File
@@ -1,36 +0,0 @@
{
"cooldown": 20,
"mustSneak": false,
"delay": 0,
"maxChain": 100,
"needCorrectTool": true,
"searchRadius": 1,
"permissionRestricted": false,
"mergeItemDrops": false,
"autoUpdate": false,
"decreaseDurability": true,
"hungerPerBlock": 0.0,
"miningSpeedModifier": 0.0,
"separateGroupMining": false,
"dontDisplayUpdatesAndIWillNotAskForSupportBeforeUpdatingISwear": false,
"debug": false,
"client": {
"allow": true,
"require": false,
"translucentBlockHighlight": true,
"allBlocks": false,
"overrides": {
"cooldown": null,
"mustSneak": null,
"delay": null,
"maxChain": null,
"needCorrectTool": null,
"searchRadius": null,
"permissionRestricted": null,
"decreaseDurability": null,
"hungerPerBlock": null,
"miningSpeedModifier": null,
"separateGroupMining": null
}
}
}
-12
View File
@@ -1,12 +0,0 @@
{
"create_version": "26.301.2",
"rendering": {
"custom_fog_rendering": true,
"nether_thick_fog": true,
"fog_density": 1.0
},
"ui": {
"survives_on_hint": true
},
"modify_version": "26.301.2"
}
-7
View File
@@ -1,7 +0,0 @@
{
"create_version": "26.301.2",
"patches": {
"apply_patches": true
},
"modify_version": "26.301.2"
}
@@ -1,116 +0,0 @@
{
"schema_version": 1,
"blacklist_item_id_regex": "pickaxe",
"fallback_compatibility": [
{
"item_id_regex": "claymore|great_sword|greatsword",
"weapon_attributes": "bettercombat:claymore"
},
{
"item_id_regex": "great_hammer|greathammer|war_hammer|warhammer|maul",
"weapon_attributes": "bettercombat:hammer"
},
{
"item_id_regex": "double_axe|doubleaxe|war_axe|waraxe|great_axe|greataxe",
"weapon_attributes": "bettercombat:double_axe"
},
{
"item_id_regex": "scythe",
"weapon_attributes": "bettercombat:scythe"
},
{
"item_id_regex": "halberd",
"weapon_attributes": "bettercombat:halberd"
},
{
"item_id_regex": "glaive",
"weapon_attributes": "bettercombat:glaive"
},
{
"item_id_regex": "spear",
"weapon_attributes": "bettercombat:spear"
},
{
"item_id_regex": "lance",
"weapon_attributes": "bettercombat:lance"
},
{
"item_id_regex": "anchor",
"weapon_attributes": "bettercombat:anchor"
},
{
"item_id_regex": "battlestaff|battle_staff",
"weapon_attributes": "bettercombat:battlestaff"
},
{
"item_id_regex": "claw",
"weapon_attributes": "bettercombat:claw"
},
{
"item_id_regex": "fist|gauntlet",
"weapon_attributes": "bettercombat:fist"
},
{
"item_id_regex": "trident|javelin|impaled",
"weapon_attributes": "bettercombat:trident"
},
{
"item_id_regex": "katana",
"weapon_attributes": "bettercombat:katana"
},
{
"item_id_regex": "rapier",
"weapon_attributes": "bettercombat:rapier"
},
{
"item_id_regex": "sickle",
"weapon_attributes": "bettercombat:sickle"
},
{
"item_id_regex": "soul_knife",
"weapon_attributes": "bettercombat:soul_knife"
},
{
"item_id_regex": "dagger|knife",
"weapon_attributes": "bettercombat:dagger"
},
{
"item_id_regex": "staff|wand|sceptre|stave|rod",
"weapon_attributes": "bettercombat:wand"
},
{
"item_id_regex": "mace|hammer|flail",
"weapon_attributes": "bettercombat:mace"
},
{
"item_id_regex": "axe",
"weapon_attributes": "bettercombat:axe"
},
{
"item_id_regex": "coral_blade",
"weapon_attributes": "bettercombat:coral_blade"
},
{
"item_id_regex": "twin_blade|twinblade",
"weapon_attributes": "bettercombat:twin_blade"
},
{
"item_id_regex": "cutlass|scimitar|machete",
"weapon_attributes": "bettercombat:cutlass"
},
{
"item_id_regex": "sword|blade",
"weapon_attributes": "bettercombat:sword"
}
],
"ranged_weapons": [
{
"item_id_regex": "two_handed_crossbow",
"weapon_attributes": "bettercombat:crossbow_two_handed_heavy"
},
{
"item_id_regex": "two_handed_bow",
"weapon_attributes": "bettercombat:bow_two_handed_heavy"
}
]
}
-112
View File
@@ -1,112 +0,0 @@
{
/* Upswing (aka windup) is the first phase of the attack (between clicking and performing the damage).
Typical duration of upswing is `weapon cooldown * 0.5`. (Weapon specific upswing values can be defined in weapon attributes)
This config allows you to change upswing duration.
Example values:
- `0.5` (default, fast paced attack initiation) upswing typically lasts 25% of the attack cooldown
- `1.0` (classic setting, realistic attack initiation) upswing typically lasts 50% of the attack cooldown
*/
"upswing_multiplier": 0.5,
// Bypass damage receive throttling of LivingEntity from player attacks.
"allow_fast_attacks": true,
// Allows client-side target search and server-side attack request execution against currently mounted entity of the player
"allow_attacking_mount": false,
// The minimum number of ticks between two attacks
"attack_interval_cap": 2,
/* Blacklist for entities that are acting as vehicle but should not be treated as protected mounts.
Classical example is an alexsmobs:crocodile attempting a death spin.
(Note all hostile mobs hittable by default, this config is to fix faulty mobs)
*/
"hostile_player_vehicles": [
"alexsmobs:crocodile"
],
// Allows vanilla sweeping mechanic to work and Sweeping Edge enchantment
"allow_vanilla_sweeping": false,
// Allows new sweeping mechanic (by Better Combat) to work, including Sweeping Edge enchantment
"allow_reworked_sweeping": true,
/* The more additional targets a weapon swing hits, the weaker it will get.
Entities struck (+1) in a swing more than this, won't get weakened any further.
*/
"reworked_sweeping_extra_target_count": 4,
/* Determines how weak the attack becomes when striking `reworked_sweeping_extra_target_count + 1` targets.
Example values:
- `0.5` -50% damage
*/
"reworked_sweeping_maximum_damage_penalty": 0.5,
"reworked_sweeping_plays_sound": true,
"reworked_sweeping_emits_particles": true,
"reworked_sweeping_sound_and_particles_only_for_swords": true,
// Allows client-side target search to ignore obstacles. WARNING! Setting this to `false` significantly increases the load on clients.
"allow_attacking_thru_walls": false,
// Applies movement speed multiplier while attacking. (Min: 0, Max: 1). Use `0` for a full stop while attacking. Use `1` for no movement speed penalty
"movement_speed_while_attacking": 0.5,
// Determines if applying the movement speed multiplier while attacking is done smoothly or instantly
"movement_speed_applied_smoothly": true,
// Determines whether or not to apply movement speed reduction while attacking mounted
"movement_speed_effected_while_mounting": false,
// Attacks faster than a vanilla threshold will do smaller knockback, proportionally.
"knockback_reduced_for_fast_attacks": true,
// Attack cooldown in ticks, below which knockback will be reduced
"knockback_reduction_threshold": 12.5,
// Knockback reduction curve. Options: LINEAR, SQUARE, HALF_SQUARE
"knockback_reduction_curve": "HALF_SQUARE",
// Combo is reset after idling `combo_reset_rate * weapon_cooldown`
"combo_reset_rate": 3.0,
// Multiplier for `attack_range`, during target lookup on both sides. Large sized entities may be colliding with weapon hitbox, but center of entities can have bigger distance than `attack_range`
"target_search_range_multiplier": 2.0,
// Determines if the server should validate the target range. (If the target is within the range of the weapon)
"server_target_range_validation": false,
/* Determines how much the scale (size) of the player affects `attack_range`. (Also applies to scales from Pehkui)
Example values:
- `0` scale has no effect on attack range
- `0.5` attack range is affected by scale half as much
- `1` (default) attack range changes proportionally with scale
- `2` attack range is affected by scale twice as much
*/
"entity_scale_attack_range_influence": 1.0,
// Total multiplier, (examples: +30% = 1.3, -30% = 0.7)
"dual_wielding_attack_speed_multiplier": 1.2000000476837158,
// Total multiplier, (examples: +30% = 1.3, -30% = 0.7)
"dual_wielding_main_hand_damage_multiplier": 1.0,
// Total multiplier, (examples: +30% = 1.3, -30% = 0.7)
"dual_wielding_off_hand_damage_multiplier": 1.0,
/* Relations determine when players' undirected weapon swings (cleaves) will hurt another entity (target).
- `FRIENDLY` - The target can never be damaged by the player.
- `NEUTRAL` - The target can be damaged only if the player is directly looking at it.
- `HOSTILE` - The target can be damaged if located within the weapon swing area.
(NOTE: Vanilla sweeping can still hit targets, if not disabled via `allow_sweeping`)
The various relation related configs are being checked in the following order:
- `player_relations`
- `player_relation_to_passives`
- `player_relation_to_hostiles`
- `player_relation_to_other`
(The first relation to be found for the target will be applied.)
*/
"player_relations": {
"minecraft:player": "NEUTRAL",
"minecraft:villager": "NEUTRAL",
"minecraft:iron_golem": "NEUTRAL",
"minecraft:copper_golem": "NEUTRAL",
"guardvillagers:guard": "NEUTRAL"
},
"player_relation_tags": {
"minecraft:undead": "HOSTILE"
},
// Relation to self, and self owned pets (tamed entities), changing this to `FRIENDLY` will make it impossible to hit pets with weapons
"player_relation_to_self_and_pets": "NEUTRAL",
// Relation to teammates (entities in the same team), changing this to `FRIENDLY` will automatically disable friendly fire for all teams
"player_relation_to_teammates": "NEUTRAL",
// Relation to unspecified entities those are instance of PassiveEntity(Yarn)
"player_relation_to_passives": "HOSTILE",
// Relation to unspecified entities those are instance of HostileEntity(Yarn)
"player_relation_to_hostiles": "HOSTILE",
// Fallback relation
"player_relation_to_other": "HOSTILE",
// Try to guess and apply a preset for items without weapon attributes data file
"fallback_compatibility_enabled": true,
// Allow printing the content of weapon attributes registry
"weapon_registry_logging": false,
// Compress the weapon attributes registry for transmission
"weapon_registry_compression": true
}
@@ -1,297 +0,0 @@
{
"trail_appearance": {
"default_appearance": {
"primary": {
"color_rgba": 4294967193,
"glows": false
},
"secondary": {
"color_rgba": 2576980326,
"glows": false
}
},
"conditional": {
"is_enchanted": {
"primary": {
"color_rgba": 1725562777,
"glows": true
},
"secondary": {
"color_rgba": 2582052684,
"glows": true
}
}
}
},
"animation_based": {
"bettercombat:one_handed_slash_horizontal_right": [
{
"particle_type": "slash90",
"x_addition": 0.0,
"y_addition": -0.1,
"z_addition": 0.0,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": 0.0
}
],
"bettercombat:one_handed_slash_horizontal_left": [
{
"particle_type": "slash90",
"x_addition": 0.0,
"y_addition": -0.1,
"z_addition": 0.0,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": 180.0
}
],
"bettercombat:one_handed_uppercut_right": [
{
"particle_type": "slash90",
"x_addition": 0.0,
"y_addition": -0.1,
"z_addition": 0.0,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": 75.0
}
],
"bettercombat:one_handed_swipe_horizontal_right": [
{
"particle_type": "slash90",
"x_addition": 0.0,
"y_addition": -0.1,
"z_addition": 0.0,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": -0.1
}
],
"bettercombat:one_handed_slam": [
{
"particle_type": "slash90",
"x_addition": 0.05,
"y_addition": -0.1,
"z_addition": 0.0,
"local_yaw": 45.0,
"pitch_addition": 0.0,
"roll_set": -85.0
}
],
"bettercombat:one_handed_stab": [
{
"particle_type": "stab",
"x_addition": 0.0,
"y_addition": -0.13,
"z_addition": 0.2,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": 0.0
}
],
"bettercombat:one_handed_stab_mounted": [
{
"particle_type": "stab",
"x_addition": 0.0,
"y_addition": 0.15,
"z_addition": 0.0,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": 0.0
}
],
"bettercombat:one_handed_punch": [
{
"particle_type": "stab",
"x_addition": 0.0,
"y_addition": -0.1,
"z_addition": 0.15,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": 0.0
}
],
"bettercombat:dual_handed_slash_cross": [
{
"particle_type": "slash180",
"x_addition": 0.2,
"y_addition": -0.15,
"z_addition": 0.0,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": -120.0
},
{
"particle_type": "slash180",
"x_addition": -0.2,
"y_addition": -0.15,
"z_addition": 0.0,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": -60.0
}
],
"bettercombat:dual_handed_slash_uncross": [
{
"particle_type": "slash180",
"x_addition": 0.2,
"y_addition": -0.15,
"z_addition": 0.0,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": 240.0
},
{
"particle_type": "slash180",
"x_addition": -0.2,
"y_addition": -0.15,
"z_addition": 0.0,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": 300.0
}
],
"bettercombat:dual_handed_stab": [
{
"particle_type": "stab",
"x_addition": 0.4,
"y_addition": -0.3,
"z_addition": 0.0,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": 0.0
},
{
"particle_type": "stab",
"x_addition": -0.4,
"y_addition": -0.3,
"z_addition": 0.0,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": 0.0
}
],
"bettercombat:two_handed_stab_left": [
{
"particle_type": "stab",
"x_addition": 0.0,
"y_addition": -0.15,
"z_addition": 0.0,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": 0.0
}
],
"bettercombat:two_handed_stab_right": [
{
"particle_type": "stab",
"x_addition": 0.0,
"y_addition": -0.15,
"z_addition": 0.0,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": 0.0
}
],
"bettercombat:two_handed_slash_horizontal_right": [
{
"particle_type": "slash180",
"x_addition": 0.0,
"y_addition": -0.1,
"z_addition": 0.0,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": -5.0
}
],
"bettercombat:two_handed_slash_horizontal_left": [
{
"particle_type": "slash180",
"x_addition": 0.0,
"y_addition": -0.1,
"z_addition": 0.0,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": 180.0
}
],
"bettercombat:one_handed_slash_switch_blade_right": [
{
"particle_type": "slash180",
"x_addition": 0.0,
"y_addition": -0.1,
"z_addition": 0.0,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": 0.0
}
],
"bettercombat:one_handed_slash_switch_blade_left": [
{
"particle_type": "slash180",
"x_addition": 0.0,
"y_addition": -0.1,
"z_addition": 0.0,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": 180.0
}
],
"bettercombat:two_handed_spin": [
{
"particle_type": "slash360",
"x_addition": 0.0,
"y_addition": -0.1,
"z_addition": 0.0,
"local_yaw": 0.0,
"pitch_addition": 0.0,
"roll_set": 180.0
}
],
"bettercombat:two_handed_slash_vertical_right": [
{
"particle_type": "slash90",
"x_addition": 0.0,
"y_addition": -0.1,
"z_addition": 0.0,
"local_yaw": 45.0,
"pitch_addition": 0.0,
"roll_set": -80.0
}
],
"bettercombat:two_handed_slash_vertical_left": [
{
"particle_type": "slash90",
"x_addition": 0.0,
"y_addition": -0.1,
"z_addition": 0.0,
"local_yaw": 45.0,
"pitch_addition": 0.0,
"roll_set": -100.0
}
],
"bettercombat:two_handed_slam": [
{
"particle_type": "slash180",
"x_addition": 0.1,
"y_addition": -0.1,
"z_addition": 0.0,
"local_yaw": 45.0,
"pitch_addition": 0.0,
"roll_set": -86.0
}
],
"bettercombat:two_handed_slam_heavy": [
{
"particle_type": "slash180",
"x_addition": 0.1,
"y_addition": -0.1,
"z_addition": 0.0,
"local_yaw": 45.0,
"pitch_addition": 0.0,
"roll_set": -86.0
}
]
}
}
-9
View File
@@ -1,9 +0,0 @@
{
"create_version": "26.301.2",
"rendering": {
"custom_sky": true,
"sulfur_water_color": true,
"blend_biome_music": true
},
"modify_version": "26.301.2"
}
-58
View File
@@ -1,58 +0,0 @@
{
"create_version": "26.301.2",
"structure": {
"generate_central_island": true,
"generate_obsidian_platform": true,
"has_portal": true,
"replace_portal": true,
"has_pillars": true,
"replace_pillars": true,
"biome_size_caves": 32,
"end_city_fail_chance": 1
},
"generator": {
"use_new_generator": true,
"layers": {
"big_islands": {
"distance": 300.0,
"scale": 200.0,
"average_height": 0.546875,
"height_variation": 0.078125,
"coverage": 0.5,
"contains_central_island": false
},
"medium_islands": {
"distance": 150.0,
"scale": 100.0,
"average_height": 0.546875,
"height_variation": 0.15625,
"coverage": 0.5,
"contains_central_island": true
},
"small_islands": {
"distance": 60.0,
"scale": 50.0,
"average_height": 0.546875,
"height_variation": 0.234375,
"coverage": 0.5,
"contains_central_island": false
}
}
},
"entity": {
"has_dragon_fights": true,
"spawn": {
"has_spawn": false,
"point": {
"x": 20,
"y": 65,
"z": 0
}
},
"give_guide_book": true
},
"cosmetic": {
"change_chorus_plant": true
},
"modify_version": "26.301.2"
}
-11
View File
@@ -1,11 +0,0 @@
{
"portals": [
{
"dimension": "minecraft:overworld",
"item": "betterend:eternal_crystal",
"colorRed": 255,
"colorGreen": 255,
"colorBlue": 255
}
]
}
@@ -1,70 +0,0 @@
{
"auroral_garden_enabled": true,
"bayou_enabled": true,
"bog_enabled": true,
"cold_desert_enabled": true,
"coniferous_forest_enabled": true,
"crag_enabled": true,
"crystalline_chasm_enabled": true,
"dead_forest_enabled": true,
"dryland_enabled": true,
"dune_beach_enabled": true,
"end_corruption_enabled": true,
"end_flats_enabled": true,
"end_reef_enabled": true,
"end_wilds_enabled": true,
"erupting_inferno_enabled": true,
"field_enabled": true,
"fir_clearing_enabled": true,
"floodplain_enabled": true,
"forested_field_enabled": true,
"fungal_jungle_enabled": true,
"glowing_grotto_enabled": true,
"gravel_beach_enabled": true,
"highland_enabled": true,
"hot_springs_enabled": true,
"jade_cliffs_enabled": true,
"lavender_field_enabled": true,
"lush_desert_enabled": true,
"lush_savanna_enabled": true,
"maple_woods_enabled": true,
"marsh_enabled": true,
"mediterranean_forest_enabled": true,
"moor_enabled": true,
"muskeg_enabled": true,
"mystic_grove_enabled": true,
"old_growth_dead_forest_enabled": true,
"old_growth_woodland_enabled": true,
"ominous_woods_enabled": true,
"orchard_enabled": true,
"origin_valley_enabled": true,
"overgrown_greens_enabled": true,
"pasture_enabled": true,
"prairie_enabled": true,
"pumpkin_patch_enabled": true,
"rainforest_enabled": true,
"redwood_forest_enabled": true,
"rocky_rainforest_enabled": true,
"rocky_shrubland_enabled": true,
"scrubland_enabled": true,
"seasonal_forest_enabled": true,
"shrubland_enabled": true,
"snowblossom_grove_enabled": true,
"snowy_coniferous_forest_enabled": true,
"snowy_fir_clearing_enabled": true,
"snowy_maple_woods_enabled": true,
"spider_nest_enabled": true,
"subtropics_enabled": true,
"tropics_enabled": true,
"tundra_enabled": true,
"undergrowth_enabled": true,
"visceral_heap_enabled": true,
"volcanic_plains_enabled": true,
"volcano_enabled": true,
"wasteland_enabled": true,
"wasteland_steppe_enabled": true,
"wetland_enabled": true,
"wintry_origin_valley_enabled": true,
"withered_abyss_enabled": true,
"woodland_enabled": true
}
@@ -1,12 +0,0 @@
[nether]
#The weighting of bop biome regions in the nether.
bop_nether_region_weight = 13
#The weighting of rare bop biome regions in the nether.
bop_nether_rare_region_weight = 2
[overworld]
#The weighting of primary bop biome regions in the overworld.
bop_primary_overworld_region_weight = 10
#The weighting of rare bop biome regions in the overworld.
bop_overworld_rare_region_weight = 2
#The weighting of secondary bop biome regions in the overworld.
bop_secondary_overworld_region_weight = 8
-4
View File
@@ -1,4 +0,0 @@
{
"cooldown": 5,
"maxHomes": 5
}
-32
View File
@@ -1,32 +0,0 @@
{
"jade:loot_table": null,
"jade:object_name": null,
"minecraft:animal_owner": null,
"minecraft:beehive": null,
"minecraft:brewing_stand": null,
"minecraft:campfire": null,
"minecraft:command_block": null,
"minecraft:energy_storage": null,
"minecraft:energy_storage.default": null,
"minecraft:entity_health": null,
"minecraft:fluid_storage": null,
"minecraft:fluid_storage.default": null,
"minecraft:furnace": null,
"minecraft:hopper_lock": null,
"minecraft:item_storage": null,
"minecraft:item_storage.default": null,
"minecraft:jukebox": null,
"minecraft:lectern": null,
"minecraft:mob_breeding": null,
"minecraft:mob_growth": null,
"minecraft:mob_spawner.cooldown": null,
"minecraft:next_entity_drop": null,
"minecraft:pet_armor": null,
"minecraft:potion_effects": null,
"minecraft:progress": null,
"minecraft:redstone": null,
"minecraft:shelf": null,
"minecraft:villager_restock": null,
"minecraft:waxed": null,
"minecraft:zombie_villager": null
}
@@ -1,22 +0,0 @@
# Name: Server Cheat Permissions
# Description: Choose who can use JEI cheat mode on this server.
[permissions]
# Name: Allow Cheat Mode for Operators
# Description: Allow players with operator status (/op) to use JEI cheat mode.
# Valid Values: [true, false]
# Default Value: true
enableCheatModeForOp = true
# Name: Allow Cheat Mode in Creative Mode
# Description: Allow players in Creative Mode to use JEI cheat mode.
# Valid Values: [true, false]
# Default Value: true
enableCheatModeForCreative = true
# Name: Allow Cheat Mode with Give Permission
# Description: Allow players who can use the /give command to use JEI cheat mode.
# Valid Values: [true, false]
# Default Value: false
enableCheatModeForGive = false
-34
View File
@@ -1,34 +0,0 @@
{
"common": {
"allowTeleportComment": "Allows a player to teleport to a located biome when in creative mode, opped, or in cheat mode.",
"allowTeleport": true,
"maxNextSearchesComment": "The maximum number of times a player can search for the next instance of a located biome. Set to 0 to disable searching for additional biome instances and make the compass always locate the nearest biome.",
"maxNextSearches": 25,
"displayCoordinatesComment": "Allows players to view the precise coordinates and distance of a located structure on the HUD, rather than relying on the direction the compass is pointing.",
"displayCoordinates": true,
"maxSamplesComment": "The maximum number of samples to be taken when searching for a biome.",
"maxSamples": 50000,
"radiusModifierComment": "biomeSize * radiusModifier = maxSearchRadius. Raising this value will increase search accuracy but will potentially make the process more resource .",
"radiusModifier": 2500,
"sampleSpaceModifierComment": "biomeSize * sampleSpaceModifier = sampleSpace. Lowering this value will increase search accuracy but will make the process more resource intensive.",
"sampleSpaceModifier": 16,
"biomeBlacklistComment": "A list of biomes that the compass will not be able to search for, specified by resource location. The wildcard character * can be used to match any number of characters, and ? can be used to match one character. Ex (ignore backslashes): [\"minecraft:savanna\", \"minecraft:desert\", \"minecraft:*ocean*\"]",
"biomeBlacklist": [],
"defaultXpLevelsComment": "The default number of XP levels consumed when searching for a biome. Individual biomes can be configured via xpLevelOverrides. Max of 3 levels.",
"defaultXpLevels": 0,
"perBiomeXpLevelsComment": "A map of per-biome XP level costs that override defaultXpLevels. Biomes not listed here use defaultXpLevels. Max of 3 levels. The wildcard character * can be used to match any number of characters, and ? can be used to match one character. Ex: {\"minecraft:deep_dark\":3, \"minecraft:end*\":2, \"minecraft:*caves\":3}",
"perBiomeXpLevels": {},
"compassDurabilityComment": "The number of successful biome searches before the compass breaks and must be repaired. Set to 0 to disable durability.",
"compassDurability": 0
},
"client": {
"displayWithChatOpenComment": "Displays Nature's Compass information even while chat is open.",
"displayWithChatOpen": true,
"fixBiomeNamesComment": "Fixes biome names by adding missing spaces. Ex: ForestHills becomes Forest Hills",
"fixBiomeNames": true,
"overlayLineOffsetComment": "The line offset for information rendered on the HUD.",
"overlayLineOffset": 1,
"overlaySideComment": "The side for information rendered on the HUD. Ex: LEFT, RIGHT",
"overlaySide": "LEFT"
}
}
-18
View File
@@ -1,18 +0,0 @@
#Packet Fixer config file.
#Default values (minecraft default): nbtMaxSize 2097152, packetSize 1048576, decoderSize 8388608 and varInt21Size 3.
#Max values are 2147483647 for packetSize/decoderSize/varInt21 and 9223372036854775807 for nbtMaxSize.
#Sun Oct 18 22:04:32 CEST 2026
allSizesUnlimited=true
chunkPacketData=2097152
decoderSize=8388608
forceUnlimitedNbtEnabled=false
keepAliveTimeout=120
loginTimeout=120
nbtMaxSize=2097152
packetSize=1048576
playerIdleTimeout=0
readTimeout=120
stringSize=32767
varInt=5
varInt21=3
varLong=10

Some files were not shown because too many files have changed in this diff Show More