Repository files navigation

GitHubDiscordStewBeet

Dépôt GitHub du serveur Switch

Le datapack et le resource pack du serveur Switch (Paralya) : un lobby, une infinité de mini-jeux votés entre chaque partie, un système de maps régénérables, des boutiques, des statistiques et des succès.

Tout est généré en Python avec StewBeet, un framework au-dessus de beet. Aucun fichier .mcfunction n'est écrit à la main : ils sont produits par le code de src/.

Documents (Google Sheet) :


Sommaire


Prérequis

  • Python 3.14+
  • StewBeet : pip install -U stewbeet

Build

Une seule commande, à la racine du dépôt :

stewbeet
CommandeEffet
stewbeetBuild complet (équivaut à stewbeet build)
stewbeet rebuildNettoie les caches puis rebuild
stewbeet cleanNettoie les caches et les dossiers de sortie
stewbeet --helpListe toutes les commandes

Le build remplit build/ puis copie les .zip vers les chemins de build_copy_destinations (beet.yml).

Ces chemins sont personnels (resourcepacks local, SFTP du serveur) : adaptez-les chez vous, mais ne committez pas vos chemins locaux.


⚠️ Ne jamais éditer le dossier build

build/ est entièrement généré et écrasé à chaque build : il n'est versionné que parce que le serveur pull le dépôt, donc on ne le commit qu'après un vrai stewbeet.

Pour retrouver le code d'une commande in-game, grep son texte dans src/ : le chemin de la fonction est le premier argument de write_function.


Structure du dépôt

Switch/
├── ⚙️ beet.yml # Config du projet et du pipeline de build
├── 🚀 upload.py # Publication d'une release GitHub
├── 🧰 tools/ # Scaffolding et garde-fous (voir plus bas)
├── 🐍 src/ # TOUT le code source
│ ├── setup_definitions.py # Étape 1 : items, blocs, matériaux, disques
│ ├── link.py # Étape 2 : appelle tous les générateurs
│ ├── validation.py # Contrôles de cohérence, échoue le build
│ ├── 📦 database/ # Items et comportements de blocs
│ ├── 🎨 resource_pack/ # Langues, sounds.json, shaders, textures GUI, fonts
│ └── 📂 datapack/
│ ├── main.py # Définitions brutes du datapack
│ ├── definitions/ # Advancements, dimensions, loot tables, prédicats, tags...
│ ├── 🎮 modes/ # Un dossier par mini-jeu, + spec/catalogue/emit
│ ├── 🧠 engine/ # Vote, démarrage, arrêt, signaux, pop-ups
│ ├── 🧍 player/ # Layout d'inventaire, practice, jump timer
│ ├── 🏛️ lobby/ # Boards, leaderboards, PNJ, tick hors partie
│ ├── 🗺️ maps/ # Chargement, géométrie, et generation/ (régénération)
│ ├── 🎒 kits/ # Modèle déclaratif des kits (Kit, KitItem, rôles)
│ ├── 🛒 shop/ # Boutiques (consomme le registre des modes)
│ ├── 📊 stats/ # Classements et statistiques
│ ├── 🏆 advancements/ # Succès et pourcentages
│ ├── 🎬 cinematic/ # Cinématiques d'intro
│ ├── 🎵 music/ # Lecteur de musique et Note Block Studio
│ ├── 🌐 translations/ # Textes partagés FR/EN
│ ├── 🔧 utils/ # Primitives appelées par une vingtaine de modes
│ ├── 🛠️ devtools/ # test_mode, lag artificiel, profiling
│ └── 🌱 root/ # load, tick, second et fonctions racine
├── 🖼️ assets/ # Textures, sons, disques, pack.png
├── 📚 libs/ # Packs externes fusionnés au build
├── 🎼 note_block_studio/ # Musiques (midi, datapacks générés)
├── 📤 continuous_delivery/ # Config de la release GitHub
└── ⛔ build/ # SORTIE GÉNÉRÉE, ne jamais éditer

Le pipeline de build

L'ordre vient de la clé pipeline de beet.yml. Deux entrées seulement sont du code maison :

  1. src.setup_definitions remplit la base d'items et de blocs (Item, Block, matériaux de ORES_CONFIGS, disques) et charge les définitions écrites à la main. Les plugins StewBeet suivants s'appuient dessus.
  2. src.link appelle generate_all_modes() puis chaque générateur de sous-système : c'est là que la quasi-totalité des .mcfunction sont écrites.

Le reste vient de plugins StewBeet : headers, constantes de scoreboard, dépendances, merge Smithed Weld, zip, copie, sha1.

src/datapack/modes/__init__.pyimporte dynamiquement chaque dossier contenant un main.py et appelle son write_mode(), puis le write_resources() de son resources.py s'il existe. Aucune liste d'imports à maintenir.


Où se trouve quoi ?

Je veux modifier...Fichier
🗳️ La liste des mini-jeux votables, descriptions, temps estimé, auteurssrc/datapack/modes/catalogue.py (MODES)
🧩 Les groupes de vote (variantes sous une même entrée)src/datapack/modes/catalogue.py (GROUPS)
🎮 La logique d'un mini-jeusrc/datapack/modes/<mode>/main.py
🌐 Les messages FR/EN d'un mini-jeusrc/datapack/modes/<mode>/translations.py
📜 Les advancements, prédicats, loot tables, structures d'un mini-jeusrc/datapack/modes/<mode>/resources.py
🔁 Un helper partagé entre plusieurs modessrc/datapack/modes/emit.py ou src/datapack/modes/_common/main.py
🧠 Le vote, le lancement, l'arrêt d'une partie, les signauxsrc/datapack/engine/main.py
🌍 Les maps de jeu (dimensions, régénération, zones)src/datapack/survival_maps/definitions.py
🏁 Les checkpoints de course et les cycles de spawnsrc/datapack/maps/main.py
🎒 Un kit ou une classesrc/datapack/kits/ et src/datapack/modes/<mode>/kits.py
🛒 Une boutiquesrc/datapack/modes/<mode>/shop.py, agrégé par src/datapack/shop/shared_memory.py
📦 Un item ou un bloc customsrc/database/misc_items.py, src/database/blocks_behaviors.py
⛏️ Les matériaux générés (armures, outils)src/setup_definitions.py (ORES_CONFIGS)
🖼️ Une texture d'itemassets/textures/**/<item_id>.png, détection automatique
🔊 Un sonassets/sounds/, ou src/datapack/modes/<mode>/sounds/ pour un son propre à un mode
🛠️ Un outil de dev (test mode, lag, profiling)src/datapack/devtools/
✨ Les shaderssrc/resource_pack/shaders.py
🪟 Les textures de GUI et de tooltipssrc/resource_pack/textures.py

Ajouter un mode de jeu

1. Générer le squelette

python tools/new_mode.py mon_mode

Le mode obtenu build et se lance en jeu immédiatement. La découverte est automatique : aucun import à ajouter ailleurs.

src/datapack/modes/mon_mode/
├── __init__.py # vide
├── main.py # write_mode()
└── translations.py # write_translations()

2. Remplir main.py

Le fichier généré ressemble déjà à ceci, avec les six hooks branchés. Il ne reste qu'à écrire les mécaniques du jeu.

# ImportsfromstewbeetimportMem, write_functionfrom ..emitimportwrite_modes_calls, write_time_xp_barfrom .translationsimportwrite_translationsdefwrite_mode():
ns: str=Mem.ctx.project_idmode: str="mon_mode"path: str=f"{ns}:modes/{mode}"# Écrit /calls/* (le dispatch appelé par le moteur) et /_force_startwrite_modes_calls(mode)
write_translations()
# /start : appelé une fois au lancement de la partiewrite_function(f"{path}/start", f"""effect give @a[tag=!detached] saturation infinite 255 true# Choix de la map parmi une liste (les ids viennent de survival_maps/definitions.py)scoreboard players set #do_spreadplayers {ns}.data 1function {ns}:utils/choose_map_for {{id:"{mode}", maps:["switch_space"]}}scoreboard players set #mon_mode_seconds {ns}.data -6scoreboard players set #process_end {ns}.data 0""")
# /tick : chaque tick de jeuwrite_function(f"{path}/tick", f"""function {ns}:utils/on_death_run_function {{function:"{path}/death"}}""")
# /second : chaque seconde de jeuwrite_function(f"{path}/second", f"""scoreboard players add #mon_mode_seconds {ns}.data 1function {path}/xp_barfunction {path}/translations/second""")
# /joined : un joueur rejoint en cours de partiewrite_function(f"{path}/joined", f"""gamemode spectator @s""")
# /stop : nettoyage en fin de partiewrite_function(f"{path}/stop", f"""scoreboard objectives remove {ns}.temp.mon_score""")
# /xp_bar : barre d'XP servant de timerwrite_time_xp_bar(f"{path}/xp_bar", 300, "#mon_mode_seconds", "#mon_mode_seconds")

Les hooks disponibles sont joined, second, start, stop, tick et inventory_changed. Le moteur les appelle via switch:engine/signals/macro_*, qui redirige vers switch:modes/<mode>/calls/<hook>. write_modes_calls() génère ces redirections et le garde-fou sur current_game qui empêche un mode de tourner pendant qu'un autre est en cours.

3. Déclarer le mode dans la liste de vote

Dans src/datapack/modes/definitions.py, ajoutez une entrée à MODES :

{
"min_players":2, "max_players":-1, "id":"mon_mode", "name_fr":"Mon Mode",
"estimated_time": "2-5 mins", "inspiration": "Épicube", "suggested_by": "Pseudo", "developed_by": "Pseudo",
"description": {
"fr": [{"text":"Première ligne de description.\n"},{"text":"Deuxième ligne.\n"}],
"en": [{"text":"First description line.\n"},{"text":"Second line.\n"}]
},
},
Clé / comportementDétail
idExactement le nom du dossier
max_playersUNLIMITED quand le mode n'a pas de plafond
groupOptionnel : les jeux d'un même groupe forment une entrée de vote, puis un second vote départage (voir GROUPS)
Lore, index, pop-upsGénérés automatiquement à partir de cette entrée
Entrée commentéeLe mode est quand même généré : testable via _force_start sans polluer le vote
Erreur de saisieLe build échoue avec un message qui nomme le problème et suggère la valeur la plus proche

4. Tester en jeu

/function switch:devtools/test_mode
/function switch:modes/mon_mode/_force_start

5. Ressources supplémentaires (optionnel)

Advancements, prédicats, loot tables, item modifiers, tags ou structures vont dans un resources.py exposant write_resources(), appelé automatiquement après write_mode().


Anatomie d'un mode

