Полу-кастомный игрок и каталог объектов ремейка
Игрок: - 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 (декомпил).
This commit is contained in:
@@ -753,6 +753,42 @@ ROLE_ELDER / ROLE_CREATOR`).
|
||||
(MTSDF, 2048×2048, тайлы 64×64) по именам `rusticon` из базы предметов.
|
||||
- **Пометка: заменить на свои** (см. п. 6).
|
||||
|
||||
### 2.8 Игрок: полу-кастомный объект
|
||||
|
||||
Игрок — **ванильный, но с нашими числами движения**. Класс `RsxPlayer extends
|
||||
EntityPlayerSP` (в `src-overlay`), рядом `RsxMovementInput extends
|
||||
MovementInputFromOptions` и `RsxPlayerTuning` с числами.
|
||||
|
||||
| что | ванильно | у нас |
|
||||
|---|---|---|
|
||||
| скорость подкрадывания | ~1.30 блока/с (множитель 0.3) | **1.5 блока/с** (множитель 0.34746) |
|
||||
| высота прыжка | 1.25 блока (импульс 0.42) | **1.35 блока** (импульс 0.43713) |
|
||||
|
||||
- Скорость прыжка считается по высоте: `v = 0.42 · √(h / 1.25)`.
|
||||
- Подкрадывание: ванильный множитель 0.3 отменяется и ставится наш.
|
||||
- **Правило пользователя:** если задать значение меньше, оно не принимается —
|
||||
подкрадывание остаётся 1.5 блока/с, прыжок 1.5 блока. Значения по умолчанию
|
||||
(1.5 и 1.35) через это правило не проходят.
|
||||
- Переопределяется из командной строки: `-Drsx.sneakSpeed=1.6 -Drsx.jumpHeight=1.4`.
|
||||
- Мобы прыгают как в ванилле: переопределение живёт в `RsxPlayer`, а не в
|
||||
`EntityLivingBase`.
|
||||
- Патчи в ванильное дерево — три строки (`PlayerControllerMP` создаёт
|
||||
`RsxPlayer`, `Minecraft` создаёт `RsxMovementInput`), вся логика — в нашем коде.
|
||||
|
||||
### 2.9 Декомпил ремейка лежит на диске
|
||||
|
||||
`G:\src-decoded` (5207 файлов, строки расшифрованы), также `G:\src`,
|
||||
`G:\builds\v31\src{,-decoded}` и `G:\decompiled-rustex`. Это **исходники клиента
|
||||
ремейка** — читаемый Java. Значит, поведение клиента можно смотреть, а не
|
||||
угадывать: конфиг оружия с отдачей (`recoilYawMin`, `recoilPitchMax`,
|
||||
`ADSScale`, `movementPenalty`, `shotsUntilMax`, `aimconeCurve`), схема
|
||||
viewmodel (`sway`, `bob`, `crouch`, `lower`, `freeLook`), формат данных —
|
||||
Unity-подобные ключи (`m_Name`, `MonoBehaviour`), читаются через `ctkmb`.
|
||||
|
||||
**Важно:** сами **значения** приходят с сервера (Unity-ассеты), в клиенте есть
|
||||
только схема и значения по умолчанию. Урон, отдача и спрей огнестрела — серверные
|
||||
данные; в клиенте только магазин, тип патрона, зум ADS, прочность и reach.
|
||||
|
||||
---
|
||||
|
||||
## 3. Архитектура
|
||||
@@ -997,8 +1033,15 @@ rsx-client\scripts\run-game.ps1 # клиент
|
||||
восстанавливается по сигнатуре формата: PNG — `b[0] ^ 0x89`, OGG — `b[0] ^ 0x4F`,
|
||||
JSON — `b[0] ^ 0x7B`. Уже декодировано: `assets/rustex/` (1.65 ГБ, 19 тыс. файлов).
|
||||
Скрипты: `tools/extract-rustex-assets.py`, `tools/extract-items.py`,
|
||||
`tools/extract-sounds.py`. **Осталось непрочитанным:** 553 `.obj`, 106 `.rrs`,
|
||||
113 `.nbt` — у них не однобайтовый шифр, нужен разбор кода.
|
||||
`tools/extract-sounds.py`, `tools/catalog-remake.py`.
|
||||
**Каталог всего, что есть у ремейка:** `assets/rustex/CATALOG.md` +
|
||||
`catalog.json` (218 предметов, 91 объект nextgen, 553 модели `.obj`
|
||||
(384 оружия, 128 постройки, 36 мебели, 4 руды), 133 иконки, 2004 звука,
|
||||
текстуры монументов и животных, скины игрока в `assets/rustex/player/`).
|
||||
- ⚠️ **Классы ремейка прочитать нельзя без взлома загрузчика.** Классы в `game.jar`
|
||||
помечены `DE AD C0 DE` и зашифрованы своим форматом (не однобайтовым XOR, как
|
||||
ассеты). **Но декомпил лежит на диске** — `G:\src-decoded` (п. 2.9): читать
|
||||
поведение клиента можно, REA не нужен.
|
||||
- **Текстуры предметов — временные** (взяты из ремейка как заглушки, иконки из
|
||||
`assets/rustex/gamejar/assets/rustex/icons/atlas.png`). **ПОМЕТКА: заменить на свои.**
|
||||
- **База предметов** — `rsx-protocol/src/main/resources/items/items.csv` (218 шт,
|
||||
|
||||
@@ -1247,3 +1247,19 @@ import org.lwjgl.opengl.ContextCapabilities;
|
||||
}
|
||||
|
||||
Entity entity = this.mc.getRenderViewEntity();
|
||||
|
||||
# Our player. The class is ours, the behaviour is the vanilla one, and the two
|
||||
# numbers that differ from the vanilla live in RsxPlayerTuning.
|
||||
=== FILE net/minecraft/client/multiplayer/PlayerControllerMP.java
|
||||
--- BEFORE
|
||||
return new EntityPlayerSP(this.mc, world, this.connection, statisticsmanager, recipebook);
|
||||
--- AFTER
|
||||
return new net.rustnotmc.client.RsxPlayer(this.mc, world, this.connection, statisticsmanager, recipebook);
|
||||
|
||||
# The movement input of our player: the same input with our crouch in it.
|
||||
=== FILE net/minecraft/client/Minecraft.java
|
||||
--- BEFORE
|
||||
this.player.movementInput = new MovementInputFromOptions(this.gameSettings);
|
||||
--- AFTER
|
||||
this.player.movementInput = new net.rustnotmc.client.RsxMovementInput(this.gameSettings);
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package net.rustnotmc.client;
|
||||
|
||||
import net.minecraft.client.settings.GameSettings;
|
||||
import net.minecraft.util.MovementInputFromOptions;
|
||||
|
||||
/**
|
||||
* The movement input with our crouch.
|
||||
*
|
||||
* The vanilla already slows the movement down by a third while the crouch key
|
||||
* is down. We undo that and apply our own factor, so the crouch is a little
|
||||
* faster than the vanilla one and the number itself lives in
|
||||
* {@link RsxPlayerTuning}.
|
||||
*/
|
||||
public class RsxMovementInput extends MovementInputFromOptions {
|
||||
|
||||
public RsxMovementInput(GameSettings settings) {
|
||||
super(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePlayerMoveState() {
|
||||
super.updatePlayerMoveState();
|
||||
if (!this.sneak) {
|
||||
return;
|
||||
}
|
||||
float correction = RsxPlayerTuning.sneakCorrection();
|
||||
this.moveStrafe = (float) ((double) this.moveStrafe * (double) correction);
|
||||
this.moveForward = (float) ((double) this.moveForward * (double) correction);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package net.rustnotmc.client;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.entity.EntityPlayerSP;
|
||||
import net.minecraft.client.network.NetHandlerPlayClient;
|
||||
import net.minecraft.stats.RecipeBook;
|
||||
import net.minecraft.stats.StatisticsManager;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
/**
|
||||
* Our player.
|
||||
*
|
||||
* The vanilla player with two things changed: the crouch is a little faster
|
||||
* and the jump is a little higher. Everything else is the vanilla behaviour,
|
||||
* so the player still walks, swims, rides, fights and dies the way the game
|
||||
* always did.
|
||||
*
|
||||
* The class exists so that the numbers of our movement live in our code and
|
||||
* not in the patches of the vanilla tree: the patches only have to say "the
|
||||
* player is ours", and everything else is here.
|
||||
*/
|
||||
public class RsxPlayer extends EntityPlayerSP {
|
||||
|
||||
public RsxPlayer(Minecraft minecraft, World world, NetHandlerPlayClient connection,
|
||||
StatisticsManager statistics, RecipeBook recipeBook) {
|
||||
super(minecraft, world, connection, statistics, recipeBook);
|
||||
this.movementInput = new RsxMovementInput(minecraft.gameSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* How high the player jumps.
|
||||
*
|
||||
* The override lives here and not in EntityLivingBase, because the mobs
|
||||
* have to keep jumping the way they always did.
|
||||
*/
|
||||
@Override
|
||||
protected float getJumpUpwardsMotion() {
|
||||
return RsxPlayerTuning.jumpMotion();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package net.rustnotmc.client;
|
||||
|
||||
/**
|
||||
* The numbers that make our player move the way our player moves.
|
||||
*
|
||||
* The player is the vanilla one with two things changed: the crouch is a
|
||||
* little faster and the jump is a little higher. Both numbers live here so
|
||||
* that the rest of the code has one place to point at, and so that a change
|
||||
* is one line.
|
||||
*
|
||||
* They can be set from the command line:
|
||||
*
|
||||
* -Drsx.sneakSpeed=1.6 -Drsx.jumpHeight=1.4
|
||||
*
|
||||
* A value that is set below the floor is not taken: the crouch stays at 1.5
|
||||
* blocks per second and the jump at 1.5 blocks. The defaults are the ones we
|
||||
* want, and they are not put through the floor.
|
||||
*/
|
||||
public final class RsxPlayerTuning {
|
||||
|
||||
/** How fast the player walks, in blocks per second. The vanilla. */
|
||||
public static final float WALK_SPEED = 4.317F;
|
||||
|
||||
/** How fast the player crouches, in blocks per second. */
|
||||
public static final float SNEAK_SPEED = 1.5F;
|
||||
|
||||
/** How much the vanilla crouch slows the movement down. */
|
||||
public static final float VANILLA_SNEAK_FACTOR = 0.3F;
|
||||
|
||||
/** How high the player jumps, in blocks. The vanilla jumps 1.25. */
|
||||
public static final float JUMP_HEIGHT = 1.35F;
|
||||
|
||||
/** The crouch never goes below this, in blocks per second. */
|
||||
public static final float SNEAK_SPEED_FLOOR = 1.5F;
|
||||
|
||||
/** The jump never goes below this, in blocks. */
|
||||
public static final float JUMP_HEIGHT_FLOOR = 1.5F;
|
||||
|
||||
/** The jump the vanilla has, in blocks. Used to scale the jump speed. */
|
||||
private static final float VANILLA_JUMP_HEIGHT = 1.25F;
|
||||
|
||||
/** The speed the vanilla jumps with. */
|
||||
private static final float VANILLA_JUMP_MOTION = 0.42F;
|
||||
|
||||
private RsxPlayerTuning() {
|
||||
}
|
||||
|
||||
/** The crouch speed: the setting, or the default. */
|
||||
public static float sneakSpeed() {
|
||||
return setting("rsx.sneakSpeed", SNEAK_SPEED, SNEAK_SPEED_FLOOR);
|
||||
}
|
||||
|
||||
/** The jump height: the setting, or the default. */
|
||||
public static float jumpHeight() {
|
||||
return setting("rsx.jumpHeight", JUMP_HEIGHT, JUMP_HEIGHT_FLOOR);
|
||||
}
|
||||
|
||||
/** The factor the crouch slows the movement down by. */
|
||||
public static float sneakFactor() {
|
||||
return sneakSpeed() / WALK_SPEED;
|
||||
}
|
||||
|
||||
/**
|
||||
* How much faster our crouch is than the vanilla one.
|
||||
*
|
||||
* The vanilla already multiplied the movement by a third. We undo that
|
||||
* and put ours in its place.
|
||||
*/
|
||||
public static float sneakCorrection() {
|
||||
return sneakFactor() / VANILLA_SNEAK_FACTOR;
|
||||
}
|
||||
|
||||
/** The upward speed a jump of our height needs. */
|
||||
public static float jumpMotion() {
|
||||
return jumpMotion(jumpHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* The upward speed a jump of that height needs.
|
||||
*
|
||||
* The vanilla jumps with 0.42 and that carries it 1.25 blocks. The height
|
||||
* grows with the square of the speed, so the speed for a height is the
|
||||
* vanilla one times the square root of the ratio.
|
||||
*/
|
||||
public static float jumpMotion(float height) {
|
||||
if (height <= 0.0F) {
|
||||
return VANILLA_JUMP_MOTION;
|
||||
}
|
||||
return VANILLA_JUMP_MOTION * (float) Math.sqrt(height / VANILLA_JUMP_HEIGHT);
|
||||
}
|
||||
|
||||
private static float setting(String name, float fallback, float floor) {
|
||||
String value = System.getProperty(name);
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
return fallback;
|
||||
}
|
||||
float parsed;
|
||||
try {
|
||||
parsed = Float.parseFloat(value.trim());
|
||||
} catch (NumberFormatException ignored) {
|
||||
return fallback;
|
||||
}
|
||||
if (parsed != parsed) {
|
||||
return fallback;
|
||||
}
|
||||
return parsed < floor ? floor : parsed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user