Структура проекта: папки по назначению, карты самодостаточны, инструменты, README
- Перенос: Net→net, Player→player, World/*→maps/{hub,sandbox,backrooms}, core, objects,
models/*→chasers/{rock,verity} и maps/*/models. Имена внутри папок с .glb не менялись
(рядом лежат извлечённые текстуры). Все пути, пресеты экспорта и автолоады обновлены.
- Преследователи: rock-follow.gd → chasers/chaser.gd; камень в песочнице теперь инстанс
chasers/rock/rock.tscn (как Верити).
- Удалён мусор: неиспользуемое небо sunflowers, Tree_01.glb, rock-path.gd, старая
модель Верити, unwrap_cache (теперь в .gitignore).
- Основной пакет игры 70 КБ (камень и Верити уехали в пакеты карт).
- tools/: export_all.sh, serve_web.py (nginx-подобный dev-сервер), deploy_server.sh,
test/ (боты, временный проект и сервер). Net: флаг --client для headless-клиента.
- deploy/: боевые nginx.conf и my-world.service, project.godot выделенного сервера.
- README переписан: что есть и чего нет, структура, карты, преследователи, сеть, пакеты,
деплой, грабли, дорожная карта (телефон).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Локальный веб-сервер для проверки сборки, повторяет боевой nginx:
|
||||
заголовки COOP/COEP (нужны Godot web) и gzip без Content-Length (чанками).
|
||||
Так я поймал баг: браузер сам распаковывает gzip, и HTTPRequest в движке не должен делать это второй раз.
|
||||
|
||||
python3 tools/serve_web.py ../grid-world 8080 # открой http://127.0.0.1:8080
|
||||
"""
|
||||
import gzip, http.server, os, sys
|
||||
|
||||
ROOT = sys.argv[1] if len(sys.argv) > 1 else '../grid-world'
|
||||
PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 8080
|
||||
CACHE = {}
|
||||
TYPES = {'.wasm': 'application/wasm', '.js': 'application/javascript', '.html': 'text/html',
|
||||
'.pck': 'application/octet-stream', '.png': 'image/png'}
|
||||
|
||||
|
||||
class Handler(http.server.BaseHTTPRequestHandler):
|
||||
protocol_version = 'HTTP/1.1'
|
||||
|
||||
def do_GET(self):
|
||||
path = os.path.join(ROOT, self.path.split('?')[0].lstrip('/') or 'index.html')
|
||||
if not os.path.isfile(path):
|
||||
self.send_response(404); self.send_header('Content-Length', '0'); self.end_headers(); return
|
||||
data = open(path, 'rb').read()
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', TYPES.get(os.path.splitext(path)[1], 'application/octet-stream'))
|
||||
self.send_header('Cross-Origin-Opener-Policy', 'same-origin')
|
||||
self.send_header('Cross-Origin-Embedder-Policy', 'require-corp')
|
||||
if len(data) > 1024:
|
||||
key = (path, os.path.getmtime(path))
|
||||
gz = CACHE.get(key) or CACHE.setdefault(key, gzip.compress(data, 1))
|
||||
self.send_header('Content-Encoding', 'gzip')
|
||||
self.send_header('Transfer-Encoding', 'chunked')
|
||||
self.end_headers()
|
||||
for i in range(0, len(gz), 65536):
|
||||
chunk = gz[i:i + 65536]
|
||||
self.wfile.write(b'%x\r\n' % len(chunk) + chunk + b'\r\n')
|
||||
self.wfile.write(b'0\r\n\r\n')
|
||||
else:
|
||||
self.send_header('Content-Length', str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
print('GET', self.path, flush=True)
|
||||
|
||||
def log_message(self, *args):
|
||||
pass
|
||||
|
||||
|
||||
print(f'http://127.0.0.1:{PORT} ← {os.path.abspath(ROOT)}')
|
||||
http.server.ThreadingHTTPServer(('127.0.0.1', PORT), Handler).serve_forever()
|
||||
Reference in New Issue
Block a user