FichierRôleAppelé par
main.pywrite_mode() : toute la logique, écrit les .mcfunctionautomatique
translations.pywrite_translations() : messages FR/EN dans <mode>/translations/*write_mode()
resources.pywrite_resources() : advancements, prédicats, loot tables, tags, structuresautomatique
kits.pyKits et classes du mode, construits avec Kit et KitItemwrite_mode()
shop.pyConstante SHOP : les upgrades vendues par le modeautomatique
structures/Fichiers .nbt, enregistrés via register_structures()resources.py
sounds/Fichiers .ogg propres au mode, via register_sounds()resources.py
  • _common/ : fonctions partagées écrites dans switch:modes/_common/* (mort en spectateur, fin de partie, barre d'XP, kits communs).
  • _coupdetat/ : pseudo-mode utilisé par le moteur, pas un mini-jeu votable.
  • Les helpers Python partagés vivent dans src/datapack/modes/emit.py : write_modes_calls, write_server_announce, write_time_xp_bar, write_no_drop, register_structures, register_sounds.
  • Le namespace n'est jamais écrit en dur : utilisez toujours ns: str = Mem.ctx.project_id.

Ajouter autre chose

  • Une map : un clone_survival(...) ou fill_survival(...) dans src/datapack/survival_maps/definitions.py (coordonnées, id, nom, auteurs, view de la cinématique). Elle devient utilisable dans le maps:[...] d'un choose_map_for.
  • Un item ou un bloc : Item(...) ou Block(...) dans src/database/misc_items.py. Texture trouvée automatiquement si un .png du même nom existe sous assets/textures/ ; modèle, loot table et recettes sont générés.
  • Un son : le .ogg dans assets/sounds/, ou dans le sounds/ du mode s'il lui est propre. Voir assets/compress_ogg.py, force_mono.py et mp3_to_ogg.py.
  • Une boutique : un shop.py exposant une constante SHOP, sur le modèle de spleef/shop.py. Le registre la récupère tout seul ; son rang dans la boutique vient de SHOP_ORDER (src/datapack/shop/shared_memory.py).

Conventions de code

  • Typage strict : pyright en mode strict (pyrightconfig.json), pas de Any.
  • Indentation par tabulations, alignement des colonnes par espaces.
  • Code auto-explicatif : peu de commentaires, les vraies explications vont dans les docstrings.
  • Préférez le déclaratif et le paramétré à la duplication. Le CI lance jscpd sur src/ pour traquer le copier-coller.
  • Un fichier qui dépasse environ 300 lignes devient un sous-module. Exception assumée : le main.py d'un mode de jeu. Toute la logique du mode reste au même endroit, ce qui rend le Ctrl+F immédiat et évite d'avoir à deviner dans quel fichier se trouve une mécanique. build_battle/main.py fait 935 lignes, et c'est très bien ainsi.
  • Lint : ruff check src tools --fix (la config vit dans ruff.toml).
  • Perfs : le serveur tourne à 20 tps avec beaucoup de joueurs. Évitez les @e non filtrés dans les tick, préférez tags et scores.

Garde-fous

Six outils, tous lançables à la main. Les trois premiers tournent aussi en CI.

CommandeCe qu'elle garantit
ruff check src toolsStyle et imports
pyrightTypage strict, sans Any
python tools/check_conventions.pyTaille des fichiers, pureté du modèle, aucun import descendant vers un mode nommé
python tools/check_output_drift.pyLe refactoring n'a rien changé : rebuild puis build/ identique à HEAD
python tools/report_merged_functions.pyAucune fonction n'est écrite par deux émetteurs sans que ce soit déclaré
python tools/check_rename_only.py "a=b"Un diff de build/ est exactement les renommages annoncés, et rien d'autre

Ces deux derniers vont par paire : check_output_drift.py prouve qu'un refactoring n'a rien changé, check_rename_only.py prouve qu'un déplacement de chemins n'a fait que déplacer. Le plus important reste check_output_drift.py. write_functionajoute à la suite par défaut, donc deux émetteurs visant le même chemin fusionnent silencieusement dans l'ordre d'appel. Après tout déplacement de code, un build/ inchangé est la preuve que rien n'a bougé en jeu.

Deux règles s'appuient sur des listes explicites qui ne doivent que diminuer :

  • LONG_FILE_DEBT dans tools/check_conventions.py : les fichiers qui dépassent encore 300 lignes.
  • DECLARED_MERGES dans tools/report_merged_functions.py : les fonctions volontairement construites par ajouts successifs.

Workflow Git

  • Une branche par fonctionnalité, puis Pull Request vers main.
  • Commits courts, format conventionnel avec gitmoji après les deux-points :
    • feat(modes): ✨ Ajout du mode Block Party
    • fix(race): 🐛 Correction des checkpoints sur rainbow_road
    • perf(engine): ⚡️ Réduction du coût du tick
    • build: 🚀 Built with latest StewBeet version
  • Les releases sont publiées avec python upload.py (nécessite ~/stewbeet/credentials.yml).

About

Data pack gérant le serveur switch de Paralya V2

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

GitHubDiscordStewBeet

Dépôt GitHub du serveur Switch

Le datapack et le resource pack du serveur Switch (Paralya) : un lobby, une infinité de mini-jeux votés entre chaque partie, un système de maps régénérables, des boutiques, des statistiques et des succès.

Tout est généré en Python avec StewBeet, un framework au-dessus de beet. Aucun fichier .mcfunction n'est écrit à la main : ils sont produits par le code de src/.

Documents (Google Sheet) :


Sommaire


Prérequis

  • Python 3.14+
  • StewBeet : pip install -U stewbeet

Build

Une seule commande, à la racine du dépôt :

stewbeet
CommandeEffet
stewbeetBuild complet (équivaut à stewbeet build)
stewbeet rebuildNettoie les caches puis rebuild
stewbeet cleanNettoie les caches et les dossiers de sortie
stewbeet --helpListe toutes les commandes

Le build remplit build/ puis copie les .zip vers les chemins de build_copy_destinations (beet.yml).

Ces chemins sont personnels (resourcepacks local, SFTP du serveur) : adaptez-les chez vous, mais ne committez pas vos chemins locaux.


⚠️ Ne jamais éditer le dossier build

build/ est entièrement généré et écrasé à chaque build : il n'est versionné que parce que le serveur pull le dépôt, donc on ne le commit qu'après un vrai stewbeet.

Pour retrouver le code d'une commande in-game, grep son texte dans src/ : le chemin de la fonction est le premier argument de write_function.


Structure du dépôt

Switch/
├── ⚙️ beet.yml # Config du projet et du pipeline de build
├── 🚀 upload.py # Publication d'une release GitHub
├── 🧰 tools/ # Scaffolding et garde-fous (voir plus bas)
├── 🐍 src/ # TOUT le code source
│ ├── setup_definitions.py # Étape 1 : items, blocs, matériaux, disques
│ ├── link.py # Étape 2 : appelle tous les générateurs
│ ├── validation.py # Contrôles de cohérence, échoue le build
│ ├── 📦 database/ # Items et comportements de blocs
│ ├── 🎨 resource_pack/ # Langues, sounds.json, shaders, textures GUI, fonts
│ └── 📂 datapack/
│ ├── main.py # Définitions brutes du datapack
│ ├── definitions/ # Advancements, dimensions, loot tables, prédicats, tags...
│ ├── 🎮 modes/ # Un dossier par mini-jeu, + spec/catalogue/emit
│ ├── 🧠 engine/ # Vote, démarrage, arrêt, signaux, pop-ups
│ ├── 🧍 player/ # Layout d'inventaire, practice, jump timer
│ ├── 🏛️ lobby/ # Boards, leaderboards, PNJ, tick hors partie
│ ├── 🗺️ maps/ # Chargement, géométrie, et generation/ (régénération)
│ ├── 🎒 kits/ # Modèle déclaratif des kits (Kit, KitItem, rôles)
│ ├── 🛒 shop/ # Boutiques (consomme le registre des modes)
│ ├── 📊 stats/ # Classements et statistiques
│ ├── 🏆 advancements/ # Succès et pourcentages
│ ├── 🎬 cinematic/ # Cinématiques d'intro
│ ├── 🎵 music/ # Lecteur de musique et Note Block Studio
│ ├── 🌐 translations/ # Textes partagés FR/EN
│ ├── 🔧 utils/ # Primitives appelées par une vingtaine de modes
│ ├── 🛠️ devtools/ # test_mode, lag artificiel, profiling
│ └── 🌱 root/ # load, tick, second et fonctions racine
├── 🖼️ assets/ # Textures, sons, disques, pack.png
├── 📚 libs/ # Packs externes fusionnés au build
├── 🎼 note_block_studio/ # Musiques (midi, datapacks générés)
├── 📤 continuous_delivery/ # Config de la release GitHub
└── ⛔ build/ # SORTIE GÉNÉRÉE, ne jamais éditer

Le pipeline de build

L'ordre vient de la clé pipeline de beet.yml. Deux entrées seulement sont du code maison :

  1. src.setup_definitions remplit la base d'items et de blocs (Item, Block, matériaux de ORES_CONFIGS, disques) et charge les définitions écrites à la main. Les plugins StewBeet suivants s'appuient dessus.
  2. src.link appelle generate_all_modes() puis chaque générateur de sous-système : c'est là que la quasi-totalité des .mcfunction sont écrites.

Le reste vient de plugins StewBeet : headers, constantes de scoreboard, dépendances, merge Smithed Weld, zip, copie, sha1.

src/datapack/modes/__init__.pyimporte dynamiquement chaque dossier contenant un main.py et appelle son write_mode(), puis le write_resources() de son resources.py s'il existe. Aucune liste d'imports à maintenir.


Où se trouve quoi ?

Je veux modifier...Fichier
🗳️ La liste des mini-jeux votables, descriptions, temps estimé, auteurssrc/datapack/modes/catalogue.py (MODES)
🧩 Les groupes de vote (variantes sous une même entrée)src/datapack/modes/catalogue.py (GROUPS)
🎮 La logique d'un mini-jeusrc/datapack/modes/<mode>/main.py
🌐 Les messages FR/EN d'un mini-jeusrc/datapack/modes/<mode>/translations.py
📜 Les advancements, prédicats, loot tables, structures d'un mini-jeusrc/datapack/modes/<mode>/resources.py
🔁 Un helper partagé entre plusieurs modessrc/datapack/modes/emit.py ou src/datapack/modes/_common/main.py
🧠 Le vote, le lancement, l'arrêt d'une partie, les signauxsrc/datapack/engine/main.py
🌍 Les maps de jeu (dimensions, régénération, zones)src/datapack/survival_maps/definitions.py
🏁 Les checkpoints de course et les cycles de spawnsrc/datapack/maps/main.py
🎒 Un kit ou une classesrc/datapack/kits/ et src/datapack/modes/<mode>/kits.py
🛒 Une boutiquesrc/datapack/modes/<mode>/shop.py, agrégé par src/datapack/shop/shared_memory.py
📦 Un item ou un bloc customsrc/database/misc_items.py, src/database/blocks_behaviors.py
⛏️ Les matériaux générés (armures, outils)src/setup_definitions.py (ORES_CONFIGS)
🖼️ Une texture d'itemassets/textures/**/<item_id>.png, détection automatique
🔊 Un sonassets/sounds/, ou src/datapack/modes/<mode>/sounds/ pour un son propre à un mode
🛠️ Un outil de dev (test mode, lag, profiling)src/datapack/devtools/
✨ Les shaderssrc/resource_pack/shaders.py
🪟 Les textures de GUI et de tooltipssrc/resource_pack/textures.py

Ajouter un mode de jeu

1. Générer le squelette

python tools/new_mode.py mon_mode

Le mode obtenu build et se lance en jeu immédiatement. La découverte est automatique : aucun import à ajouter ailleurs.

src/datapack/modes/mon_mode/
├── __init__.py # vide
├── main.py # write_mode()
└── translations.py # write_translations()

2. Remplir main.py

Le fichier généré ressemble déjà à ceci, avec les six hooks branchés. Il ne reste qu'à écrire les mécaniques du jeu.

# ImportsfromstewbeetimportMem, write_functionfrom ..emitimportwrite_modes_calls, write_time_xp_barfrom .translationsimportwrite_translationsdefwrite_mode():
ns: str=Mem.ctx.project_idmode: str="mon_mode"path: str=f"{ns}:modes/{mode}"# Écrit /calls/* (le dispatch appelé par le moteur) et /_force_startwrite_modes_calls(mode)
write_translations()
# /start : appelé une fois au lancement de la partiewrite_function(f"{path}/start", f"""effect give @a[tag=!detached] saturation infinite 255 true# Choix de la map parmi une liste (les ids viennent de survival_maps/definitions.py)scoreboard players set #do_spreadplayers {ns}.data 1function {ns}:utils/choose_map_for {{id:"{mode}", maps:["switch_space"]}}scoreboard players set #mon_mode_seconds {ns}.data -6scoreboard players set #process_end {ns}.data 0""")
# /tick : chaque tick de jeuwrite_function(f"{path}/tick", f"""function {ns}:utils/on_death_run_function {{function:"{path}/death"}}""")
# /second : chaque seconde de jeuwrite_function(f"{path}/second", f"""scoreboard players add #mon_mode_seconds {ns}.data 1function {path}/xp_barfunction {path}/translations/second""")
# /joined : un joueur rejoint en cours de partiewrite_function(f"{path}/joined", f"""gamemode spectator @s""")
# /stop : nettoyage en fin de partiewrite_function(f"{path}/stop", f"""scoreboard objectives remove {ns}.temp.mon_score""")
# /xp_bar : barre d'XP servant de timerwrite_time_xp_bar(f"{path}/xp_bar", 300, "#mon_mode_seconds", "#mon_mode_seconds")

Les hooks disponibles sont joined, second, start, stop, tick et inventory_changed. Le moteur les appelle via switch:engine/signals/macro_*, qui redirige vers switch:modes/<mode>/calls/<hook>. write_modes_calls() génère ces redirections et le garde-fou sur current_game qui empêche un mode de tourner pendant qu'un autre est en cours.

3. Déclarer le mode dans la liste de vote

Dans src/datapack/modes/definitions.py, ajoutez une entrée à MODES :

{
"min_players":2, "max_players":-1, "id":"mon_mode", "name_fr":"Mon Mode",
"estimated_time": "2-5 mins", "inspiration": "Épicube", "suggested_by": "Pseudo", "developed_by": "Pseudo",
"description": {
"fr": [{"text":"Première ligne de description.\n"},{"text":"Deuxième ligne.\n"}],
"en": [{"text":"First description line.\n"},{"text":"Second line.\n"}]
},
},
Clé / comportementDétail
idExactement le nom du dossier
max_playersUNLIMITED quand le mode n'a pas de plafond
groupOptionnel : les jeux d'un même groupe forment une entrée de vote, puis un second vote départage (voir GROUPS)
Lore, index, pop-upsGénérés automatiquement à partir de cette entrée
Entrée commentéeLe mode est quand même généré : testable via _force_start sans polluer le vote
Erreur de saisieLe build échoue avec un message qui nomme le problème et suggère la valeur la plus proche

4. Tester en jeu

/function switch:devtools/test_mode
/function switch:modes/mon_mode/_force_start

5. Ressources supplémentaires (optionnel)

Advancements, prédicats, loot tables, item modifiers, tags ou structures vont dans un resources.py exposant write_resources(), appelé automatiquement après write_mode().


Anatomie d'un mode

