This commit is contained in:
team3
2026-09-16 12:09:33 +02:00
parent 9410d18ea5
commit 8a188c5d5c
29 changed files with 2016 additions and 564 deletions

View File

@@ -16,6 +16,8 @@ const BENCH_OFFSET := 1.95 ## Abstand der Bank hinter der letzten Reihe
var game: Game = null
var camera: Camera3D = null
## Brett eines anderen Spielers, das in der Planung oben gezeigt wird.
var scout_player: PlayerState = null
var selected_slot := {} ## {"kind": "board"/"bench", ...}
var hover_slot := {}
var drag_active := false
@@ -31,6 +33,12 @@ var _bench_tiles := {} ## int -> MeshInstance3D
var _bench_mats := {}
var _bench_z := 0.0
var _units := {} ## key -> Node3D
var _dying := {} ## key -> Restzeit der Sterbe-Animation
const DEATH_TIME := 0.45
var _shots: Array = [] ## fliegende Geschosse {"node", "from", "to", "t", "dur"}
var shots_total := 0 ## Zähler aller Geschosse, für Tests
var _shot_mesh: SphereMesh
var _slash_mesh: BoxMesh
var _origin := Vector3.ZERO
var _tile_mesh: CylinderMesh
var _ring_mesh: TorusMesh
@@ -48,6 +56,13 @@ func _ready() -> void:
_ring_mesh.outer_radius = 0.66
_ring_mesh.ring_segments = 8
_ring_mesh.rings = 24
_shot_mesh = SphereMesh.new()
_shot_mesh.radius = 0.11
_shot_mesh.height = 0.22
_shot_mesh.radial_segments = 8
_shot_mesh.rings = 4
_slash_mesh = BoxMesh.new()
_slash_mesh.size = Vector3(0.12, 0.12, 0.7)
_build_tiles()
_build_bench()
set_process(true)
@@ -212,6 +227,46 @@ func _process(delta: float) -> void:
return
_update_tiles()
_update_units(delta)
_update_shots(delta)
## Sichtbarer Angriff: Fernkampf schickt eine Kugel, Nahkampf einen kurzen Hieb.
func spawn_attack(from: Vector3, to: Vector3, ranged: bool, col: Color) -> void:
var mi := MeshInstance3D.new()
mi.mesh = _shot_mesh if ranged else _slash_mesh
var m := StandardMaterial3D.new()
m.albedo_color = col
m.emission_enabled = true
m.emission = col
m.emission_energy_multiplier = 1.8
mi.material_override = m
var a := from + Vector3(0.0, 0.7, 0.0)
var b := to + Vector3(0.0, 0.6, 0.0)
if not ranged:
# Der Hieb startet vor dem Angreifer und endet am Ziel.
a = a.lerp(b, 0.35)
mi.position = a
if (b - a).length() > 0.01:
mi.look_at_from_position(a, b, Vector3.UP)
add_child(mi)
shots_total += 1
_shots.append({"node": mi, "from": a, "to": b, "t": 0.0, "dur": 0.2 if ranged else 0.09})
func _update_shots(delta: float) -> void:
var keep := []
for sh in _shots:
sh["t"] += delta
var f: float = sh["t"] / sh["dur"]
var node: MeshInstance3D = sh["node"]
if f >= 1.0:
node.queue_free()
continue
var pos: Vector3 = sh["from"].lerp(sh["to"], f)
pos.y += sin(f * PI) * 0.35
node.position = pos
keep.append(sh)
_shots = keep
func _update_tiles() -> void:
@@ -284,6 +339,10 @@ func _update_units(delta: float) -> void:
"side": 0 if u.team == my_team else 1,
"cast": u.cast_flash > 0.0,
"hit": u.hit_flash > 0.0,
"burn": u.burn_timer > 0.0,
"slow": u.slow_timer > 0.0 and u.slow > 0.0,
"stun": u.stun_timer > 0.0,
"shield": clampf(u.shield / maxf(1.0, u.max_hp), 0.0, 1.0),
}
else:
for bh in game.human.board:
@@ -292,6 +351,13 @@ func _update_units(delta: float) -> void:
"avatar": a, "world": world_pos(from_board(bh)), "hp": 1.0, "mana": 0.0,
"side": 0, "cast": false, "hit": false,
}
if scout_player != null and scout_player.alive:
for bh in scout_player.board:
var sa: Avatar = scout_player.board[bh]
wanted["s%d" % sa.id] = {
"avatar": sa, "world": world_pos(Combat._arena_pos(bh, 1)), "hp": 1.0,
"mana": 0.0, "side": 1, "cast": false, "hit": false,
}
# Die Bank ist immer sichtbar, auch während des Kampfes.
for i in game.human.bench.size():
var ba = game.human.bench[i]
@@ -310,9 +376,25 @@ func _update_units(delta: float) -> void:
wanted[drag_key]["dragged"] = true
for key in _units.keys():
if not wanted.has(key):
_units[key].queue_free()
if wanted.has(key):
continue
var node: Node3D = _units[key]
if not _dying.has(key):
# Im Kampf schrumpft die Einheit weg, in der Planung verschwindet sie sofort.
if game.phase == Game.Phase.COMBAT and key.begins_with("c"):
_dying[key] = DEATH_TIME
else:
node.queue_free()
_units.erase(key)
continue
_dying[key] -= delta
var t: float = maxf(0.0, _dying[key] / DEATH_TIME)
node.scale = Vector3(t, t * 0.6, t)
node.position.y = -0.4 * (1.0 - t)
if _dying[key] <= 0.0:
node.queue_free()
_units.erase(key)
_dying.erase(key)
for key in wanted:
var d: Dictionary = wanted[key]
@@ -332,10 +414,31 @@ func _update_units(delta: float) -> void:
var body: MeshInstance3D = node.get_meta("body")
var mat: StandardMaterial3D = body.material_override
var base: Color = d["avatar"].color()
mat.albedo_color = base.lightened(0.5) if d["hit"] else base
var col := base
if d.get("slow", false):
col = col.lerp(Color(0.55, 0.8, 1.0), 0.45)
if d.get("stun", false):
col = col.lerp(Color(1.0, 0.95, 0.4), 0.5)
if d["hit"]:
col = col.lightened(0.5)
mat.albedo_color = col
var dragged: bool = d.get("dragged", false)
mat.emission_energy_multiplier = 1.6 if d["cast"] else (0.45 if dragged else 0.12)
node.scale = Vector3.ONE * (1.12 if (d["cast"] or dragged) else 1.0)
var emit := 0.12
if d["cast"]:
emit = 1.6
elif d.get("burn", false):
emit = 0.9
mat.emission = Color(1.0, 0.45, 0.15)
elif dragged:
emit = 0.45
else:
mat.emission = base
mat.emission_energy_multiplier = emit
var sc := 1.12 if (d["cast"] or dragged) else 1.0
if d.get("stun", false):
node.scale = Vector3(sc * 1.08, sc * 0.8, sc * 1.08)
else:
node.scale = Vector3.ONE * sc
draw_units.append({
"pos": node.position, "avatar": d["avatar"], "hp": d["hp"],
"mana": d["mana"], "side": d["side"], "cast": d["cast"],
@@ -347,12 +450,77 @@ func unit_node(avatar_id: int) -> Node3D:
return _units.get("a%d" % avatar_id)
## Für das Overlay: Arena-Feld so spiegeln, wie das Brett gezeichnet wird.
func mirror_for_view(p: Vector2i, my_team: int) -> Vector2i:
return _mirror(p, my_team)
func _mirror(p: Vector2i, my_team: int) -> Vector2i:
if my_team == 0:
return p
return Vector2i(COLS - 1 - p.x, ROWS - 1 - p.y)
## Kleines Kennzeichen des Hauptelements auf oder neben dem Körper.
func _add_ornament(root: Node3D, a: Avatar, body_y: float) -> void:
var e := a.primary_element()
if e < 0:
return
var col: Color = Data.ELEMENT_COLORS[e]
var m := StandardMaterial3D.new()
m.albedo_color = col.lightened(0.15)
m.emission_enabled = true
m.emission = col
m.emission_energy_multiplier = 0.5
var top := body_y + 0.5
match e:
Data.Element.FEUER:
# Flamme: kleiner Kegel oben.
var c := CylinderMesh.new()
c.top_radius = 0.0
c.bottom_radius = 0.16
c.height = 0.38
c.radial_segments = 8
_orn(root, c, m, Vector3(0.0, top + 0.18, 0.0))
Data.Element.WASSER:
# Tropfen: flache Kugel oben.
var sp := SphereMesh.new()
sp.radius = 0.16
sp.height = 0.22
_orn(root, sp, m, Vector3(0.0, top + 0.12, 0.0))
Data.Element.WIND:
# Finne: dünne, hohe Platte.
var b := BoxMesh.new()
b.size = Vector3(0.05, 0.42, 0.28)
_orn(root, b, m, Vector3(0.0, top + 0.2, 0.0))
Data.Element.BLITZ:
# Zacke: Prisma oben.
var pr := PrismMesh.new()
pr.size = Vector3(0.28, 0.4, 0.12)
_orn(root, pr, m, Vector3(0.0, top + 0.2, 0.0))
Data.Element.ERDE:
# Schultern: zwei Würfel seitlich.
for sx in [-1.0, 1.0]:
var bx := BoxMesh.new()
bx.size = Vector3(0.22, 0.22, 0.22)
_orn(root, bx, m, Vector3(sx * 0.5, body_y + 0.1, 0.0))
Data.Element.EIS:
# Kristall: umgedrehtes Prisma oben.
var cr := PrismMesh.new()
cr.size = Vector3(0.22, 0.42, 0.22)
var n := _orn(root, cr, m, Vector3(0.0, top + 0.2, 0.0))
n.rotation_degrees = Vector3(180.0, 45.0, 0.0)
func _orn(root: Node3D, mesh: Mesh, mat: Material, pos: Vector3) -> MeshInstance3D:
var mi := MeshInstance3D.new()
mi.mesh = mesh
mi.material_override = mat
mi.position = pos
root.add_child(mi)
return mi
func _make_unit(a: Avatar, side: int) -> Node3D:
var root := Node3D.new()
@@ -390,6 +558,8 @@ func _make_unit(a: Avatar, side: int) -> Node3D:
root.add_child(body)
root.set_meta("body", body)
_add_ornament(root, a, body.position.y)
# Bodenring: weiss = eigenes Team, rot = Gegner.
var ring := MeshInstance3D.new()
ring.mesh = _ring_mesh