代码初始化

This commit is contained in:
Giao committed 2026-10-09 18:23:57 +08:00
1 parent c2f5cd98ff
commit e3efeb74ad
2439 files changed
+305661 -1

No files matched your search

+497
View File
@@ -0,0 +1,497 @@
# -*- coding: utf-8 -*-
"""救护车一次性生成器 (v3,支持多车型):
对 CARS 里的每个车型:
1) 依据其源模型(用户原始模型,已按网易规则做过 X 镜像)逐立方体做层级变换,
得到世界空间 AABB。关键:网易方块实体的渲染映射为 world = (-geo_x/16, geo_y/16, geo_z/16),
即 X 轴镜像;因此碰撞检测必须使用镜像后的世界坐标。
2) 对 4 个车体旋转角 φ(0/90/180/270) 分别求「每格与车体的交集 AABB」,量化到 1/QUANT 格并去重;
几何体根骨骼 Y 角设为 90 - φ(才能得到世界旋转 φ),并把整车居中到主方块中心、车底贴地。
3) 生成:每个车型一份预旋转几何体(4 条,按朝向)、4 个主方块 + 若干形状共享占位方块;
汇总生成 blocks.json、terrain_texture.json,并把布局数据写入 ambulanceServer.py 末尾。
车型命名规则(tag):
主方块 dn_sa:<tag>_main_<yaw>
占位方块 dn_sa:<tag>_ph_<NN> (形状共享,仅提供碰撞)
几何体 geometry.dn_sa_<tag>_<yaw> -> resource_pack/models/entity/<tag>_geo.json
client_entity entity/<tag>_main_<yaw>_block_entity.json
"""
import json
import math
import os
import io
ROOT = r"e:\MCStudioDownload\work\3027246179@qq.com\Cpp\AddOn\0aaaa4f2f16c46e8964025a5ab0ecc02"
BP = os.path.join(ROOT, "behavior_pack_kcH4M5u0")
RP = os.path.join(ROOT, "resource_pack_t7eJjrxY")
BEH_BLOCKS_DIR = os.path.join(BP, "netease_blocks")
ENTITY_DIR = os.path.join(RP, "entity")
MODELS_DIR = os.path.join(RP, r"models\entity")
# ---------------------------------------------------------------- 车型配置
# tag 决定所有资源的命名;src 是源模型;tex 是该车的整车贴图(client_entity 用)
CARS = [
{
"item": "dn_sa:ambulance_car",
"tag": "ambulance_car",
"src": os.path.join(ROOT, "_gen_amb_src.geo.json"),
"tex": "textures/blocks/ambulance_car",
},
{
"item": "dn_sa:ambulance_car_broken",
"tag": "ambulance_car_broken",
"src": os.path.join(ROOT, "_gen_amb_src_broken.geo.json"),
"tex": "textures/blocks/ambulance_broken",
},
{
"item": "dn_sa:ambulance_car_destroyed",
"tag": "ambulance_car_destroyed",
"src": os.path.join(ROOT, "_gen_amb_src_destroyed.geo.json"),
"tex": "textures/blocks/ambulance_destroyed",
},
]
# 模型原点(0,0,0) 对应的主方块格上的位置(最小角)。若游戏内整车整体偏移,改这里即可。
ANCHOR = (0.0, 0.0, 0.0)
QUANT = 8 # 碰撞盒量化精度:1/8 格(约 2 个贴图像素)
YAWS = (0, 90, 180, 270) # 车体在世界空间的旋转角 φ(几何体根骨骼 Y 角 = 90 - φ)
# 玩家朝向象限(0=南+Z,90=西-X,180=北-Z,270=东+X) -> 车体世界旋转角 φ
# 依据:φ=0 时车头(capo)朝 -X;把 -X 经 Ry(φ) 转到玩家朝向
EXTRA_BY_FACING = {0: 90, 90: 0, 180: 270, 270: 180}
# 「车体局部格」(φ=0 坐标系,相对主方块的偏移) —— 这些格子不生成占位方块。
# 1) 挡风玻璃前上方 / 引擎盖正上方那一片:玻璃是斜的(rot -60),它的整体包围盒把它前上方
# 那几格也算了进去,但那里几乎全是空气,碰撞盒会悬在车外。
# 2) 引擎盖最前面(x=-3) 顶层(y=2):该层只有车头那 0.3 格高的一小条,比它后面一格
# (x=-2 顶层已被剔除) 高出来一截,看起来像引擎盖前端多出一块,一并剔除。
# 3) 警灯上方(x=-1) 顶层(y=3):驾驶室顶只有 2.79 高、警灯顶 3.02,这一层(3.0~3.5)全部
# 来自斜挡风玻璃包围盒的溢出,正好悬在警灯上方,看起来像警灯位置高出来一块。
SKIP_LOCAL = {
(-2, 2, -1), (-2, 2, 0), (-2, 2, 1),
(-2, 3, -1), (-2, 3, 0), (-2, 3, 1),
(-3, 2, -1), (-3, 2, 0), (-3, 2, 1),
(-1, 3, -1), (-1, 3, 0), (-1, 3, 1),
}
BLANK_MODEL = "dn_sa:ambulance_blank_model"
# 主方块碰撞盒模式(网易方块实体的模型可见性跟着「方块所在位置」走,主方块碰撞盒太小时,
# 玩家把屏幕转开、方块位置离开视野,整车的实体模型就会停止渲染而消失):
# "model":给主方块一个涵盖整车的碰撞盒(解决上述消失问题)
# "cell" :只用主方块自己那一格的碰撞盒(旧行为,其余碰撞靠占位方块)
MAIN_BOX_MODE = "model"
def model_box(bones, anchor):
"""整车在世界空间(相对主方块)的包围盒 (lo, hi)。"""
boxes = world_boxes(bones, False) # 含零厚度面片,才能真的涵盖整个模型
lo = [min(bx[0][i] for bx in boxes) + anchor[i] for i in range(3)]
hi = [max(bx[1][i] for bx in boxes) + anchor[i] for i in range(3)]
return lo, hi
def skip_cells(phi):
"""把车体局部格按 yaw 旋转成世界格(与几何体同向)。"""
out = set()
for (x, y, z) in SKIP_LOCAL:
if phi == 0:
ax, az = x, z
elif phi == 90:
ax, az = z, -x
elif phi == 180:
ax, az = -x, -z
else:
ax, az = -z, x
out.add((ax, y, az))
return out
# ---------------------------------------------------------------- math
def mat_mul(a, b):
return [[sum(a[i][k] * b[k][j] for k in range(3)) for j in range(3)] for i in range(3)]
def rot_axis(axis, deg):
t = math.radians(deg)
c, s = math.cos(t), math.sin(t)
if axis == 'x':
return [[1, 0, 0], [0, c, -s], [0, s, c]]
if axis == 'y':
return [[c, 0, s], [0, 1, 0], [-s, 0, c]]
return [[c, -s, 0], [s, c, 0], [0, 0, 1]]
def euler(rx, ry, rz):
m = rot_axis('y', ry)
m = mat_mul(m, rot_axis('x', rx))
m = mat_mul(m, rot_axis('z', rz))
return m
def apply(m, v, pivot):
d = [v[i] - pivot[i] for i in range(3)]
return [sum(m[i][k] * d[k] for k in range(3)) + pivot[i] for i in range(3)]
def cube_corners(cube):
o = cube['origin']
s = cube['size']
pt = cube.get('pivot', [0, 0, 0])
pts = [[o[0] + s[0] * dx, o[1] + s[1] * dy, o[2] + s[2] * dz]
for dx in (0, 1) for dy in (0, 1) for dz in (0, 1)]
rot = cube.get('rotation')
if rot and any(abs(v) > 1e-9 for v in rot):
m = euler(rot[0], rot[1], rot[2])
pts = [apply(m, p, pt) for p in pts]
return pts
def bone_world(by_name, bone, p):
b = bone
while b is not None:
rot = b.get('rotation')
if rot and any(abs(v) > 1e-9 for v in rot):
m = euler(rot[0], rot[1], rot[2])
p = apply(m, p, b.get('pivot', [0, 0, 0]))
b = by_name.get(b.get('parent'))
return p
def world_boxes(bones, skip_degenerate=True):
by_name = {b['name']: b for b in bones}
out = []
for b in bones:
for cube in b.get('cubes', []):
pts = [bone_world(by_name, b, p) for p in cube_corners(cube)]
# 网易方块实体:世界 X = -几何体 X(镜像),Y/Z 不变
pts = [[-p[0] / 16.0, p[1] / 16.0, p[2] / 16.0] for p in pts]
lo = [min(p[i] for p in pts) for i in range(3)]
hi = [max(p[i] for p in pts) for i in range(3)]
if skip_degenerate and (hi[0] - lo[0] < 1e-9 or hi[1] - lo[1] < 1e-9
or hi[2] - lo[2] < 1e-9):
continue # 零厚度面片不产生碰撞
out.append((lo, hi))
return out
def cells_for(boxes, anchor):
cells = {}
for lo, hi in boxes:
lo = [lo[i] + anchor[i] for i in range(3)]
hi = [hi[i] + anchor[i] for i in range(3)]
for ix in range(int(math.floor(lo[0])), int(math.floor(hi[0] - 1e-9)) + 1):
for iy in range(int(math.floor(lo[1])), int(math.floor(hi[1] - 1e-9)) + 1):
for iz in range(int(math.floor(lo[2])), int(math.floor(hi[2] - 1e-9)) + 1):
key = (ix, iy, iz)
l = [max(lo[0], ix), max(lo[1], iy), max(lo[2], iz)]
h = [min(hi[0], ix + 1), min(hi[1], iy + 1), min(hi[2], iz + 1)]
if key not in cells:
cells[key] = [l, h]
else:
pl, ph = cells[key]
cells[key] = [[min(pl[i], l[i]) for i in range(3)],
[max(ph[i], h[i]) for i in range(3)]]
return cells
def q(v, quant):
return round(v * quant) / float(quant)
def local_aabb(cell, l, h, quant=QUANT):
"""返回该格内 0~1 的局部 AABB(量化 1/quant),退化则返回 None。"""
lo = [min(1.0, max(0.0, q(l[i] - cell[i], quant))) for i in range(3)]
hi = [min(1.0, max(0.0, q(h[i] - cell[i], quant))) for i in range(3)]
if any(hi[i] - lo[i] <= 1e-6 for i in range(3)):
return None
return tuple(lo + hi)
# ---------------------------------------------------------------- per-car 计算
def load_model(path):
with io.open(path, 'r', encoding='utf-8') as f:
raw = json.load(f)
geo = raw['minecraft:geometry'][0]
root = [b['name'] for b in geo['bones'] if 'parent' not in b][0]
return geo, root
def bones_for_yaw(base_geo, root_name, phi):
"""车体世界旋转 φ 时对应的几何体骨骼:根骨骼 Y=90-φ,并把整车居中到主方块中心、车底贴地。"""
bones = json.loads(json.dumps(base_geo['bones']))
for b in bones:
if b['name'] == root_name:
r = list(b.get('rotation', [0, 0, 0]))
b['rotation'] = [r[0], r[1] - phi, r[2]]
# 求世界包围盒(已含 X 镜像,含零厚度面片),据此算居中/贴地平移量
boxes = world_boxes(bones, False)
lo = [min(bx[0][i] for bx in boxes) for i in range(3)]
hi = [max(bx[1][i] for bx in boxes) for i in range(3)]
cx = (lo[0] + hi[0]) / 2.0
cz = (lo[2] + hi[2]) / 2.0
tw = (0.5 - cx, -lo[1], 0.5 - cz) # 期望世界平移:X/Z 居中,车底 y=0
shift = [-16.0 * tw[0], 16.0 * tw[1], 16.0 * tw[2]] # 几何体空间平移 Δ = 16*diag(-1,1,1)*tw
for b in bones:
p = b.get('pivot', [0, 0, 0])
b['pivot'] = [p[i] + shift[i] for i in range(3)]
for c in b.get('cubes', []):
o = c['origin']
c['origin'] = [o[i] + shift[i] for i in range(3)]
if 'pivot' in c: # 立方体自带旋转轴心也需平移,否则非纯平移
cp = c['pivot']
c['pivot'] = [cp[i] + shift[i] for i in range(3)]
return bones
def wjson(path, obj):
with io.open(path, 'w', encoding='utf-8') as f:
f.write(json.dumps(obj, indent=2, ensure_ascii=False))
f.write(u"\n")
def block_json(identifier, aabb):
lo, hi = aabb
return {
"format_version": "1.10.0",
"minecraft:block": {
"description": {
"identifier": identifier,
"register_to_creative_menu": False,
"is_experimental": False,
"category": "dn_sa",
},
"components": {
"netease:solid": {"value": True},
"netease:render_layer": {"value": "alpha"},
"minecraft:destroy_time": {"value": 1.0},
"minecraft:block_light_absorption": {"value": 0},
"netease:aabb": {
"collision": {"min": list(lo), "max": list(hi)},
"clip": {"min": list(lo), "max": list(hi)},
},
},
},
}
# 清理可能残留的旧文件(旧命名 / 旧的占位方块 / 旧的渲染锚点方块与客户端实体)
for f in os.listdir(BEH_BLOCKS_DIR):
if not f.endswith(".json"):
continue
if f == "ambulance_car_main.json" or ("ambulance_car" in f and "_ph_" in f):
os.remove(os.path.join(BEH_BLOCKS_DIR, f))
for f in os.listdir(ENTITY_DIR):
if not f.endswith(".json"):
continue
if "ambulance_car" in f and f.endswith("_block_entity.json"):
os.remove(os.path.join(ENTITY_DIR, f))
old_ent = os.path.join(ENTITY_DIR, "ambulance_car_main_block_entity.json")
if os.path.exists(old_ent):
os.remove(old_ent)
results = []
for car in CARS:
tag = car["tag"]
main_prefix = "dn_sa:%s_main" % tag
ph_prefix = "dn_sa:%s_ph_" % tag
geo_id = "geometry.dn_sa_%s" % tag
base_geo, root_name = load_model(car["src"])
per_yaw = {} # yaw -> {cell: local_aabb}(逐格碰撞盒)
yaw_bones = {} # yaw -> 已旋转/居中/贴地的骨骼
main_box = {} # yaw -> 整车包围盒(主方块「涵盖整个模型」碰撞盒)
shapes = {} # local_aabb -> None(形状共享)
for yaw in YAWS:
bones = bones_for_yaw(base_geo, root_name, yaw)
yaw_bones[yaw] = bones
cells = cells_for(world_boxes(bones), ANCHOR)
skip = skip_cells(yaw)
table = {}
removed = []
for cell, (l, h) in sorted(cells.items()):
if cell in skip:
removed.append(cell)
continue
la = local_aabb(cell, l, h)
if la is None:
continue
table[cell] = la
per_yaw[yaw] = table
main_box[yaw] = model_box(bones, ANCHOR)
for la in table.values():
shapes.setdefault(la, None)
print("[%s] yaw=%3d cells=%3d shapes=%3d removed=%s"
% (tag, yaw, len(table), len(set(table.values())), removed))
# 形状共享占位方块命名(每种「格内碰撞盒」一个变体,被多格复用)
names = {}
for i, shape in enumerate(sorted(shapes.keys())):
names[shape] = "%s%02d" % (ph_prefix, i)
# 每格最终方块名:主方块所在格用主方块,其余用形状共享占位方块
cellname = {}
for yaw in YAWS:
m = {}
for cell, la in per_yaw[yaw].items():
if cell == (0, 0, 0):
continue
m[cell] = names[la]
cellname[yaw] = m
print("[%s] placeholder shapes=%d" % (tag, len(names)))
# 1) 预旋转几何体:每个朝向一条,渲染整车
geos = []
for yaw in YAWS:
desc = dict(base_geo["description"])
desc["identifier"] = "%s_%d" % (geo_id, yaw)
geos.append({"description": desc, "bones": yaw_bones[yaw]})
wjson(os.path.join(MODELS_DIR, "%s_geo.json" % tag),
{"format_version": "1.21.0", "minecraft:geometry": geos})
# 2) client_entity:每个朝向一份,渲染整车
for yaw in YAWS:
name = "%s_%d" % (main_prefix, yaw)
ent = {
"format_version": "1.10.0",
"minecraft:client_entity": {
"description": {
"identifier": name,
"geometry": {"default": "%s_%d" % (geo_id, yaw)},
"textures": {"default": car["tex"]},
"materials": {"default": "entity_alphatest"},
"render_controllers": [
{"controller.render.dn_sa_ambulance_car_main": "variable.variant == 0"}
],
"scripts": {"initialize": ["variable.variant = 0;"]},
}
},
}
wjson(os.path.join(ENTITY_DIR, "%s_main_%d_block_entity.json" % (tag, yaw)), ent)
# 3) 方块定义:主方块(带 block_entity) + 形状共享占位方块
for yaw in YAWS:
name = "%s_%d" % (main_prefix, yaw)
if MAIN_BOX_MODE == "model":
lo, hi = main_box[yaw] # 一个碰撞盒涵盖整车
else:
la = per_yaw[yaw].get((0, 0, 0)) or (0.0, 0.0, 0.0, 1.0, 1.0, 1.0)
lo, hi = la[:3], la[3:]
obj = block_json(name, (lo, hi))
obj["minecraft:block"]["components"]["netease:block_entity"] = {"tick": False, "movable": False}
wjson(os.path.join(BEH_BLOCKS_DIR, "%s_main_%d.json" % (tag, yaw)), obj)
for shape, name in sorted(names.items(), key=lambda kv: kv[1]):
short = name.split(":", 1)[1]
wjson(os.path.join(BEH_BLOCKS_DIR, short + ".json"),
block_json(name, (shape[:3], shape[3:])))
results.append({
"item": car["item"], "tag": tag, "tex": car["tex"],
"main_prefix": main_prefix, "ph_prefix": ph_prefix,
"cellname": cellname, "names": names,
})
print("[%s] wrote geo + %d client_entity + %d block defs"
% (tag, len(YAWS), len(YAWS) + len(names)))
# ---------------------------------------------------------------- blocks.json
blocks = {"format_version": [1, 1, 0]}
blocks["dn_sa:generator"] = {
"sound": "stone",
"client_entity": {
"identifier": "dn_sa:generator",
"hand_model_use_client_entity": True,
"destroyed_textures": "dn_sa:generator_icon",
"block_icon": "dn_sa:generator_icon",
},
}
blocks["dn_sa:salvage_bin"] = {
"sound": "stone",
"client_entity": {
"identifier": "dn_sa:salvage_bin",
"hand_model_use_client_entity": True,
"destroyed_textures": "dn_sa:salvage_bin_icon",
"block_icon": "dn_sa:salvage_bin_icon",
},
}
for car in results:
for yaw in YAWS:
name = "%s_%d" % (car["main_prefix"], yaw)
blocks[name] = {
"sound": "stone",
"client_entity": {
"identifier": name,
"hand_model_use_client_entity": True,
"destroyed_textures": car["item"],
"block_icon": car["item"],
},
}
for car in results:
for shape, name in sorted(car["names"].items(), key=lambda kv: kv[1]):
blocks[name] = {"sound": "stone", "netease_model": BLANK_MODEL}
wjson(os.path.join(RP, "blocks.json"), blocks)
print("wrote blocks.json (%d entries)" % len(blocks))
# ---------------------------------------------------------------- terrain_texture.json
tt_path = os.path.join(RP, r"textures\terrain_texture.json")
with io.open(tt_path, 'r', encoding='utf-8') as f:
tt = json.load(f)
td = tt["texture_data"]
for legacy in ("dn_sa_ambulance_car_main", "dn_sa_ambulance_car_placeholder"):
td.pop(legacy, None)
for key in [k for k in td if k.startswith("dn_sa:ambulance") and "_ph_a" in k]:
td.pop(key, None) # 清掉上一次生成的渲染锚点贴图条目
for car in results:
td["dn_sa_%s" % car["tag"]] = {"textures": car["tex"]} # 方块破坏/图标贴图
for yaw in YAWS:
td["%s_%d" % (car["main_prefix"], yaw)] = {"textures": "textures/blocks/dn_sa_blank"}
wjson(tt_path, tt)
print("wrote terrain_texture.json")
# ---------------------------------------------------------------- 注入碰撞数据到 ambulanceServer.py
MARKER = u"# ==== AUTO-GENERATED LAYOUT DATA (by _gen_amb.py, do not edit) ===="
lines = []
lines.append(u"")
lines.append(MARKER)
lines.append(u"# 玩家朝向象限(0=南+Z,90=西-X,180=北-Z,270=东+X) -> 车体额外 Y 旋转角")
lines.append(u"EXTRA_BY_FACING = {")
for k in sorted(EXTRA_BY_FACING):
lines.append(u" %d: %d," % (k, EXTRA_BY_FACING[k]))
lines.append(u"}")
lines.append(u"# 物品名 -> 车型数据")
lines.append(u"CARS = {}")
for car in results:
lines.append(u"CARS['%s'] = {" % car["item"])
lines.append(u" 'main_prefix': '%s'," % car["main_prefix"])
lines.append(u" 'ph_prefix': '%s'," % car["ph_prefix"])
lines.append(u" 'main': {")
for yaw in YAWS:
lines.append(u" %d: '%s_%d'," % (yaw, car["main_prefix"], yaw))
lines.append(u" },")
lines.append(u" 'cells': {")
for yaw in YAWS:
lines.append(u" %d: {" % yaw)
for cell in sorted(car["cellname"][yaw].keys()):
if cell == (0, 0, 0):
continue
lines.append(u" (%d, %d, %d): '%s',"
% (cell[0], cell[1], cell[2], car["cellname"][yaw][cell]))
lines.append(u" },")
lines.append(u" },")
lines.append(u"}")
data_block = u"\n".join(lines)
server_path = os.path.join(BP, r"Script_dn_sa\ambulanceServer.py")
with io.open(server_path, 'r', encoding='utf-8') as f:
content = f.read()
idx = content.find(MARKER)
if idx < 0:
raise RuntimeError("marker not found in ambulanceServer.py")
with io.open(server_path, 'w', encoding='utf-8') as f:
f.write(content[:idx] + data_block + u"\n")
print("injected layout data into ambulanceServer.py")
old_layout = os.path.join(BP, r"Script_dn_sa\ambulanceLayout.py")
if os.path.exists(old_layout):
os.remove(old_layout)
print("DONE")