FichierRôleAppelé par
main.pywrite_mode() : toute la logique, écrit les .mcfunctionautomatique
translations.pywrite_translations() : messages FR/EN dans <mode>/translations/*write_mode()
resources.pywrite_resources() : advancements, prédicats, loot tables, tags, structuresautomatique
kits.pyKits et classes du mode, construits avec Kit et KitItemwrite_mode()
shop.pyConstante SHOP : les upgrades vendues par le modeautomatique
structures/Fichiers .nbt, enregistrés via register_structures()resources.py
sounds/Fichiers .ogg propres au mode, via register_sounds()resources.py
  • _common/ : fonctions partagées écrites dans switch:modes/_common/* (mort en spectateur, fin de partie, barre d'XP, kits communs).
  • _coupdetat/ : pseudo-mode utilisé par le moteur, pas un mini-jeu votable.
  • Les helpers Python partagés vivent dans src/datapack/modes/emit.py : write_modes_calls, write_server_announce, write_time_xp_bar, write_no_drop, register_structures, register_sounds.
  • Le namespace n'est jamais écrit en dur : utilisez toujours ns: str = Mem.ctx.project_id.

Ajouter autre chose

  • Une map : un clone_survival(...) ou fill_survival(...) dans src/datapack/survival_maps/definitions.py (coordonnées, id, nom, auteurs, view de la cinématique). Elle devient utilisable dans le maps:[...] d'un choose_map_for.
  • Un item ou un bloc : Item(...) ou Block(...) dans src/database/misc_items.py. Texture trouvée automatiquement si un .png du même nom existe sous assets/textures/ ; modèle, loot table et recettes sont générés.
  • Un son : le .ogg dans assets/sounds/, ou dans le sounds/ du mode s'il lui est propre. Voir assets/compress_ogg.py, force_mono.py et mp3_to_ogg.py.
  • Une boutique : un shop.py exposant une constante SHOP, sur le modèle de spleef/shop.py. Le registre la récupère tout seul ; son rang dans la boutique vient de SHOP_ORDER (src/datapack/shop/shared_memory.py).

Conventions de code

  • Typage strict : pyright en mode strict (pyrightconfig.json), pas de Any.
  • Indentation par tabulations, alignement des colonnes par espaces.
  • Code auto-explicatif : peu de commentaires, les vraies explications vont dans les docstrings.
  • Préférez le déclaratif et le paramétré à la duplication. Le CI lance jscpd sur src/ pour traquer le copier-coller.
  • Un fichier qui dépasse environ 300 lignes devient un sous-module. Exception assumée : le main.py d'un mode de jeu. Toute la logique du mode reste au même endroit, ce qui rend le Ctrl+F immédiat et évite d'avoir à deviner dans quel fichier se trouve une mécanique. build_battle/main.py fait 935 lignes, et c'est très bien ainsi.
  • Lint : ruff check src tools --fix (la config vit dans ruff.toml).
  • Perfs : le serveur tourne à 20 tps avec beaucoup de joueurs. Évitez les @e non filtrés dans les tick, préférez tags et scores.

Garde-fous

Six outils, tous lançables à la main. Les trois premiers tournent aussi en CI.

CommandeCe qu'elle garantit
ruff check src toolsStyle et imports
pyrightTypage strict, sans Any
python tools/check_conventions.pyTaille des fichiers, pureté du modèle, aucun import descendant vers un mode nommé
python tools/check_output_drift.pyLe refactoring n'a rien changé : rebuild puis build/ identique à HEAD
python tools/report_merged_functions.pyAucune fonction n'est écrite par deux émetteurs sans que ce soit déclaré
python tools/check_rename_only.py "a=b"Un diff de build/ est exactement les renommages annoncés, et rien d'autre

Ces deux derniers vont par paire : check_output_drift.py prouve qu'un refactoring n'a rien changé, check_rename_only.py prouve qu'un déplacement de chemins n'a fait que déplacer. Le plus important reste check_output_drift.py. write_functionajoute à la suite par défaut, donc deux émetteurs visant le même chemin fusionnent silencieusement dans l'ordre d'appel. Après tout déplacement de code, un build/ inchangé est la preuve que rien n'a bougé en jeu.

Deux règles s'appuient sur des listes explicites qui ne doivent que diminuer :

  • LONG_FILE_DEBT dans tools/check_conventions.py : les fichiers qui dépassent encore 300 lignes.
  • DECLARED_MERGES dans tools/report_merged_functions.py : les fonctions volontairement construites par ajouts successifs.

Workflow Git

  • Une branche par fonctionnalité, puis Pull Request vers main.
  • Commits courts, format conventionnel avec gitmoji après les deux-points :
    • feat(modes): ✨ Ajout du mode Block Party
    • fix(race): 🐛 Correction des checkpoints sur rainbow_road
    • perf(engine): ⚡️ Réduction du coût du tick
    • build: 🚀 Built with latest StewBeet version
  • Les releases sont publiées avec python upload.py (nécessite ~/stewbeet/credentials.yml).

About

Data pack gérant le serveur switch de Paralya V2

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

GitHubDiscordStewBeet

Dépôt GitHub du serveur Switch

Le datapack et le resource pack du serveur Switch (Paralya) : un lobby, une infinité de mini-jeux votés entre chaque partie, un système de maps régénérables, des boutiques, des statistiques et des succès.

Tout est généré en Python avec StewBeet, un framework au-dessus de beet. Aucun fichier .mcfunction n'est écrit à la main : ils sont produits par le code de src/.

Documents (Google Sheet) :


Sommaire


Prérequis

  • Python 3.14+
  • StewBeet : pip install -U stewbeet

Build

Une seule commande, à la racine du dépôt :

stewbeet
CommandeEffet
stewbeetBuild complet (équivaut à stewbeet build)
stewbeet rebuildNettoie les caches puis rebuild
stewbeet cleanNettoie les caches et les dossiers de sortie
stewbeet --helpListe toutes les commandes

Le build remplit build/ puis copie les .zip vers les chemins de build_copy_destinations (beet.yml).

Ces chemins sont personnels (resourcepacks local, SFTP du serveur) : adaptez-les chez vous, mais ne committez pas vos chemins locaux.


⚠️ Ne jamais éditer le dossier build

build/ est entièrement généré et écrasé à chaque build : il n'est versionné que parce que le serveur pull le dépôt, donc on ne le commit qu'après un vrai stewbeet.

Pour retrouver le code d'une commande in-game, grep son texte dans src/ : le chemin de la fonction est le premier argument de write_function.


Structure du dépôt

Switch/
├── ⚙️ beet.yml # Config du projet et du pipeline de build
├── 🚀 upload.py # Publication d'une release GitHub
├── 🧰 tools/ # Scaffolding et garde-fous (voir plus bas)
├── 🐍 src/ # TOUT le code source
│ ├── setup_definitions.py # Étape 1 : items, blocs, matériaux, disques
│ ├── link.py # Étape 2 : appelle tous les générateurs
│ ├── validation.py # Contrôles de cohérence, échoue le build
│ ├── 📦 database/ # Items et comportements de blocs
│ ├── 🎨 resource_pack/ # Langues, sounds.json, shaders, textures GUI, fonts
│ └── 📂 datapack/
│ ├── main.py # Définitions brutes du datapack
│ ├── definitions/ # Advancements, dimensions, loot tables, prédicats, tags...
│ ├── 🎮 modes/ # Un dossier par mini-jeu, + spec/catalogue/emit
│ ├── 🧠 engine/ # Vote, démarrage, arrêt, signaux, pop-ups
│ ├── 🧍 player/ # Layout d'inventaire, practice, jump timer
│ ├── 🏛️ lobby/ # Boards, leaderboards, PNJ, tick hors partie
│ ├── 🗺️ maps/ # Chargement, géométrie, et generation/ (régénération)
│ ├── 🎒 kits/ # Modèle déclaratif des kits (Kit, KitItem, rôles)
│ ├── 🛒 shop/ # Boutiques (consomme le registre des modes)
│ ├── 📊 stats/ # Classements et statistiques
│ ├── 🏆 advancements/ # Succès et pourcentages
│ ├── 🎬 cinematic/ # Cinématiques d'intro
│ ├── 🎵 music/ # Lecteur de musique et Note Block Studio
│ ├── 🌐 translations/ # Textes partagés FR/EN
│ ├── 🔧 utils/ # Primitives appelées par une vingtaine de modes
│ ├── 🛠️ devtools/ # test_mode, lag artificiel, profiling
│ └── 🌱 root/ # load, tick, second et fonctions racine
├── 🖼️ assets/ # Textures, sons, disques, pack.png
├── 📚 libs/ # Packs externes fusionnés au build
├── 🎼 note_block_studio/ # Musiques (midi, datapacks générés)
├── 📤 continuous_delivery/ # Config de la release GitHub
└── ⛔ build/ # SORTIE GÉNÉRÉE, ne jamais éditer

Le pipeline de build

L'ordre vient de la clé pipeline de beet.yml. Deux entrées seulement sont du code maison :

  1. src.setup_definitions remplit la base d'items et de blocs (Item, Block, matériaux de ORES_CONFIGS, disques) et charge les définitions écrites à la main. Les plugins StewBeet suivants s'appuient dessus.
  2. src.link appelle generate_all_modes() puis chaque générateur de sous-système : c'est là que la quasi-totalité des .mcfunction sont écrites.

Le reste vient de plugins StewBeet : headers, constantes de scoreboard, dépendances, merge Smithed Weld, zip, copie, sha1.

src/datapack/modes/__init__.pyimporte dynamiquement chaque dossier contenant un main.py et appelle son write_mode(), puis le write_resources() de son resources.py s'il existe. Aucune liste d'imports à maintenir.


Où se trouve quoi ?

Je veux modifier...Fichier
🗳️ La liste des mini-jeux votables, descriptions, temps estimé, auteurssrc/datapack/modes/catalogue.py (MODES)
🧩 Les groupes de vote (variantes sous une même entrée)src/datapack/modes/catalogue.py (GROUPS)
🎮 La logique d'un mini-jeusrc/datapack/modes/<mode>/main.py
🌐 Les messages FR/EN d'un mini-jeusrc/datapack/modes/<mode>/translations.py
📜 Les advancements, prédicats, loot tables, structures d'un mini-jeusrc/datapack/modes/<mode>/resources.py
🔁 Un helper partagé entre plusieurs modessrc/datapack/modes/emit.py ou src/datapack/modes/_common/main.py
🧠 Le vote, le lancement, l'arrêt d'une partie, les signauxsrc/datapack/engine/main.py
🌍 Les maps de jeu (dimensions, régénération, zones)src/datapack/survival_maps/definitions.py
🏁 Les checkpoints de course et les cycles de spawnsrc/datapack/maps/main.py
🎒 Un kit ou une classesrc/datapack/kits/ et src/datapack/modes/<mode>/kits.py
🛒 Une boutiquesrc/datapack/modes/<mode>/shop.py, agrégé par src/datapack/shop/shared_memory.py
📦 Un item ou un bloc customsrc/database/misc_items.py, src/database/blocks_behaviors.py
⛏️ Les matériaux générés (armures, outils)src/setup_definitions.py (ORES_CONFIGS)
🖼️ Une texture d'itemassets/textures/**/<item_id>.png, détection automatique
🔊 Un sonassets/sounds/, ou src/datapack/modes/<mode>/sounds/ pour un son propre à un mode
🛠️ Un outil de dev (test mode, lag, profiling)src/datapack/devtools/
✨ Les shaderssrc/resource_pack/shaders.py
🪟 Les textures de GUI et de tooltipssrc/resource_pack/textures.py

Ajouter un mode de jeu

1. Générer le squelette

python tools/new_mode.py mon_mode

Le mode obtenu build et se lance en jeu immédiatement. La découverte est automatique : aucun import à ajouter ailleurs.

src/datapack/modes/mon_mode/
├── __init__.py # vide
├── main.py # write_mode()
└── translations.py # write_translations()

2. Remplir main.py

Le fichier généré ressemble déjà à ceci, avec les six hooks branchés. Il ne reste qu'à écrire les mécaniques du jeu.

# ImportsfromstewbeetimportMem, write_functionfrom ..emitimportwrite_modes_calls, write_time_xp_barfrom .translationsimportwrite_translationsdefwrite_mode():
ns: str=Mem.ctx.project_idmode: str="mon_mode"path: str=f"{ns}:modes/{mode}"# Écrit /calls/* (le dispatch appelé par le moteur) et /_force_startwrite_modes_calls(mode)
write_translations()
# /start : appelé une fois au lancement de la partiewrite_function(f"{path}/start", f"""effect give @a[tag=!detached] saturation infinite 255 true# Choix de la map parmi une liste (les ids viennent de survival_maps/definitions.py)scoreboard players set #do_spreadplayers {ns}.data 1function {ns}:utils/choose_map_for {{id:"{mode}", maps:["switch_space"]}}scoreboard players set #mon_mode_seconds {ns}.data -6scoreboard players set #process_end {ns}.data 0""")
# /tick : chaque tick de jeuwrite_function(f"{path}/tick", f"""function {ns}:utils/on_death_run_function {{function:"{path}/death"}}""")
# /second : chaque seconde de jeuwrite_function(f"{path}/second", f"""scoreboard players add #mon_mode_seconds {ns}.data 1function {path}/xp_barfunction {path}/translations/second""")
# /joined : un joueur rejoint en cours de partiewrite_function(f"{path}/joined", f"""gamemode spectator @s""")
# /stop : nettoyage en fin de partiewrite_function(f"{path}/stop", f"""scoreboard objectives remove {ns}.temp.mon_score""")
# /xp_bar : barre d'XP servant de timerwrite_time_xp_bar(f"{path}/xp_bar", 300, "#mon_mode_seconds", "#mon_mode_seconds")

Les hooks disponibles sont joined, second, start, stop, tick et inventory_changed. Le moteur les appelle via switch:engine/signals/macro_*, qui redirige vers switch:modes/<mode>/calls/<hook>. write_modes_calls() génère ces redirections et le garde-fou sur current_game qui empêche un mode de tourner pendant qu'un autre est en cours.

3. Déclarer le mode dans la liste de vote

Dans src/datapack/modes/definitions.py, ajoutez une entrée à MODES :

{
"min_players":2, "max_players":-1, "id":"mon_mode", "name_fr":"Mon Mode",
"estimated_time": "2-5 mins", "inspiration": "Épicube", "suggested_by": "Pseudo", "developed_by": "Pseudo",
"description": {
"fr": [{"text":"Première ligne de description.\n"},{"text":"Deuxième ligne.\n"}],
"en": [{"text":"First description line.\n"},{"text":"Second line.\n"}]
},
},
Clé / comportementDétail
idExactement le nom du dossier
max_playersUNLIMITED quand le mode n'a pas de plafond
groupOptionnel : les jeux d'un même groupe forment une entrée de vote, puis un second vote départage (voir GROUPS)
Lore, index, pop-upsGénérés automatiquement à partir de cette entrée
Entrée commentéeLe mode est quand même généré : testable via _force_start sans polluer le vote
Erreur de saisieLe build échoue avec un message qui nomme le problème et suggère la valeur la plus proche

4. Tester en jeu

/function switch:devtools/test_mode
/function switch:modes/mon_mode/_force_start

5. Ressources supplémentaires (optionnel)

Advancements, prédicats, loot tables, item modifiers, tags ou structures vont dans un resources.py exposant write_resources(), appelé automatiquement après write_mode().


Anatomie d'un mode

