a6587d55ce
Игрок: - RsxPlayer extends EntityPlayerSP: наш игрок с нашими числами движения. - RsxMovementInput extends MovementInputFromOptions: отменяет ванильный множитель подкрадывания 0.3 и ставит наш. - RsxPlayerTuning: подкрадывание 1.5 блока/с (было ~1.3), прыжок 1.35 блока (было 1.25). Прыжок считается по высоте: v = 0.42 * sqrt(h / 1.25) = 0.43713. Правило пользователя: значение меньше заданного не принимается, остаётся 1.5. Переопределяется из командной строки: -Drsx.sneakSpeed, -Drsx.jumpHeight. - Мобы прыгают как в ванилле: переопределение в RsxPlayer, а не в EntityLivingBase. - Три строки патчей в ванильное дерево: PlayerControllerMP создаёт RsxPlayer, Minecraft создаёт RsxMovementInput. Вся логика в нашем коде. - Форк собирается чисто, 3278 классов. Каталог: - tools/catalog-remake.py собирает assets/rustex/CATALOG.md и catalog.json. - Что нашли у ремейка: 218 предметов (46 одежды, 44 ресурса, 33 оружия, 31 медицина, 21 инструмент, 17 построек, 11 боеприпасов, 11 разных, 4 ловушки), 91 объект nextgen (бочки, ящики, двери, верстаки, печи, руда, модели монументов), 553 модели .obj (384 оружия с перезарядкой, 128 деталей постройки, 36 мебели, 4 руды), 133 иконки предметов, 2004 звука, текстуры монументов и животных, скины игрока (9 файлов 256x256 плюс 9 с моргающими глазами) в assets/rustex/player/. - Скины игрока - текстуры ремейка, модель тела ванильная. Декомпил ремейка лежит на диске: G:\src-decoded (5207 файлов, строки расшифрованы). Поведение клиента можно читать, а не угадывать: конфиг оружия с отдачей, схема viewmodel, формат данных. Значения же приходят с сервера, в клиенте только схема и значения по умолчанию. План дополнен разделами 2.8 (игрок) и 2.9 (декомпил).
243 lines
9.3 KiB
Python
243 lines
9.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Catalogue everything the Rustex remake has, so we know what we can use.
|
|
|
|
The remake is a Rust-like game built on a Minecraft 1.12.2 client, and it
|
|
carries a lot of things we do not have yet: the models of the monuments, the
|
|
three dimensional models of the guns and of the building pieces, the icons of
|
|
the items, the sounds of the animals, the player skins.
|
|
|
|
This script walks the unpacked assets and writes two files:
|
|
|
|
assets/rustex/CATALOG.md for a person to read
|
|
assets/rustex/catalog.json for a program to read
|
|
|
|
Usage: catalog-remake.py [assets dir]
|
|
"""
|
|
|
|
import csv
|
|
import json
|
|
import re
|
|
import sys
|
|
from collections import Counter, defaultdict
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(r"G:\RustNotMC")
|
|
ASSETS = Path(sys.argv[1]) if len(sys.argv) > 1 else ROOT / "assets" / "rustex"
|
|
LEGACY = ASSETS / "legacy" / "minecraft"
|
|
NEXTGEN = ASSETS / "nextgen" / "models"
|
|
OUT_MD = ASSETS / "CATALOG.md"
|
|
OUT_JSON = ASSETS / "catalog.json"
|
|
|
|
|
|
def group_of(path: Path, base: Path):
|
|
try:
|
|
rel = path.relative_to(base)
|
|
except ValueError:
|
|
return ""
|
|
parts = rel.parts
|
|
return parts[0] if len(parts) > 1 else ""
|
|
|
|
|
|
def scan_models():
|
|
"""The three dimensional models of the remake."""
|
|
out = {}
|
|
|
|
# The nextgen models: one folder per object, with the textures beside it.
|
|
nextgen = {}
|
|
if NEXTGEN.is_dir():
|
|
for folder in sorted(NEXTGEN.iterdir()):
|
|
if not folder.is_dir():
|
|
continue
|
|
files = [f for f in folder.rglob("*") if f.is_file()]
|
|
nextgen[folder.name] = {
|
|
"textures": [str(f.relative_to(folder)).replace("\\", "/") for f in files],
|
|
"bytes": sum(f.stat().st_size for f in files),
|
|
}
|
|
out["nextgen"] = nextgen
|
|
|
|
# The legacy models of the remake itself: the guns, the building pieces and
|
|
# the furniture. They are in the remake's own format and are still locked,
|
|
# but their names say what they are.
|
|
obj = defaultdict(list)
|
|
models = LEGACY / "models"
|
|
if models.is_dir():
|
|
for path in sorted(models.rglob("*.obj")):
|
|
rel = path.relative_to(models)
|
|
parts = rel.parts
|
|
# models/item/build/doorway/doorway_hqm0.obj -> the group is "build"
|
|
kind = parts[1] if len(parts) > 3 else (parts[0] if len(parts) > 1 else "other")
|
|
obj[kind].append("/".join(parts))
|
|
out["obj"] = {k: sorted(v) for k, v in sorted(obj.items())}
|
|
return out
|
|
|
|
|
|
def scan_item_textures():
|
|
"""The pictures of the items in the style of the vanilla game."""
|
|
out = {}
|
|
textures = LEGACY / "textures"
|
|
for folder in ("items", "guns", "monuments", "remake", "terrain", "models"):
|
|
path = textures / folder
|
|
if path.is_dir():
|
|
files = list(path.rglob("*.png"))
|
|
if files:
|
|
out[folder] = len(files)
|
|
return out
|
|
|
|
|
|
def scan_icons():
|
|
"""The icons of the items: one small picture per item."""
|
|
misc = LEGACY / "textures" / "models" / "misc"
|
|
if not misc.is_dir():
|
|
return []
|
|
return sorted(p.stem for p in misc.glob("*.png"))
|
|
|
|
|
|
def scan_entities():
|
|
"""The creatures and the player."""
|
|
out = {}
|
|
entity = LEGACY / "textures" / "entity"
|
|
if entity.is_dir():
|
|
for folder in sorted(entity.iterdir()):
|
|
if not folder.is_dir():
|
|
continue
|
|
files = [f for f in folder.rglob("*.png")]
|
|
if files:
|
|
out[folder.name] = sorted(f.name for f in files)
|
|
return out
|
|
|
|
|
|
def scan_blocks():
|
|
"""The blocks the remake adds to the vanilla ones."""
|
|
out = {}
|
|
models = LEGACY / "models" / "block"
|
|
if models.is_dir():
|
|
names = sorted(p.stem for p in models.glob("*.json"))
|
|
out["block_models"] = len(names)
|
|
out["block_model_names"] = names
|
|
states = LEGACY / "blockstates"
|
|
if states.is_dir():
|
|
out["blockstates"] = len(list(states.glob("*.json")))
|
|
textures = LEGACY / "textures" / "blocks"
|
|
if textures.is_dir():
|
|
out["block_textures"] = len(list(textures.glob("*.png")))
|
|
return out
|
|
|
|
|
|
def scan_sounds():
|
|
"""The sound events of the remake."""
|
|
path = ROOT / "rsx-protocol" / "src" / "main" / "resources" / "items" / "sounds.csv"
|
|
if not path.is_file():
|
|
return {}
|
|
groups = Counter()
|
|
with path.open(encoding="utf-8") as handle:
|
|
for row in csv.DictReader(handle):
|
|
ident = row["id"]
|
|
if ident.startswith("rust."):
|
|
groups[ident.split(".")[1]] += 1
|
|
else:
|
|
groups["vanilla"] += 1
|
|
return dict(groups)
|
|
|
|
|
|
def scan_items():
|
|
"""The item database of the remake."""
|
|
path = ROOT / "rsx-protocol" / "src" / "main" / "resources" / "items" / "items.csv"
|
|
if not path.is_file():
|
|
return []
|
|
with path.open(encoding="utf-8") as handle:
|
|
return list(csv.DictReader(handle))
|
|
|
|
|
|
def main():
|
|
models = scan_models()
|
|
icons = scan_icons()
|
|
entities = scan_entities()
|
|
blocks = scan_blocks()
|
|
sounds = scan_sounds()
|
|
items = scan_items()
|
|
|
|
by_category = Counter(item.get("category", "") for item in items)
|
|
with_stats = sum(1 for item in items if item.get("damage"))
|
|
tools = [item["rid"] for item in items if item.get("category") == "tools"]
|
|
|
|
data = {
|
|
"items": {"count": len(items), "by_category": dict(by_category),
|
|
"with_combat_stats": with_stats, "tools": tools},
|
|
"models": {"nextgen": len(models["nextgen"]),
|
|
"nextgen_names": sorted(models["nextgen"]),
|
|
"obj_groups": {k: len(v) for k, v in models["obj"].items()},
|
|
"obj_total": sum(len(v) for v in models["obj"].values())},
|
|
"icons": {"count": len(icons), "names": icons},
|
|
"entities": {k: len(v) for k, v in entities.items()},
|
|
"blocks": {k: v for k, v in blocks.items() if not k.endswith("_names")},
|
|
"sounds": sounds,
|
|
}
|
|
OUT_JSON.write_text(json.dumps(data, ensure_ascii=False, indent=1), encoding="utf-8")
|
|
|
|
lines = []
|
|
lines.append("# Каталог объектов ремейка Rustex\n")
|
|
lines.append("Собрано скриптом `tools/catalog-remake.py` по распакованным ассетам.\n")
|
|
lines.append("Это то, что у ремейка есть и чего у нас пока нет.\n")
|
|
|
|
lines.append("\n## Предметы\n")
|
|
lines.append("Всего: **%d**, с боевыми числами: %d.\n" % (len(items), with_stats))
|
|
lines.append("| Категория | Сколько |")
|
|
lines.append("|---|---|")
|
|
for name, count in sorted(by_category.items(), key=lambda kv: -kv[1]):
|
|
lines.append("| %s | %d |" % (name or "-", count))
|
|
|
|
lines.append("\n## Модели\n")
|
|
lines.append("### Nextgen (объекты целиком, своя геометрия): %d\n" % len(models["nextgen"]))
|
|
lines.append("```")
|
|
lines.append(", ".join(sorted(models["nextgen"])))
|
|
lines.append("```")
|
|
lines.append("\n### Модели ремейка (.obj, свой формат, пока зашифрованы): %d\n"
|
|
% sum(len(v) for v in models["obj"].values()))
|
|
lines.append("| Группа | Сколько | Примеры |")
|
|
lines.append("|---|---|---|")
|
|
for group, names in models["obj"].items():
|
|
sample = ", ".join(n.split("/")[-1].replace(".obj", "") for n in names[:5])
|
|
lines.append("| %s | %d | %s |" % (group, len(names), sample))
|
|
|
|
lines.append("\n### Текстуры в стиле ванильного клиента\n")
|
|
lines.append("| Папка | Файлов |")
|
|
lines.append("|---|---|")
|
|
for folder, count in sorted(scan_item_textures().items()):
|
|
lines.append("| %s | %d |" % (folder, count))
|
|
|
|
lines.append("\n## Иконки предметов: %d\n" % len(icons))
|
|
lines.append("```")
|
|
lines.append(", ".join(icons))
|
|
lines.append("```")
|
|
|
|
lines.append("\n## Сущности и игрок\n")
|
|
lines.append("| Папка | Текстур |")
|
|
lines.append("|---|---|")
|
|
for name, files in sorted(entities.items()):
|
|
lines.append("| %s | %d |" % (name, len(files)))
|
|
lines.append("\nСкины игрока: `textures/entity/skins` (9 файлов 256x256: чёрный, белый, "
|
|
"жёлтый, по три) и `skins_blink` (то же, но с моргающими глазами). "
|
|
"Модель тела — ванильная, от ремейка только текстуры.")
|
|
|
|
lines.append("\n## Блоки\n")
|
|
lines.append("- моделей блоков: %s" % blocks.get("block_models", 0))
|
|
lines.append("- blockstates: %s" % blocks.get("blockstates", 0))
|
|
lines.append("- текстур блоков: %s" % blocks.get("block_textures", 0))
|
|
|
|
lines.append("\n## Звуки\n")
|
|
lines.append("| Группа | Событий |")
|
|
lines.append("|---|---|")
|
|
for name, count in sorted(sounds.items(), key=lambda kv: -kv[1]):
|
|
lines.append("| %s | %d |" % (name, count))
|
|
|
|
OUT_MD.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
print("каталог: %s" % OUT_MD)
|
|
print("данные: %s" % OUT_JSON)
|
|
print("предметов: %d, моделей nextgen: %d, .obj: %d, иконок: %d, звуков: %d"
|
|
% (len(items), len(models["nextgen"]),
|
|
sum(len(v) for v in models["obj"].values()), len(icons), sum(sounds.values())))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|