From 08817925b5c6921016a371612344bb8eaba67475 Mon Sep 17 00:00:00 2001 From: min Date: Mon, 8 Jun 2026 21:05:15 +0300 Subject: [PATCH] =?UTF-8?q?fix(import):=20spawn=20point=20=D0=B2=D1=8B?= =?UTF-8?q?=D1=88=D0=B5=20=D0=BF=D0=BE=D0=BB=D0=B0=20+=20auto-fallback=20?= =?UTF-8?q?=D0=B5=D1=81=D0=BB=D0=B8=20SpawnLocation=20=D0=BD=D0=B5=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Юзер: "персонаж в стене заспавнился, ходить не может". Проблема: SpawnLocation в старых .rbxl ставится по CFrame с минимальным отступом от пола. Наш +1.5 недостаточно — толстые Floor'ы 2-3 units high полностью утопляют персонажа. Anchored=True (наш фикс) не даёт выпрыгнуть. Фиксы: 1. SpawnLocation +1.5 → +5 единиц выше плиты. 2. Auto-fallback: если spawnPoint остался дефолтом (0,2,0) = в карте не было SpawnLocation вообще — ставим над самой высокой Part: y = max(part.y + part.sy/2) + 5. Игрок упадёт на крышу/верх. Deploy converter.py на VM 130. Юзер должен переимпортировать карту чтобы получить новый spawnPoint. --- .../src/__pycache__/converter.cpython-314.pyc | Bin 60712 -> 61603 bytes rbxl-importer/src/converter.py | 20 +++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/rbxl-importer/src/__pycache__/converter.cpython-314.pyc b/rbxl-importer/src/__pycache__/converter.cpython-314.pyc index 4e9d3dba9e16132df1d1cca3525f4172b04ec3d4..85731e35eb01785f877cf9c839d011f6b6a0ddc1 100644 GIT binary patch delta 4956 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 diff --git a/rbxl-importer/src/converter.py b/rbxl-importer/src/converter.py index c86bbf8..3478cd8 100644 --- a/rbxl-importer/src/converter.py +++ b/rbxl-importer/src/converter.py @@ -294,6 +294,19 @@ class Converter: for inst in self.model.instances: self._convert_one(inst, scene) + # Spawn fallback: если SpawnLocation в карте НЕ был (или дефолт 0,2,0 + # остался) — поднимаем выше самой высокой Part'ы. Иначе игрок + # появляется внутри Anchored=True геометрии и не может двигаться. + sp = scene.get('spawnPoint', {'x': 0, 'y': 2, 'z': 0}) + if sp.get('x') == 0 and sp.get('y') == 2 and sp.get('z') == 0: + prims = scene.get('primitives', []) + if prims: + max_top = max( + (p['y'] + p.get('sy', 1) / 2) for p in prims + if isinstance(p.get('y'), (int, float)) + ) + scene['spawnPoint'] = {'x': 0, 'y': max_top + 5, 'z': 0} + # Финальный отчёт о скипнутых классах for cls, n in sorted(self.stats.skipped_classes.items(), key=lambda x: -x[1])[:30]: self.stats.warnings.append(f"skipped {n}× {cls}") @@ -690,11 +703,12 @@ class Converter: 'neutral': not bool(props.get('Neutral', True)) and team_color.code != 0, }) - # Spawn должен быть чуть выше — Roblox SpawnLocation это плоская плита, - # юзер появляется на её верхней грани. + # Spawn должен быть значительно выше — старые Roblox-карты часто имеют + # толстый Floor выше плиты, юзер появляется внутри стены/пола если + # не дать запас. +5 единиц достаточно — гравитация уронит на пол. scene['spawnPoint'] = { 'x': pos['x'], - 'y': pos['y'] + 1.5, # отступ вверх чтобы не залипнуть в плите + 'y': pos['y'] + 5, 'z': pos['z'], }