FichierRôleAppelé par
main.pywrite_mode() : toute la logique, écrit les .mcfunctionautomatique
translations.pywrite_translations() : messages FR/EN dans <mode>/translations/*write_mode()
resources.pywrite_resources() : advancements, prédicats, loot tables, tags, structuresautomatique
kits.pyKits et classes du mode, construits avec Kit et KitItemwrite_mode()
shop.pyConstante SHOP : les upgrades vendues par le modeautomatique
structures/Fichiers .nbt, enregistrés via register_structures()resources.py
sounds/Fichiers .ogg propres au mode, via register_sounds()resources.py
  • _common/ : fonctions partagées écrites dans switch:modes/_common/* (mort en spectateur, fin de partie, barre d'XP, kits communs).
  • _coupdetat/ : pseudo-mode utilisé par le moteur, pas un mini-jeu votable.
  • Les helpers Python partagés vivent dans src/datapack/modes/emit.py : write_modes_calls, write_server_announce, write_time_xp_bar, write_no_drop, register_structures, register_sounds.
  • Le namespace n'est jamais écrit en dur : utilisez toujours ns: str = Mem.ctx.project_id.

Ajouter autre chose

  • Une map : un clone_survival(...) ou fill_survival(...) dans src/datapack/survival_maps/definitions.py (coordonnées, id, nom, auteurs, view de la cinématique). Elle devient utilisable dans le maps:[...] d'un choose_map_for.
  • Un item ou un bloc : Item(...) ou Block(...) dans src/database/misc_items.py. Texture trouvée automatiquement si un .png du même nom existe sous assets/textures/ ; modèle, loot table et recettes sont générés.
  • Un son : le .ogg dans assets/sounds/, ou dans le sounds/ du mode s'il lui est propre. Voir assets/compress_ogg.py, force_mono.py et mp3_to_ogg.py.
  • Une boutique : un shop.py exposant une constante SHOP, sur le modèle de spleef/shop.py. Le registre la récupère tout seul ; son rang dans la boutique vient de SHOP_ORDER (src/datapack/shop/shared_memory.py).

Conventions de code

  • Typage strict : pyright en mode strict (pyrightconfig.json), pas de Any.
  • Indentation par tabulations, alignement des colonnes par espaces.
  • Code auto-explicatif : peu de commentaires, les vraies explications vont dans les docstrings.
  • Préférez le déclaratif et le paramétré à la duplication. Le CI lance jscpd sur src/ pour traquer le copier-coller.
  • Un fichier qui dépasse environ 300 lignes devient un sous-module. Exception assumée : le main.py d'un mode de jeu. Toute la logique du mode reste au même endroit, ce qui rend le Ctrl+F immédiat et évite d'avoir à deviner dans quel fichier se trouve une mécanique. build_battle/main.py fait 935 lignes, et c'est très bien ainsi.
  • Lint : ruff check src tools --fix (la config vit dans ruff.toml).
  • Perfs : le serveur tourne à 20 tps avec beaucoup de joueurs. Évitez les @e non filtrés dans les tick, préférez tags et scores.

Garde-fous

Six outils, tous lançables à la main. Les trois premiers tournent aussi en CI.

CommandeCe qu'elle garantit
ruff check src toolsStyle et imports
pyrightTypage strict, sans Any
python tools/check_conventions.pyTaille des fichiers, pureté du modèle, aucun import descendant vers un mode nommé
python tools/check_output_drift.pyLe refactoring n'a rien changé : rebuild puis build/ identique à HEAD
python tools/report_merged_functions.pyAucune fonction n'est écrite par deux émetteurs sans que ce soit déclaré
python tools/check_rename_only.py "a=b"Un diff de build/ est exactement les renommages annoncés, et rien d'autre

Ces deux derniers vont par paire : check_output_drift.py prouve qu'un refactoring n'a rien changé, check_rename_only.py prouve qu'un déplacement de chemins n'a fait que déplacer. Le plus important reste check_output_drift.py. write_functionajoute à la suite par défaut, donc deux émetteurs visant le même chemin fusionnent silencieusement dans l'ordre d'appel. Après tout déplacement de code, un build/ inchangé est la preuve que rien n'a bougé en jeu.

Deux règles s'appuient sur des listes explicites qui ne doivent que diminuer :

  • LONG_FILE_DEBT dans tools/check_conventions.py : les fichiers qui dépassent encore 300 lignes.
  • DECLARED_MERGES dans tools/report_merged_functions.py : les fonctions volontairement construites par ajouts successifs.

Workflow Git

  • Une branche par fonctionnalité, puis Pull Request vers main.
  • Commits courts, format conventionnel avec gitmoji après les deux-points :
    • feat(modes): ✨ Ajout du mode Block Party
    • fix(race): 🐛 Correction des checkpoints sur rainbow_road
    • perf(engine): ⚡️ Réduction du coût du tick
    • build: 🚀 Built with latest StewBeet version
  • Les releases sont publiées avec python upload.py (nécessite ~/stewbeet/credentials.yml).

About

Data pack gérant le serveur switch de Paralya V2

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

GitHubDiscordStewBeet

Dépôt GitHub du serveur Switch

Le datapack et le resource pack du serveur Switch (Paralya) : un lobby, une infinité de mini-jeux votés entre chaque partie, un système de maps régénérables, des boutiques, des statistiques et des succès.

Tout est généré en Python avec StewBeet, un framework au-dessus de beet. Aucun fichier .mcfunction n'est écrit à la main : ils sont produits par le code de src/.

Documents (Google Sheet) :


Sommaire


Prérequis

  • Python 3.14+
  • StewBeet : pip install -U stewbeet

Build

Une seule commande, à la racine du dépôt :

stewbeet
CommandeEffet
stewbeetBuild complet (équivaut à stewbeet build)
stewbeet rebuildNettoie les caches puis rebuild
stewbeet cleanNettoie les caches et les dossiers de sortie
stewbeet --helpListe toutes les commandes

Le build remplit build/ puis copie les .zip vers les chemins de build_copy_destinations (beet.yml).

Ces chemins sont personnels (resourcepacks local, SFTP du serveur) : adaptez-les chez vous, mais ne committez pas vos chemins locaux.


⚠️ Ne jamais éditer le dossier build

build/ est entièrement généré et écrasé à chaque build : il n'est versionné que parce que le serveur pull le dépôt, donc on ne le commit qu'après un vrai stewbeet.

Pour retrouver le code d'une commande in-game, grep son texte dans src/ : le chemin de la fonction est le premier argument de write_function.


Structure du dépôt

Switch/
├── ⚙️ beet.yml # Config du projet et du pipeline de build
├── 🚀 upload.py # Publication d'une release GitHub
├── 🧰 tools/ # Scaffolding et garde-fous (voir plus bas)
├── 🐍 src/ # TOUT le code source
│ ├── setup_definitions.py # Étape 1 : items, blocs, matériaux, disques
│ ├── link.py # Étape 2 : appelle tous les générateurs
│ ├── validation.py # Contrôles de cohérence, échoue le build
│ ├── 📦 database/ # Items et comportements de blocs
│ ├── 🎨 resource_pack/ # Langues, sounds.json, shaders, textures GUI, fonts
│ └── 📂 datapack/
│ ├── main.py # Définitions brutes du datapack
│ ├── definitions/ # Advancements, dimensions, loot tables, prédicats, tags...
│ ├── 🎮 modes/ # Un dossier par mini-jeu, + spec/catalogue/emit
│ ├── 🧠 engine/ # Vote, démarrage, arrêt, signaux, pop-ups
│ ├── 🧍 player/ # Layout d'inventaire, practice, jump timer
│ ├── 🏛️ lobby/ # Boards, leaderboards, PNJ, tick hors partie
│ ├── 🗺️ maps/ # Chargement, géométrie, et generation/ (régénération)
│ ├── 🎒 kits/ # Modèle déclaratif des kits (Kit, KitItem, rôles)
│ ├── 🛒 shop/ # Boutiques (consomme le registre des modes)
│ ├── 📊 stats/ # Classements et statistiques
│ ├── 🏆 advancements/ # Succès et pourcentages
│ ├── 🎬 cinematic/ # Cinématiques d'intro
│ ├── 🎵 music/ # Lecteur de musique et Note Block Studio
│ ├── 🌐 translations/ # Textes partagés FR/EN
│ ├── 🔧 utils/ # Primitives appelées par une vingtaine de modes
│ ├── 🛠️ devtools/ # test_mode, lag artificiel, profiling
│ └── 🌱 root/ # load, tick, second et fonctions racine
├── 🖼️ assets/ # Textures, sons, disques, pack.png
├── 📚 libs/ # Packs externes fusionnés au build
├── 🎼 note_block_studio/ # Musiques (midi, datapacks générés)
├── 📤 continuous_delivery/ # Config de la release GitHub
└── ⛔ build/ # SORTIE GÉNÉRÉE, ne jamais éditer

Le pipeline de build

L'ordre vient de la clé pipeline de beet.yml. Deux entrées seulement sont du code maison :

  1. src.setup_definitions remplit la base d'items et de blocs (Item, Block, matériaux de ORES_CONFIGS, disques) et charge les définitions écrites à la main. Les plugins StewBeet suivants s'appuient dessus.
  2. src.link appelle generate_all_modes() puis chaque générateur de sous-système : c'est là que la quasi-totalité des .mcfunction sont écrites.

Le reste vient de plugins StewBeet : headers, constantes de scoreboard, dépendances, merge Smithed Weld, zip, copie, sha1.

src/datapack/modes/__init__.pyimporte dynamiquement chaque dossier contenant un main.py et appelle son write_mode(), puis le write_resources() de son resources.py s'il existe. Aucune liste d'imports à maintenir.


Où se trouve quoi ?

Je veux modifier...Fichier
🗳️ La liste des mini-jeux votables, descriptions, temps estimé, auteurssrc/datapack/modes/catalogue.py (MODES)
🧩 Les groupes de vote (variantes sous une même entrée)src/datapack/modes/catalogue.py (GROUPS)
🎮 La logique d'un mini-jeusrc/datapack/modes/<mode>/main.py
🌐 Les messages FR/EN d'un mini-jeusrc/datapack/modes/<mode>/translations.py
📜 Les advancements, prédicats, loot tables, structures d'un mini-jeusrc/datapack/modes/<mode>/resources.py
🔁 Un helper partagé entre plusieurs modessrc/datapack/modes/emit.py ou src/datapack/modes/_common/main.py
🧠 Le vote, le lancement, l'arrêt d'une partie, les signauxsrc/datapack/engine/main.py
🌍 Les maps de jeu (dimensions, régénération, zones)src/datapack/survival_maps/definitions.py
🏁 Les checkpoints de course et les cycles de spawnsrc/datapack/maps/main.py
🎒 Un kit ou une classesrc/datapack/kits/ et src/datapack/modes/<mode>/kits.py
🛒 Une boutiquesrc/datapack/modes/<mode>/shop.py, agrégé par src/datapack/shop/shared_memory.py
📦 Un item ou un bloc customsrc/database/misc_items.py, src/database/blocks_behaviors.py
⛏️ Les matériaux générés (armures, outils)src/setup_definitions.py (ORES_CONFIGS)
🖼️ Une texture d'itemassets/textures/**/<item_id>.png, détection automatique
🔊 Un sonassets/sounds/, ou src/datapack/modes/<mode>/sounds/ pour un son propre à un mode
🛠️ Un outil de dev (test mode, lag, profiling)src/datapack/devtools/
✨ Les shaderssrc/resource_pack/shaders.py
🪟 Les textures de GUI et de tooltipssrc/resource_pack/textures.py

Ajouter un mode de jeu

1. Générer le squelette

python tools/new_mode.py mon_mode

Le mode obtenu build et se lance en jeu immédiatement. La découverte est automatique : aucun import à ajouter ailleurs.

src/datapack/modes/mon_mode/
├── __init__.py # vide
├── main.py # write_mode()
└── translations.py # write_translations()

2. Remplir main.py

Le fichier généré ressemble déjà à ceci, avec les six hooks branchés. Il ne reste qu'à écrire les mécaniques du jeu.

# ImportsfromstewbeetimportMem, write_functionfrom ..emitimportwrite_modes_calls, write_time_xp_barfrom .translationsimportwrite_translationsdefwrite_mode():
ns: str=Mem.ctx.project_idmode: str="mon_mode"path: str=f"{ns}:modes/{mode}"# Écrit /calls/* (le dispatch appelé par le moteur) et /_force_startwrite_modes_calls(mode)
write_translations()
# /start : appelé une fois au lancement de la partiewrite_function(f"{path}/start", f"""effect give @a[tag=!detached] saturation infinite 255 true# Choix de la map parmi une liste (les ids viennent de survival_maps/definitions.py)scoreboard players set #do_spreadplayers {ns}.data 1function {ns}:utils/choose_map_for {{id:"{mode}", maps:["switch_space"]}}scoreboard players set #mon_mode_seconds {ns}.data -6scoreboard players set #process_end {ns}.data 0""")
# /tick : chaque tick de jeuwrite_function(f"{path}/tick", f"""function {ns}:utils/on_death_run_function {{function:"{path}/death"}}""")
# /second : chaque seconde de jeuwrite_function(f"{path}/second", f"""scoreboard players add #mon_mode_seconds {ns}.data 1function {path}/xp_barfunction {path}/translations/second""")
# /joined : un joueur rejoint en cours de partiewrite_function(f"{path}/joined", f"""gamemode spectator @s""")
# /stop : nettoyage en fin de partiewrite_function(f"{path}/stop", f"""scoreboard objectives remove {ns}.temp.mon_score""")
# /xp_bar : barre d'XP servant de timerwrite_time_xp_bar(f"{path}/xp_bar", 300, "#mon_mode_seconds", "#mon_mode_seconds")

Les hooks disponibles sont joined, second, start, stop, tick et inventory_changed. Le moteur les appelle via switch:engine/signals/macro_*, qui redirige vers switch:modes/<mode>/calls/<hook>. write_modes_calls() génère ces redirections et le garde-fou sur current_game qui empêche un mode de tourner pendant qu'un autre est en cours.

3. Déclarer le mode dans la liste de vote

Dans src/datapack/modes/definitions.py, ajoutez une entrée à MODES :

{
"min_players":2, "max_players":-1, "id":"mon_mode", "name_fr":"Mon Mode",
"estimated_time": "2-5 mins", "inspiration": "Épicube", "suggested_by": "Pseudo", "developed_by": "Pseudo",
"description": {
"fr": [{"text":"Première ligne de description.\n"},{"text":"Deuxième ligne.\n"}],
"en": [{"text":"First description line.\n"},{"text":"Second line.\n"}]
},
},
Clé / comportementDétail
idExactement le nom du dossier
max_playersUNLIMITED quand le mode n'a pas de plafond
groupOptionnel : les jeux d'un même groupe forment une entrée de vote, puis un second vote départage (voir GROUPS)
Lore, index, pop-upsGénérés automatiquement à partir de cette entrée
Entrée commentéeLe mode est quand même généré : testable via _force_start sans polluer le vote
Erreur de saisieLe build échoue avec un message qui nomme le problème et suggère la valeur la plus proche

4. Tester en jeu

/function switch:devtools/test_mode
/function switch:modes/mon_mode/_force_start

5. Ressources supplémentaires (optionnel)

Advancements, prédicats, loot tables, item modifiers, tags ou structures vont dans un resources.py exposant write_resources(), appelé automatiquement après write_mode().


Anatomie d'un mode

