You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
437 lines
20 KiB
437 lines
20 KiB
#!/usr/bin/env python3
|
|
'''
|
|
ENLIVEN subgame builder - creates ENLIVEN based on minetest_game
|
|
Merges mods & settings from old bash installer script
|
|
'''
|
|
from __future__ import print_function
|
|
import sys
|
|
import os
|
|
import argparse
|
|
# import configparser
|
|
import shutil
|
|
from typing import List, Dict, Union
|
|
|
|
# Assuming these exist in your pyenliven module
|
|
from pyenliven import (
|
|
echo0,
|
|
# getSGPath, # not used here anymore
|
|
# profile,
|
|
MODS_STOPGAP_DIR,
|
|
)
|
|
|
|
# ──────────────────────────────────────────────────────────────
|
|
# M O D L I S T F R O M B A S H S C R I P T
|
|
# ──────────────────────────────────────────────────────────────
|
|
|
|
# Format:
|
|
# 'name': folder name expected in mods/ or mods_stopgap/
|
|
# 'repo': git URL (str or list[str] — first = highest priority)
|
|
# 'branch': optional branch name
|
|
# 'stopgap_only': True → only use from MODS_STOPGAP_DIR, ignore repo
|
|
|
|
gamespec: Dict[str, any] = {}
|
|
gamespec['remove_mods'] = [
|
|
"facade", # no recipes
|
|
"placecraft", # interferes with eating
|
|
"more_chests", # https://github.com/poikilos/EnlivenMinetest/issues/446
|
|
"emeralds", # https://github.com/poikilos/EnlivenMinetest/issues/497
|
|
"give_initial_stuff",
|
|
"xban2",
|
|
"dynamic_liquid",
|
|
"stamina",
|
|
"hbarmor",
|
|
"hbhunger",
|
|
"hudbars",
|
|
"hbsprint",
|
|
"dungeon_loot", # replaced by treasurer + trm_*
|
|
"helicopter", # known crash issues in older versions
|
|
# add others from issue #310 if desired
|
|
]
|
|
|
|
gamespec['add_mods']: List[Dict[str, any]] = [
|
|
|
|
# ── Utility / Admin ────────────────────────────────────────
|
|
{'name': 'invhack', 'repo': "https://github.com/salahzar/minetest-invhack.git"},
|
|
{'name': 'worldedit', 'repo': "https://github.com/Uberi/MineTest-WorldEdit.git"},
|
|
{'name': 'metatools', 'repo': "https://github.com/poikilos/metatools.git"},
|
|
{'name': 'protector', 'repo': "https://notabug.org/TenPlus1/protector.git"},
|
|
{'name': 'advancedban', 'repo': "https://github.com/srifqi/advancedban"},
|
|
{'name': 'areas', 'repo': "https://github.com/ShadowNinja/areas.git"},
|
|
{'name': 'whitelist', 'repo': "https://github.com/ShadowNinja/whitelist.git"},
|
|
{'name': 'vote', 'repo': "https://github.com/minetest-mods/vote.git"},
|
|
|
|
# ── Mobs & Worldgen ────────────────────────────────────────
|
|
{'name': 'worldedge', 'repo': "https://github.com/minetest-mods/worldedge.git"},
|
|
{'name': 'mobs_redo', 'repo': "https://notabug.org/TenPlus1/mobs_redo.git"},
|
|
{'name': 'mobs_monster', 'repo': "https://notabug.org/TenPlus1/mobs_monster.git"},
|
|
{'name': 'mobs_animal', 'repo': "https://notabug.org/TenPlus1/mobs_animal.git"},
|
|
{'name': 'mob_horse', 'repo': "https://notabug.org/tenplus1/mob_horse.git"},
|
|
{'name': 'mobs_sky', 'repo': "https://github.com/poikilos/mobs_sky.git"},
|
|
{'name': 'spawners', 'repo': "https://bitbucket.org/minetest_gamers/spawners.git"},
|
|
{'name': 'tsm_pyramids', 'repo': "https://github.com/poikilos/tsm_pyramids.git"},
|
|
{'name': 'tsm_chests_dungeon', 'repo': "http://repo.or.cz/minetest_tsm_chests_dungeon.git"},
|
|
{'name': 'treasurer', 'repo': "http://repo.or.cz/minetest_treasurer.git"},
|
|
{'name': 'trm_pyramids'}, # special – files copied directly in bash → handle manually or stopgap
|
|
{'name': 'moreblocks', 'repo': "https://github.com/minetest-mods/moreblocks.git"},
|
|
{'name': 'plantlife_modpack','repo': "https://github.com/mt-mods/plantlife_modpack.git"},
|
|
{'name': 'bushes_soil', 'repo': "https://github.com/poikilos/bushes_soil.git"},
|
|
{'name': 'lapis', 'repo': "https://github.com/Napiophelios/LapisLazuli.git"},
|
|
{'name': 'biome_lib', 'repo': "https://github.com/mt-mods/biome_lib.git"},
|
|
{'name': 'moretrees', 'repo': "https://github.com/mt-mods/moretrees.git"},
|
|
{'name': 'mesecons', 'repo': "https://github.com/minetest-mods/mesecons"},
|
|
{'name': 'pipeworks', 'repo': "https://github.com/mt-mods/pipeworks.git"},
|
|
{'name': 'technic', 'repo': "https://github.com/minetest-mods/technic.git"},
|
|
{'name': 'technic_armor', 'repo': "https://github.com/stujones11/technic_armor.git"},
|
|
{'name': 'mapgen_helper', 'repo': "https://github.com/minetest-mods/mapgen_helper.git"},
|
|
{'name': 'subterrane', 'repo': "https://github.com/minetest-mods/subterrane.git"},
|
|
{'name': 'caverealms', 'repo': "https://github.com/FaceDeer/minetest-caverealms.git"},
|
|
{'name': 'moreores', 'repo': "https://github.com/minetest-mods/moreores.git"},
|
|
{'name': 'tsm_mines', 'repo': "http://repo.or.cz/tsm_mines.git"},
|
|
{'name': 'tsm_railcorridors','repo': "http://repo.or.cz/RailCorridors/tsm_railcorridors.git"},
|
|
{'name': 'birthstones', 'repo': "https://github.com/poikilos/birthstones.git"},
|
|
{'name': 'bakedclay', 'repo': "https://notabug.org/tenplus1/bakedclay.git"},
|
|
{'name': 'quartz', 'repo': "https://github.com/minetest-mods/quartz"},
|
|
{'name': 'magma_conduits', 'repo': "https://github.com/FaceDeer/magma_conduits.git"},
|
|
{'name': 'boost_cart', 'repo': "https://github.com/SmallJoker/boost_cart.git"},
|
|
|
|
# ── Gameplay / Items ───────────────────────────────────────
|
|
{'name': 'throwing', 'repo': "https://github.com/minetest-mods/throwing.git"},
|
|
{'name': 'throwing_arrows', 'repo': "https://github.com/minetest-mods/throwing_arrows.git"},
|
|
{'name': 'fishing', 'repo': "https://github.com/MinetestForFun/fishing.git",
|
|
'issues': ["Make sure fishing rods recipe works"]},
|
|
{'name': 'compassgps', 'repo': "https://github.com/poikilos/compassgps.git"},
|
|
{'name': 'sounding_line', 'repo': "https://github.com/minetest-mods/sounding_line.git"},
|
|
{'name': 'mywalls', 'repo': "https://github.com/minetest-mods/mywalls.git"},
|
|
{'name': 'mymasonhammer', 'repo': "https://github.com/minetest-mods/mymasonhammer.git"},
|
|
{'name': 'ts_furniture', 'repo': "https://github.com/minetest-mods/ts_furniture.git"},
|
|
{'name': '3d_armor', 'repo': "https://github.com/stujones11/minetest-3d_armor.git"},
|
|
{'name': 'basic_materials','repo': "https://github.com/mt-mods/basic_materials.git"},
|
|
{'name': 'homedecor_modpack','repo': "https://github.com/mt-mods/homedecor_modpack.git"},
|
|
{'name': 'homedecor_ua', 'repo': "https://github.com/poikilos/homedecor_ua.git"},
|
|
{'name': 'unifieddyes', 'repo': "https://github.com/mt-mods/unifieddyes.git"},
|
|
{'name': 'travelnet', 'repo': "https://github.com/Sokomine/travelnet.git"},
|
|
{'name': 'anvil', 'repo': "https://github.com/minetest-mods/anvil.git"},
|
|
{'name': 'sling', 'repo': "https://github.com/minetest-mods/sling.git"},
|
|
{'name': 'signs_lib', 'repo': "https://github.com/mt-mods/signs_lib.git"},
|
|
{'name': 'slimenodes', 'repo': "https://github.com/poikilos/slimenodes.git"},
|
|
{'name': 'ropes', 'repo': "https://github.com/minetest-mods/ropes.git"},
|
|
{'name': 'digilines', 'repo': "https://github.com/minetest-mods/digilines.git"},
|
|
{'name': 'trmp_minetest_game','repo': "https://github.com/poikilos/trmp_minetest_game.git"},
|
|
{'name': 'awards', 'repo': "https://gitlab.com/rubenwardy/awards.git"},
|
|
{'name': 'awards_board', 'repo': "https://framagit.org/xisd-minetest/awards_board.git"},
|
|
{'name': 'item_drop', 'repo': "https://github.com/minetest-mods/item_drop.git"},
|
|
{'name': 'sponge', 'repo': "https://github.com/BenjieFiftysix/sponge"},
|
|
|
|
# ── Player UX ──────────────────────────────────────────────
|
|
{'name': 'money', 'repo': "https://notabug.org/TenPlus1/money"},
|
|
{'name': 'lightning', 'repo': "https://github.com/minetest-mods/lightning.git"},
|
|
{'name': 'unified_inventory','repo': [
|
|
"https://github.com/minetest-mods/unified_inventory.git", # official
|
|
"https://github.com/MinetestForFun/unified_inventory" # old bash preference
|
|
]},
|
|
{'name': 'player_monoids', 'repo': "https://github.com/minetest-mods/player_monoids.git"},
|
|
{'name': 'sprint', 'repo': "https://github.com/GunshipPenguin/sprint.git"},
|
|
{'name': 'hunger_ng', 'repo': "https://gitlab.com/4w/hunger_ng.git"},
|
|
{'name': 'playereffects', 'repo': "https://github.com/sys4-fr/playereffects"},
|
|
{'name': 'ambience', 'repo': "https://notabug.org/tenplus1/ambience.git"},
|
|
{'name': 'playeranim', 'repo': "https://github.com/minetest-mods/playeranim.git"},
|
|
{'name': 'skinsdb', 'repo': "https://github.com/minetest-mods/skinsdb.git"},
|
|
{'name': 'woodcutting', 'repo': "https://github.com/minetest-mods/woodcutting.git"},
|
|
|
|
# ── Legacy / Special ───────────────────────────────────────
|
|
#{'name': 'animal_materials_legacy'},
|
|
#{'name': 'elk_legacy'},
|
|
#{'name': 'glooptest_missing'},
|
|
#{'name': 'nftools_legacy'},
|
|
]
|
|
|
|
'''
|
|
mt_conf_by_mod settings should be placed in minetest.conf such as
|
|
/opt/minebest/mtworlds/center/ENLIVEN/minetest.conf
|
|
but for now just use
|
|
- patches/subgame/minetest.conf
|
|
To define the game.
|
|
If minebest is present, combine minetest.conf and
|
|
minetest.server-example.conf
|
|
but maybe make an alternate version with stuff that isn't in
|
|
world.conf.
|
|
For other conf settings:
|
|
- patches/subgame/minetest.server-example.conf goes in the server only.
|
|
- Place the result in the game directory such as will result in
|
|
/opt/minebest/mtworlds/center/ENLIVEN/minetest.conf
|
|
- patches/subgame/minetest.client-example.conf goes in clients only.
|
|
'''
|
|
|
|
BASE_ENLIVEN_CONF_SETTINGS = [
|
|
# General / map
|
|
"enable_lapis_mod_columns = true",
|
|
"map_generation_limit = 5000",
|
|
# Protector
|
|
"protector_radius = 7",
|
|
"protector_flip = true",
|
|
"protector_pvp = true",
|
|
"protector_pvp_spawn = 10",
|
|
"protector_drop = false",
|
|
"protector_hurt = 3",
|
|
# Other gameplay
|
|
"world_edge = 5000",
|
|
"default_privs = interact,shout,home",
|
|
"max_users = 50",
|
|
"motd = \"Actions and chat messages are logged. Use inventory to see recipes (use web for live map if available).\"",
|
|
"disallow_empty_passwords = true",
|
|
"secure.trusted_mods = advanced_npc",
|
|
"server_dedicated = false",
|
|
"bones_position_message = true",
|
|
# Sprint (GunshipPenguin sprint settings)
|
|
"sprint_speed = 2.25",
|
|
"sprint_jump = 1.25",
|
|
"sprint_stamina_drain = .5",
|
|
]
|
|
|
|
# Per-mod overrides / extras
|
|
mt_conf_by_mod = {
|
|
'item_drop': {
|
|
'item_drop.pickup_radius': "1.425",
|
|
},
|
|
'throwing_arrows': {
|
|
'throwing.enable_arrow': "true",
|
|
},
|
|
}
|
|
why = {}
|
|
why["https://github.com/MinetestForFun/unified_inventory"] = '''
|
|
This fork makes a "nicer interface". The fork hasn't been tested yet.
|
|
'''
|
|
# deprecates https://github.com/poikilos/vines.git fork of Facedeer's:
|
|
why["https://github.com/FaceDeer/vines.git"] = '''
|
|
> I've finally done it, I've split this mod in twain. The new
|
|
> stand-alone ropes mod has no dependency on biome_lib and no vine
|
|
> content, though its crafting recipes remain compatible with the vines
|
|
> produced by this mod.
|
|
>
|
|
> My fork of this vines mod has had the rope-related content removed
|
|
> from it, leaving it as just a vines mod. Note that I haven't tested
|
|
> it extensively - I have to admit, I've mainly been in this for the
|
|
> ropes. :) I'll do what I can to maintain it, though, if anyone has
|
|
> bug reports or requests.
|
|
>
|
|
> I've added a node upgrade function to the new ropes mod that will
|
|
> convert the ropes from both my fork of the vines mod and the original
|
|
> version of the vines mod by bas080 to the new ropes mod's ropes. So
|
|
> if you wish to upgrade an existing world it should work.
|
|
|
|
- FaceDeer on [[Mod] Vines and Rope [2.3] [vines]]
|
|
(https://forums.minetest.org/viewtopic.php?f=11&t=2344&start=50
|
|
&sid=bf15c996963e891cd3f2460c2525044a)
|
|
|
|
Note that vines requires:
|
|
|
|
default
|
|
biome_lib
|
|
moretrees?
|
|
doc?
|
|
intllib?
|
|
mobs?
|
|
creatures?
|
|
'''
|
|
gamespec['disable_mobs'] = [
|
|
"old_lady",
|
|
]
|
|
|
|
"""
|
|
known_issues = {}
|
|
known_issues['fishing'] = "Make sure fishing rods recipe works"
|
|
|
|
|
|
server_only_mods = [
|
|
'ircpack',
|
|
'chat3', # debatable – was removed in some places
|
|
]
|
|
|
|
# ──────────────────────────────────────────────────────────────
|
|
|
|
class GameBuilder:
|
|
def __init__(self, minetest_game_path: str, minetest_version: str = "5"):
|
|
self.source_game = os.path.realpath(minetest_game_path)
|
|
self.target_parent = os.path.dirname(self.source_game)
|
|
self.target_game = os.path.join(self.target_parent, "ENLIVEN")
|
|
self.mods_target = os.path.join(self.target_game, "mods")
|
|
self.minetest_version = minetest_version # "5" or "0.4"
|
|
|
|
if minetest_version not in ("5", "0.4"):
|
|
raise ValueError("minetest_version must be '5' or '0.4'")
|
|
|
|
if not os.path.isdir(self.source_game):
|
|
raise FileNotFoundError(f"minetest_game not found: {self.source_game}")
|
|
|
|
echo0(f"Building ENLIVEN → {self.target_game}")
|
|
echo0(f" from base: {self.source_game}")
|
|
echo0(f" target Minetest compatibility: {self.minetest_version}")
|
|
|
|
def prepare_target(self):
|
|
"""Copy minetest_game → ENLIVEN if needed"""
|
|
if os.path.exists(self.target_game):
|
|
echo0(f"Target already exists: {self.target_game}")
|
|
echo0("→ delete it manually if you want fresh copy")
|
|
return
|
|
|
|
echo0("Copying minetest_game → ENLIVEN ...")
|
|
shutil.copytree(self.source_game, self.target_game)
|
|
|
|
def install_mod(self, entry: Dict[str, any]):
|
|
name = entry.get('name')
|
|
repo = entry.get('repo')
|
|
branch = entry.get('branch')
|
|
stopgap_only = entry.get('stopgap_only', False)
|
|
|
|
if not name:
|
|
echo0("Skipping entry without 'name'")
|
|
return
|
|
|
|
dest = os.path.join(self.mods_target, name)
|
|
|
|
# 1. Prefer stopgap if exists
|
|
stopgap_src = os.path.join(MODS_STOPGAP_DIR, name)
|
|
|
|
if os.path.isdir(stopgap_src):
|
|
echo0(f" [stopgap] {name}")
|
|
if os.path.exists(dest):
|
|
shutil.rmtree(dest)
|
|
shutil.copytree(stopgap_src, dest)
|
|
return
|
|
|
|
# 2. Git clone if we have repo URL(s)
|
|
if repo and not stopgap_only:
|
|
urls = [repo] if isinstance(repo, str) else repo
|
|
cloned = False
|
|
|
|
for url in urls:
|
|
echo0(f" trying → {url}")
|
|
# Here should be real git clone logic.
|
|
# For now just placeholder message.
|
|
# You can use subprocess.run(["git", "clone", ...])
|
|
echo0(f" [TODO git clone] {url} → {dest}")
|
|
cloned = True # pretend success
|
|
break
|
|
|
|
if cloned:
|
|
return
|
|
|
|
echo0(f"→ WARNING: no source found for {name}")
|
|
|
|
def remove_mod(self, modname: str):
|
|
path = os.path.join(self.mods_target, modname)
|
|
if os.path.isdir(path):
|
|
echo0(f" removing {modname}")
|
|
shutil.rmtree(path)
|
|
|
|
def apply_remove_list(self):
|
|
for m in gamespec.get('remove_mods', []):
|
|
self.remove_mod(m)
|
|
|
|
def install_all_mods(self):
|
|
for entry in gamespec.get('add_mods', []):
|
|
self.install_mod(entry)
|
|
|
|
def write_game_conf(self):
|
|
path = os.path.join(self.target_game, "game.conf")
|
|
with open(path, "w", encoding="utf-8") as f:
|
|
f.write("name = ENLIVEN\n")
|
|
f.write("description = Enhanced minetest_game experience\n")
|
|
|
|
def update_conf(self, path: str):
|
|
"""Append settings only if not already present (line-based, stripped comparison)"""
|
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
|
|
# Collect base settings
|
|
desired_lines = list(BASE_ENLIVEN_CONF_SETTINGS)
|
|
|
|
# Add version-specific player animation setting
|
|
if self.minetest_version == "5":
|
|
desired_lines.append("playeranim.model_version = MTG_4_Nov_2017")
|
|
else: # "0.4"
|
|
desired_lines.append("playeranim.model_version = MTG_4_Jun_2017")
|
|
|
|
# Add per-mod settings
|
|
for modname, settings in mt_conf_by_mod.items():
|
|
desired_lines.append(f"# settings from {modname}")
|
|
for k, v in settings.items():
|
|
desired_lines.append(f"{k} = {v}")
|
|
desired_lines.append("")
|
|
|
|
if not desired_lines:
|
|
return
|
|
|
|
# Normalize for comparison (strip, remove empty)
|
|
desired_set = {line.strip() for line in desired_lines if line.strip()}
|
|
|
|
# Read existing content
|
|
existing_lines = []
|
|
if os.path.isfile(path):
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
existing_lines = [line.rstrip("\n") for line in f]
|
|
existing_set = {line.strip() for line in existing_lines if line.strip()}
|
|
else:
|
|
existing_set = set()
|
|
|
|
# Find lines to add
|
|
to_add = [line for line in desired_lines if line.strip() and line.strip() not in existing_set]
|
|
|
|
if not to_add:
|
|
echo0(f"minetest.conf already contains all desired ENLIVEN settings ({path})")
|
|
return
|
|
|
|
# Write mode
|
|
mode = "a" if os.path.isfile(path) else "w"
|
|
with open(path, mode, encoding="utf-8") as f:
|
|
if mode == "w":
|
|
f.write(f"# ENLIVEN subgame recommended settings (Minetest {self.minetest_version} compatibility)\n\n")
|
|
for line in to_add:
|
|
f.write(line + "\n")
|
|
|
|
echo0(f"Updated {path} — added {len(to_add)} new line(s)")
|
|
|
|
def build(self, conf_path: str = None):
|
|
self.prepare_target()
|
|
self.apply_remove_list()
|
|
self.install_all_mods()
|
|
self.write_game_conf()
|
|
|
|
if not conf_path:
|
|
conf_path = os.path.join(self.source_game, "minetest.conf.enliven")
|
|
|
|
self.update_conf(conf_path)
|
|
|
|
echo0("\nBuild finished.")
|
|
echo0(f"Game location: {self.target_game}")
|
|
echo0(f"Config : {conf_path}")
|
|
echo0("Next steps:")
|
|
echo0(" • review & edit the minetest.conf file")
|
|
echo0(" • test in Minetest")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Build ENLIVEN subgame from minetest_game")
|
|
parser.add_argument("minetest_game_path", help="Path to minetest_game directory")
|
|
parser.add_argument("--conf", "-c", dest="conf_path",
|
|
help="Path to write/append ENLIVEN minetest.conf settings "
|
|
"(default: minetest_game_path/minetest.conf.enliven)")
|
|
parser.add_argument("--minetest-version", "-v", dest="minetest_version",
|
|
choices=["0.4", "5"], default="5",
|
|
help="Target Minetest version compatibility: '5' (default) or '0.4'")
|
|
|
|
args = parser.parse_args()
|
|
|
|
try:
|
|
builder = GameBuilder(
|
|
args.minetest_game_path,
|
|
minetest_version=args.minetest_version
|
|
)
|
|
builder.build(conf_path=args.conf_path)
|
|
return 0
|
|
except Exception as exc:
|
|
echo0(f"ERROR: {exc}", file=sys.stderr)
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|
|
|