Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00e9ff003c | ||
|
|
e5b2fc5a79 | ||
|
|
2bb5b30857 | ||
|
|
4fd8aa09b6 | ||
|
|
177a765783 | ||
|
|
4c7da4cab5 | ||
|
|
1ff5f52ce8 | ||
|
|
d02bb4b0ac | ||
|
|
49e429ce1b | ||
|
|
4f95e82812 | ||
|
|
89e6b6f680 | ||
|
|
fc80845f5c | ||
|
|
8053411565 | ||
|
|
c92059db26 | ||
|
|
fdc7d867ec | ||
|
|
b8a283a177 | ||
|
|
3a2b403acc | ||
|
|
37fcdfb277 | ||
|
|
20330310fe | ||
|
|
f4372ab8a4 | ||
|
|
7a1a4306b1 | ||
|
|
058c18c4cb | ||
|
|
3f389f65f2 | ||
|
|
19f0e05c8e | ||
|
|
722802c001 | ||
|
|
4a48d2c721 | ||
|
|
159a594b2c |
@@ -22,16 +22,11 @@ server/crash-reports/
|
||||
server/versions/
|
||||
server/*.log
|
||||
server/*.json
|
||||
server/config/
|
||||
server/fabricloader.log
|
||||
server/fabric-server-launcher.properties
|
||||
quarantine/*.jar
|
||||
|
||||
# server runtime
|
||||
server/world/
|
||||
server/logs/
|
||||
server/crash-reports/
|
||||
server/*.log
|
||||
|
||||
# OS / IDE
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -43,10 +38,10 @@ Thumbs.db
|
||||
.env
|
||||
atlas/.env
|
||||
server/.fabric/
|
||||
server/config/**/update/
|
||||
mods/*/.gradle/
|
||||
mods/*/build/
|
||||
mods/*/logs/
|
||||
.gradle/
|
||||
mods/pala/.direnv/
|
||||
.direnv/
|
||||
mods/pala/.classpath
|
||||
mods/pala/.project
|
||||
|
||||
@@ -4,8 +4,8 @@ Type=Application
|
||||
Name=SurviePote
|
||||
GenericName=Minecraft Launcher
|
||||
Comment=Launcher officiel SurviePote — rejoins mcpv.ihorizon.org
|
||||
TryExec=/home/kisakay/Applications/SurviePote-setup-1.0.0.AppImage
|
||||
Exec=/home/kisakay/Applications/SurviePote-setup-1.0.0.AppImage --no-sandbox %U
|
||||
TryExec=/home/kisakay/Applications/SurviePote-setup-1.1.1.AppImage
|
||||
Exec=/home/kisakay/Applications/SurviePote-setup-1.1.1.AppImage --no-sandbox %U
|
||||
Icon=surviepote
|
||||
Terminal=false
|
||||
Categories=Game;
|
||||
|
||||
@@ -10,7 +10,12 @@
|
||||
"address": "mcpv.ihorizon.org:25565",
|
||||
"minecraftVersion": "26.3",
|
||||
"mainServer": true,
|
||||
"autoconnect": true,
|
||||
"autoconnect": false,
|
||||
"javaOptions": {
|
||||
"distribution": "TEMURIN",
|
||||
"supported": ">=25.x",
|
||||
"suggestedMajor": 25
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"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",
|
||||
"name": "AppleSkin",
|
||||
@@ -147,6 +162,26 @@
|
||||
"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",
|
||||
"name": "Better Combat",
|
||||
@@ -223,6 +258,16 @@
|
||||
"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",
|
||||
"name": "Continuity",
|
||||
@@ -259,6 +304,16 @@
|
||||
"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",
|
||||
"name": "Fabric API",
|
||||
@@ -292,6 +347,16 @@
|
||||
"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",
|
||||
"name": "GlitchCore",
|
||||
@@ -303,13 +368,13 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "com.blocklegend001:immersiveores:26.3-0.1@jar",
|
||||
"name": "ImmersiveOres",
|
||||
"id": "generated.fabricmod:ipo:7@jar",
|
||||
"name": "ImprovedPillagerOutpost",
|
||||
"type": "FabricMod",
|
||||
"artifact": {
|
||||
"size": 543252,
|
||||
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/ImmersiveOres-26.3-0.1.jar",
|
||||
"MD5": "840a11c59c29eadd909cba3ce6f569bb"
|
||||
"size": 614666,
|
||||
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/ImprovedPillagerOutpost-v7.jar",
|
||||
"MD5": "5806e860495c8a4a7294a5249b41f968"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -391,6 +456,16 @@
|
||||
"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",
|
||||
"name": "Nature's Compass",
|
||||
@@ -402,13 +477,26 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "fr.surviepote:nosingleplayer:1.1.2@jar",
|
||||
"id": "com.aizistral:nochatreports:26.3-v2.21.0@jar",
|
||||
"name": "No Chat Reports",
|
||||
"type": "FabricMod",
|
||||
"artifact": {
|
||||
"size": 239561,
|
||||
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/NoChatReports-FABRIC-26.3-v2.21.0.jar",
|
||||
"MD5": "8434b3b01bbad6e6deb5309d3e2c5800"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "fr.surviepote:nosingleplayer:1.2.0@jar",
|
||||
"name": "NoSingleplayer (SurviePote)",
|
||||
"type": "FabricMod",
|
||||
"artifact": {
|
||||
"size": 2292471,
|
||||
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/nosingleplayer-1.1.2.jar",
|
||||
"MD5": "2ba50ec99adb670cdb34fe41b0e3d7dd"
|
||||
"size": 870981,
|
||||
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/optionalon/nosingleplayer-1.2.0.jar",
|
||||
"MD5": "107e3b749568d916e556479efbbda3c6"
|
||||
},
|
||||
"required": {
|
||||
"value": false
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -461,6 +549,26 @@
|
||||
"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",
|
||||
"name": "Shogi",
|
||||
@@ -471,6 +579,16 @@
|
||||
"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",
|
||||
"name": "3d-Skin-Layers",
|
||||
@@ -517,6 +635,16 @@
|
||||
"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",
|
||||
"name": "Tree Harvester",
|
||||
@@ -527,6 +655,16 @@
|
||||
"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",
|
||||
"name": "Upgrader",
|
||||
@@ -619,6 +757,26 @@
|
||||
"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",
|
||||
"name": "settings.json",
|
||||
@@ -773,39 +931,6 @@
|
||||
"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",
|
||||
"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",
|
||||
"address": "mcpv.ihorizon.org:25565",
|
||||
"mainServer": true,
|
||||
"autoconnect": true
|
||||
"autoconnect": false,
|
||||
"javaOptions": {
|
||||
"distribution": "TEMURIN",
|
||||
"supported": ">=25.x",
|
||||
"suggestedMajor": 25
|
||||
}
|
||||
},
|
||||
"fabric": {
|
||||
"version": "0.20.0"
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
<%- include('frame') %>
|
||||
<div id="main">
|
||||
<%- include('welcome') %>
|
||||
<%- include('login') %>
|
||||
<%- include('waiting') %>
|
||||
<%- include('loginOptions') %>
|
||||
<%- include('settings') %>
|
||||
|
||||
@@ -3208,109 +3208,6 @@ input:checked + .toggleSwitchSlider:before {
|
||||
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
|
||||
* * */
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* AuthManager
|
||||
*
|
||||
* This module aims to abstract login procedures. Results from Mojang's REST api
|
||||
* are retrieved through our Mojang module. These results are processed and stored,
|
||||
* if applicable, in the config using the ConfigManager. All login procedures should
|
||||
* be made through this module.
|
||||
* This module aims to abstract login procedures. Microsoft auth results
|
||||
* are retrieved through our Microsoft module. These results are processed and
|
||||
* stored, if applicable, in the config using the ConfigManager. All login
|
||||
* procedures should be made through this module.
|
||||
*
|
||||
* @module authmanager
|
||||
*/
|
||||
@@ -12,7 +12,6 @@
|
||||
const ConfigManager = require('./configmanager')
|
||||
const { LoggerUtil } = require('helios-core')
|
||||
const { RestResponseStatus } = require('helios-core/common')
|
||||
const { MojangRestAPI, MojangErrorCode } = require('helios-core/mojang')
|
||||
const { MicrosoftAuth, MicrosoftErrorCode } = require('helios-core/microsoft')
|
||||
const { AZURE_CLIENT_ID } = require('./ipcconstants')
|
||||
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
|
||||
|
||||
/**
|
||||
* 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 }
|
||||
|
||||
/**
|
||||
@@ -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.
|
||||
*
|
||||
* @param {string} authCode The authCode obtained from microsoft.
|
||||
@@ -266,31 +151,6 @@ exports.addMicrosoftAccount = async function(authCode) {
|
||||
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
|
||||
* 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,
|
||||
* 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(){
|
||||
const current = ConfigManager.getSelectedAccount()
|
||||
|
||||
if(current.type === 'microsoft') {
|
||||
return await validateSelectedMicrosoftAccount()
|
||||
} else {
|
||||
return await validateSelectedMojangAccount()
|
||||
// Auth Mojang supprimee (depreciee) : plus de sign-in Mojang au bootstrap.
|
||||
// Seuls les comptes Microsoft sont valides ; un ancien compte Mojang est
|
||||
// considere invalide sans appel reseau (l'authserver Mojang est ferme).
|
||||
if(current.type !== 'microsoft') {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
return await validateSelectedMicrosoftAccount()
|
||||
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ const DEFAULT_CONFIG = {
|
||||
resWidth: 1280,
|
||||
resHeight: 720,
|
||||
fullscreen: false,
|
||||
autoConnect: true,
|
||||
autoConnect: false,
|
||||
launchDetached: true
|
||||
},
|
||||
launcher: {
|
||||
@@ -94,7 +94,6 @@ const DEFAULT_CONFIG = {
|
||||
content: null,
|
||||
dismissed: false
|
||||
},
|
||||
clientToken: null,
|
||||
selectedServer: null, // Resolved
|
||||
selectedAccount: null,
|
||||
authenticationDatabase: {},
|
||||
@@ -255,25 +254,6 @@ exports.getInstanceDirectory = function(){
|
||||
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.
|
||||
*
|
||||
@@ -281,7 +261,7 @@ exports.setClientToken = function(clientToken){
|
||||
* @returns {string} The ID of the selected serverpack.
|
||||
*/
|
||||
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]
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@@ -357,7 +301,7 @@ exports.addMojangAuthAccount = function(uuid, accessToken, username, displayName
|
||||
* @param {string} msAccessToken The new Microsoft Access Token
|
||||
* @param {string} msRefreshToken The new Microsoft Refresh Token
|
||||
* @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.
|
||||
*/
|
||||
@@ -376,7 +320,7 @@ exports.updateMicrosoftAuthAccount = function(uuid, accessToken, msAccessToken,
|
||||
* @param {string} uuid The uuid 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 {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} msRefreshToken The microsoft refresh token
|
||||
* @param {date} msExpires The date when the microsoft access token expires
|
||||
@@ -419,7 +363,6 @@ exports.removeAuthAccount = function(uuid){
|
||||
config.selectedAccount = keys[0]
|
||||
} else {
|
||||
config.selectedAccount = null
|
||||
config.clientToken = null
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -185,6 +185,10 @@ class ProcessBuilder {
|
||||
const child = child_process.spawn(ConfigManager.getJavaExecutable(this.server.rawServer.id), args, {
|
||||
cwd: this.gameDir,
|
||||
detached: ConfigManager.getLaunchDetached(),
|
||||
// Windows : sans ça, java.exe ouvre sa propre console (detached => nouvelle
|
||||
// console) : une fenêtre noire, vide (stdout est pipe vers le launcher),
|
||||
// qui traîne par-dessus la fenêtre du jeu pendant toute la session.
|
||||
windowsHide: true,
|
||||
env: gameEnv
|
||||
})
|
||||
|
||||
@@ -431,20 +435,6 @@ class ProcessBuilder {
|
||||
|
||||
}
|
||||
|
||||
_processAutoConnectArg(args){
|
||||
if(ConfigManager.getAutoConnect() && this.server.rawServer.autoconnect){
|
||||
if(mcVersionAtLeast('1.20', this.server.rawServer.minecraftVersion)){
|
||||
args.push('--quickPlayMultiplayer')
|
||||
args.push(`${this.server.hostname}:${this.server.port}`)
|
||||
} else {
|
||||
args.push('--server')
|
||||
args.push(this.server.hostname)
|
||||
args.push('--port')
|
||||
args.push(this.server.port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the argument array that will be passed to the JVM process.
|
||||
*
|
||||
@@ -616,7 +606,7 @@ class ProcessBuilder {
|
||||
val = this.authUser.accessToken
|
||||
break
|
||||
case 'user_type':
|
||||
val = this.authUser.type === 'microsoft' ? 'msa' : 'mojang'
|
||||
val = 'msa'
|
||||
break
|
||||
case 'version_type':
|
||||
val = this.vanillaManifest.type
|
||||
@@ -647,10 +637,6 @@ class ProcessBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// Autoconnect
|
||||
this._processAutoConnectArg(args)
|
||||
|
||||
|
||||
// Forge Specific Arguments
|
||||
args = args.concat(this.modManifest.arguments.game)
|
||||
|
||||
@@ -700,7 +686,7 @@ class ProcessBuilder {
|
||||
val = this.authUser.accessToken
|
||||
break
|
||||
case 'user_type':
|
||||
val = this.authUser.type === 'microsoft' ? 'msa' : 'mojang'
|
||||
val = 'msa'
|
||||
break
|
||||
case 'user_properties': // 1.8.9 and below.
|
||||
val = '{}'
|
||||
@@ -715,9 +701,6 @@ class ProcessBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// Autoconnect to the selected server.
|
||||
this._processAutoConnectArg(mcArgs)
|
||||
|
||||
// Prepare game resolution
|
||||
if(ConfigManager.getFullscreen()){
|
||||
mcArgs.push('--fullscreen')
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
// Requirements
|
||||
const { URL } = require('url')
|
||||
const {
|
||||
MojangRestAPI,
|
||||
getServerStatus
|
||||
} = require('helios-core/mojang')
|
||||
const {
|
||||
@@ -176,65 +175,6 @@ server_selection_button.onclick = async e => {
|
||||
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)};">•</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) => {
|
||||
loggerLanding.info('Refreshing Server Status')
|
||||
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.
|
||||
|
||||
// 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.
|
||||
let serverStatusListener = setInterval(() => refreshServerStatus(true), 300000)
|
||||
|
||||
|
||||
@@ -1,234 +0,0 @@
|
||||
/**
|
||||
* Script for login.ejs
|
||||
*/
|
||||
// Validation Regexes.
|
||||
const validUsername = /^[a-zA-Z0-9_]{1,16}$/
|
||||
const basicEmail = /^\S+@\S+\.\S+$/
|
||||
//const validEmail = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i
|
||||
|
||||
// Login Elements
|
||||
const loginCancelContainer = document.getElementById('loginCancelContainer')
|
||||
const loginCancelButton = document.getElementById('loginCancelButton')
|
||||
const loginEmailError = document.getElementById('loginEmailError')
|
||||
const loginUsername = document.getElementById('loginUsername')
|
||||
const loginPasswordError = document.getElementById('loginPasswordError')
|
||||
const loginPassword = document.getElementById('loginPassword')
|
||||
const checkmarkContainer = document.getElementById('checkmarkContainer')
|
||||
const loginRememberOption = document.getElementById('loginRememberOption')
|
||||
const loginButton = document.getElementById('loginButton')
|
||||
const loginForm = document.getElementById('loginForm')
|
||||
|
||||
// Control variables.
|
||||
let lu = false, lp = false
|
||||
|
||||
|
||||
/**
|
||||
* Show a login error.
|
||||
*
|
||||
* @param {HTMLElement} element The element on which to display the error.
|
||||
* @param {string} value The error text.
|
||||
*/
|
||||
function showError(element, value){
|
||||
element.innerHTML = value
|
||||
element.style.opacity = 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Shake a login error to add emphasis.
|
||||
*
|
||||
* @param {HTMLElement} element The element to shake.
|
||||
*/
|
||||
function shakeError(element){
|
||||
if(element.style.opacity == 1){
|
||||
element.classList.remove('shake')
|
||||
void element.offsetWidth
|
||||
element.classList.add('shake')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that an email field is neither empty nor invalid.
|
||||
*
|
||||
* @param {string} value The email value.
|
||||
*/
|
||||
function validateEmail(value){
|
||||
if(value){
|
||||
if(!basicEmail.test(value) && !validUsername.test(value)){
|
||||
showError(loginEmailError, Lang.queryJS('login.error.invalidValue'))
|
||||
loginDisabled(true)
|
||||
lu = false
|
||||
} else {
|
||||
loginEmailError.style.opacity = 0
|
||||
lu = true
|
||||
if(lp){
|
||||
loginDisabled(false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lu = false
|
||||
showError(loginEmailError, Lang.queryJS('login.error.requiredValue'))
|
||||
loginDisabled(true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the password field is not empty.
|
||||
*
|
||||
* @param {string} value The password value.
|
||||
*/
|
||||
function validatePassword(value){
|
||||
if(value){
|
||||
loginPasswordError.style.opacity = 0
|
||||
lp = true
|
||||
if(lu){
|
||||
loginDisabled(false)
|
||||
}
|
||||
} else {
|
||||
lp = false
|
||||
showError(loginPasswordError, Lang.queryJS('login.error.invalidValue'))
|
||||
loginDisabled(true)
|
||||
}
|
||||
}
|
||||
|
||||
// Emphasize errors with shake when focus is lost.
|
||||
loginUsername.addEventListener('focusout', (e) => {
|
||||
validateEmail(e.target.value)
|
||||
shakeError(loginEmailError)
|
||||
})
|
||||
loginPassword.addEventListener('focusout', (e) => {
|
||||
validatePassword(e.target.value)
|
||||
shakeError(loginPasswordError)
|
||||
})
|
||||
|
||||
// Validate input for each field.
|
||||
loginUsername.addEventListener('input', (e) => {
|
||||
validateEmail(e.target.value)
|
||||
})
|
||||
loginPassword.addEventListener('input', (e) => {
|
||||
validatePassword(e.target.value)
|
||||
})
|
||||
|
||||
/**
|
||||
* Enable or disable the login button.
|
||||
*
|
||||
* @param {boolean} v True to enable, false to disable.
|
||||
*/
|
||||
function loginDisabled(v){
|
||||
if(loginButton.disabled !== v){
|
||||
loginButton.disabled = v
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable loading elements.
|
||||
*
|
||||
* @param {boolean} v True to enable, false to disable.
|
||||
*/
|
||||
function loginLoading(v){
|
||||
if(v){
|
||||
loginButton.setAttribute('loading', v)
|
||||
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.login'), Lang.queryJS('login.loggingIn'))
|
||||
} else {
|
||||
loginButton.removeAttribute('loading')
|
||||
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.loggingIn'), Lang.queryJS('login.login'))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable login form.
|
||||
*
|
||||
* @param {boolean} v True to enable, false to disable.
|
||||
*/
|
||||
function formDisabled(v){
|
||||
loginDisabled(v)
|
||||
loginCancelButton.disabled = v
|
||||
loginUsername.disabled = v
|
||||
loginPassword.disabled = v
|
||||
if(v){
|
||||
checkmarkContainer.setAttribute('disabled', v)
|
||||
} else {
|
||||
checkmarkContainer.removeAttribute('disabled')
|
||||
}
|
||||
loginRememberOption.disabled = v
|
||||
}
|
||||
|
||||
let loginViewOnSuccess = VIEWS.landing
|
||||
let loginViewOnCancel = VIEWS.settings
|
||||
let loginViewCancelHandler
|
||||
|
||||
function loginCancelEnabled(val){
|
||||
if(val){
|
||||
$(loginCancelContainer).show()
|
||||
} else {
|
||||
$(loginCancelContainer).hide()
|
||||
}
|
||||
}
|
||||
|
||||
loginCancelButton.onclick = (e) => {
|
||||
switchView(getCurrentView(), loginViewOnCancel, 500, 500, () => {
|
||||
loginUsername.value = ''
|
||||
loginPassword.value = ''
|
||||
loginCancelEnabled(false)
|
||||
if(loginViewCancelHandler != null){
|
||||
loginViewCancelHandler()
|
||||
loginViewCancelHandler = null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Disable default form behavior.
|
||||
loginForm.onsubmit = () => { return false }
|
||||
|
||||
// Bind login button behavior.
|
||||
loginButton.addEventListener('click', () => {
|
||||
// Disable form.
|
||||
formDisabled(true)
|
||||
|
||||
// Show loading stuff.
|
||||
loginLoading(true)
|
||||
|
||||
AuthManager.addMojangAccount(loginUsername.value, loginPassword.value).then((value) => {
|
||||
updateSelectedAccount(value)
|
||||
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.loggingIn'), Lang.queryJS('login.success'))
|
||||
$('.circle-loader').toggleClass('load-complete')
|
||||
$('.checkmark').toggle()
|
||||
setTimeout(() => {
|
||||
switchView(VIEWS.login, loginViewOnSuccess, 500, 500, async () => {
|
||||
// Temporary workaround
|
||||
if(loginViewOnSuccess === VIEWS.settings){
|
||||
await prepareSettings()
|
||||
}
|
||||
loginViewOnSuccess = VIEWS.landing // Reset this for good measure.
|
||||
loginCancelEnabled(false) // Reset this for good measure.
|
||||
loginViewCancelHandler = null // Reset this for good measure.
|
||||
loginUsername.value = ''
|
||||
loginPassword.value = ''
|
||||
$('.circle-loader').toggleClass('load-complete')
|
||||
$('.checkmark').toggle()
|
||||
loginLoading(false)
|
||||
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.success'), Lang.queryJS('login.login'))
|
||||
formDisabled(false)
|
||||
})
|
||||
}, 1000)
|
||||
}).catch((displayableError) => {
|
||||
loginLoading(false)
|
||||
|
||||
let actualDisplayableError
|
||||
if(isDisplayableError(displayableError)) {
|
||||
msftLoginLogger.error('Error while logging in.', displayableError)
|
||||
actualDisplayableError = displayableError
|
||||
} else {
|
||||
// Uh oh.
|
||||
msftLoginLogger.error('Unhandled error during login.', displayableError)
|
||||
actualDisplayableError = Lang.queryJS('login.error.unknown')
|
||||
}
|
||||
|
||||
setOverlayContent(actualDisplayableError.title, actualDisplayableError.desc, Lang.queryJS('login.tryAgain'))
|
||||
setOverlayHandler(() => {
|
||||
formDisabled(false)
|
||||
toggleOverlay(false)
|
||||
})
|
||||
toggleOverlay(true)
|
||||
})
|
||||
|
||||
})
|
||||
@@ -502,32 +502,10 @@ let msAccDomElementCache
|
||||
function processLogOut(val, isLastAccount){
|
||||
const parent = val.closest('.settingsAuthAccount')
|
||||
const uuid = parent.getAttribute('uuid')
|
||||
const prevSelAcc = ConfigManager.getSelectedAccount()
|
||||
const targetAcc = ConfigManager.getAuthAccount(uuid)
|
||||
if(targetAcc.type === 'microsoft') {
|
||||
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()
|
||||
})
|
||||
}
|
||||
msAccDomElementCache = parent
|
||||
switchView(getCurrentView(), VIEWS.waiting, 500, 500, () => {
|
||||
ipcRenderer.send(MSFT_OPCODE.OPEN_LOGOUT, uuid, isLastAccount)
|
||||
})
|
||||
}
|
||||
|
||||
// Bind reply for Microsoft Logout.
|
||||
|
||||
@@ -17,7 +17,6 @@ let fatalStartupError = false
|
||||
const VIEWS = {
|
||||
landing: '#landingContainer',
|
||||
loginOptions: '#loginOptionsContainer',
|
||||
login: '#loginContainer',
|
||||
settings: '#settingsContainer',
|
||||
welcome: '#welcomeContainer',
|
||||
waiting: '#waitingContainer'
|
||||
@@ -336,36 +335,21 @@ async function validateSelectedAccount(){
|
||||
)
|
||||
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()
|
||||
loginOptionsViewOnLoginCancel = VIEWS.loginOptions
|
||||
|
||||
if(accLen > 0) {
|
||||
loginOptionsViewOnCancel = getCurrentView()
|
||||
loginOptionsViewCancelHandler = () => {
|
||||
if(isMicrosoft) {
|
||||
ConfigManager.addMicrosoftAuthAccount(
|
||||
selectedAcc.uuid,
|
||||
selectedAcc.accessToken,
|
||||
selectedAcc.username,
|
||||
selectedAcc.expiresAt,
|
||||
selectedAcc.microsoft.access_token,
|
||||
selectedAcc.microsoft.refresh_token,
|
||||
selectedAcc.microsoft.expires_at
|
||||
)
|
||||
} else {
|
||||
ConfigManager.addMojangAuthAccount(selectedAcc.uuid, selectedAcc.accessToken, selectedAcc.username, selectedAcc.displayName)
|
||||
}
|
||||
ConfigManager.addMicrosoftAuthAccount(
|
||||
selectedAcc.uuid,
|
||||
selectedAcc.accessToken,
|
||||
selectedAcc.username,
|
||||
selectedAcc.expiresAt,
|
||||
selectedAcc.microsoft.access_token,
|
||||
selectedAcc.microsoft.refresh_token,
|
||||
selectedAcc.microsoft.expires_at
|
||||
)
|
||||
ConfigManager.save()
|
||||
validateSelectedAccount()
|
||||
}
|
||||
|
||||
@@ -39,25 +39,6 @@
|
||||
<span class="bot_label" id="landingPlayerLabel"><%- lang('landing.serverStatus') %></span>
|
||||
<span id="player_count"><%- lang('landing.serverStatusPlaceholder') %></span>
|
||||
</div>
|
||||
<div class="bot_divider"></div>
|
||||
<div id="mojangStatusWrapper">
|
||||
<span class="bot_label"><%- lang('landing.mojangStatus') %></span>
|
||||
<span id="mojang_status_icon">•</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>
|
||||
|
||||
@@ -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>
|
||||
@@ -73,17 +73,6 @@
|
||||
</label>
|
||||
</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="settingsFieldLeft">
|
||||
<span class="settingsFieldTitle"><%- lang('settings.launchDetachedTitle') %></span>
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
{
|
||||
"name": "surviepote-launcher",
|
||||
"version": "1.0.0",
|
||||
"productName": "SurviePote",
|
||||
"description": "Launcher officiel SurviePote - Rejoins mcpv.ihorizon.org",
|
||||
"author": "SurviePote",
|
||||
"license": "MIT",
|
||||
"homepage": "https://git.kisakay.com/k/mc",
|
||||
"bugs": {
|
||||
"url": "https://github.com/dscalzi/HeliosLauncher/issues"
|
||||
},
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"dist": "electron-builder build",
|
||||
"dist:win": "npm run dist -- -w",
|
||||
"dist:mac": "npm run dist -- -m",
|
||||
"dist:linux": "npm run dist -- -l",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"engines": {
|
||||
"node": "22.x.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron/remote": "^2.1.3",
|
||||
"adm-zip": "^0.5.16",
|
||||
"ejs": "^3.1.10",
|
||||
"ejs-electron": "^3.0.0",
|
||||
"electron-updater": "^6.7.3",
|
||||
"fs-extra": "^11.3.3",
|
||||
"github-syntax-dark": "^0.5.0",
|
||||
"got": "^11.8.5",
|
||||
"helios-core": "~2.3.0",
|
||||
"helios-distribution-types": "^1.3.0",
|
||||
"jquery": "^3.7.1",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"semver": "^7.7.3",
|
||||
"toml": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@stylistic/eslint-plugin": "^5.6.1",
|
||||
"electron": "^39.2.7",
|
||||
"electron-builder": "^26.4.0",
|
||||
"eslint": "^9.39.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://git.kisakay.com/k/mc.git"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "surviepote-launcher",
|
||||
"version": "1.1.1",
|
||||
"productName": "SurviePote",
|
||||
"description": "Launcher officiel SurviePote - Rejoins mcpv.ihorizon.org",
|
||||
"author": "SurviePote",
|
||||
"license": "MIT",
|
||||
"homepage": "https://git.kisakay.com/k/mc",
|
||||
"bugs": {
|
||||
"url": "https://github.com/dscalzi/HeliosLauncher/issues"
|
||||
},
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"dist": "electron-builder build",
|
||||
"dist:win": "npm run dist -- -w",
|
||||
"dist:mac": "npm run dist -- -m",
|
||||
"dist:linux": "npm run dist -- -l",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"engines": {
|
||||
"node": "22.x.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron/remote": "^2.1.3",
|
||||
"adm-zip": "^0.5.16",
|
||||
"ejs": "^3.1.10",
|
||||
"ejs-electron": "^3.0.0",
|
||||
"electron-updater": "^6.7.3",
|
||||
"fs-extra": "^11.3.3",
|
||||
"github-syntax-dark": "^0.5.0",
|
||||
"got": "^11.8.5",
|
||||
"helios-core": "~2.3.0",
|
||||
"helios-distribution-types": "^1.3.0",
|
||||
"jquery": "^3.7.1",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"semver": "^7.7.3",
|
||||
"toml": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@stylistic/eslint-plugin": "^5.6.1",
|
||||
"electron": "^39.2.7",
|
||||
"electron-builder": "^26.4.0",
|
||||
"eslint": "^9.39.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://git.kisakay.com/k/mc.git"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
# 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`.
|
||||
- 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
|
||||
@@ -8,4 +13,27 @@ Désactive le bouton **Solo** du menu principal (Mixin sur `TitleScreen.init`).
|
||||
pas de refmap nécessaire.
|
||||
|
||||
Build : `cd mods/nosingleplayer && ./gradlew build` (dans nix-shell racine pour Java 25).
|
||||
Jar : `build/libs/nosingleplayer-1.0.0.jar` → copié dans `atlas/.../fabricmods/required/`.
|
||||
Jar : `build/libs/nosingleplayer-1.2.0.jar` → copié dans `atlas/.../fabricmods/optionalon/`.
|
||||
|
||||
## Textures
|
||||
|
||||
MC 26.3 n'expose pas les ressources des mods au ResourceManager (vérifié : le panorama
|
||||
vanilla affiche le stock Mojang, pas les `panorama_*.png` du mod). Les PNG sont donc
|
||||
lus **dans le jar** et enregistrés via le mécanisme officiel de MC
|
||||
(`ReloadableTexture` + `TextureContents`) : c'est MC qui crée la texture GPU et le
|
||||
sampler, exactement comme pour ses propres textures.
|
||||
|
||||
⚠️ La texture GPU doit être créée **hors de l'extraction du rendu** (ici dans
|
||||
`TitleScreen.init`) : la créer pendant l'enregistrement des commandes d'une frame
|
||||
invalide le frame entier sur les backends à command encoder (Vulkan) → menu titre
|
||||
**entièrement noir** (constaté sur Windows + NVIDIA).
|
||||
|
||||
## Diagnostic
|
||||
|
||||
- `-Dsurviepote.customTitle=false` (options JVM du launcher) : repasser au menu **vanilla**
|
||||
(panorama vanilla au lieu du fond custom). Sert à isoler un souci de rendu du fond custom.
|
||||
- Le mod log au démarrage du menu : `[SurviePote] Texture enregistrée : … (1920x1200)` et
|
||||
`[SurviePote] GPU: <nom> | <vendor> | pilote <driver> | backend <OpenGL|Vulkan>`.
|
||||
- Si le fond custom échoue (texture refusée par le GPU, jar corrompu…), le mod se repli
|
||||
automatiquement sur le panorama vanilla au lieu de laisser un écran noir.
|
||||
|
||||
|
||||
@@ -42,6 +42,9 @@ tasks.register('downloadMcLibs') {
|
||||
new File(outDir, 'slf4j-api.jar'))
|
||||
dl('https://libraries.minecraft.net/com/mojang/brigadier/1.3.11/brigadier-1.3.11.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 {
|
||||
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'
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ loader_version=0.20.0
|
||||
loom_version=1.19.1
|
||||
|
||||
# Mod Properties
|
||||
mod_version=1.1.2
|
||||
mod_version=1.2.0
|
||||
maven_group=fr.surviepote
|
||||
archives_base_name=nosingleplayer
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package fr.surviepote.nosingleplayer;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.TextureContents;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
/**
|
||||
* Enregistrement des textures du mod dans le TextureManager de MC.
|
||||
*
|
||||
* <p>IMPORTANT : l'enregistrement doit être fait sur le thread de rendu mais
|
||||
* <b>en dehors</b> de l'extraction du rendu (ex. dans {@code TitleScreen.init}).
|
||||
* Créer une ressource GPU pendant l'enregistrement des commandes d'une frame
|
||||
* invalide le frame entier sur les backends à command encoder (Vulkan/NVIDIA) :
|
||||
* résultat, un menu titre entièrement noir. D'où le try/catch + le repli sur le
|
||||
* panorama vanilla dans TitleScreenRenderMixin.
|
||||
*/
|
||||
public final class ModTextures {
|
||||
|
||||
private static final Map<Identifier, SurviePoteTexture> CACHE = new HashMap<>();
|
||||
|
||||
private ModTextures() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre une texture lue dans le jar du mod.
|
||||
*
|
||||
* @param id identifiant sous lequel la texture est enregistrée
|
||||
* @param resourcePath chemin dans le jar, ex {@code /assets/nosingleplayer/...}
|
||||
* @param blur true = filtrage linéaire, false = nearest (pixel art)
|
||||
* @return true si la texture est utilisable
|
||||
*/
|
||||
public static boolean register(Identifier id, String resourcePath, boolean blur) {
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
SurviePoteTexture cached = CACHE.get(id);
|
||||
if (cached != null && minecraft.getTextureManager().getTexture(id) == cached) {
|
||||
return true; // déjà enregistrée et toujours en place
|
||||
}
|
||||
try {
|
||||
SurviePoteTexture texture = new SurviePoteTexture(id, resourcePath, blur);
|
||||
TextureContents contents = texture.loadContents(minecraft.getResourceManager());
|
||||
texture.apply(contents);
|
||||
minecraft.getTextureManager().register(id, texture);
|
||||
CACHE.put(id, texture);
|
||||
NoSingleplayer.LOGGER.info("[SurviePote] Texture enregistrée : {} ({}x{})",
|
||||
id, texture.getWidth(), texture.getHeight());
|
||||
return true;
|
||||
} catch (Throwable t) {
|
||||
NoSingleplayer.LOGGER.error("[SurviePote] Échec d'enregistrement de la texture " + resourcePath, t);
|
||||
CACHE.remove(id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** True si la texture est enregistrée ET encore présente dans le TextureManager. */
|
||||
public static boolean isRegistered(Identifier id) {
|
||||
SurviePoteTexture cached = CACHE.get(id);
|
||||
return cached != null
|
||||
&& Minecraft.getInstance().getTextureManager().getTexture(id) == cached;
|
||||
}
|
||||
|
||||
/** Largeur de la texture enregistrée (0 si inconnue). */
|
||||
public static int getWidth(Identifier id) {
|
||||
SurviePoteTexture cached = CACHE.get(id);
|
||||
return cached != null ? cached.getWidth() : 0;
|
||||
}
|
||||
|
||||
/** Hauteur de la texture enregistrée (0 si inconnue). */
|
||||
public static int getHeight(Identifier id) {
|
||||
SurviePoteTexture cached = CACHE.get(id);
|
||||
return cached != null ? cached.getHeight() : 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package fr.surviepote.nosingleplayer;
|
||||
|
||||
import com.mojang.blaze3d.platform.NativeImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import net.minecraft.client.renderer.texture.MipmapStrategy;
|
||||
import net.minecraft.client.renderer.texture.ReloadableTexture;
|
||||
import net.minecraft.client.renderer.texture.TextureContents;
|
||||
import net.minecraft.client.resources.metadata.texture.TextureMetadataSection;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
|
||||
/**
|
||||
* Texture du mod (PNG lu dans le jar) gérée exactement comme une texture vanilla.
|
||||
*
|
||||
* <p>MC 26.3 n'expose plus les ressources des mods au ResourceManager : les packs ne
|
||||
* sont lus que depuis des dossiers, pas depuis les jars du classpath. Une texture
|
||||
* référencée par son Identifier est donc introuvable (placeholder magenta). On lit
|
||||
* donc le PNG directement dans le jar du mod et on l'enregistre via le mécanisme
|
||||
* officiel {@link ReloadableTexture} : c'est MC qui crée la texture GPU ET le
|
||||
* sampler, exactement comme pour ses propres textures. Aucun sampler custom, aucun
|
||||
* upload manuel => compatible avec tous les backends (OpenGL, Vulkan) et tous les
|
||||
* pilotes (Mesa, NVIDIA, AMD, Intel).
|
||||
*/
|
||||
public class SurviePoteTexture extends ReloadableTexture {
|
||||
|
||||
private final String resourcePath;
|
||||
private final boolean blur;
|
||||
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
public SurviePoteTexture(Identifier id, String resourcePath, boolean blur) {
|
||||
super(id);
|
||||
this.resourcePath = resourcePath;
|
||||
this.blur = blur;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextureContents loadContents(ResourceManager resourceManager) throws IOException {
|
||||
try (InputStream in = SurviePoteTexture.class.getResourceAsStream(this.resourcePath)) {
|
||||
if (in == null) {
|
||||
throw new IOException("Texture introuvable dans le jar : " + this.resourcePath);
|
||||
}
|
||||
NativeImage image = NativeImage.read(in);
|
||||
this.width = image.getWidth();
|
||||
this.height = image.getHeight();
|
||||
// blur=true => filtrage linéaire (image agrandie/réduite),
|
||||
// clamp=true => pas de répétition sur les bords.
|
||||
return new TextureContents(image,
|
||||
new TextureMetadataSection(this.blur, true, MipmapStrategy.AUTO, 0.0f));
|
||||
}
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return this.height;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package fr.surviepote.nosingleplayer.gui;
|
||||
|
||||
import fr.surviepote.nosingleplayer.ModTextures;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.narration.NarrationElementOutput;
|
||||
import net.minecraft.client.gui.components.AbstractButton;
|
||||
import net.minecraft.client.input.InputWithModifiers;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
/**
|
||||
* Bouton "Join Discord" : fond vanilla, logo Discord en pixel art et libellé.
|
||||
* Ouvre l'invite Discord au clic.
|
||||
*/
|
||||
public class DiscordButton extends AbstractButton {
|
||||
|
||||
public static final Identifier TEXTURE = Identifier.fromNamespaceAndPath(
|
||||
"nosingleplayer", "textures/gui/discord.png");
|
||||
|
||||
private static final String TEXTURE_PATH = "/assets/nosingleplayer/textures/gui/discord.png";
|
||||
|
||||
private static final int ICON_SIZE = 16;
|
||||
private static final int ICON_GAP = 5;
|
||||
private static final int TEXT_COLOR = 0xFFFFFFFF;
|
||||
private static final int TEXT_HEIGHT = 9;
|
||||
|
||||
private final Runnable action;
|
||||
|
||||
public DiscordButton(int x, int y, int width, int height, String label, Runnable action) {
|
||||
super(x, y, width, height, Component.literal(label));
|
||||
this.action = action;
|
||||
// La texture doit être enregistrée avant le premier rendu.
|
||||
ModTextures.register(TEXTURE, TEXTURE_PATH, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void extractContents(GuiGraphicsExtractor extractor, int mouseX, int mouseY, float partialTick) {
|
||||
extractDefaultSprite(extractor); // fond standard (survol/clic inclus)
|
||||
|
||||
Font font = Minecraft.getInstance().font;
|
||||
String label = getMessage().getString();
|
||||
int textWidth = font.width(label);
|
||||
int groupWidth = ICON_SIZE + ICON_GAP + textWidth;
|
||||
int startX = getX() + (getWidth() - groupWidth) / 2;
|
||||
int iconY = getY() + (getHeight() - ICON_SIZE) / 2;
|
||||
int textY = getY() + (getHeight() - TEXT_HEIGHT) / 2;
|
||||
|
||||
extractor.blit(RenderPipelines.GUI_TEXTURED, TEXTURE,
|
||||
startX, iconY, 0.0F, 0.0F, ICON_SIZE, ICON_SIZE, ICON_SIZE, ICON_SIZE, -1);
|
||||
extractor.text(font, label, startX + ICON_SIZE + ICON_GAP, textY, TEXT_COLOR, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPress(InputWithModifiers input) {
|
||||
this.action.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateWidgetNarration(NarrationElementOutput output) {
|
||||
defaultButtonNarrationText(output);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package fr.surviepote.nosingleplayer.gui;
|
||||
|
||||
import fr.surviepote.nosingleplayer.ModTextures;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.narration.NarrationElementOutput;
|
||||
import net.minecraft.client.gui.components.AbstractButton;
|
||||
import net.minecraft.client.input.InputWithModifiers;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
/**
|
||||
* Bouton icône vanilla (fond standard) avec une texture du mod, sans texte.
|
||||
*/
|
||||
public class IconButton extends AbstractButton {
|
||||
|
||||
public static final Identifier OPTIONS_TEXTURE = Identifier.fromNamespaceAndPath(
|
||||
"nosingleplayer", "textures/gui/options.png");
|
||||
public static final Identifier QUIT_TEXTURE = Identifier.fromNamespaceAndPath(
|
||||
"nosingleplayer", "textures/gui/quit.png");
|
||||
|
||||
private static final int ICON_SIZE = 16;
|
||||
|
||||
private final Identifier texture;
|
||||
private final Runnable action;
|
||||
|
||||
public IconButton(int x, int y, int width, int height, Component message,
|
||||
Identifier texture, String resourcePath, Runnable action) {
|
||||
super(x, y, width, height, message);
|
||||
this.texture = texture;
|
||||
this.action = action;
|
||||
// La texture doit être enregistrée avant le premier rendu.
|
||||
ModTextures.register(texture, resourcePath, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void extractContents(GuiGraphicsExtractor extractor, int mouseX, int mouseY, float partialTick) {
|
||||
extractDefaultSprite(extractor); // fond standard (survol/clic inclus)
|
||||
int iconX = getX() + (getWidth() - ICON_SIZE) / 2;
|
||||
int iconY = getY() + (getHeight() - ICON_SIZE) / 2;
|
||||
extractor.blit(RenderPipelines.GUI_TEXTURED, this.texture,
|
||||
iconX, iconY, 0.0F, 0.0F, ICON_SIZE, ICON_SIZE, ICON_SIZE, ICON_SIZE, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPress(InputWithModifiers input) {
|
||||
this.action.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateWidgetNarration(NarrationElementOutput output) {
|
||||
defaultButtonNarrationText(output);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package fr.surviepote.nosingleplayer.gui;
|
||||
|
||||
import fr.surviepote.nosingleplayer.NoSingleplayer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.client.gui.narration.NarrationElementOutput;
|
||||
import net.minecraft.client.multiplayer.ServerData;
|
||||
import net.minecraft.client.multiplayer.ServerStatusPinger;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.network.EventLoopGroupHolder;
|
||||
|
||||
/**
|
||||
* Panneau "statut du serveur" affiché en haut à gauche du menu principal :
|
||||
* état en ligne / hors ligne, nombre de joueurs et ping.
|
||||
*
|
||||
* <p>Note : dans ServerStatusPinger.pingServer, les deux Runnable passés ne sont pas
|
||||
* des callbacks de succès/échec (le 1er ne fire que si le favicon change, le 2nd est
|
||||
* le pong). On lit donc directement l'état dans ServerData : `status` contient le
|
||||
* compteur de joueurs ("0/20") quand le statut est arrivé, et est vidé en cas d'échec.
|
||||
*/
|
||||
public class ServerStatusWidget extends AbstractWidget {
|
||||
|
||||
private static final String HOST = "mcpv.ihorizon.org:25565";
|
||||
private static final long REFRESH_MS = 15000L;
|
||||
private static final long TIMEOUT_MS = 8000L;
|
||||
|
||||
/** Thread dédié : le ping ne doit jamais bloquer le thread de rendu. */
|
||||
private static final java.util.concurrent.ExecutorService PING_EXECUTOR =
|
||||
java.util.concurrent.Executors.newSingleThreadExecutor(r -> {
|
||||
Thread t = new Thread(r, "SurviePote-ServerStatus");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
|
||||
private static final int BG_COLOR = 0xB0000000;
|
||||
private static final int ONLINE_COLOR = 0xFF4CD964;
|
||||
private static final int OFFLINE_COLOR = 0xFFE04B4B;
|
||||
private static final int PENDING_COLOR = 0xFFAAAAAA;
|
||||
private static final int TEXT_COLOR = 0xFFFFFFFF;
|
||||
|
||||
private final ServerStatusPinger pinger = new ServerStatusPinger();
|
||||
private final ServerData data = new ServerData("SurviePote", HOST, ServerData.Type.OTHER);
|
||||
|
||||
private volatile boolean resolved;
|
||||
private volatile boolean online;
|
||||
private volatile String playersText = "";
|
||||
private volatile long ping = -1L;
|
||||
|
||||
private long nextRefresh;
|
||||
private long pingStartedAt;
|
||||
private boolean pending;
|
||||
|
||||
public ServerStatusWidget(int x, int y, int width, int height) {
|
||||
super(x, y, width, height, Component.literal("Statut du serveur SurviePote"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void extractWidgetRenderState(GuiGraphicsExtractor extractor, int mouseX, int mouseY, float partialTick) {
|
||||
long now = System.currentTimeMillis();
|
||||
if (now >= this.nextRefresh) {
|
||||
this.nextRefresh = now + REFRESH_MS;
|
||||
refresh();
|
||||
}
|
||||
if (this.pending && now - this.pingStartedAt > TIMEOUT_MS) {
|
||||
this.pending = false; // aucune réponse du serveur
|
||||
}
|
||||
this.pinger.tick();
|
||||
readStatus();
|
||||
|
||||
Font font = Minecraft.getInstance().font;
|
||||
boolean online = this.online;
|
||||
boolean resolved = this.resolved;
|
||||
|
||||
String statusText = !resolved ? "Connexion..." : (online ? "En ligne" : "Hors ligne");
|
||||
int statusColor = !resolved ? PENDING_COLOR : (online ? ONLINE_COLOR : OFFLINE_COLOR);
|
||||
String playersText = online
|
||||
? (this.playersText.matches("\\d+/\\d+") ? this.playersText + " joueurs" : this.playersText)
|
||||
: (resolved ? "Joueurs : -" : "");
|
||||
String pingText = online && this.ping > 0 ? this.ping + " ms" : "";
|
||||
|
||||
// Fond du panneau.
|
||||
extractor.fill(getX(), getY(), getX() + getWidth(), getY() + getHeight(), BG_COLOR);
|
||||
// Pastille d'état.
|
||||
extractor.fill(getX() + 4, getY() + 4, getX() + 9, getY() + 9, statusColor);
|
||||
// Textes.
|
||||
extractor.text(font, statusText, getX() + 12, getY() + 3, statusColor, true);
|
||||
if (!playersText.isEmpty()) {
|
||||
extractor.text(font, playersText, getX() + 12, getY() + 13, TEXT_COLOR, true);
|
||||
}
|
||||
if (!pingText.isEmpty()) {
|
||||
extractor.text(font, pingText,
|
||||
getX() + getWidth() - font.width(pingText) - 4, getY() + 3, PENDING_COLOR, true);
|
||||
}
|
||||
}
|
||||
|
||||
/** Lit l'état réel du ping dans ServerData (mis à jour par le thread réseau). */
|
||||
private void readStatus() {
|
||||
Component status = this.data.status;
|
||||
String text = status != null ? status.getString() : "";
|
||||
if (!text.isEmpty()) {
|
||||
this.online = true;
|
||||
this.resolved = true;
|
||||
this.playersText = text; // "0/20"
|
||||
} else if (this.resolved) {
|
||||
this.online = false; // le status a été vidé => échec du ping
|
||||
} else if (!this.pending) {
|
||||
this.resolved = true; // timeout : serveur injoignable
|
||||
}
|
||||
if (this.data.ping > 0) {
|
||||
this.ping = this.data.ping;
|
||||
}
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
// ATTENTION : pingServer() fait une résolution DNS + ouvre une connexion.
|
||||
// Appelé depuis extractWidgetRenderState (thread de rendu), ça BLOQUE le
|
||||
// rendu du menu (écran figé/noir) le temps du timeout DNS. On le lance
|
||||
// donc sur un thread dédié ; l'état lu par le rendu est "volatile".
|
||||
try {
|
||||
this.pingStartedAt = System.currentTimeMillis();
|
||||
this.pending = true;
|
||||
PING_EXECUTOR.execute(() -> {
|
||||
try {
|
||||
this.pinger.pingServer(this.data, () -> {
|
||||
}, () -> {
|
||||
}, EventLoopGroupHolder.remote(Minecraft.getInstance().options.useNativeTransport()));
|
||||
} catch (Exception e) {
|
||||
NoSingleplayer.LOGGER.warn("[SurviePote] Ping serveur impossible : {}", e.toString());
|
||||
this.pending = false;
|
||||
this.resolved = true;
|
||||
this.online = false;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
NoSingleplayer.LOGGER.warn("[SurviePote] Ping serveur impossible : {}", e.toString());
|
||||
this.pending = false;
|
||||
this.resolved = true;
|
||||
this.online = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateWidgetNarration(NarrationElementOutput output) {
|
||||
defaultButtonNarrationText(output);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package fr.surviepote.nosingleplayer.mixin;
|
||||
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
/** Permet de récupérer l'action d'un bouton vanilla (pour la conserver). */
|
||||
@Mixin(Button.class)
|
||||
public interface ButtonAccessor {
|
||||
|
||||
@Accessor("onPress")
|
||||
Button.OnPress surviepote$getOnPress();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package fr.surviepote.nosingleplayer.mixin;
|
||||
|
||||
import fr.surviepote.nosingleplayer.NoSingleplayer;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
/**
|
||||
* Titre de la fenêtre : "Minecraft* 26.3" -> "SurviePote [26.3]".
|
||||
* Minecraft.createTitle() est privé, appelé au démarrage (création de la fenêtre)
|
||||
* et par updateTitle() (changement de monde, focus, resize...).
|
||||
*/
|
||||
@Mixin(Minecraft.class)
|
||||
public abstract class MinecraftTitleMixin {
|
||||
|
||||
@Unique
|
||||
private static boolean surviepote$titleLogged;
|
||||
|
||||
@Inject(method = "createTitle", at = @At("HEAD"), cancellable = true)
|
||||
private void surviepote$customWindowTitle(CallbackInfoReturnable<String> cir) {
|
||||
String title = "SurviePote [" + SharedConstants.getCurrentVersion().name() + "]";
|
||||
cir.setReturnValue(title);
|
||||
if (!surviepote$titleLogged) {
|
||||
surviepote$titleLogged = true;
|
||||
NoSingleplayer.LOGGER.info("[SurviePote] Titre de fenêtre : {}", title);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package fr.surviepote.nosingleplayer.mixin;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.PauseScreen;
|
||||
import net.minecraft.client.gui.screens.TitleScreen;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.network.chat.CommonComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(PauseScreen.class)
|
||||
public abstract class PauseScreenMixin {
|
||||
|
||||
@Unique
|
||||
private static final Component SURVIEPOTE_QUIT = Component.literal("Quitter SurviePote");
|
||||
|
||||
// Quitte le monde comme le bouton vanilla, mais renvoie vers l'écran d'accueil
|
||||
// (au lieu de la liste des serveurs pour un serveur distant).
|
||||
@Unique
|
||||
private static void surviepote$quitToTitle(Button button) {
|
||||
button.active = false;
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
// Conserve la gestion vanilla du brouillon de signalement, puis redirige.
|
||||
minecraft.getReportingContext().draftReportHandled(minecraft, minecraft.gui.screen(), () -> {
|
||||
minecraft.disconnectFromWorld(ClientLevel.DEFAULT_QUIT_MESSAGE);
|
||||
minecraft.setScreenAndShow(new TitleScreen());
|
||||
}, true);
|
||||
}
|
||||
|
||||
@Redirect(method = "createPauseMenu",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/gui/components/Button;builder(Lnet/minecraft/network/chat/Component;Lnet/minecraft/client/gui/components/Button$OnPress;)Lnet/minecraft/client/gui/components/Button$Builder;"))
|
||||
private static Button.Builder surviepote$relabelDisconnect(Component message, Button.OnPress onPress) {
|
||||
// createPauseMenu ne crée que deux boutons via Button.builder : "Retour au jeu"
|
||||
// et "Déconnexion". On ne remplace que ce dernier.
|
||||
if (message.getString().equals(CommonComponents.GUI_DISCONNECT.getString())) {
|
||||
return Button.builder(SURVIEPOTE_QUIT, PauseScreenMixin::surviepote$quitToTitle);
|
||||
}
|
||||
return Button.builder(message, onPress);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,24 @@
|
||||
package fr.surviepote.nosingleplayer.mixin;
|
||||
|
||||
import java.util.List;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.Renderable;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(Screen.class)
|
||||
public interface ScreenAccessor {
|
||||
|
||||
@Accessor("renderables")
|
||||
List<Renderable> surviepote$getRenderables();
|
||||
|
||||
/**
|
||||
* Panorama vanilla (déclaré dans Screen, appelé par TitleScreen).
|
||||
* Utilisé en repli quand le fond custom de SurviePote n'a pas pu être chargé :
|
||||
* sans ça, la redirection de l'appel laisse l'écran entièrement noir.
|
||||
*/
|
||||
@Invoker("extractPanorama")
|
||||
void surviepote$extractVanillaPanorama(GuiGraphicsExtractor extractor, float partialTick);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package fr.surviepote.nosingleplayer.mixin;
|
||||
|
||||
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.List;
|
||||
import java.util.Map;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
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.TitleScreen;
|
||||
import net.minecraft.client.multiplayer.ServerData;
|
||||
import net.minecraft.client.multiplayer.TransferState;
|
||||
import net.minecraft.client.multiplayer.resolver.ServerAddress;
|
||||
import net.minecraft.network.chat.Component;
|
||||
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 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
|
||||
private static void surviepote$removeWidget(TitleScreen self, GuiEventListener widget) {
|
||||
self.children().remove(widget);
|
||||
@@ -36,41 +45,39 @@ public abstract class TitleScreenMixin {
|
||||
|
||||
@Inject(method = "init", at = @At("TAIL"))
|
||||
private void surviepote$customizeMenu(CallbackInfo ci) {
|
||||
TitleScreen self = (TitleScreen) (Object) this;
|
||||
// Garde-fou : si la personnalisation échoue, on NE DOIT PAS avoir retiré les
|
||||
// boutons vanilla (menu vide + fond noir = "écran cassé"). Les retraits ne
|
||||
// sont appliqués qu'après la création des widgets, en fin de méthode.
|
||||
try {
|
||||
surviepote$buildMenu((TitleScreen) (Object) this);
|
||||
} catch (Throwable t) {
|
||||
NoSingleplayer.LOGGER.error("[SurviePote] Personnalisation du menu échouée, menu vanilla conservé.", t);
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void surviepote$buildMenu(TitleScreen self) {
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
|
||||
String solo = Component.translatable("menu.singleplayer").getString();
|
||||
String multi = Component.translatable("menu.multiplayer").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 joinY = minecraft.getWindow().getGuiScaledHeight() / 4 + 72;
|
||||
boolean foundMulti = false;
|
||||
|
||||
List<GuiEventListener> toRemove = new ArrayList<>();
|
||||
// TODO DEBUG temporaire : lister les widgets du menu titre.
|
||||
NoSingleplayer.LOGGER.info("[SurviePote] init TAIL, {} widgets:", self.children().size());
|
||||
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);
|
||||
}
|
||||
Button optionsButton = null;
|
||||
Button quitButton = null;
|
||||
for (GuiEventListener child : new ArrayList<>(self.children())) {
|
||||
if (child instanceof Button button) {
|
||||
String msg = button.getMessage().getString();
|
||||
if (msg.equals(solo)) {
|
||||
button.active = false;
|
||||
} 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 (child instanceof FriendsButton) {
|
||||
// ATTENTION : FriendsButton/SpriteIconButton/PlainTextButton héritent tous
|
||||
// de Button. Il faut donc les tester AVANT "instanceof Button", sinon la
|
||||
// première branche les avale et ils ne sont jamais retirés.
|
||||
if (child instanceof FriendsButton) {
|
||||
// Bouton "amis" (interactions sociales).
|
||||
toRemove.add(child);
|
||||
} else if (child instanceof SpriteIconButton) {
|
||||
// Bas-gauche : langue, accessibilité, etc.
|
||||
@@ -78,25 +85,97 @@ public abstract class TitleScreenMixin {
|
||||
} else if (child instanceof PlainTextButton) {
|
||||
// Copyright Mojang cliquable en bas.
|
||||
toRemove.add(child);
|
||||
} else if (child instanceof Button button) {
|
||||
String msg = button.getMessage().getString();
|
||||
if (msg.equals(solo)) {
|
||||
// Solo retiré complètement (plutôt que grisé).
|
||||
toRemove.add(button);
|
||||
} else if (msg.equals(multi)) {
|
||||
joinX = button.getX();
|
||||
joinY = button.getY();
|
||||
foundMulti = true;
|
||||
toRemove.add(button);
|
||||
} else if (msg.equals(realms)) {
|
||||
toRemove.add(button);
|
||||
} else if (msg.equals(options)) {
|
||||
// Remplacé plus bas par un bouton icône (engrenage).
|
||||
optionsButton = button;
|
||||
toRemove.add(button);
|
||||
} else if (msg.equals(quit)) {
|
||||
// Remplacé plus bas par un bouton icône (power).
|
||||
quitButton = button;
|
||||
toRemove.add(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (GuiEventListener widget : toRemove) {
|
||||
surviepote$removeWidget(self, widget);
|
||||
}
|
||||
// ATTENTION : les retraits des widgets vanilla ne sont appliqués qu'APRÈS la
|
||||
// création des widgets SurviePote (fin de méthode). Si une exception survient
|
||||
// entre-temps, le menu vanilla est conservé au lieu d'être vidé.
|
||||
|
||||
Button join = Button.builder(Component.literal("Rejoindre SurviePote"), pressed -> {
|
||||
ServerData data = new ServerData("SurviePote", SERVER_HOST, ServerData.Type.OTHER);
|
||||
ConnectScreen.startConnecting(self, minecraft, ServerAddress.parseString(SERVER_HOST), data, false,
|
||||
new TransferState(Map.of(), Map.of(), false));
|
||||
// TransferState null = connexion normale. En passer un déclenche une connexion
|
||||
// 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();
|
||||
((ScreenAccessor) self).surviepote$getRenderables().add(join);
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
List rawChildren = (List) self.children();
|
||||
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) {
|
||||
w.visible = true;
|
||||
w.active = true;
|
||||
}
|
||||
if (discord instanceof AbstractWidget w) {
|
||||
w.visible = true;
|
||||
w.active = true;
|
||||
}
|
||||
|
||||
// Tout est prêt : on retire enfin les widgets vanilla (Solo, Multi, Realms,
|
||||
// réseau social, icônes bas) remplacés ci-dessus.
|
||||
for (GuiEventListener widget : toRemove) {
|
||||
surviepote$removeWidget(self, widget);
|
||||
}
|
||||
|
||||
if (!foundMulti) {
|
||||
NoSingleplayer.LOGGER.warn("[SurviePote] Bouton Multi introuvable, Join placé par défaut.");
|
||||
}
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
package fr.surviepote.nosingleplayer.mixin;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
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.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.Font;
|
||||
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.renderer.RenderPipelines;
|
||||
import net.minecraft.resources.Identifier;
|
||||
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.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(TitleScreen.class)
|
||||
public abstract class TitleScreenRenderMixin {
|
||||
@@ -19,23 +26,144 @@ public abstract class TitleScreenRenderMixin {
|
||||
private static final Identifier SURVIEPOTE_BACKGROUND = Identifier.fromNamespaceAndPath(
|
||||
"nosingleplayer", "textures/gui/background.png");
|
||||
|
||||
// Taille réelle de background.png (fond plein écran étiré).
|
||||
// Le PNG est lu directement dans le jar du mod (voir ModTextures).
|
||||
@Unique
|
||||
private static final int BACKGROUND_TEX_W = 3840;
|
||||
private static final String SURVIEPOTE_BACKGROUND_RESOURCE =
|
||||
"/assets/nosingleplayer/textures/gui/background.png";
|
||||
|
||||
/**
|
||||
* Option de diagnostic : lancer le jeu avec
|
||||
* {@code -Dsurviepote.customTitle=false} pour repasser au menu vanilla
|
||||
* (utilisable dans les options JVM du launcher). Sert à isoler un éventuel
|
||||
* souci de rendu du fond custom sur un poste donné.
|
||||
*/
|
||||
@Unique
|
||||
private static final int BACKGROUND_TEX_H = 2400;
|
||||
private static final String DISABLE_CUSTOM_TITLE_PROPERTY = "surviepote.customTitle";
|
||||
|
||||
@Unique
|
||||
private static boolean surviepote$gpuLogged;
|
||||
|
||||
/**
|
||||
* Enregistre le fond custom AVANT le premier rendu du menu.
|
||||
*
|
||||
* <p>La texture GPU doit être créée hors de l'extraction du rendu : la créer
|
||||
* pendant l'enregistrement des commandes de la frame invalide le frame entier
|
||||
* sur les backends à command encoder (Vulkan) => menu titre entièrement noir
|
||||
* (le PC affected est sous Windows + NVIDIA, qui utilise le backend Vulkan).
|
||||
*/
|
||||
@Inject(method = "init", at = @At("HEAD"))
|
||||
private void surviepote$registerBackground(CallbackInfo ci) {
|
||||
try {
|
||||
ModTextures.register(SURVIEPOTE_BACKGROUND, SURVIEPOTE_BACKGROUND_RESOURCE, true);
|
||||
} catch (Throwable t) {
|
||||
NoSingleplayer.LOGGER.error("[SurviePote] Enregistrement du fond custom impossible.", t);
|
||||
}
|
||||
}
|
||||
|
||||
// Pas de panorama dynamique : fond statique custom à la place.
|
||||
// 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.
|
||||
// Le fond couvre l'écran sans déformation : recadrage centré type "cover".
|
||||
@Redirect(method = "extractRenderState",
|
||||
at = @At(value = "INVOKE",
|
||||
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) {
|
||||
int w = Minecraft.getInstance().getWindow().getGuiScaledWidth();
|
||||
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);
|
||||
// Les widgets du menu sont dessinés par super.extractRenderState(), juste APRÈS
|
||||
// cet appel. Toute exception levée ici interromp le rendu du menu entier =>
|
||||
// écran 100 % noir (sans boutons). D'où les garde-fous ci-dessous.
|
||||
surviepote$logGpuOnce();
|
||||
|
||||
// Menu vanilla si l'utilisateur a forcé -Dsurviepote.customTitle=false,
|
||||
// ou si le fond custom n'est pas (ou plus) enregistré.
|
||||
final boolean customEnabled = !"false".equalsIgnoreCase(
|
||||
System.getProperty(DISABLE_CUSTOM_TITLE_PROPERTY, "true"));
|
||||
if (!customEnabled || !ModTextures.isRegistered(SURVIEPOTE_BACKGROUND)) {
|
||||
surviepote$vanillaPanorama(self, extractor, partialTick);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
int w = Minecraft.getInstance().getWindow().getGuiScaledWidth();
|
||||
int h = Minecraft.getInstance().getWindow().getGuiScaledHeight();
|
||||
int texW = ModTextures.getWidth(SURVIEPOTE_BACKGROUND);
|
||||
int texH = ModTextures.getHeight(SURVIEPOTE_BACKGROUND);
|
||||
if (texW <= 0 || texH <= 0 || w <= 0 || h <= 0) {
|
||||
surviepote$vanillaPanorama(self, extractor, partialTick);
|
||||
return;
|
||||
}
|
||||
|
||||
float screenAspect = (float) w / (float) h;
|
||||
float texAspect = (float) texW / (float) texH;
|
||||
|
||||
int u, v, srcW, srcH;
|
||||
if (screenAspect > texAspect) {
|
||||
// Écran plus large que l'image : on rogne en haut/bas.
|
||||
srcW = texW;
|
||||
srcH = Math.round(texW / screenAspect);
|
||||
u = 0;
|
||||
v = (texH - srcH) / 2;
|
||||
} else {
|
||||
// Écran plus haut que l'image : on rogne à gauche/droite.
|
||||
srcH = texH;
|
||||
srcW = Math.round(texH * screenAspect);
|
||||
u = (texW - srcW) / 2;
|
||||
v = 0;
|
||||
}
|
||||
|
||||
// Surcharge complète : (u, v) = origine dans la texture, (w, h) = taille à
|
||||
// l'écran, (srcW, srcH) = taille de la région source dans la texture.
|
||||
extractor.blit(RenderPipelines.GUI_TEXTURED, SURVIEPOTE_BACKGROUND,
|
||||
0, 0, (float) u, (float) v,
|
||||
w, h, srcW, srcH,
|
||||
texW, texH, -1);
|
||||
} catch (Throwable t) {
|
||||
// Repli : on bascule sur le panorama vanilla pour ne pas laisser un écran noir.
|
||||
NoSingleplayer.LOGGER.error("[SurviePote] Échec du fond custom, repli sur le panorama vanilla.", t);
|
||||
surviepote$vanillaPanorama(self, extractor, partialTick);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redessine le panorama vanilla (appelé dans Screen, pas dans TitleScreen :
|
||||
* la redirection porte sur l'appel, donc pas de récursion).
|
||||
*/
|
||||
@Unique
|
||||
private void surviepote$vanillaPanorama(TitleScreen self, GuiGraphicsExtractor extractor, float partialTick) {
|
||||
try {
|
||||
((ScreenAccessor) self).surviepote$extractVanillaPanorama(extractor, partialTick);
|
||||
} catch (Throwable t) {
|
||||
NoSingleplayer.LOGGER.error("[SurviePote] Échec du panorama vanilla.", t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Log une fois les infos GPU : permet de comparer un poste qui plante d'un poste qui marche. */
|
||||
@Unique
|
||||
private static void surviepote$logGpuOnce() {
|
||||
if (surviepote$gpuLogged) {
|
||||
return;
|
||||
}
|
||||
surviepote$gpuLogged = true;
|
||||
try {
|
||||
var info = RenderSystem.getDevice().getDeviceInfo();
|
||||
NoSingleplayer.LOGGER.info("[SurviePote] GPU: {} | {} | pilote {} | backend {}",
|
||||
info.name(), info.vendorName(), info.driverInfo(), info.backendName());
|
||||
} catch (Throwable t) {
|
||||
NoSingleplayer.LOGGER.warn("[SurviePote] Infos GPU indisponibles : {}", t.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Pas de texte de version ("26.3 mc") en bas à gauche.
|
||||
@Redirect(method = "extractRenderState",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/gui/GuiGraphicsExtractor;text(Lnet/minecraft/client/gui/Font;Ljava/lang/String;III)V"))
|
||||
private void surviepote$skipVersionText(GuiGraphicsExtractor extractor, Font font, String text, int x, int y, int color) {
|
||||
}
|
||||
|
||||
// Pas de texte de splash (message rotatif) sur le menu principal.
|
||||
@Redirect(method = "extractRenderState",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/gui/components/SplashRenderer;extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;ILnet/minecraft/client/gui/Font;F)V"))
|
||||
private void surviepote$skipSplash(SplashRenderer splash, GuiGraphicsExtractor extractor, int width, Font font, float fade) {
|
||||
}
|
||||
|
||||
// Pas de logo Minecraft.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 98 B |
|
Before Width: | Height: | Size: 98 B |
|
Before Width: | Height: | Size: 629 KiB After Width: | Height: | Size: 826 KiB |
|
After Width: | Height: | Size: 904 B |
|
After Width: | Height: | Size: 645 B |
|
After Width: | Height: | Size: 704 B |
@@ -5,8 +5,10 @@
|
||||
"client": [
|
||||
"TitleScreenMixin",
|
||||
"TitleScreenRenderMixin",
|
||||
"TitleScreenTextMixin",
|
||||
"MinecraftTitleMixin",
|
||||
"ButtonAccessor",
|
||||
"RealmsNotificationsMixin",
|
||||
"PauseScreenMixin",
|
||||
"ScreenAccessor"
|
||||
],
|
||||
"injectors": {
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
- À 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.
|
||||
|
||||
## ImmersiveOres — voir server/mods-client-only/
|
||||
- `ImmersiveOres-26.3-0.1.jar` : mixin client (`ToolOverlayRenderer` → `LevelRenderer`) dans la
|
||||
config commune → crash serveur dédié. Gardé côté CLIENT (atlas required) pour les blocs/items,
|
||||
exclu du serveur. À signaler à l'auteur.
|
||||
## ImmersiveOres — RETIRÉ (mod cassé, 19/10/2026)
|
||||
- `ImmersiveOres-26.3-0.1.jar` : `environment: *` mais mixin client (`ToolOverlayRenderer` → `LevelRenderer`)
|
||||
dans la config commune → crash serveur dédié. Mod jugé cassé → retiré partout : serveur,
|
||||
`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.
|
||||
|
||||
@@ -12,11 +12,20 @@ Serveur **Minecraft 26.3 + Fabric Loader 0.20.0**, pré-configuré en « défaut
|
||||
|
||||
```bash
|
||||
cd server
|
||||
echo "eula=true" > eula.txt
|
||||
./start.sh
|
||||
echo "eula=true" > eula.txt # déjà présent (eula=false) : à passer à true
|
||||
./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.
|
||||
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 |
|
||||
| --- | --- |
|
||||
| `fabric-server-launch.jar` | Lanceur Fabric (généré par fabric-installer 1.1.2) |
|
||||
| `server.jar` | Serveur Minecraft 26.3 vanilla (téléchargé par l'installeur) |
|
||||
| `fabric-installer.jar` | Installeur (pour réinstaller/réparer) |
|
||||
| `mods/` | 31 mods serveur (copie physique) — voir `mods/SOURCE.md` |
|
||||
| `config/` | Configs générées au premier lancement (+ customs à déposer ici) |
|
||||
| `setup.sh` / `setup.bat` | Installe/repare le runtime Fabric (télécharge vanilla + lanceur Fabric) |
|
||||
| `fabric-server-launch.jar` | Lanceur Fabric (généré par fabric-installer 1.1.2 — non versionné) |
|
||||
| `server.jar` | Serveur Minecraft 26.3 vanilla (téléchargé par l'installeur — non versionné) |
|
||||
| `fabric-installer.jar` | Installeur (pour réinstaller/réparer — non versionné) |
|
||||
| `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) |
|
||||
| `eula.txt` | Passer à `eula=true` avant de lancer |
|
||||
| `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
|
||||
|
||||
- **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.
|
||||
- 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
|
||||
|
||||
- `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.
|
||||
|
||||
## 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 +0,0 @@
|
||||
[]
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"cooldown": 5,
|
||||
"maxHomes": 5
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||