FichierRôleAppelé par
main.pywrite_mode() : toute la logique, écrit les .mcfunctionautomatique
translations.pywrite_translations() : messages FR/EN dans <mode>/translations/*write_mode()
resources.pywrite_resources() : advancements, prédicats, loot tables, tags, structuresautomatique
kits.pyKits et classes du mode, construits avec Kit et KitItemwrite_mode()
shop.pyConstante SHOP : les upgrades vendues par le modeautomatique
structures/Fichiers .nbt, enregistrés via register_structures()resources.py
sounds/Fichiers .ogg propres au mode, via register_sounds()resources.py
  • _common/ : fonctions partagées écrites dans switch:modes/_common/* (mort en spectateur, fin de partie, barre d'XP, kits communs).
  • _coupdetat/ : pseudo-mode utilisé par le moteur, pas un mini-jeu votable.
  • Les helpers Python partagés vivent dans src/datapack/modes/emit.py : write_modes_calls, write_server_announce, write_time_xp_bar, write_no_drop, register_structures, register_sounds.
  • Le namespace n'est jamais écrit en dur : utilisez toujours ns: str = Mem.ctx.project_id.

Ajouter autre chose

  • Une map : un clone_survival(...) ou fill_survival(...) dans src/datapack/survival_maps/definitions.py (coordonnées, id, nom, auteurs, view de la cinématique). Elle devient utilisable dans le maps:[...] d'un choose_map_for.
  • Un item ou un bloc : Item(...) ou Block(...) dans src/database/misc_items.py. Texture trouvée automatiquement si un .png du même nom existe sous assets/textures/ ; modèle, loot table et recettes sont générés.
  • Un son : le .ogg dans assets/sounds/, ou dans le sounds/ du mode s'il lui est propre. Voir assets/compress_ogg.py, force_mono.py et mp3_to_ogg.py.
  • Une boutique : un shop.py exposant une constante SHOP, sur le modèle de spleef/shop.py. Le registre la récupère tout seul ; son rang dans la boutique vient de SHOP_ORDER (src/datapack/shop/shared_memory.py).

Conventions de code

  • Typage strict : pyright en mode strict (pyrightconfig.json), pas de Any.
  • Indentation par tabulations, alignement des colonnes par espaces.
  • Code auto-explicatif : peu de commentaires, les vraies explications vont dans les docstrings.
  • Préférez le déclaratif et le paramétré à la duplication. Le CI lance jscpd sur src/ pour traquer le copier-coller.
  • Un fichier qui dépasse environ 300 lignes devient un sous-module. Exception assumée : le main.py d'un mode de jeu. Toute la logique du mode reste au même endroit, ce qui rend le Ctrl+F immédiat et évite d'avoir à deviner dans quel fichier se trouve une mécanique. build_battle/main.py fait 935 lignes, et c'est très bien ainsi.
  • Lint : ruff check src tools --fix (la config vit dans ruff.toml).
  • Perfs : le serveur tourne à 20 tps avec beaucoup de joueurs. Évitez les @e non filtrés dans les tick, préférez tags et scores.

Garde-fous

Six outils, tous lançables à la main. Les trois premiers tournent aussi en CI.

CommandeCe qu'elle garantit
ruff check src toolsStyle et imports
pyrightTypage strict, sans Any
python tools/check_conventions.pyTaille des fichiers, pureté du modèle, aucun import descendant vers un mode nommé
python tools/check_output_drift.pyLe refactoring n'a rien changé : rebuild puis build/ identique à HEAD
python tools/report_merged_functions.pyAucune fonction n'est écrite par deux émetteurs sans que ce soit déclaré
python tools/check_rename_only.py "a=b"Un diff de build/ est exactement les renommages annoncés, et rien d'autre

Ces deux derniers vont par paire : check_output_drift.py prouve qu'un refactoring n'a rien changé, check_rename_only.py prouve qu'un déplacement de chemins n'a fait que déplacer. Le plus important reste check_output_drift.py. write_functionajoute à la suite par défaut, donc deux émetteurs visant le même chemin fusionnent silencieusement dans l'ordre d'appel. Après tout déplacement de code, un build/ inchangé est la preuve que rien n'a bougé en jeu.

Deux règles s'appuient sur des listes explicites qui ne doivent que diminuer :

  • LONG_FILE_DEBT dans tools/check_conventions.py : les fichiers qui dépassent encore 300 lignes.
  • DECLARED_MERGES dans tools/report_merged_functions.py : les fonctions volontairement construites par ajouts successifs.

Workflow Git

  • Une branche par fonctionnalité, puis Pull Request vers main.
  • Commits courts, format conventionnel avec gitmoji après les deux-points :
    • feat(modes): ✨ Ajout du mode Block Party
    • fix(race): 🐛 Correction des checkpoints sur rainbow_road
    • perf(engine): ⚡️ Réduction du coût du tick
    • build: 🚀 Built with latest StewBeet version
  • Les releases sont publiées avec python upload.py (nécessite ~/stewbeet/credentials.yml).

About

Data pack gérant le serveur switch de Paralya V2

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

GitHubDiscordStewBeet

Dépôt GitHub du serveur Switch

Le datapack et le resource pack du serveur Switch (Paralya) : un lobby, une infinité de mini-jeux votés entre chaque partie, un système de maps régénérables, des boutiques, des statistiques et des succès.

Tout est généré en Python avec StewBeet, un framework au-dessus de beet. Aucun fichier .mcfunction n'est écrit à la main : ils sont produits par le code de src/.

Documents (Google Sheet) :


Sommaire


Prérequis

  • Python 3.14+
  • StewBeet : pip install -U stewbeet

Build

Une seule commande, à la racine du dépôt :

stewbeet
CommandeEffet
stewbeetBuild complet (équivaut à stewbeet build)
stewbeet rebuildNettoie les caches puis rebuild
stewbeet cleanNettoie les caches et les dossiers de sortie
stewbeet --helpListe toutes les commandes

Le build remplit build/ puis copie les .zip vers les chemins de build_copy_destinations (beet.yml).

Ces chemins sont personnels (resourcepacks local, SFTP du serveur) : adaptez-les chez vous, mais ne committez pas vos chemins locaux.


⚠️ Ne jamais éditer le dossier build

build/ est entièrement généré et écrasé à chaque build : il n'est versionné que parce que le serveur pull le dépôt, donc on ne le commit qu'après un vrai stewbeet.

Pour retrouver le code d'une commande in-game, grep son texte dans src/ : le chemin de la fonction est le premier argument de write_function.


Structure du dépôt

Switch/
├── ⚙️ beet.yml # Config du projet et du pipeline de build
├── 🚀 upload.py # Publication d'une release GitHub
├── 🧰 tools/ # Scaffolding et garde-fous (voir plus bas)
├── 🐍 src/ # TOUT le code source
│ ├── setup_definitions.py # Étape 1 : items, blocs, matériaux, disques
│ ├── link.py # Étape 2 : appelle tous les générateurs
│ ├── validation.py # Contrôles de cohérence, échoue le build
│ ├── 📦 database/ # Items et comportements de blocs
│ ├── 🎨 resource_pack/ # Langues, sounds.json, shaders, textures GUI, fonts
│ └── 📂 datapack/
│ ├── main.py # Définitions brutes du datapack
│ ├── definitions/ # Advancements, dimensions, loot tables, prédicats, tags...
│ ├── 🎮 modes/ # Un dossier par mini-jeu, + spec/catalogue/emit
│ ├── 🧠 engine/ # Vote, démarrage, arrêt, signaux, pop-ups
│ ├── 🧍 player/ # Layout d'inventaire, practice, jump timer
│ ├── 🏛️ lobby/ # Boards, leaderboards, PNJ, tick hors partie
│ ├── 🗺️ maps/ # Chargement, géométrie, et generation/ (régénération)
│ ├── 🎒 kits/ # Modèle déclaratif des kits (Kit, KitItem, rôles)
│ ├── 🛒 shop/ # Boutiques (consomme le registre des modes)
│ ├── 📊 stats/ # Classements et statistiques
│ ├── 🏆 advancements/ # Succès et pourcentages
│ ├── 🎬 cinematic/ # Cinématiques d'intro
│ ├── 🎵 music/ # Lecteur de musique et Note Block Studio
│ ├── 🌐 translations/ # Textes partagés FR/EN
│ ├── 🔧 utils/ # Primitives appelées par une vingtaine de modes
│ ├── 🛠️ devtools/ # test_mode, lag artificiel, profiling
│ └── 🌱 root/ # load, tick, second et fonctions racine
├── 🖼️ assets/ # Textures, sons, disques, pack.png
├── 📚 libs/ # Packs externes fusionnés au build
├── 🎼 note_block_studio/ # Musiques (midi, datapacks générés)
├── 📤 continuous_delivery/ # Config de la release GitHub
└── ⛔ build/ # SORTIE GÉNÉRÉE, ne jamais éditer

Le pipeline de build

L'ordre vient de la clé pipeline de beet.yml. Deux entrées seulement sont du code maison :

  1. src.setup_definitions remplit la base d'items et de blocs (Item, Block, matériaux de ORES_CONFIGS, disques) et charge les définitions écrites à la main. Les plugins StewBeet suivants s'appuient dessus.
  2. src.link appelle generate_all_modes() puis chaque générateur de sous-système : c'est là que la quasi-totalité des .mcfunction sont écrites.

Le reste vient de plugins StewBeet : headers, constantes de scoreboard, dépendances, merge Smithed Weld, zip, copie, sha1.

src/datapack/modes/__init__.pyimporte dynamiquement chaque dossier contenant un main.py et appelle son write_mode(), puis le write_resources() de son resources.py s'il existe. Aucune liste d'imports à maintenir.


Où se trouve quoi ?

Je veux modifier...Fichier
🗳️ La liste des mini-jeux votables, descriptions, temps estimé, auteurssrc/datapack/modes/catalogue.py (MODES)
🧩 Les groupes de vote (variantes sous une même entrée)src/datapack/modes/catalogue.py (GROUPS)
🎮 La logique d'un mini-jeusrc/datapack/modes/<mode>/main.py
🌐 Les messages FR/EN d'un mini-jeusrc/datapack/modes/<mode>/translations.py
📜 Les advancements, prédicats, loot tables, structures d'un mini-jeusrc/datapack/modes/<mode>/resources.py
🔁 Un helper partagé entre plusieurs modessrc/datapack/modes/emit.py ou src/datapack/modes/_common/main.py
🧠 Le vote, le lancement, l'arrêt d'une partie, les signauxsrc/datapack/engine/main.py
🌍 Les maps de jeu (dimensions, régénération, zones)src/datapack/survival_maps/definitions.py
🏁 Les checkpoints de course et les cycles de spawnsrc/datapack/maps/main.py
🎒 Un kit ou une classesrc/datapack/kits/ et src/datapack/modes/<mode>/kits.py
🛒 Une boutiquesrc/datapack/modes/<mode>/shop.py, agrégé par src/datapack/shop/shared_memory.py
📦 Un item ou un bloc customsrc/database/misc_items.py, src/database/blocks_behaviors.py
⛏️ Les matériaux générés (armures, outils)src/setup_definitions.py (ORES_CONFIGS)
🖼️ Une texture d'itemassets/textures/**/<item_id>.png, détection automatique
🔊 Un sonassets/sounds/, ou src/datapack/modes/<mode>/sounds/ pour un son propre à un mode
🛠️ Un outil de dev (test mode, lag, profiling)src/datapack/devtools/
✨ Les shaderssrc/resource_pack/shaders.py
🪟 Les textures de GUI et de tooltipssrc/resource_pack/textures.py

Ajouter un mode de jeu

1. Générer le squelette

python tools/new_mode.py mon_mode

Le mode obtenu build et se lance en jeu immédiatement. La découverte est automatique : aucun import à ajouter ailleurs.

src/datapack/modes/mon_mode/
├── __init__.py # vide
├── main.py # write_mode()
└── translations.py # write_translations()

2. Remplir main.py

Le fichier généré ressemble déjà à ceci, avec les six hooks branchés. Il ne reste qu'à écrire les mécaniques du jeu.

# ImportsfromstewbeetimportMem, write_functionfrom ..emitimportwrite_modes_calls, write_time_xp_barfrom .translationsimportwrite_translationsdefwrite_mode():
ns: str=Mem.ctx.project_idmode: str="mon_mode"path: str=f"{ns}:modes/{mode}"# Écrit /calls/* (le dispatch appelé par le moteur) et /_force_startwrite_modes_calls(mode)
write_translations()
# /start : appelé une fois au lancement de la partiewrite_function(f"{path}/start", f"""effect give @a[tag=!detached] saturation infinite 255 true# Choix de la map parmi une liste (les ids viennent de survival_maps/definitions.py)scoreboard players set #do_spreadplayers {ns}.data 1function {ns}:utils/choose_map_for {{id:"{mode}", maps:["switch_space"]}}scoreboard players set #mon_mode_seconds {ns}.data -6scoreboard players set #process_end {ns}.data 0""")
# /tick : chaque tick de jeuwrite_function(f"{path}/tick", f"""function {ns}:utils/on_death_run_function {{function:"{path}/death"}}""")
# /second : chaque seconde de jeuwrite_function(f"{path}/second", f"""scoreboard players add #mon_mode_seconds {ns}.data 1function {path}/xp_barfunction {path}/translations/second""")
# /joined : un joueur rejoint en cours de partiewrite_function(f"{path}/joined", f"""gamemode spectator @s""")
# /stop : nettoyage en fin de partiewrite_function(f"{path}/stop", f"""scoreboard objectives remove {ns}.temp.mon_score""")
# /xp_bar : barre d'XP servant de timerwrite_time_xp_bar(f"{path}/xp_bar", 300, "#mon_mode_seconds", "#mon_mode_seconds")

Les hooks disponibles sont joined, second, start, stop, tick et inventory_changed. Le moteur les appelle via switch:engine/signals/macro_*, qui redirige vers switch:modes/<mode>/calls/<hook>. write_modes_calls() génère ces redirections et le garde-fou sur current_game qui empêche un mode de tourner pendant qu'un autre est en cours.

3. Déclarer le mode dans la liste de vote

Dans src/datapack/modes/definitions.py, ajoutez une entrée à MODES :

{
"min_players":2, "max_players":-1, "id":"mon_mode", "name_fr":"Mon Mode",
"estimated_time": "2-5 mins", "inspiration": "Épicube", "suggested_by": "Pseudo", "developed_by": "Pseudo",
"description": {
"fr": [{"text":"Première ligne de description.\n"},{"text":"Deuxième ligne.\n"}],
"en": [{"text":"First description line.\n"},{"text":"Second line.\n"}]
},
},
Clé / comportementDétail
idExactement le nom du dossier
max_playersUNLIMITED quand le mode n'a pas de plafond
groupOptionnel : les jeux d'un même groupe forment une entrée de vote, puis un second vote départage (voir GROUPS)
Lore, index, pop-upsGénérés automatiquement à partir de cette entrée
Entrée commentéeLe mode est quand même généré : testable via _force_start sans polluer le vote
Erreur de saisieLe build échoue avec un message qui nomme le problème et suggère la valeur la plus proche

4. Tester en jeu

/function switch:devtools/test_mode
/function switch:modes/mon_mode/_force_start

5. Ressources supplémentaires (optionnel)

Advancements, prédicats, loot tables, item modifiers, tags ou structures vont dans un resources.py exposant write_resources(), appelé automatiquement après write_mode().


Anatomie d'un mode

