Files
my-world/World/map_loader.gd
T
aibrashandClaude Sonnet 5.5 75aa525cb8 Фикс загрузки карты в вебе: gzip и запись пакета
HTTPRequest в браузере пытался второй раз распаковать уже распакованный gzip
(код 8), а download_file в вебе не создаёт файл. Теперь accept_gzip=false, тело
берём в память и пишем в user:// сами; в сообщении об ошибке показываем причину.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 16:33:44 +05:00

148 lines
6.1 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
extends Node
# Подгружает карту отдельным пакетом при первом входе в портал: в вебе её не качают при старте игры.
# Пакет (например backrooms.pck) лежит рядом с index.html. Если сцена уже есть в проекте
# (редактор, десктоп-запуск) — качать нечего, переходим сразу.
signal enter_failed
const PACK_URL_ARG := "--pack-url=" # Для тестов на десктопе: --pack-url=http://127.0.0.1:8000/
var _loaded := {} # имя пакета -> true, чтобы не качать и не монтировать дважды
var _busy := false
var _layer: CanvasLayer
var _label: Label
var _bar: ProgressBar
func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
func enter(map: String, scene_path: String, pack_name: String, title: String) -> void:
if _busy:
return
if pack_name != "" and not _loaded.has(pack_name) and not ResourceLoader.exists(scene_path):
_busy = true
var ok := await _download_and_mount(pack_name, title)
_busy = false
if not ok:
enter_failed.emit()
return
Net.go_to_map(map, scene_path)
func _download_and_mount(pack_name: String, title: String) -> bool:
_show("Загрузка: %s…" % title)
get_tree().paused = true # Пока качаем, мир стоит (камень не должен убить игрока на экране загрузки)
var http := HTTPRequest.new()
http.process_mode = Node.PROCESS_MODE_ALWAYS
# Если сервер сжимает пакет gzip, браузер распаковывает его сам; вторая распаковка в движке ломает загрузку
http.accept_gzip = false
add_child(http)
# Тело берём в память и пишем в user:// сами: в вебе download_file файл не создаёт
var state := {"done": false, "result": -1, "code": 0, "body": PackedByteArray()}
http.request_completed.connect(func(result: int, code: int, _h, body: PackedByteArray):
state.result = result
state.code = code
state.body = body
state.done = true)
var ok := false
var detail := "запрос не отправлен"
# no-cache: браузер сверяет файл с сервером (после обновления пакета не подсунет старый из кэша)
if http.request(_pack_url(pack_name), ["Cache-Control: no-cache"]) == OK:
while not state.done:
_update_progress(http, title)
await get_tree().process_frame
if state.result == HTTPRequest.RESULT_SUCCESS and state.code == 200:
var path := "user://" + pack_name
var file := FileAccess.open(path, FileAccess.WRITE)
if file == null:
detail = "не удалось сохранить пакет, код %d" % FileAccess.get_open_error()
else:
file.store_buffer(state.body)
file.close()
state.body = PackedByteArray() # Освобождаем память: пакет уже на диске
ok = ProjectSettings.load_resource_pack(path, false) # false: файлы основной игры в приоритете
detail = "пакет скачан, но не подключился (%s)" % _describe_file(path)
else:
detail = "HTTP %d, код загрузки %d" % [state.code, state.result]
http.queue_free()
if ok:
_loaded[pack_name] = true
_hide()
else:
printerr("MapLoader: не удалось загрузить %s: %s" % [pack_name, detail])
_label.text = "Не удалось загрузить карту\n%s" % detail
_bar.visible = false
await get_tree().create_timer(2.5).timeout
_hide()
get_tree().paused = false
return ok
# Для сообщения об ошибке: размер и первые байты скачанного файла
func _describe_file(path: String) -> String:
var f := FileAccess.open(path, FileAccess.READ)
if f == null:
return "файл не открывается, код %d" % FileAccess.get_open_error()
var head := f.get_buffer(4)
return "%d байт, начало %s" % [f.get_length(), head.hex_encode()]
func _pack_url(pack_name: String) -> String:
for arg in OS.get_cmdline_user_args():
if arg.begins_with(PACK_URL_ARG):
return arg.substr(PACK_URL_ARG.length()) + pack_name
if OS.has_feature("web"):
return str(JavaScriptBridge.eval("new URL('%s', window.location.href).href" % pack_name))
return "http://127.0.0.1:8000/" + pack_name
# ---------- Экран загрузки ----------
func _show(text: String) -> void:
_layer = CanvasLayer.new()
_layer.layer = 100
add_child(_layer)
var bg := ColorRect.new()
bg.color = Color(0, 0, 0, 0.88)
bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
_layer.add_child(bg)
bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_label = Label.new()
_label.text = text
_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
_label.add_theme_font_size_override("font_size", 40)
bg.add_child(_label)
_label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_bar = ProgressBar.new()
_bar.custom_minimum_size = Vector2(420, 14)
_bar.show_percentage = false
_bar.indeterminate = true
var track := StyleBoxFlat.new()
track.bg_color = Color(1, 1, 1, 0.15)
var fill := StyleBoxFlat.new()
fill.bg_color = Color(1.0, 0.85, 0.25)
_bar.add_theme_stylebox_override("background", track)
_bar.add_theme_stylebox_override("fill", fill)
bg.add_child(_bar)
_bar.set_anchors_and_offsets_preset(Control.PRESET_CENTER_BOTTOM, Control.PRESET_MODE_KEEP_SIZE)
_bar.position.y -= 120
func _update_progress(http: HTTPRequest, title: String) -> void:
var got := http.get_downloaded_bytes()
var total := http.get_body_size() # -1, если сервер не прислал Content-Length (например, gzip)
if total > 0:
_bar.indeterminate = false
_bar.max_value = total
_bar.value = got
_label.text = "Загрузка: %s\n%.1f / %.1f МБ" % [title, got / 1048576.0, total / 1048576.0]
else:
_label.text = "Загрузка: %s\n%.1f МБ" % [title, got / 1048576.0]
func _hide() -> void:
if _layer:
_layer.queue_free()
_layer = null