OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / maps / quake_entities.gd

114 lines · gd ref:
class_name QuakeEntities
extends RefCounted

## Shared Quake entity spawners: liquids, teleporters and func_door movers.
##
## The importer already recovers all of this (liquid_volumes, teleport_triggers /
## teleport_destinations / teleport_volumes, doors) — it is only the spawning that
## used to live as private methods on map_dc_terrain.gd, so map_dc_level got none of
## it. That is the same gap rtlights had: the data was parsed, the spawner existed,
## nobody called it.
##
## Static, taking an explicit `parent`, so any map can use them. map_dc_terrain still
## has its own copies and is deliberately NOT touched here — it works, and rewriting a
## danger-flagged file to de-duplicate is a separate change with its own risk.
## Migrating it onto these is the obvious follow-up.

const WORLD_SCALE := 0.028


## Water / lava / slime. Non-solid volumes: a warped surface at the top face plus a
## registered AABB the player uses for the screen blend and swimming.
static func spawn_liquids(parent: Node3D, importer: QuakeMapImporter,
						  wad_path: String, pal_path: String) -> int:
	var existing := parent.get_node_or_null("QuakeLiquids")
	if existing != null:
		existing.free()
	if importer.liquid_volumes.is_empty():
		return 0
	var names := {}
	for e in importer.liquid_volumes:
		names[String(e.get("tex", "")).to_lower()] = true
	var res := QuakeWadLoader.load(wad_path, pal_path, PackedStringArray(names.keys()), 64)
	var arr: Texture2DArray = res.get("array", null)
	var layers: Dictionary = res.get("layers", {})
	var container := Node3D.new()
	container.name = "QuakeLiquids"
	parent.add_child(container)
	var spawned := 0
	for e in importer.liquid_volumes:
		var layer: int = int(layers.get(String(e.get("tex", "")).to_lower(), 0))
		var lv := LiquidVolume.create(e, arr, layer, WORLD_SCALE)
		if lv != null:                 # create() returns null for degenerate slivers
			container.add_child(lv)
			spawned += 1
	return spawned


## trigger_teleport touch volumes wired to their info_teleport_destination, plus the
## *teleport swirly pads as visual-only volumes (rendered, not swimmable).
static func spawn_teleporters(parent: Node3D, importer: QuakeMapImporter,
							  wad_path: String, pal_path: String) -> Array:
	var existing := parent.get_node_or_null("QuakeTeleporters")
	if existing != null:
		existing.free()
	var container := Node3D.new()
	container.name = "QuakeTeleporters"
	parent.add_child(container)

	var wired := 0
	var dests: Dictionary = importer.teleport_destinations
	for trig in importer.teleport_triggers:
		var tgt := String(trig.get("target", ""))
		if not dests.has(tgt):
			continue                   # changelevel-style target with no local destination
		container.add_child(TeleportTrigger.create(trig, dests[tgt]))
		wired += 1

	var pads := 0
	if not importer.teleport_volumes.is_empty():
		var names := {}
		for e in importer.teleport_volumes:
			names[String(e.get("tex", "")).to_lower()] = true
		var res := QuakeWadLoader.load(wad_path, pal_path, PackedStringArray(names.keys()), 64)
		var arr: Texture2DArray = res.get("array", null)
		var layers: Dictionary = res.get("layers", {})
		for e in importer.teleport_volumes:
			var layer: int = int(layers.get(String(e.get("tex", "")).to_lower(), 0))
			var lv := LiquidVolume.create(e, arr, layer, WORLD_SCALE, true)   # visual_only
			if lv != null:
				container.add_child(lv)
				pads += 1
	return [wired, pads, dests.size()]


## func_door movers — animated AnimatableBody3D, deliberately excluded from the brush
## stamp by the importer. `locked` is already computed there from spawnflags 8/16
## (gold/silver key) or the presence of a targetname; there are no keys or buttons yet,
## so those stay shut, which is Quake's own default behaviour.
static func spawn_doors(parent: Node3D, importer: QuakeMapImporter,
						wad_path: String, pal_path: String) -> int:
	var existing := parent.get_node_or_null("QuakeDoors")
	if existing != null:
		existing.free()
	if importer.doors.is_empty():
		return 0
	var names := {}
	for d in importer.doors:
		names[String(d.get("tex", "")).to_lower()] = true
	var res := QuakeWadLoader.load(wad_path, pal_path, PackedStringArray(names.keys()), 64)
	var arr: Texture2DArray = res.get("array", null)
	var layers: Dictionary = res.get("layers", {})
	var container := Node3D.new()
	container.name = "QuakeDoors"
	parent.add_child(container)
	for d in importer.doors:
		var tex: Texture2D = null
		if arr != null:
			var layer: int = int(layers.get(String(d.get("tex", "")).to_lower(), 0))
			var img := arr.get_layer_data(layer)
			if img != null:
				tex = ImageTexture.create_from_image(img)
		container.add_child(DoorMover.create(d, tex))
	return importer.doors.size()