FichierRôleAppelé par
main.pywrite_mode() : toute la logique, écrit les .mcfunctionautomatique
translations.pywrite_translations() : messages FR/EN dans <mode>/translations/*write_mode()
resources.pywrite_resources() : advancements, prédicats, loot tables, tags, structuresautomatique
kits.pyKits et classes du mode, construits avec Kit et KitItemwrite_mode()
shop.pyConstante SHOP : les upgrades vendues par le modeautomatique
structures/Fichiers .nbt, enregistrés via register_structures()resources.py
sounds/Fichiers .ogg propres au mode, via register_sounds()resources.py
  • _common/ : fonctions partagées écrites dans switch:modes/_common/* (mort en spectateur, fin de partie, barre d'XP, kits communs).
  • _coupdetat/ : pseudo-mode utilisé par le moteur, pas un mini-jeu votable.
  • Les helpers Python partagés vivent dans src/datapack/modes/emit.py : write_modes_calls, write_server_announce, write_time_xp_bar, write_no_drop, register_structures, register_sounds.
  • Le namespace n'est jamais écrit en dur : utilisez toujours ns: str = Mem.ctx.project_id.

Ajouter autre chose

  • Une map : un clone_survival(...) ou fill_survival(...) dans src/datapack/survival_maps/definitions.py (coordonnées, id, nom, auteurs, view de la cinématique). Elle devient utilisable dans le maps:[...] d'un choose_map_for.
  • Un item ou un bloc : Item(...) ou Block(...) dans src/database/misc_items.py. Texture trouvée automatiquement si un .png du même nom existe sous assets/textures/ ; modèle, loot table et recettes sont générés.
  • Un son : le .ogg dans assets/sounds/, ou dans le sounds/ du mode s'il lui est propre. Voir assets/compress_ogg.py, force_mono.py et mp3_to_ogg.py.
  • Une boutique : un shop.py exposant une constante SHOP, sur le modèle de spleef/shop.py. Le registre la récupère tout seul ; son rang dans la boutique vient de SHOP_ORDER (src/datapack/shop/shared_memory.py).

Conventions de code

  • Typage strict : pyright en mode strict (pyrightconfig.json), pas de Any.
  • Indentation par tabulations, alignement des colonnes par espaces.
  • Code auto-explicatif : peu de commentaires, les vraies explications vont dans les docstrings.
  • Préférez le déclaratif et le paramétré à la duplication. Le CI lance jscpd sur src/ pour traquer le copier-coller.
  • Un fichier qui dépasse environ 300 lignes devient un sous-module. Exception assumée : le main.py d'un mode de jeu. Toute la logique du mode reste au même endroit, ce qui rend le Ctrl+F immédiat et évite d'avoir à deviner dans quel fichier se trouve une mécanique. build_battle/main.py fait 935 lignes, et c'est très bien ainsi.
  • Lint : ruff check src tools --fix (la config vit dans ruff.toml).
  • Perfs : le serveur tourne à 20 tps avec beaucoup de joueurs. Évitez les @e non filtrés dans les tick, préférez tags et scores.

Garde-fous

Six outils, tous lançables à la main. Les trois premiers tournent aussi en CI.

CommandeCe qu'elle garantit
ruff check src toolsStyle et imports
pyrightTypage strict, sans Any
python tools/check_conventions.pyTaille des fichiers, pureté du modèle, aucun import descendant vers un mode nommé
python tools/check_output_drift.pyLe refactoring n'a rien changé : rebuild puis build/ identique à HEAD
python tools/report_merged_functions.pyAucune fonction n'est écrite par deux émetteurs sans que ce soit déclaré
python tools/check_rename_only.py "a=b"Un diff de build/ est exactement les renommages annoncés, et rien d'autre

Ces deux derniers vont par paire : check_output_drift.py prouve qu'un refactoring n'a rien changé, check_rename_only.py prouve qu'un déplacement de chemins n'a fait que déplacer. Le plus important reste check_output_drift.py. write_functionajoute à la suite par défaut, donc deux émetteurs visant le même chemin fusionnent silencieusement dans l'ordre d'appel. Après tout déplacement de code, un build/ inchangé est la preuve que rien n'a bougé en jeu.

Deux règles s'appuient sur des listes explicites qui ne doivent que diminuer :

  • LONG_FILE_DEBT dans tools/check_conventions.py : les fichiers qui dépassent encore 300 lignes.
  • DECLARED_MERGES dans tools/report_merged_functions.py : les fonctions volontairement construites par ajouts successifs.

Workflow Git

  • Une branche par fonctionnalité, puis Pull Request vers main.
  • Commits courts, format conventionnel avec gitmoji après les deux-points :
    • feat(modes): ✨ Ajout du mode Block Party
    • fix(race): 🐛 Correction des checkpoints sur rainbow_road
    • perf(engine): ⚡️ Réduction du coût du tick
    • build: 🚀 Built with latest StewBeet version
  • Les releases sont publiées avec python upload.py (nécessite ~/stewbeet/credentials.yml).

About

Data pack gérant le serveur switch de Paralya V2

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

GitHubDiscordStewBeet

Dépôt GitHub du serveur Switch

Le datapack et le resource pack du serveur Switch (Paralya) : un lobby, une infinité de mini-jeux votés entre chaque partie, un système de maps régénérables, des boutiques, des statistiques et des succès.

Tout est généré en Python avec StewBeet, un framework au-dessus de beet. Aucun fichier .mcfunction n'est écrit à la main : ils sont produits par le code de src/.

Documents (Google Sheet) :


Sommaire


Prérequis

  • Python 3.14+
  • StewBeet : pip install -U stewbeet

Build

Une seule commande, à la racine du dépôt :

stewbeet
CommandeEffet
stewbeetBuild complet (équivaut à stewbeet build)
stewbeet rebuildNettoie les caches puis rebuild
stewbeet cleanNettoie les caches et les dossiers de sortie
stewbeet --helpListe toutes les commandes

Le build remplit build/ puis copie les .zip vers les chemins de build_copy_destinations (beet.yml).

Ces chemins sont personnels (resourcepacks local, SFTP du serveur) : adaptez-les chez vous, mais ne committez pas vos chemins locaux.


⚠️ Ne jamais éditer le dossier build

build/ est entièrement généré et écrasé à chaque build : il n'est versionné que parce que le serveur pull le dépôt, donc on ne le commit qu'après un vrai stewbeet.

Pour retrouver le code d'une commande in-game, grep son texte dans src/ : le chemin de la fonction est le premier argument de write_function.


Structure du dépôt

Switch/
├── ⚙️ beet.yml # Config du projet et du pipeline de build
├── 🚀 upload.py # Publication d'une release GitHub
├── 🧰 tools/ # Scaffolding et garde-fous (voir plus bas)
├── 🐍 src/ # TOUT le code source
│ ├── setup_definitions.py # Étape 1 : items, blocs, matériaux, disques
│ ├── link.py # Étape 2 : appelle tous les générateurs
│ ├── validation.py # Contrôles de cohérence, échoue le build
│ ├── 📦 database/ # Items et comportements de blocs
│ ├── 🎨 resource_pack/ # Langues, sounds.json, shaders, textures GUI, fonts
│ └── 📂 datapack/
│ ├── main.py # Définitions brutes du datapack
│ ├── definitions/ # Advancements, dimensions, loot tables, prédicats, tags...
│ ├── 🎮 modes/ # Un dossier par mini-jeu, + spec/catalogue/emit
│ ├── 🧠 engine/ # Vote, démarrage, arrêt, signaux, pop-ups
│ ├── 🧍 player/ # Layout d'inventaire, practice, jump timer
│ ├── 🏛️ lobby/ # Boards, leaderboards, PNJ, tick hors partie
│ ├── 🗺️ maps/ # Chargement, géométrie, et generation/ (régénération)
│ ├── 🎒 kits/ # Modèle déclaratif des kits (Kit, KitItem, rôles)
│ ├── 🛒 shop/ # Boutiques (consomme le registre des modes)
│ ├── 📊 stats/ # Classements et statistiques
│ ├── 🏆 advancements/ # Succès et pourcentages
│ ├── 🎬 cinematic/ # Cinématiques d'intro
│ ├── 🎵 music/ # Lecteur de musique et Note Block Studio
│ ├── 🌐 translations/ # Textes partagés FR/EN
│ ├── 🔧 utils/ # Primitives appelées par une vingtaine de modes
│ ├── 🛠️ devtools/ # test_mode, lag artificiel, profiling
│ └── 🌱 root/ # load, tick, second et fonctions racine
├── 🖼️ assets/ # Textures, sons, disques, pack.png
├── 📚 libs/ # Packs externes fusionnés au build
├── 🎼 note_block_studio/ # Musiques (midi, datapacks générés)
├── 📤 continuous_delivery/ # Config de la release GitHub
└── ⛔ build/ # SORTIE GÉNÉRÉE, ne jamais éditer

Le pipeline de build

L'ordre vient de la clé pipeline de beet.yml. Deux entrées seulement sont du code maison :

  1. src.setup_definitions remplit la base d'items et de blocs (Item, Block, matériaux de ORES_CONFIGS, disques) et charge les définitions écrites à la main. Les plugins StewBeet suivants s'appuient dessus.
  2. src.link appelle generate_all_modes() puis chaque générateur de sous-système : c'est là que la quasi-totalité des .mcfunction sont écrites.

Le reste vient de plugins StewBeet : headers, constantes de scoreboard, dépendances, merge Smithed Weld, zip, copie, sha1.

src/datapack/modes/__init__.pyimporte dynamiquement chaque dossier contenant un main.py et appelle son write_mode(), puis le write_resources() de son resources.py s'il existe. Aucune liste d'imports à maintenir.


Où se trouve quoi ?

Je veux modifier...Fichier
🗳️ La liste des mini-jeux votables, descriptions, temps estimé, auteurssrc/datapack/modes/catalogue.py (MODES)
🧩 Les groupes de vote (variantes sous une même entrée)src/datapack/modes/catalogue.py (GROUPS)
🎮 La logique d'un mini-jeusrc/datapack/modes/<mode>/main.py
🌐 Les messages FR/EN d'un mini-jeusrc/datapack/modes/<mode>/translations.py
📜 Les advancements, prédicats, loot tables, structures d'un mini-jeusrc/datapack/modes/<mode>/resources.py
🔁 Un helper partagé entre plusieurs modessrc/datapack/modes/emit.py ou src/datapack/modes/_common/main.py
🧠 Le vote, le lancement, l'arrêt d'une partie, les signauxsrc/datapack/engine/main.py
🌍 Les maps de jeu (dimensions, régénération, zones)src/datapack/survival_maps/definitions.py
🏁 Les checkpoints de course et les cycles de spawnsrc/datapack/maps/main.py
🎒 Un kit ou une classesrc/datapack/kits/ et src/datapack/modes/<mode>/kits.py
🛒 Une boutiquesrc/datapack/modes/<mode>/shop.py, agrégé par src/datapack/shop/shared_memory.py
📦 Un item ou un bloc customsrc/database/misc_items.py, src/database/blocks_behaviors.py
⛏️ Les matériaux générés (armures, outils)src/setup_definitions.py (ORES_CONFIGS)
🖼️ Une texture d'itemassets/textures/**/<item_id>.png, détection automatique
🔊 Un sonassets/sounds/, ou src/datapack/modes/<mode>/sounds/ pour un son propre à un mode
🛠️ Un outil de dev (test mode, lag, profiling)src/datapack/devtools/
✨ Les shaderssrc/resource_pack/shaders.py
🪟 Les textures de GUI et de tooltipssrc/resource_pack/textures.py

Ajouter un mode de jeu

1. Générer le squelette

python tools/new_mode.py mon_mode

Le mode obtenu build et se lance en jeu immédiatement. La découverte est automatique : aucun import à ajouter ailleurs.

src/datapack/modes/mon_mode/
├── __init__.py # vide
├── main.py # write_mode()
└── translations.py # write_translations()

2. Remplir main.py

Le fichier généré ressemble déjà à ceci, avec les six hooks branchés. Il ne reste qu'à écrire les mécaniques du jeu.

# ImportsfromstewbeetimportMem, write_functionfrom ..emitimportwrite_modes_calls, write_time_xp_barfrom .translationsimportwrite_translationsdefwrite_mode():
ns: str=Mem.ctx.project_idmode: str="mon_mode"path: str=f"{ns}:modes/{mode}"# Écrit /calls/* (le dispatch appelé par le moteur) et /_force_startwrite_modes_calls(mode)
write_translations()
# /start : appelé une fois au lancement de la partiewrite_function(f"{path}/start", f"""effect give @a[tag=!detached] saturation infinite 255 true# Choix de la map parmi une liste (les ids viennent de survival_maps/definitions.py)scoreboard players set #do_spreadplayers {ns}.data 1function {ns}:utils/choose_map_for {{id:"{mode}", maps:["switch_space"]}}scoreboard players set #mon_mode_seconds {ns}.data -6scoreboard players set #process_end {ns}.data 0""")
# /tick : chaque tick de jeuwrite_function(f"{path}/tick", f"""function {ns}:utils/on_death_run_function {{function:"{path}/death"}}""")
# /second : chaque seconde de jeuwrite_function(f"{path}/second", f"""scoreboard players add #mon_mode_seconds {ns}.data 1function {path}/xp_barfunction {path}/translations/second""")
# /joined : un joueur rejoint en cours de partiewrite_function(f"{path}/joined", f"""gamemode spectator @s""")
# /stop : nettoyage en fin de partiewrite_function(f"{path}/stop", f"""scoreboard objectives remove {ns}.temp.mon_score""")
# /xp_bar : barre d'XP servant de timerwrite_time_xp_bar(f"{path}/xp_bar", 300, "#mon_mode_seconds", "#mon_mode_seconds")

Les hooks disponibles sont joined, second, start, stop, tick et inventory_changed. Le moteur les appelle via switch:engine/signals/macro_*, qui redirige vers switch:modes/<mode>/calls/<hook>. write_modes_calls() génère ces redirections et le garde-fou sur current_game qui empêche un mode de tourner pendant qu'un autre est en cours.

3. Déclarer le mode dans la liste de vote

Dans src/datapack/modes/definitions.py, ajoutez une entrée à MODES :

{
"min_players":2, "max_players":-1, "id":"mon_mode", "name_fr":"Mon Mode",
"estimated_time": "2-5 mins", "inspiration": "Épicube", "suggested_by": "Pseudo", "developed_by": "Pseudo",
"description": {
"fr": [{"text":"Première ligne de description.\n"},{"text":"Deuxième ligne.\n"}],
"en": [{"text":"First description line.\n"},{"text":"Second line.\n"}]
},
},
Clé / comportementDétail
idExactement le nom du dossier
max_playersUNLIMITED quand le mode n'a pas de plafond
groupOptionnel : les jeux d'un même groupe forment une entrée de vote, puis un second vote départage (voir GROUPS)
Lore, index, pop-upsGénérés automatiquement à partir de cette entrée
Entrée commentéeLe mode est quand même généré : testable via _force_start sans polluer le vote
Erreur de saisieLe build échoue avec un message qui nomme le problème et suggère la valeur la plus proche

4. Tester en jeu

/function switch:devtools/test_mode
/function switch:modes/mon_mode/_force_start

5. Ressources supplémentaires (optionnel)

Advancements, prédicats, loot tables, item modifiers, tags ou structures vont dans un resources.py exposant write_resources(), appelé automatiquement après write_mode().


Anatomie d'un mode

