20 Commits
Author SHA1 Message Date
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
119 changed files with 1190 additions and 2625 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
+175 -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,23 @@
} }
}, },
{ {
"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.1.8@jar",
"name": "NoSingleplayer (SurviePote)", "name": "NoSingleplayer (SurviePote)",
"type": "FabricMod", "type": "FabricMod",
"artifact": { "artifact": {
"size": 2292471, "size": 3637392,
"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/required/nosingleplayer-1.1.8.jar",
"MD5": "2ba50ec99adb670cdb34fe41b0e3d7dd" "MD5": "dc69cd7ebc5d10ba4797c4e212e47e0c"
} }
}, },
{ {
@@ -461,6 +546,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 +576,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",
@@ -497,6 +612,16 @@
"value": false "value": false
} }
}, },
{
"id": "generated.fabricmod:spatial:gui-1.8+26.3-fabric@jar",
"name": "Spatial",
"type": "FabricMod",
"artifact": {
"size": 276562,
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/spatial-gui-1.8+26.3-fabric.jar",
"MD5": "80fcb9b1ed083b8515fa78e6a5cd86a4"
}
},
{ {
"id": "com.jaquadro.minecraft:storagedrawers:26.3.0.1@jar", "id": "com.jaquadro.minecraft:storagedrawers:26.3.0.1@jar",
"name": "Storage Drawers", "name": "Storage Drawers",
@@ -517,6 +642,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 +662,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 +764,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 +938,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
+2 -23
View File
@@ -431,20 +431,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 +602,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 +633,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 +682,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 +697,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)
})
})
+4 -26
View File
@@ -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() msAccDomElementCache = parent
const targetAcc = ConfigManager.getAuthAccount(uuid) switchView(getCurrentView(), VIEWS.waiting, 500, 500, () => {
if(targetAcc.type === 'microsoft') { ipcRenderer.send(MSFT_OPCODE.OPEN_LOGOUT, uuid, isLastAccount)
msAccDomElementCache = parent })
switchView(getCurrentView(), VIEWS.waiting, 500, 500, () => {
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.
+9 -25
View File
@@ -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,36 +335,21 @@ 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, selectedAcc.username,
selectedAcc.username, selectedAcc.expiresAt,
selectedAcc.expiresAt, selectedAcc.microsoft.access_token,
selectedAcc.microsoft.access_token, 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.0",
"productName": "SurviePote", "productName": "SurviePote",
"description": "Launcher officiel SurviePote - Rejoins mcpv.ihorizon.org", "description": "Launcher officiel SurviePote - Rejoins mcpv.ihorizon.org",
"author": "SurviePote", "author": "SurviePote",
+7 -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,4 @@ 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.1.4.jar` → copié dans `atlas/.../fabricmods/required/`.
+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.1.8
maven_group=fr.surviepote maven_group=fr.surviepote
archives_base_name=nosingleplayer archives_base_name=nosingleplayer
@@ -0,0 +1,69 @@
package fr.surviepote.nosingleplayer;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.renderpearl.api.textures.FilterMode;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.resources.Identifier;
/**
* Chargement des textures du mod.
*
* <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 enregistre une DynamicTexture dans
* le TextureManager.
*/
public final class ModTextures {
private static final Map<Identifier, DynamicTexture> CACHE = new HashMap<>();
private ModTextures() {
}
/**
* Renvoie la texture (chargée depuis le jar du mod), ou null si le fichier est absent.
*
* @param id identifiant sous lequel la texture est enregistrée
* @param resourcePath chemin dans le jar, ex {@code /assets/nosingleplayer/...}
* @param name nom affiché dans les crash reports GPU
* @param linear true = filtrage linéaire (images agrandies), false = nearest (pixel art)
*/
public static DynamicTexture get(Identifier id, String resourcePath, String name, boolean linear) {
DynamicTexture texture = CACHE.get(id);
var textures = Minecraft.getInstance().getTextureManager();
if (texture != null && textures.getTexture(id) == texture) {
return texture; // déjà chargée et toujours enregistrée
}
try (InputStream in = ModTextures.class.getResourceAsStream(resourcePath)) {
if (in == null) {
NoSingleplayer.LOGGER.error("[SurviePote] Texture introuvable dans le jar : {}", resourcePath);
return null;
}
NativeImage image = NativeImage.read(in);
texture = linear
? new LinearTexture(() -> name, image)
: new DynamicTexture(() -> name, image);
CACHE.put(id, texture);
textures.register(id, texture);
return texture;
} catch (IOException e) {
NoSingleplayer.LOGGER.error("[SurviePote] Échec de chargement de la texture " + resourcePath, e);
return null;
}
}
/** DynamicTexture avec filtrage linéaire (utile quand l'image est réduite à l'écran). */
private static final class LinearTexture extends DynamicTexture {
LinearTexture(java.util.function.Supplier<String> name, NativeImage image) {
super(name, image);
this.sampler = RenderSystem.getSamplerCache().getClampToEdge(FilterMode.LINEAR);
}
}
}
@@ -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.get(TEXTURE, TEXTURE_PATH, "SurviePote Discord", 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.get(texture, resourcePath, message.getString(), 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,127 @@
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;
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() {
try {
this.pingStartedAt = System.currentTimeMillis();
this.pending = true;
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;
}
}
@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,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);
@@ -42,35 +51,22 @@ public abstract class TitleScreenMixin {
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,6 +74,27 @@ 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) { for (GuiEventListener widget : toRemove) {
@@ -86,17 +103,61 @@ public abstract class TitleScreenMixin {
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;
}
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,11 +1,16 @@
package fr.surviepote.nosingleplayer.mixin; package fr.surviepote.nosingleplayer.mixin;
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.client.renderer.texture.DynamicTexture;
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;
@@ -19,23 +24,98 @@ 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 surviepote$loadBackground).
@Unique @Unique
private static final int BACKGROUND_TEX_W = 3840; private static final String SURVIEPOTE_BACKGROUND_RESOURCE =
"/assets/nosingleplayer/textures/gui/background.png";
@Unique @Unique
private static final int BACKGROUND_TEX_H = 2400; private static DynamicTexture surviepote$background;
@Unique
private static int surviepote$backgroundWidth;
@Unique
private static int surviepote$backgroundHeight;
// 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) {
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,
w, h, BACKGROUND_TEX_W, BACKGROUND_TEX_H, -1); surviepote$loadBackground();
if (surviepote$background == null) {
return;
}
float screenAspect = (float) w / (float) h;
float texAspect = (float) surviepote$backgroundWidth / (float) surviepote$backgroundHeight;
int u, v, srcW, srcH;
if (screenAspect > texAspect) {
// Écran plus large que l'image : on rogne en haut/bas.
srcW = surviepote$backgroundWidth;
srcH = Math.round(surviepote$backgroundWidth / screenAspect);
u = 0;
v = (surviepote$backgroundHeight - srcH) / 2;
} else {
// Écran plus haut que l'image : on rogne à gauche/droite.
srcH = surviepote$backgroundHeight;
srcW = Math.round(surviepote$backgroundHeight * screenAspect);
u = (surviepote$backgroundWidth - 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.
// L'overload court réutilise w/h comme région source (=> zoom sur un coin).
extractor.blit(RenderPipelines.GUI_TEXTURED, SURVIEPOTE_BACKGROUND,
0, 0, (float) u, (float) v,
w, h, srcW, srcH,
surviepote$backgroundWidth, surviepote$backgroundHeight, -1);
}
/**
* 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). Le PNG est donc lu dans le jar du mod (voir ModTextures).
*/
@Unique
private static void surviepote$loadBackground() {
DynamicTexture texture = ModTextures.get(SURVIEPOTE_BACKGROUND, SURVIEPOTE_BACKGROUND_RESOURCE,
"SurviePote Background", true);
if (texture == null) {
surviepote$background = null;
return;
}
if (surviepote$background != texture) {
surviepote$background = texture;
surviepote$backgroundWidth = texture.getPixels().getWidth();
surviepote$backgroundHeight = texture.getPixels().getHeight();
NoSingleplayer.LOGGER.info("[SurviePote] Fond chargé : {}x{}",
surviepote$backgroundWidth, surviepote$backgroundHeight);
}
}
// 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: 629 KiB

After

Width:  |  Height:  |  Size: 1.9 MiB

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.
+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
-12
View File
@@ -1,12 +0,0 @@
{
"debug": false,
"version": "1.0.0",
"listMode": "DISABLED",
"rateLimit": {
"enable": false,
"tokenLimit": 20,
"tokenIncrement": 1,
"tokenIncrementInterval": "1sec",
"permissionBypass": "patpat.ratelimit.bypass"
}
}
-55
View File
@@ -1,55 +0,0 @@
{
"//": "These settings are easier to change in-game! Mod List -> Collective -> Config",
"disabledSerilumMods": {
"//": "The Serilum mods that are turned off, picked from the ones installed. A mod's own 'Enable' setting follows this list, and the other way round. Takes effect after a restart.",
"value": ""
},
"enableUpdateChecker": {
"//": "Whether Collective should show a message in the console if a dependent mod has an update available. Update checks are optional and async.",
"value": true
},
"transferItemsBetweenReplacedEntities": {
"//": "When enabled, transfer the held items and armour from replaced entities by any of the Entity Spawn mods which depend on Collective.",
"value": true
},
"loopsAmountUsedToGetAllEntityDrops": {
"//": "The amount of times Collective loops through possible mob drops to get them all procedurally. Drops are only generated when a dependent mod uses them. Lowering this can increase world load time but decrease accuracy. min: 1, max: 500",
"value": 100
},
"findABlockCheckAroundEntitiesDelayMs": {
"//": "The delay of the is-there-a-block-around-check around entities in ms. Used in mods which depends on a specific blockstate in the world. Increasing this number can increase TPS if needed. min: 0, max: 3,600,000",
"value": 30000
},
"enablePatronPets": {
"//": "Enables pets for Patrons. Will be added in a future release.",
"value": true
},
"downloadNonEnglishTranslations": {
"//": "Whether Collective should attempt to download translations if the client language is set to something other than English. Uses files from https://github.com/Serilum/.translations, downloaded via https://translations.serilum.com/. Hosted via CloudFlare Pages.",
"value": true
},
"pushTranslationResourcePack": {
"//": "On a server, sends connecting players without Collective a resource pack so custom item names from server-side Serilum mods show up in their own language. Off by default; without it they see the server language instead (see serverLanguage).",
"value": false
},
"requireTranslationResourcePack": {
"//": "Marks the resource pack as required, so an accidental decline only kicks the player and asks again next join instead of being remembered for good. Turn off to make it optional.",
"value": true
},
"serverLanguage": {
"//": "The language Serilum mod text shows in for players without Collective who aren't sent the resource pack. Uses locale codes like en_us or de_de.",
"value": "en_us"
},
"itemNameTranslationMode": {
"//": "How custom item names show for players without Collective when the resource pack is off. 'auto' copies the first player to join and remembers it; 'server' always uses the server language; 'client' uses each player's own language, but those without Collective then see raw keys. options: auto, server, client",
"value": "auto"
},
"configScreenGuiScale": {
"//": "The GUI scale of the config screens of Serilum's mods, also set with the button next to the ? on those screens. 0 uses the game's own GUI scale. min: 0, max: 10",
"value": 0
},
"updateMinecraftWindowTitleInDevMode": {
"//": "Used in my local development flow. Has no effect in production, only when ran from an IDE with Collective installed.",
"value": false
}
}
-91
View File
@@ -1,91 +0,0 @@
{
"//": "These settings are easier to change in-game! Mod List -> Tree Harvester -> Config",
"Enable Tree Harvester": {
"//": "Whether Tree Harvester is enabled. When disabled, none of its features run and its other settings are hidden. Takes effect after a restart.",
"value": true
},
"mustHoldAxeForTreeHarvest": {
"//": "If enabled, tree harvesting only works when a player is holding an axe in the main hand.",
"value": true
},
"axeWhitelist": {
"//": "A comma separated list of item ids that can harvest trees too, for modded tools that are not detected as an axe, like paxels and multitools. Use * as a wildcard. An id without a namespace matches items of every mod. Applies after rejoining the world.",
"value": "easy_emerald:*_paxel,piercingpaxels:*_paxel,randomisfits:*_multitool,tetra:modular_double"
},
"treeHarvestWithoutSneak": {
"//": "If enabled, tree harvesting works when not holding the sneak button. If disabled it's reversed, and only works when sneaking.",
"value": false
},
"automaticallyFindBottomBlock": {
"//": "Whether the mod should attempt to find the actual bottom log of the tree and start there. This means you can break a tree in the middle and it will still completely be felled.",
"value": true
},
"enableFastLeafDecay": {
"//": "If enabled, the leaves around a broken tree will quickly disappear.",
"value": true
},
"enableNetherTrees": {
"//": "If enabled, the warped stem/crimson trees in the nether will also be chopped down quickly.",
"value": true
},
"enableHugeMushrooms": {
"//": "If enabled, giant/huge mushrooms will also be chopped down quickly.",
"value": true
},
"ignorePlayerMadeTrees": {
"//": "If enabled, trees with leaves placed by players won't be destroyed.",
"value": true
},
"logBlockBlacklist": {
"//": "A comma separated list of block ids that are never harvested as a tree, also when they're detected as a log. Use * as a wildcard. An id without a namespace matches blocks of every mod. For example 'minecraft:jungle_log,*crimson_stem'. Applies after rejoining the world.",
"value": ""
},
"replaceSaplingOnTreeHarvest": {
"//": "If enabled, automatically replaces the sapling from the drops when a tree is harvested.",
"value": true
},
"replaceMushroomOnMushroomHarvest": {
"//": "If enabled, automatically replaces the sapling from the drops when a huge mushroom is harvested and 'enableHugeMushrooms' is enabled.",
"value": true
},
"loseDurabilityPerHarvestedLog": {
"//": "If enabled, for every log harvested, the axe held loses durability.",
"value": true
},
"loseDurabilityModifier": {
"//": "Here you can set how much durability chopping down a tree should take from the axe. For example if set to 0.1, this means that every 10 logs take 1 durability. min: 0.001, max: 1",
"value": 1.0
},
"preventAxeBreakingOnTreeHarvest": {
"//": "If enabled, harvesting a tree never breaks the axe. The whole tree is still felled, but the axe stops at 1 durability. An axe with 1 durability left can't harvest trees until it's repaired.",
"value": true
},
"increaseExhaustionPerHarvestedLog": {
"//": "If enabled, players' exhaustion level increases 0.005 per harvested log (Minecraft's default per broken block) * increaseExhaustionModifier.",
"value": true
},
"increaseExhaustionModifier": {
"//": "This determines how much exhaustion should be added to the player per harvested log. By default 0.005 * 1.0. min: 0.001, max: 1",
"value": 1.0
},
"increaseHarvestingTimePerLog": {
"//": "If enabled, harvesting time will increase per existing log in the tree. The amount is determined by 'increasedHarvestingTimePerLogModifier'.",
"value": true
},
"increasedHarvestingTimePerLogModifier": {
"//": "How much longer it takes to harvest a tree with 'increaseHarvestingTimePerLog' enabled. The actual speed is: newSpeed = originalSpeed / (1 + (logCount * increasedHarvestingTimePerLogModifier)). min: 0.01, max: 10",
"value": 0.2
},
"amountOfLeavesBrokenPerTick": {
"//": "How many leaves should be broken per tick after a tree has been harvested. Increasing this will speed up the fast leaf decay, but costs more processing power per tick. min: 1, max: 16",
"value": 5
},
"maxAmountOfLogsBrokenPerHarvest": {
"//": "The maximum amount of logs broken in one harvest. The logs closest to the broken log go first, so a bigger tree is only partly felled. Useful against lag from huge modded trees or forests with connected logs. -1 means no limit. min: -1, max: 100,000",
"value": -1
},
"triggerBlockBreakEventPerHarvestedLog": {
"//": "If enabled, every harvested log in a tree triggers a block break event, as if broken by hand. This lets other mods see each log, like skill mods that give experience for chopping or claim mods that protect an area. A log is skipped when another mod cancels its event.",
"value": true
}
}
@@ -1,11 +0,0 @@
// To disable a certain axe from being able to harvest trees, add an exclamation mark (!) in front of the line,
minecraft:wooden_axe,
minecraft:copper_axe,
minecraft:stone_axe,
minecraft:golden_axe,
minecraft:iron_axe,
minecraft:diamond_axe,
minecraft:netherite_axe,
betterend:terminite_axe,
betterend:thallasium_axe,
betterend:aeternium_axe,
-26
View File
@@ -1,26 +0,0 @@
[general]
#The size of overworld biome regions from each mod that uses TerraBlender.
overworld_region_size = 3
#The weighting of vanilla biome regions in the nether.
vanilla_nether_region_weight = 10
#The size of nether biome regions from each mod that uses TerraBlender.
nether_region_size = 2
#The weighting of vanilla biome regions in the overworld.
vanilla_overworld_region_weight = 10
[end]
#The size of edge end biomes.
edge_biome_size = 3
#The size of highlands end biomes.
highlands_biome_size = 4
#The weight of Vanilla end barrens biomes.
vanilla_end_barrens_weight = 10
#The size of island end biomes.
island_biome_size = 2
#The weight of Vanilla end highlands biomes.
vanilla_end_highlands_weight = 10
#The weight of Vanilla end midlands biomes.
vanilla_end_midlands_weight = 10
#The size of midlands end biomes.
midlands_biome_size = 4
#The weight of Vanilla small end islands biomes.
vanilla_small_end_islands_weight = 10
-13
View File
@@ -1,13 +0,0 @@
{
"houseEdge": 0.9,
"minChance": 0.001,
"maxChance": 0.9,
"spinDurationTicks": 70,
"valueOverrides": [],
"blacklist": [],
"globalValueMultiplier": 1.0,
"valueMultipliers": [],
"maxTargetCount": 64,
"attemptLimit": 0,
"attemptWindowSeconds": 0
}
-123
View File
@@ -1,123 +0,0 @@
[blueMap]
# Controls whether Waystones' BlueMap integration should be loaded
enabled = true
# If enabled, sharestones will be tracked as markers on BlueMap.
includeSharestones = true
# If enabled, undiscovered waystones will be tracked as markers on BlueMap.
includeUndiscoveredWaystones = false
# If enabled, waystones will be tracked as markers on BlueMap
includeWaystones = true
[client]
# If enabled, the text overlay on waystones will no longer always render at full brightness.
disableTextGlow = false
[dynmap]
# If enabled, Waystones will add markers for waystones and sharestones to Dynmap.
enabled = true
[inventoryButton]
# The y position of the inventory button in the creative menu.
creativeInventoryButtonX = 88
# The y position of the inventory button in the creative menu.
creativeInventoryButtonY = 33
# Set to 'none' for no inventory button. Set to 'nearest' for an inventory button that teleports to the nearest waystone. Set to 'any' for an inventory button that opens the waystone selection menu. Set to a waystone name for an inventory button that teleports to a specifically named waystone.
inventoryButton = ""
# The x position of the inventory button in the inventory.
inventoryButtonX = 58
# The y position of the inventory button in the inventory.
inventoryButtonY = 60
[journeyMap]
# If enabled, JourneyMap waypoints will be created for each activated waystone.
enabled = true
# If enabled, JourneyMap waypoints will only be created if the mod 'JourneyMap Integration' is not installed
preferJourneyMapIntegrationMod = true
[rules]
# Set to true to allow everyone to manage global waystones. Always true if `defaultVisibility` is `global`. Can be overridden by Shogi Rule `may_manage_global_waystones`.
allowEveryoneToManageGlobalWaystones = false
# Set to "team" or "global" to have newly placed or found waystones use that visibility by default.
defaultVisibility = "activation"
# Set to false to simply disable all cooldowns. See rules for more fine-grained control.
enableCooldowns = true
# Set to false to simply disable all durability costs. See rules for more fine-grained control.
enableDurability = true
# Set to true to enable warp modifier items for applying status effects on teleports.
enableModifiers = true
# Set to false to simply disable all xp costs. See rules for more fine-grained control.
enableXpCosts = true
# List of entities that cannot be teleported, either as pet, leashed, or on warp plates.
entityDenyList = [ "minecraft:wither" ]
# Set to "enabled" to have leashed mobs teleport with you. Set to "same_dimension" to have leashed mobs teleport with you only if you're not changing dimensions. Set to "disabled" to disable.
transportLeashed = "enabled"
# Set to "enabled" to have nearby pets teleport with you. Set to "same_dimension" to have nearby pets teleport with you only if you're not changing dimensions. Set to "disabled" to disable.
transportPets = "disabled"
# List of warp requirements in Shogi format.
warpRequirements = [
"$uses_xp #= and(any(source(is_waystone), source(is_warp_stone)), not(target(is_fleeting_memorial)))",
"$uses_xp -> $xp_points_cost = $distance * $xp_per_block",
"$uses_xp + is_interdimensional -> $xp_points_cost = $interdimensional_xp_cost",
"$uses_xp -> $xp_points_cost = clamp($xp_points_cost, 0, $max_xp_cost)",
"is_warp_stone -> damage_item(80)",
"is_inventory_button -> cooldown_cost('inventory_button', $inventory_button_cooldown)"
]
# List of variables used in the warpRequirements configuration.
warpSettings = [
"$xp_per_block = 0.01",
"$min_xp_cost = 0",
"$max_xp_cost = 27",
"$interdimensional_xp_cost = 27",
"$inventory_button_cooldown = '300s'"
]
[worldGen]
# Approximate chunk distance between wild waystones being generated. Set to 0 to disable generation.
chunksBetweenWildWaystones = 25
# Set to 'preset_first' to first use names from the nameGenerationPresets. Set to 'preset_only' to use only those custom names. Set to 'mixed' to have some waystones use custom names, and others random names.
nameGenerationMode = "preset_first"
# These names will be used for the preset name generation mode. See the nameGenerationMode option for more info.
nameGenerationPresets = [ ]
# The template to use when generating new names. Supported placeholders are {Biome} (english biome name) and {MrPork} (the default name generator).
nameGenerationTemplate = "{MrPork}"
# Set to "regular" to have waystones spawn in some villages. Set to "frequent" to have waystones spawn in most villages. Set to "disabled" to disable waystone generation in villages. Waystones will only spawn in vanilla or supported villages.
spawnInVillages = "regular"
# Set to 'default' to only generate the normally textured waystones. Set to 'mossy' or 'sandy' to generate all as that variant. Set to 'biome' to make the style depend on the biome it is generated in.
wildWaystoneStyle = "biome"
# List of dimensions that wild waystones are allowed to spawn in. If left empty, all dimensions except those in wildWaystonesDimensionDenyList will be able to spawn waystones, provided the biomes are in the `has_structure/waystone` tag.
wildWaystonesDimensionAllowList = [ "minecraft:the_end", "minecraft:overworld", "minecraft:the_nether" ]
# List of dimensions that wild waystones are not allowed to spawn in, even if the biome is in the `has_structure/waystone` tag. Only used if wildWaystonesDimensionAllowList is empty.
wildWaystonesDimensionDenyList = [ ]
-7
View File
@@ -1,7 +0,0 @@
{
"create_version": "26.301.2",
"log": {
"verbose": false
},
"modify_version": "26.301.2"
}
+56
View File
@@ -0,0 +1,56 @@
/**
* PM2 — SurviePote serveur Minecraft (MC 26.3 / Fabric 0.20.0 / Java 25).
*
* Démarre le serveur DANS le shell Nix du repo (shell.nix => jdk25), sans
* dépendre du Java du système.
*
* cd /srv/mc/server
* pm2 start ecosystem.config.cjs
* pm2 logs surviepote-mc
* pm2 restart surviepote-mc
* pm2 stop surviepote-mc
* pm2 save
*
* Les chemins sont résolus depuis ce fichier (__dirname) :
* SERVER_DIR = /srv/mc/server
* ROOT_DIR = /srv/mc
* donc le fichier marche quel que soit l'endroit d'où pm2 est lancé.
*/
const path = require('path')
const SERVER_DIR = __dirname
const ROOT_DIR = path.resolve(__dirname, '..')
const START_SCRIPT = path.join(SERVER_DIR, 'start.sh')
// cd /srv/mc/server && nix-shell /srv/mc --run "bash /srv/mc/server/start.sh"
const command = [
`cd ${JSON.stringify(SERVER_DIR)}`,
`exec nix-shell ${JSON.stringify(ROOT_DIR)} --run ${JSON.stringify(`bash ${START_SCRIPT}`)}`
].join(' && ')
module.exports = {
apps: [
{
name: 'surviepote-mc',
cwd: SERVER_DIR,
// On exécute `bash -lc "<commande>"` directement (pas un script Node).
script: 'bash',
args: ['-lc', command],
interpreter: 'none',
autorestart: true,
max_restarts: 10,
restart_delay: 5000,
min_uptime: '60s',
kill_timeout: 30000,
treekill: true, // SIGINT/SIGKILL propagés jusqu'à java (arrêt propre)
watch: false,
env: {
// On est déjà dans nix-shell : empêche start.sh de s'y relancer.
SURVIEPOTE_NIX: '1'
}
}
]
}
Binary file not shown.
Binary file not shown.

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