FichierRôleAppelé par
main.pywrite_mode() : toute la logique, écrit les .mcfunctionautomatique
translations.pywrite_translations() : messages FR/EN dans <mode>/translations/*write_mode()
resources.pywrite_resources() : advancements, prédicats, loot tables, tags, structuresautomatique
kits.pyKits et classes du mode, construits avec Kit et KitItemwrite_mode()
shop.pyConstante SHOP : les upgrades vendues par le modeautomatique
structures/Fichiers .nbt, enregistrés via register_structures()resources.py
sounds/Fichiers .ogg propres au mode, via register_sounds()resources.py
  • _common/ : fonctions partagées écrites dans switch:modes/_common/* (mort en spectateur, fin de partie, barre d'XP, kits communs).
  • _coupdetat/ : pseudo-mode utilisé par le moteur, pas un mini-jeu votable.
  • Les helpers Python partagés vivent dans src/datapack/modes/emit.py : write_modes_calls, write_server_announce, write_time_xp_bar, write_no_drop, register_structures, register_sounds.
  • Le namespace n'est jamais écrit en dur : utilisez toujours ns: str = Mem.ctx.project_id.

Ajouter autre chose

  • Une map : un clone_survival(...) ou fill_survival(...) dans src/datapack/survival_maps/definitions.py (coordonnées, id, nom, auteurs, view de la cinématique). Elle devient utilisable dans le maps:[...] d'un choose_map_for.
  • Un item ou un bloc : Item(...) ou Block(...) dans src/database/misc_items.py. Texture trouvée automatiquement si un .png du même nom existe sous assets/textures/ ; modèle, loot table et recettes sont générés.
  • Un son : le .ogg dans assets/sounds/, ou dans le sounds/ du mode s'il lui est propre. Voir assets/compress_ogg.py, force_mono.py et mp3_to_ogg.py.
  • Une boutique : un shop.py exposant une constante SHOP, sur le modèle de spleef/shop.py. Le registre la récupère tout seul ; son rang dans la boutique vient de SHOP_ORDER (src/datapack/shop/shared_memory.py).

Conventions de code

  • Typage strict : pyright en mode strict (pyrightconfig.json), pas de Any.
  • Indentation par tabulations, alignement des colonnes par espaces.
  • Code auto-explicatif : peu de commentaires, les vraies explications vont dans les docstrings.
  • Préférez le déclaratif et le paramétré à la duplication. Le CI lance jscpd sur src/ pour traquer le copier-coller.
  • Un fichier qui dépasse environ 300 lignes devient un sous-module. Exception assumée : le main.py d'un mode de jeu. Toute la logique du mode reste au même endroit, ce qui rend le Ctrl+F immédiat et évite d'avoir à deviner dans quel fichier se trouve une mécanique. build_battle/main.py fait 935 lignes, et c'est très bien ainsi.
  • Lint : ruff check src tools --fix (la config vit dans ruff.toml).
  • Perfs : le serveur tourne à 20 tps avec beaucoup de joueurs. Évitez les @e non filtrés dans les tick, préférez tags et scores.

Garde-fous

Six outils, tous lançables à la main. Les trois premiers tournent aussi en CI.

CommandeCe qu'elle garantit
ruff check src toolsStyle et imports
pyrightTypage strict, sans Any
python tools/check_conventions.pyTaille des fichiers, pureté du modèle, aucun import descendant vers un mode nommé
python tools/check_output_drift.pyLe refactoring n'a rien changé : rebuild puis build/ identique à HEAD
python tools/report_merged_functions.pyAucune fonction n'est écrite par deux émetteurs sans que ce soit déclaré
python tools/check_rename_only.py "a=b"Un diff de build/ est exactement les renommages annoncés, et rien d'autre

Ces deux derniers vont par paire : check_output_drift.py prouve qu'un refactoring n'a rien changé, check_rename_only.py prouve qu'un déplacement de chemins n'a fait que déplacer. Le plus important reste check_output_drift.py. write_functionajoute à la suite par défaut, donc deux émetteurs visant le même chemin fusionnent silencieusement dans l'ordre d'appel. Après tout déplacement de code, un build/ inchangé est la preuve que rien n'a bougé en jeu.

Deux règles s'appuient sur des listes explicites qui ne doivent que diminuer :

  • LONG_FILE_DEBT dans tools/check_conventions.py : les fichiers qui dépassent encore 300 lignes.
  • DECLARED_MERGES dans tools/report_merged_functions.py : les fonctions volontairement construites par ajouts successifs.

Workflow Git

  • Une branche par fonctionnalité, puis Pull Request vers main.
  • Commits courts, format conventionnel avec gitmoji après les deux-points :
    • feat(modes): ✨ Ajout du mode Block Party
    • fix(race): 🐛 Correction des checkpoints sur rainbow_road
    • perf(engine): ⚡️ Réduction du coût du tick
    • build: 🚀 Built with latest StewBeet version
  • Les releases sont publiées avec python upload.py (nécessite ~/stewbeet/credentials.yml).

About

Data pack gérant le serveur switch de Paralya V2

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

GitHubDiscordStewBeet

Dépôt GitHub du serveur Switch

Le datapack et le resource pack du serveur Switch (Paralya) : un lobby, une infinité de mini-jeux votés entre chaque partie, un système de maps régénérables, des boutiques, des statistiques et des succès.

Tout est généré en Python avec StewBeet, un framework au-dessus de beet. Aucun fichier .mcfunction n'est écrit à la main : ils sont produits par le code de src/.

Documents (Google Sheet) :


Sommaire


Prérequis

  • Python 3.14+
  • StewBeet : pip install -U stewbeet

Build

Une seule commande, à la racine du dépôt :

stewbeet
CommandeEffet
stewbeetBuild complet (équivaut à stewbeet build)
stewbeet rebuildNettoie les caches puis rebuild
stewbeet cleanNettoie les caches et les dossiers de sortie
stewbeet --helpListe toutes les commandes

Le build remplit build/ puis copie les .zip vers les chemins de build_copy_destinations (beet.yml).

Ces chemins sont personnels (resourcepacks local, SFTP du serveur) : adaptez-les chez vous, mais ne committez pas vos chemins locaux.


⚠️ Ne jamais éditer le dossier build

build/ est entièrement généré et écrasé à chaque build : il n'est versionné que parce que le serveur pull le dépôt, donc on ne le commit qu'après un vrai stewbeet.

Pour retrouver le code d'une commande in-game, grep son texte dans src/ : le chemin de la fonction est le premier argument de write_function.


Structure du dépôt

Switch/
├── ⚙️ beet.yml # Config du projet et du pipeline de build
├── 🚀 upload.py # Publication d'une release GitHub
├── 🧰 tools/ # Scaffolding et garde-fous (voir plus bas)
├── 🐍 src/ # TOUT le code source
│ ├── setup_definitions.py # Étape 1 : items, blocs, matériaux, disques
│ ├── link.py # Étape 2 : appelle tous les générateurs
│ ├── validation.py # Contrôles de cohérence, échoue le build
│ ├── 📦 database/ # Items et comportements de blocs
│ ├── 🎨 resource_pack/ # Langues, sounds.json, shaders, textures GUI, fonts
│ └── 📂 datapack/
│ ├── main.py # Définitions brutes du datapack
│ ├── definitions/ # Advancements, dimensions, loot tables, prédicats, tags...
│ ├── 🎮 modes/ # Un dossier par mini-jeu, + spec/catalogue/emit
│ ├── 🧠 engine/ # Vote, démarrage, arrêt, signaux, pop-ups
│ ├── 🧍 player/ # Layout d'inventaire, practice, jump timer
│ ├── 🏛️ lobby/ # Boards, leaderboards, PNJ, tick hors partie
│ ├── 🗺️ maps/ # Chargement, géométrie, et generation/ (régénération)
│ ├── 🎒 kits/ # Modèle déclaratif des kits (Kit, KitItem, rôles)
│ ├── 🛒 shop/ # Boutiques (consomme le registre des modes)
│ ├── 📊 stats/ # Classements et statistiques
│ ├── 🏆 advancements/ # Succès et pourcentages
│ ├── 🎬 cinematic/ # Cinématiques d'intro
│ ├── 🎵 music/ # Lecteur de musique et Note Block Studio
│ ├── 🌐 translations/ # Textes partagés FR/EN
│ ├── 🔧 utils/ # Primitives appelées par une vingtaine de modes
│ ├── 🛠️ devtools/ # test_mode, lag artificiel, profiling
│ └── 🌱 root/ # load, tick, second et fonctions racine
├── 🖼️ assets/ # Textures, sons, disques, pack.png
├── 📚 libs/ # Packs externes fusionnés au build
├── 🎼 note_block_studio/ # Musiques (midi, datapacks générés)
├── 📤 continuous_delivery/ # Config de la release GitHub
└── ⛔ build/ # SORTIE GÉNÉRÉE, ne jamais éditer

Le pipeline de build

L'ordre vient de la clé pipeline de beet.yml. Deux entrées seulement sont du code maison :

  1. src.setup_definitions remplit la base d'items et de blocs (Item, Block, matériaux de ORES_CONFIGS, disques) et charge les définitions écrites à la main. Les plugins StewBeet suivants s'appuient dessus.
  2. src.link appelle generate_all_modes() puis chaque générateur de sous-système : c'est là que la quasi-totalité des .mcfunction sont écrites.

Le reste vient de plugins StewBeet : headers, constantes de scoreboard, dépendances, merge Smithed Weld, zip, copie, sha1.

src/datapack/modes/__init__.pyimporte dynamiquement chaque dossier contenant un main.py et appelle son write_mode(), puis le write_resources() de son resources.py s'il existe. Aucune liste d'imports à maintenir.


Où se trouve quoi ?

Je veux modifier...Fichier
🗳️ La liste des mini-jeux votables, descriptions, temps estimé, auteurssrc/datapack/modes/catalogue.py (MODES)
🧩 Les groupes de vote (variantes sous une même entrée)src/datapack/modes/catalogue.py (GROUPS)
🎮 La logique d'un mini-jeusrc/datapack/modes/<mode>/main.py
🌐 Les messages FR/EN d'un mini-jeusrc/datapack/modes/<mode>/translations.py
📜 Les advancements, prédicats, loot tables, structures d'un mini-jeusrc/datapack/modes/<mode>/resources.py
🔁 Un helper partagé entre plusieurs modessrc/datapack/modes/emit.py ou src/datapack/modes/_common/main.py
🧠 Le vote, le lancement, l'arrêt d'une partie, les signauxsrc/datapack/engine/main.py
🌍 Les maps de jeu (dimensions, régénération, zones)src/datapack/survival_maps/definitions.py
🏁 Les checkpoints de course et les cycles de spawnsrc/datapack/maps/main.py
🎒 Un kit ou une classesrc/datapack/kits/ et src/datapack/modes/<mode>/kits.py
🛒 Une boutiquesrc/datapack/modes/<mode>/shop.py, agrégé par src/datapack/shop/shared_memory.py
📦 Un item ou un bloc customsrc/database/misc_items.py, src/database/blocks_behaviors.py
⛏️ Les matériaux générés (armures, outils)src/setup_definitions.py (ORES_CONFIGS)
🖼️ Une texture d'itemassets/textures/**/<item_id>.png, détection automatique
🔊 Un sonassets/sounds/, ou src/datapack/modes/<mode>/sounds/ pour un son propre à un mode
🛠️ Un outil de dev (test mode, lag, profiling)src/datapack/devtools/
✨ Les shaderssrc/resource_pack/shaders.py
🪟 Les textures de GUI et de tooltipssrc/resource_pack/textures.py

Ajouter un mode de jeu

1. Générer le squelette

python tools/new_mode.py mon_mode

Le mode obtenu build et se lance en jeu immédiatement. La découverte est automatique : aucun import à ajouter ailleurs.

src/datapack/modes/mon_mode/
├── __init__.py # vide
├── main.py # write_mode()
└── translations.py # write_translations()

2. Remplir main.py

Le fichier généré ressemble déjà à ceci, avec les six hooks branchés. Il ne reste qu'à écrire les mécaniques du jeu.

# ImportsfromstewbeetimportMem, write_functionfrom ..emitimportwrite_modes_calls, write_time_xp_barfrom .translationsimportwrite_translationsdefwrite_mode():
ns: str=Mem.ctx.project_idmode: str="mon_mode"path: str=f"{ns}:modes/{mode}"# Écrit /calls/* (le dispatch appelé par le moteur) et /_force_startwrite_modes_calls(mode)
write_translations()
# /start : appelé une fois au lancement de la partiewrite_function(f"{path}/start", f"""effect give @a[tag=!detached] saturation infinite 255 true# Choix de la map parmi une liste (les ids viennent de survival_maps/definitions.py)scoreboard players set #do_spreadplayers {ns}.data 1function {ns}:utils/choose_map_for {{id:"{mode}", maps:["switch_space"]}}scoreboard players set #mon_mode_seconds {ns}.data -6scoreboard players set #process_end {ns}.data 0""")
# /tick : chaque tick de jeuwrite_function(f"{path}/tick", f"""function {ns}:utils/on_death_run_function {{function:"{path}/death"}}""")
# /second : chaque seconde de jeuwrite_function(f"{path}/second", f"""scoreboard players add #mon_mode_seconds {ns}.data 1function {path}/xp_barfunction {path}/translations/second""")
# /joined : un joueur rejoint en cours de partiewrite_function(f"{path}/joined", f"""gamemode spectator @s""")
# /stop : nettoyage en fin de partiewrite_function(f"{path}/stop", f"""scoreboard objectives remove {ns}.temp.mon_score""")
# /xp_bar : barre d'XP servant de timerwrite_time_xp_bar(f"{path}/xp_bar", 300, "#mon_mode_seconds", "#mon_mode_seconds")

Les hooks disponibles sont joined, second, start, stop, tick et inventory_changed. Le moteur les appelle via switch:engine/signals/macro_*, qui redirige vers switch:modes/<mode>/calls/<hook>. write_modes_calls() génère ces redirections et le garde-fou sur current_game qui empêche un mode de tourner pendant qu'un autre est en cours.

3. Déclarer le mode dans la liste de vote

Dans src/datapack/modes/definitions.py, ajoutez une entrée à MODES :

{
"min_players":2, "max_players":-1, "id":"mon_mode", "name_fr":"Mon Mode",
"estimated_time": "2-5 mins", "inspiration": "Épicube", "suggested_by": "Pseudo", "developed_by": "Pseudo",
"description": {
"fr": [{"text":"Première ligne de description.\n"},{"text":"Deuxième ligne.\n"}],
"en": [{"text":"First description line.\n"},{"text":"Second line.\n"}]
},
},
Clé / comportementDétail
idExactement le nom du dossier
max_playersUNLIMITED quand le mode n'a pas de plafond
groupOptionnel : les jeux d'un même groupe forment une entrée de vote, puis un second vote départage (voir GROUPS)
Lore, index, pop-upsGénérés automatiquement à partir de cette entrée
Entrée commentéeLe mode est quand même généré : testable via _force_start sans polluer le vote
Erreur de saisieLe build échoue avec un message qui nomme le problème et suggère la valeur la plus proche

4. Tester en jeu

/function switch:devtools/test_mode
/function switch:modes/mon_mode/_force_start

5. Ressources supplémentaires (optionnel)

Advancements, prédicats, loot tables, item modifiers, tags ou structures vont dans un resources.py exposant write_resources(), appelé automatiquement après write_mode().


Anatomie d'un mode

FichierRôleAppelé par
main.pywrite_mode() : toute la logique, écrit les .mcfunctionautomatique
translations.pywrite_translations() : messages FR/EN dans <mode>/translations/*write_mode()
resources.pywrite_resources() : advancements, prédicats, loot tables, tags, structuresautomatique
kits.pyKits et classes du mode, construits avec Kit et KitItemwrite_mode()
shop.pyConstante SHOP : les upgrades vendues par le modeautomatique
structures/Fichiers .nbt, enregistrés via register_structures()resources.py
sounds/Fichiers .ogg propres au mode, via register_sounds()resources.py
  • _common/ : fonctions partagées écrites dans switch:modes/_common/* (mort en spectateur, fin de partie, barre d'XP, kits communs).
  • _coupdetat/ : pseudo-mode utilisé par le moteur, pas un mini-jeu votable.
  • Les helpers Python partagés vivent dans src/datapack/modes/emit.py : write_modes_calls, write_server_announce, write_time_xp_bar, write_no_drop, register_structures, register_sounds.
  • Le namespace n'est jamais écrit en dur : utilisez toujours ns: str = Mem.ctx.project_id.

Ajouter autre chose

  • Une map : un clone_survival(...) ou fill_survival(...) dans src/datapack/survival_maps/definitions.py (coordonnées, id, nom, auteurs, view de la cinématique). Elle devient utilisable dans le maps:[...] d'un choose_map_for.
  • Un item ou un bloc : Item(...) ou Block(...) dans src/database/misc_items.py. Texture trouvée automatiquement si un .png du même nom existe sous assets/textures/ ; modèle, loot table et recettes sont générés.
  • Un son : le .ogg dans assets/sounds/, ou dans le sounds/ du mode s'il lui est propre. Voir assets/compress_ogg.py, force_mono.py et mp3_to_ogg.py.
  • Une boutique : un shop.py exposant une constante SHOP, sur le modèle de spleef/shop.py. Le registre la récupère tout seul ; son rang dans la boutique vient de SHOP_ORDER (src/datapack/shop/shared_memory.py).

Conventions de code

  • Typage strict : pyright en mode strict (pyrightconfig.json), pas de Any.
  • Indentation par tabulations, alignement des colonnes par espaces.
  • Code auto-explicatif : peu de commentaires, les vraies explications vont dans les docstrings.
  • Préférez le déclaratif et le paramétré à la duplication. Le CI lance jscpd sur src/ pour traquer le copier-coller.
  • Un fichier qui dépasse environ 300 lignes devient un sous-module. Exception assumée : le main.py d'un mode de jeu. Toute la logique du mode reste au même endroit, ce qui rend le Ctrl+F immédiat et évite d'avoir à deviner dans quel fichier se trouve une mécanique. build_battle/main.py fait 935 lignes, et c'est très bien ainsi.
  • Lint : ruff check src tools --fix (la config vit dans ruff.toml).
  • Perfs : le serveur tourne à 20 tps avec beaucoup de joueurs. Évitez les @e non filtrés dans les tick, préférez tags et scores.

Garde-fous

Six outils, tous lançables à la main. Les trois premiers tournent aussi en CI.

CommandeCe qu'elle garantit
ruff check src toolsStyle et imports
pyrightTypage strict, sans Any
python tools/check_conventions.pyTaille des fichiers, pureté du modèle, aucun import descendant vers un mode nommé
python tools/check_output_drift.pyLe refactoring n'a rien changé : rebuild puis build/ identique à HEAD
python tools/report_merged_functions.pyAucune fonction n'est écrite par deux émetteurs sans que ce soit déclaré
python tools/check_rename_only.py "a=b"Un diff de build/ est exactement les renommages annoncés, et rien d'autre

Ces deux derniers vont par paire : check_output_drift.py prouve qu'un refactoring n'a rien changé, check_rename_only.py prouve qu'un déplacement de chemins n'a fait que déplacer. Le plus important reste check_output_drift.py. write_functionajoute à la suite par défaut, donc deux émetteurs visant le même chemin fusionnent silencieusement dans l'ordre d'appel. Après tout déplacement de code, un build/ inchangé est la preuve que rien n'a bougé en jeu.

Deux règles s'appuient sur des listes explicites qui ne doivent que diminuer :

  • LONG_FILE_DEBT dans tools/check_conventions.py : les fichiers qui dépassent encore 300 lignes.
  • DECLARED_MERGES dans tools/report_merged_functions.py : les fonctions volontairement construites par ajouts successifs.

Workflow Git

  • Une branche par fonctionnalité, puis Pull Request vers main.
  • Commits courts, format conventionnel avec gitmoji après les deux-points :
    • feat(modes): ✨ Ajout du mode Block Party
    • fix(race): 🐛 Correction des checkpoints sur rainbow_road
    • perf(engine): ⚡️ Réduction du coût du tick
    • build: 🚀 Built with latest StewBeet version
  • Les releases sont publiées avec python upload.py (nécessite ~/stewbeet/credentials.yml).

About

Data pack gérant le serveur switch de Paralya V2

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

GitHubDiscordStewBeet

Dépôt GitHub du serveur Switch

Le datapack et le resource pack du serveur Switch (Paralya) : un lobby, une infinité de mini-jeux votés entre chaque partie, un système de maps régénérables, des boutiques, des statistiques et des succès.

Tout est généré en Python avec StewBeet, un framework au-dessus de beet. Aucun fichier .mcfunction n'est écrit à la main : ils sont produits par le code de src/.

Documents (Google Sheet) :


Sommaire


Prérequis

  • Python 3.14+
  • StewBeet : pip install -U stewbeet

Build

Une seule commande, à la racine du dépôt :

stewbeet
CommandeEffet
stewbeetBuild complet (équivaut à stewbeet build)
stewbeet rebuildNettoie les caches puis rebuild
stewbeet cleanNettoie les caches et les dossiers de sortie
stewbeet --helpListe toutes les commandes

Le build remplit build/ puis copie les .zip vers les chemins de build_copy_destinations (beet.yml).

Ces chemins sont personnels (resourcepacks local, SFTP du serveur) : adaptez-les chez vous, mais ne committez pas vos chemins locaux.


⚠️ Ne jamais éditer le dossier build

build/ est entièrement généré et écrasé à chaque build : il n'est versionné que parce que le serveur pull le dépôt, donc on ne le commit qu'après un vrai stewbeet.

Pour retrouver le code d'une commande in-game, grep son texte dans src/ : le chemin de la fonction est le premier argument de write_function.


Structure du dépôt

Switch/
├── ⚙️ beet.yml # Config du projet et du pipeline de build
├── 🚀 upload.py # Publication d'une release GitHub
├── 🧰 tools/ # Scaffolding et garde-fous (voir plus bas)
├── 🐍 src/ # TOUT le code source
│ ├── setup_definitions.py # Étape 1 : items, blocs, matériaux, disques
│ ├── link.py # Étape 2 : appelle tous les générateurs
│ ├── validation.py # Contrôles de cohérence, échoue le build
│ ├── 📦 database/ # Items et comportements de blocs
│ ├── 🎨 resource_pack/ # Langues, sounds.json, shaders, textures GUI, fonts
│ └── 📂 datapack/
│ ├── main.py # Définitions brutes du datapack
│ ├── definitions/ # Advancements, dimensions, loot tables, prédicats, tags...
│ ├── 🎮 modes/ # Un dossier par mini-jeu, + spec/catalogue/emit
│ ├── 🧠 engine/ # Vote, démarrage, arrêt, signaux, pop-ups
│ ├── 🧍 player/ # Layout d'inventaire, practice, jump timer
│ ├── 🏛️ lobby/ # Boards, leaderboards, PNJ, tick hors partie
│ ├── 🗺️ maps/ # Chargement, géométrie, et generation/ (régénération)
│ ├── 🎒 kits/ # Modèle déclaratif des kits (Kit, KitItem, rôles)
│ ├── 🛒 shop/ # Boutiques (consomme le registre des modes)
│ ├── 📊 stats/ # Classements et statistiques
│ ├── 🏆 advancements/ # Succès et pourcentages
│ ├── 🎬 cinematic/ # Cinématiques d'intro
│ ├── 🎵 music/ # Lecteur de musique et Note Block Studio
│ ├── 🌐 translations/ # Textes partagés FR/EN
│ ├── 🔧 utils/ # Primitives appelées par une vingtaine de modes
│ ├── 🛠️ devtools/ # test_mode, lag artificiel, profiling
│ └── 🌱 root/ # load, tick, second et fonctions racine
├── 🖼️ assets/ # Textures, sons, disques, pack.png
├── 📚 libs/ # Packs externes fusionnés au build
├── 🎼 note_block_studio/ # Musiques (midi, datapacks générés)
├── 📤 continuous_delivery/ # Config de la release GitHub
└── ⛔ build/ # SORTIE GÉNÉRÉE, ne jamais éditer

Le pipeline de build

L'ordre vient de la clé pipeline de beet.yml. Deux entrées seulement sont du code maison :

  1. src.setup_definitions remplit la base d'items et de blocs (Item, Block, matériaux de ORES_CONFIGS, disques) et charge les définitions écrites à la main. Les plugins StewBeet suivants s'appuient dessus.
  2. src.link appelle generate_all_modes() puis chaque générateur de sous-système : c'est là que la quasi-totalité des .mcfunction sont écrites.

Le reste vient de plugins StewBeet : headers, constantes de scoreboard, dépendances, merge Smithed Weld, zip, copie, sha1.

src/datapack/modes/__init__.pyimporte dynamiquement chaque dossier contenant un main.py et appelle son write_mode(), puis le write_resources() de son resources.py s'il existe. Aucune liste d'imports à maintenir.


Où se trouve quoi ?

Je veux modifier...Fichier
🗳️ La liste des mini-jeux votables, descriptions, temps estimé, auteurssrc/datapack/modes/catalogue.py (MODES)
🧩 Les groupes de vote (variantes sous une même entrée)src/datapack/modes/catalogue.py (GROUPS)
🎮 La logique d'un mini-jeusrc/datapack/modes/<mode>/main.py
🌐 Les messages FR/EN d'un mini-jeusrc/datapack/modes/<mode>/translations.py
📜 Les advancements, prédicats, loot tables, structures d'un mini-jeusrc/datapack/modes/<mode>/resources.py
🔁 Un helper partagé entre plusieurs modessrc/datapack/modes/emit.py ou src/datapack/modes/_common/main.py
🧠 Le vote, le lancement, l'arrêt d'une partie, les signauxsrc/datapack/engine/main.py
🌍 Les maps de jeu (dimensions, régénération, zones)src/datapack/survival_maps/definitions.py
🏁 Les checkpoints de course et les cycles de spawnsrc/datapack/maps/main.py
🎒 Un kit ou une classesrc/datapack/kits/ et src/datapack/modes/<mode>/kits.py
🛒 Une boutiquesrc/datapack/modes/<mode>/shop.py, agrégé par src/datapack/shop/shared_memory.py
📦 Un item ou un bloc customsrc/database/misc_items.py, src/database/blocks_behaviors.py
⛏️ Les matériaux générés (armures, outils)src/setup_definitions.py (ORES_CONFIGS)
🖼️ Une texture d'itemassets/textures/**/<item_id>.png, détection automatique
🔊 Un sonassets/sounds/, ou src/datapack/modes/<mode>/sounds/ pour un son propre à un mode
🛠️ Un outil de dev (test mode, lag, profiling)src/datapack/devtools/
✨ Les shaderssrc/resource_pack/shaders.py
🪟 Les textures de GUI et de tooltipssrc/resource_pack/textures.py

Ajouter un mode de jeu

1. Générer le squelette

python tools/new_mode.py mon_mode

Le mode obtenu build et se lance en jeu immédiatement. La découverte est automatique : aucun import à ajouter ailleurs.

src/datapack/modes/mon_mode/
├── __init__.py # vide
├── main.py # write_mode()
└── translations.py # write_translations()

2. Remplir main.py

Le fichier généré ressemble déjà à ceci, avec les six hooks branchés. Il ne reste qu'à écrire les mécaniques du jeu.

# ImportsfromstewbeetimportMem, write_functionfrom ..emitimportwrite_modes_calls, write_time_xp_barfrom .translationsimportwrite_translationsdefwrite_mode():
ns: str=Mem.ctx.project_idmode: str="mon_mode"path: str=f"{ns}:modes/{mode}"# Écrit /calls/* (le dispatch appelé par le moteur) et /_force_startwrite_modes_calls(mode)
write_translations()
# /start : appelé une fois au lancement de la partiewrite_function(f"{path}/start", f"""effect give @a[tag=!detached] saturation infinite 255 true# Choix de la map parmi une liste (les ids viennent de survival_maps/definitions.py)scoreboard players set #do_spreadplayers {ns}.data 1function {ns}:utils/choose_map_for {{id:"{mode}", maps:["switch_space"]}}scoreboard players set #mon_mode_seconds {ns}.data -6scoreboard players set #process_end {ns}.data 0""")
# /tick : chaque tick de jeuwrite_function(f"{path}/tick", f"""function {ns}:utils/on_death_run_function {{function:"{path}/death"}}""")
# /second : chaque seconde de jeuwrite_function(f"{path}/second", f"""scoreboard players add #mon_mode_seconds {ns}.data 1function {path}/xp_barfunction {path}/translations/second""")
# /joined : un joueur rejoint en cours de partiewrite_function(f"{path}/joined", f"""gamemode spectator @s""")
# /stop : nettoyage en fin de partiewrite_function(f"{path}/stop", f"""scoreboard objectives remove {ns}.temp.mon_score""")
# /xp_bar : barre d'XP servant de timerwrite_time_xp_bar(f"{path}/xp_bar", 300, "#mon_mode_seconds", "#mon_mode_seconds")

Les hooks disponibles sont joined, second, start, stop, tick et inventory_changed. Le moteur les appelle via switch:engine/signals/macro_*, qui redirige vers switch:modes/<mode>/calls/<hook>. write_modes_calls() génère ces redirections et le garde-fou sur current_game qui empêche un mode de tourner pendant qu'un autre est en cours.

3. Déclarer le mode dans la liste de vote

Dans src/datapack/modes/definitions.py, ajoutez une entrée à MODES :

{
"min_players":2, "max_players":-1, "id":"mon_mode", "name_fr":"Mon Mode",
"estimated_time": "2-5 mins", "inspiration": "Épicube", "suggested_by": "Pseudo", "developed_by": "Pseudo",
"description": {
"fr": [{"text":"Première ligne de description.\n"},{"text":"Deuxième ligne.\n"}],
"en": [{"text":"First description line.\n"},{"text":"Second line.\n"}]
},
},
Clé / comportementDétail
idExactement le nom du dossier
max_playersUNLIMITED quand le mode n'a pas de plafond
groupOptionnel : les jeux d'un même groupe forment une entrée de vote, puis un second vote départage (voir GROUPS)
Lore, index, pop-upsGénérés automatiquement à partir de cette entrée
Entrée commentéeLe mode est quand même généré : testable via _force_start sans polluer le vote
Erreur de saisieLe build échoue avec un message qui nomme le problème et suggère la valeur la plus proche

4. Tester en jeu

/function switch:devtools/test_mode
/function switch:modes/mon_mode/_force_start

5. Ressources supplémentaires (optionnel)

Advancements, prédicats, loot tables, item modifiers, tags ou structures vont dans un resources.py exposant write_resources(), appelé automatiquement après write_mode().


Anatomie d'un mode

FichierRôleAppelé par
main.pywrite_mode() : toute la logique, écrit les .mcfunctionautomatique
translations.pywrite_translations() : messages FR/EN dans <mode>/translations/*write_mode()
resources.pywrite_resources() : advancements, prédicats, loot tables, tags, structuresautomatique
kits.pyKits et classes du mode, construits avec Kit et KitItemwrite_mode()
shop.pyConstante SHOP : les upgrades vendues par le modeautomatique
structures/Fichiers .nbt, enregistrés via register_structures()resources.py
sounds/Fichiers .ogg propres au mode, via register_sounds()resources.py
  • _common/ : fonctions partagées écrites dans switch:modes/_common/* (mort en spectateur, fin de partie, barre d'XP, kits communs).
  • _coupdetat/ : pseudo-mode utilisé par le moteur, pas un mini-jeu votable.
  • Les helpers Python partagés vivent dans src/datapack/modes/emit.py : write_modes_calls, write_server_announce, write_time_xp_bar, write_no_drop, register_structures, register_sounds.
  • Le namespace n'est jamais écrit en dur : utilisez toujours ns: str = Mem.ctx.project_id.

Ajouter autre chose

  • Une map : un clone_survival(...) ou fill_survival(...) dans src/datapack/survival_maps/definitions.py (coordonnées, id, nom, auteurs, view de la cinématique). Elle devient utilisable dans le maps:[...] d'un choose_map_for.
  • Un item ou un bloc : Item(...) ou Block(...) dans src/database/misc_items.py. Texture trouvée automatiquement si un .png du même nom existe sous assets/textures/ ; modèle, loot table et recettes sont générés.
  • Un son : le .ogg dans assets/sounds/, ou dans le sounds/ du mode s'il lui est propre. Voir assets/compress_ogg.py, force_mono.py et mp3_to_ogg.py.
  • Une boutique : un shop.py exposant une constante SHOP, sur le modèle de spleef/shop.py. Le registre la récupère tout seul ; son rang dans la boutique vient de SHOP_ORDER (src/datapack/shop/shared_memory.py).

Conventions de code

  • Typage strict : pyright en mode strict (pyrightconfig.json), pas de Any.
  • Indentation par tabulations, alignement des colonnes par espaces.
  • Code auto-explicatif : peu de commentaires, les vraies explications vont dans les docstrings.
  • Préférez le déclaratif et le paramétré à la duplication. Le CI lance jscpd sur src/ pour traquer le copier-coller.
  • Un fichier qui dépasse environ 300 lignes devient un sous-module. Exception assumée : le main.py d'un mode de jeu. Toute la logique du mode reste au même endroit, ce qui rend le Ctrl+F immédiat et évite d'avoir à deviner dans quel fichier se trouve une mécanique. build_battle/main.py fait 935 lignes, et c'est très bien ainsi.
  • Lint : ruff check src tools --fix (la config vit dans ruff.toml).
  • Perfs : le serveur tourne à 20 tps avec beaucoup de joueurs. Évitez les @e non filtrés dans les tick, préférez tags et scores.

Garde-fous

Six outils, tous lançables à la main. Les trois premiers tournent aussi en CI.

CommandeCe qu'elle garantit
ruff check src toolsStyle et imports
pyrightTypage strict, sans Any
python tools/check_conventions.pyTaille des fichiers, pureté du modèle, aucun import descendant vers un mode nommé
python tools/check_output_drift.pyLe refactoring n'a rien changé : rebuild puis build/ identique à HEAD
python tools/report_merged_functions.pyAucune fonction n'est écrite par deux émetteurs sans que ce soit déclaré
python tools/check_rename_only.py "a=b"Un diff de build/ est exactement les renommages annoncés, et rien d'autre

Ces deux derniers vont par paire : check_output_drift.py prouve qu'un refactoring n'a rien changé, check_rename_only.py prouve qu'un déplacement de chemins n'a fait que déplacer. Le plus important reste check_output_drift.py. write_functionajoute à la suite par défaut, donc deux émetteurs visant le même chemin fusionnent silencieusement dans l'ordre d'appel. Après tout déplacement de code, un build/ inchangé est la preuve que rien n'a bougé en jeu.

Deux règles s'appuient sur des listes explicites qui ne doivent que diminuer :

  • LONG_FILE_DEBT dans tools/check_conventions.py : les fichiers qui dépassent encore 300 lignes.
  • DECLARED_MERGES dans tools/report_merged_functions.py : les fonctions volontairement construites par ajouts successifs.

Workflow Git

  • Une branche par fonctionnalité, puis Pull Request vers main.
  • Commits courts, format conventionnel avec gitmoji après les deux-points :
    • feat(modes): ✨ Ajout du mode Block Party
    • fix(race): 🐛 Correction des checkpoints sur rainbow_road
    • perf(engine): ⚡️ Réduction du coût du tick
    • build: 🚀 Built with latest StewBeet version
  • Les releases sont publiées avec python upload.py (nécessite ~/stewbeet/credentials.yml).

About

Data pack gérant le serveur switch de Paralya V2

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages