AI part 5

pull/28/head
Sascha 2025-08-20 18:04:00 +07:00
parent 1d204d1c6c
commit 3beac6a586
15 changed files with 330 additions and 274 deletions

@ -5,13 +5,21 @@
</component>
<component name="ChangeListManager">
<list default="true" id="c3091895-9cbd-4d08-bc3f-599dc2d3e86d" name="Changes" comment="">
<<<<<<< HEAD
<change beforePath="$PROJECT_DIR$/.idea/libraries/GdSdk_Master.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/libraries/GdSdk_Master.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/scenes/game.tscn" beforeDir="false" afterPath="$PROJECT_DIR$/scenes/game.tscn" afterDir="false" />
<change beforePath="$PROJECT_DIR$/scripts/enemy.gd" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/enemy.gd" afterDir="false" />
=======
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
>>>>>>> refs/remotes/origin/master
<change beforePath="$PROJECT_DIR$/scripts/enemy_overlay.gd" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/enemy_overlay.gd" afterDir="false" />
<change beforePath="$PROJECT_DIR$/scripts/game_manager.gd" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/game_manager.gd" afterDir="false" />
<change beforePath="$PROJECT_DIR$/scripts/player.gd" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/player.gd" afterDir="false" />
<change beforePath="$PROJECT_DIR$/scripts/rogue.gd" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/rogue.gd" afterDir="false" />
<change beforePath="$PROJECT_DIR$/scripts/spawn_manager.gd" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/spawn_manager.gd" afterDir="false" />
<change beforePath="$PROJECT_DIR$/scripts/spawn_point.gd" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/spawn_point.gd" afterDir="false" />
<change beforePath="$PROJECT_DIR$/scripts/survivor_male_b.gd" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/scripts/survivor_male_b.gd.uid" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/scripts/target.gd" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/target.gd" afterDir="false" />
<change beforePath="$PROJECT_DIR$/scripts/ui.gd" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/ui.gd" afterDir="false" />
<change beforePath="$PROJECT_DIR$/scripts/unit.gd" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/unit.gd" afterDir="false" />
<change beforePath="$PROJECT_DIR$/scripts/wall_doorway.gd" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/wall_doorway.gd" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -49,20 +57,12 @@
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;gdscript.promo.shown&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;,
<<<<<<< HEAD
=======
&quot;git-widget-placeholder&quot;: &quot;range__weapons&quot;,
&quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
>>>>>>> refs/remotes/origin/master
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
<<<<<<< HEAD
=======
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.build.godotPlugin&quot;,
>>>>>>> refs/remotes/origin/master
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}
}</component>
@ -78,15 +78,15 @@
<method v="2" />
</configuration>
<configuration name="Editor" type="RunNativeExe" factoryName="Native Executable">
<<<<<<< HEAD
<option name="EXE_PATH" value="$USER_HOME$/Godot/Godot_v4.4-dev6_linux.x86_64" />
=======
<option name="EXE_PATH" value="$USER_HOME$/Godot/Godot_v4.4.1-stable_linux.x86_64" />
>>>>>>> refs/remotes/origin/master
<option name="EXE_PATH" value="$USER_HOME$/Godot/Godot_v4.5-beta5_linux.x86_64" />
<option name="PROGRAM_PARAMETERS" value="--path &quot;./&quot; --editor" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="ENV_FILE_PATHS" value="" />
<option name="REDIRECT_INPUT_PATH" value="" />
<option name="PTY_MODE" value="Auto" />
<option name="MIXED_MODE_DEBUG" value="0" />
<method v="2" />
</configuration>
</component>
@ -99,14 +99,7 @@
<option name="presentableId" value="Default" />
<updated>1733914913603</updated>
<workItem from="1733914927162" duration="262000" />
<workItem from="1733915211671" duration="365000" />
<<<<<<< HEAD
<workItem from="1744011636834" duration="97000" />
=======
<workItem from="1735554556434" duration="163000" />
<workItem from="1741342963625" duration="433000" />
<workItem from="1744011637645" duration="109000" />
>>>>>>> refs/remotes/origin/master
<workItem from="1755703701834" duration="1959000" />
</task>
<servers />
</component>
@ -118,4 +111,13 @@
<component name="VcsManagerConfiguration">
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<default-breakpoints>
<breakpoint enabled="true" type="GDSCRIPT_EXCEPTION_BP_TYPE">
<properties exception="GdScript Exception" />
</breakpoint>
</default-breakpoints>
</breakpoint-manager>
</component>
</project>

File diff suppressed because one or more lines are too long

@ -89,8 +89,8 @@ func _on_state_changed(_old_state: States, new_state: States) -> void:
name_changed.emit(unit_name, States.keys()[new_state])
match new_state:
States.attacking:
if enough_stamina_available(attack_cost):
use_stamina(attack_cost)
if can_spend_stamina(attack_cost):
spend_stamina(attack_cost)
if attacks.size() > 0:
anim_state.travel(attacks.pick_random())
States.dead:

@ -35,4 +35,4 @@ func _on_name_changed(enemy_name: String, current_state: String) -> void:
func _format_name(enemy_name: String, current_state: String) -> String:
return "%s (%s)" % [enemy_name, current_state]
return "%s (%s)" % [enemy_name, current_state]

@ -29,4 +29,4 @@ func game_over() -> void:
func _schedule_enemy_count_sync() -> void:
await get_tree().create_timer(ENEMY_COUNT_INIT_DELAY).timeout
if _spawn_manager and _spawn_manager.has_method("enemy_count_changed"):
_spawn_manager.call("enemy_count_changed")
_spawn_manager.call("enemy_count_changed")

@ -11,6 +11,7 @@ const JOYPAD_DEADZONE := 0.10
signal gold_changed(current_gold: int)
signal player_loaded()
var _gold: int = 0
var was_on_floor: bool = true
var gold: int:
get:
@ -68,8 +69,6 @@ func _input(event: InputEvent) -> void:
get_tree().quit()
# --- Extracted/neu strukturierte Hilfsfunktionen ---
func apply_gravity(delta: float) -> void:
velocity.y += -gravity * delta
@ -111,8 +110,8 @@ func handle_mouse_camera(event: InputEvent) -> void:
func handle_jump(on_floor: bool) -> void:
if on_floor and Input.is_action_just_pressed("jump"):
if enough_stamina_available(jump_cost):
use_stamina(jump_cost)
if can_spend_stamina(jump_cost):
spend_stamina(jump_cost)
velocity.y = jump_speed
state = States.jumping
anim_tree.set("parameters/conditions/grounded", false)
@ -120,7 +119,7 @@ func handle_jump(on_floor: bool) -> void:
func update_ground_state(on_floor: bool) -> void:
# Gerade gelandet
if on_floor and not last_floor:
if on_floor and not was_on_floor:
if state != States.idle:
state = States.idle
anim_tree.set("parameters/conditions/grounded", true)
@ -129,4 +128,4 @@ func update_ground_state(on_floor: bool) -> void:
anim_state.travel("Jump_Idle")
anim_tree.set("parameters/conditions/grounded", false)
anim_tree.set("parameters/conditions/jumping", state == States.jumping)
last_floor = on_floor
was_on_floor = on_floor

@ -77,10 +77,10 @@ func handle_attack() -> void:
if not is_loaded:
print("No arrow loaded!")
return
if not enough_stamina_available(attack_cost):
if not can_spend_stamina(attack_cost):
return
use_stamina(attack_cost)
spend_stamina(attack_cost)
anim_state.travel(ATTACK_ANIMS.pick_random())
is_loaded = false

@ -1,5 +1,7 @@
extends Node
const PLAYER_GROUP := "player"
const ENEMY_NAME_PATTERN := "%s%d"
@export var max_enemies: int = 3
@export var enemies_container: Node
@export var enemy_scenes: Array[PackedScene]
@ -7,10 +9,10 @@ extends Node
@export var spawn_points_container: Node
var player: Player
var current_enemies: Array[Enemy] = []
var _enemy_counter: int = 0
var _enemy_ui_ready: bool = false
var spawn_points: Array[SpawnPoint]
var current_enemies: Array[Enemy] = []
var _enemy_counter: int = 0
var _enemy_ui_ready: bool = false
var spawn_points: Array[SpawnPoint] = []
var enemy_ui_ready: bool:
get:
@ -18,92 +20,109 @@ var enemy_ui_ready: bool:
set(value):
_enemy_ui_ready = value
if value:
ensure_enemy_count()
maintain_enemy_quota()
func _ready() -> void:
player = get_tree().get_first_node_in_group("player")
player = get_tree().get_first_node_in_group(PLAYER_GROUP)
if player == null:
print("Error: No player found!")
# SpawnPoints aus dem Container laden (falls gesetzt)
populate_spawn_points_from_container()
collect_spawn_points_from_container()
func populate_spawn_points_from_container() -> void:
# Sammelt SpawnPoints aus dem konfigurierten Container (falls vorhanden)
func collect_spawn_points_from_container() -> void:
if spawn_points_container == null:
push_warning("spawn_points_container is not assigned; cannot auto-collect SpawnPoints.")
return
var collected: Array[SpawnPoint] = []
var children: Array = spawn_points_container.get_children()
var children: Array = spawn_points_container.get_children()
for child in children:
if child is SpawnPoint:
collected.append(child)
spawn_points = collected
func ensure_enemy_count() -> void:
# Hält die Anzahl der aktiven Gegner auf dem gewünschten Soll
func maintain_enemy_quota() -> void:
var current := current_enemies.size()
print("Enough enemies? %d/%d" % [current, max_enemies])
print("Enemy quota: %d/%d" % [current, max_enemies])
if current >= max_enemies:
print("Enough enemies!")
return
var empty_points := find_empty_spawn_points()
if empty_points.is_empty():
print("No free SpawnPoints found!")
return
var need: int = min(max_enemies - current, empty_points.size())
for i in need:
var required_count: int = min(max_enemies - current, empty_points.size())
for i in required_count:
spawn_enemy(empty_points[i])
func spawn_enemy(spawn_point: SpawnPoint) -> void:
if enemy_scenes.is_empty():
push_warning("No enemy scenes configured.")
return
var scene: PackedScene = enemy_scenes.pick_random()
if scene == null:
push_warning("No enemy scene available to spawn.")
return
var new_enemy := scene.instantiate() as Enemy
if new_enemy == null:
push_warning("PackedScene did not instantiate as Enemy.")
return
configure_enemy(new_enemy, spawn_point)
register_enemy(new_enemy, spawn_point)
update_enemies_ui()
# Stellt Eigenschaften des Gegners vor dem Hinzufügen ein
func configure_enemy(new_enemy: Enemy, spawn_point: SpawnPoint) -> void:
new_enemy.player = player
_enemy_counter += 1
new_enemy.name = "%s%d" % [new_enemy.name, _enemy_counter]
new_enemy.name = ENEMY_NAME_PATTERN % [new_enemy.name, _enemy_counter]
new_enemy.unit_name = new_enemy.name
print("Spawn %s on %s" % [new_enemy.unit_name, spawn_point.name])
print("Spawn %s at %s" % [new_enemy.unit_name, spawn_point.name])
new_enemy.position = spawn_point.position
new_enemy.unit_died.connect(_on_unit_died)
enemies_container.add_child(new_enemy)
spawn_point.isFull = true
# Fügt Gegner zur Szene hinzu und markiert den SpawnPoint
func register_enemy(new_enemy: Enemy, spawn_point: SpawnPoint) -> void:
enemies_container.add_child(new_enemy)
spawn_point.is_occupied = true
spawn_point.enemy = new_enemy
current_enemies.append(new_enemy)
enemies_ui_manager.update_enemies_ui(current_enemies)
func update_enemies_ui() -> void:
if enemies_ui_manager != null and enemy_ui_ready:
enemies_ui_manager.update_enemies_ui(current_enemies)
func find_empty_spawn_points() -> Array[SpawnPoint]:
var empty_spawn_points: Array[SpawnPoint] = []
print("Count of spawn points: " + str(spawn_points.size()))
for sp in spawn_points:
if not sp.isFull:
if not sp.is_occupied:
empty_spawn_points.append(sp)
else:
print("Spawn point " + sp.name + " is full!")
return empty_spawn_points
func remove_enemy(enemy: Enemy) -> void:
var index := current_enemies.find(enemy)
if index != -1:
current_enemies.remove_at(index)
else:
print("Enemy is not in the list!")
func _on_unit_died(unit: Unit) -> void:
if unit is Enemy:
print("%s died -> Remove from current_enemies." % unit.unit_name)
var index := current_enemies.find(unit)
if index != -1:
current_enemies.remove_at(index)
ensure_enemy_count()
else:
print("Enemy is not in the list!")
remove_enemy(unit)
maintain_enemy_quota()
update_enemies_ui()
else:
print("Something died which is not an enemy!")

@ -1,20 +1,37 @@
class_name SpawnPoint extends Node3D
class_name SpawnPoint
extends Node3D
@export var isFull: bool:
var _is_occupied: bool = false
var _enemy: Enemy = null
@export var is_occupied: bool:
get:
return isFull
return _is_occupied
set(value):
isFull = value
_is_occupied = value
@export var enemy: Enemy:
get:
return enemy
return _enemy
set(value):
enemy = value
if enemy != null:
enemy.state_changed.connect(_on_enemy_state_changed)
if _enemy == value:
return
# Vorherige Verbindung lösen, falls vorhanden
if _enemy and _enemy.state_changed.is_connected(_on_enemy_state_changed):
_enemy.state_changed.disconnect(_on_enemy_state_changed)
_enemy = value
if _enemy:
# Neue Verbindung herstellen, doppelte Verbindungen vermeiden
if not _enemy.state_changed.is_connected(_on_enemy_state_changed):
_enemy.state_changed.connect(_on_enemy_state_changed)
_is_occupied = true
else:
_is_occupied = false
func _on_enemy_state_changed(old_state: Unit.States, new_state: Unit.States) -> void:
if old_state == Unit.States.idle and new_state != Unit.States.idle:
enemy = null
isFull = false

@ -1,52 +0,0 @@
extends CharacterBody3D
@onready var anim_tree := $AnimationTree
@onready var anim_state = $AnimationTree.get("parameters/playback")
@onready var spring_arm: SpringArm3D = $SpringArm3D
@onready var model := $Root
var jumping := false
var mouse_sensitivity := 0.006
var rotation_speed := 24.0
var lerp_val := 0.1
const SPEED = 5.0
const JUMP_VELOCITY = 4.5
func _input(event: InputEvent) -> void:
# Move camera
if event is InputEventMouseMotion:
spring_arm.rotation.x -= event.relative.y * mouse_sensitivity
spring_arm.rotation_degrees.x = clamp(spring_arm.rotation_degrees.x, -90.0, 30.0)
spring_arm.rotation.y -= event.relative.x * mouse_sensitivity
func _physics_process(delta: float) -> void:
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta
jumping = !is_on_floor()
# Get the input direction and handle the movement/deceleration.
var input = Input.get_vector("left", "right", "forward", "back")
var direction = Vector3(input.x, 0, input.y).rotated(Vector3.UP, spring_arm.rotation.y)
if direction:
if not jumping:
anim_state.travel("run")
velocity.x = lerp(velocity.x, direction.x * SPEED, lerp_val)
velocity.z = lerp(velocity.z, direction.z * SPEED, lerp_val)
else:
anim_state.travel("idle")
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
if velocity.length() > 1.0:
model.rotation.y = lerp_angle(model.rotation.y, spring_arm.rotation.y, rotation_speed * delta)
# Handle jump.
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = JUMP_VELOCITY
jumping = true
anim_tree.set("parameters/conditions/jumping", jumping)
anim_tree.set("parameters/conditions/grounded", not jumping)
move_and_slide()

@ -1 +0,0 @@
uid://cqwweorvt4hbi

@ -1,16 +1,29 @@
class_name Target extends RigidBody3D
var health: int = 10
class_name Target
extends RigidBody3D
signal damaged(amount)
const MAX_HEALTH: int = 10
var current_health: int = MAX_HEALTH
signal damaged(amount: int)
signal died()
func take_damage(damage_amount: int) -> void:
health -= damage_amount
emit_signal("damaged", damage_amount)
if health <= 0:
func is_dead() -> bool:
return current_health <= 0
func take_damage(amount: int) -> void:
if amount <= 0:
return
if is_dead():
return
var was_alive := not is_dead()
current_health = max(0, current_health - amount)
emit_signal("damaged", amount)
if was_alive and is_dead():
emit_signal("died")
die()
func die() -> void:
# Optional: spawn explosion, play animation/sound, then remove
# Optional: Explosion/Animation/Sound abspielen, dann entfernen
queue_free()

@ -1,13 +1,15 @@
extends Control
@export var health_bar: TextureProgressBar
@export var health_bar: TextureProgressBar
@export var stamina_bar: TextureProgressBar
@export var gold_label: Label
@onready var player: Player = get_tree().get_first_node_in_group("player")
@onready var cross_hair: BoxContainer = $CrossHair
@export var gold_label: Label
@onready var player: Player = get_tree().get_first_node_in_group("player")
@onready var crosshair: BoxContainer = $CrossHair
const GOLD_LABEL_PREFIX := "Gold: "
var unit: Unit
func _ready() -> void:
# Connect signals from the player to update the UI elements
player.health_changed.connect(update_health_bar)
@ -16,19 +18,24 @@ func _ready() -> void:
player.player_loaded.connect(_on_player_loaded)
# Connect the state_changed signal to update the crosshair visibility
unit = player as Unit
unit.connect("state_changed", Callable(self, "_on_state_changed"))
if unit:
unit.state_changed.connect(_on_state_changed)
func update_health_bar(current_health: int, maximum_health: int) -> void:
# Update the health bar with the current health
health_bar.value = (100.0 / maximum_health) * current_health
_set_progress(health_bar, current_health, maximum_health)
func update_stamina_bar(current_stamina: int, maximum_stamina: int) -> void:
# Update the stamina bar with the current stamina
stamina_bar.value = (100.0 / maximum_stamina) * current_stamina
_set_progress(stamina_bar, current_stamina, maximum_stamina)
func update_gold_text(gold: int) -> void:
# Update the gold label with the current gold amount
gold_label.text = "Gold: " + str(gold)
gold_label.text = "%s%d" % [GOLD_LABEL_PREFIX, gold]
func _on_player_loaded() -> void:
# Initialize the health, stamina, and gold bars when the player is loaded
@ -36,9 +43,15 @@ func _on_player_loaded() -> void:
update_stamina_bar(player.stamina, player.maximum_stamina)
update_gold_text(player.gold)
func _on_state_changed(old_state: Unit.States, new_state: Unit.States):
func _on_state_changed(old_state: Unit.States, new_state: Unit.States) -> void:
# Update the crosshair visibility based on the new state
if new_state == Unit.States.aiming:
cross_hair.visible = true
else:
cross_hair.visible = false
crosshair.visible = (new_state == Unit.States.aiming)
func _set_progress(bar: TextureProgressBar, current: int, maximum: int) -> void:
# Safeguard against division by zero and map to 0..100
if maximum <= 0:
bar.value = 0.0
return
bar.value = (100.0 * float(current)) / float(maximum)

@ -1,26 +1,29 @@
class_name Unit extends CharacterBody3D
@export var maximum_health := 10
@export var maximum_stamina := 50
@export var stamina_regeneration_rate := 2
@export var speed := 4.0
@export var acceleration := 4.0
@export var jump_speed := 8.0
@export var jump_cost := 20
@export var attack_cost := 10
@export var damage := 1
class_name Unit
extends CharacterBody3D
# Konstante(n)
const ANIM_BLOCK_HIT := "Block_Hit"
const STAMINA_TICK_SECONDS := 0.1
# Exportierte Eigenschaften (balancing)
@export var maximum_health: int = 10
@export var maximum_stamina: int = 50
@export var stamina_regeneration_rate: int = 2
@export var speed: float = 4.0
@export var acceleration: float = 4.0
@export var jump_speed: float = 8.0
@export var jump_cost: int = 20
@export var attack_cost: int = 10
@export var damage: int = 1
@export var model: Node3D
@onready var anim_tree := $AnimationTree
@onready var anim_state = $AnimationTree.get("parameters/playback")
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
var last_floor := true
var stamina_timer: float
var stamina_timer_max := 0.1
@export var hits := ["Hit_A", "Hit_B"]
@export var deaths := ["Death_A", "Death_B"]
# Animation
@onready var anim_tree: AnimationTree = $AnimationTree
@onready var anim_state: AnimationNodeStateMachinePlayback = anim_tree.get("parameters/playback")
# Animation-Sets
@export var hit_animations: PackedStringArray = ["Hit_A", "Hit_B"]
@export var death_animations: PackedStringArray = ["Death_A", "Death_B"]
# Zustände
enum States {
idle,
walking,
@ -32,83 +35,117 @@ enum States {
dead,
aiming
}
# Signale
signal name_changed(unit_name: String)
signal health_changed(current_health: int, maximum_health: int)
signal stamina_changed(current_stamina: int, maximum_stamina: int)
signal state_changed(old_state: States, new_state: States)
signal unit_died(unit: Unit)
# Zufall für Animationsauswahl
var rng := RandomNumberGenerator.new()
# Interne Backing-Felder
var _state: States = States.idle
var _unit_name: String = ""
var _health: int = maximum_health
var _stamina: int = maximum_stamina
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
var state = States.idle:
# Eigenschaften mit validen Getter/Setter (ohne Rekursion)
var state: States:
get:
return state
return _state
set(value):
state_changed.emit(state, value)
state = value
if value == _state:
return
var old: States = _state
_state = value
state_changed.emit(old, _state)
@export var unit_name: String:
get:
return unit_name
return _unit_name
set(value):
unit_name = value
name_changed.emit(unit_name, str(state))
_unit_name = value
name_changed.emit(_unit_name, str(_state))
var health : int:
var health: int:
get:
return health
return _health
set(value):
if state == States.dead:
print("Error in health handling. Unit dead!")
else:
health = clampi(value, 0, maximum_health)
health_changed.emit(health, maximum_health)
if hits.size() > 0:
anim_state.travel(hits.pick_random())
if health <= 0:
die()
var stamina : int:
if is_dead():
push_warning("Fehler in Health-Handling. Unit bereits tot!")
return
_health = clampi(value, 0, maximum_health)
health_changed.emit(_health, maximum_health)
if hit_animations.size() > 0 and _health > 0:
anim_state.travel(random_string(hit_animations))
if _health <= 0:
die()
var stamina: int:
get:
return stamina
return _stamina
set(value):
stamina = clampi(value, 0, maximum_stamina)
stamina_changed.emit(stamina, maximum_stamina)
_stamina = clampi(value, 0, maximum_stamina)
stamina_changed.emit(_stamina, maximum_stamina)
# Stamina Regeneration
var stamina_timer: float = 0.0
signal name_changed(unit_name: String)
signal health_changed(current_health: int, maximum_health: int)
signal stamina_changed(current_stamina: int, maximum_stamina: int)
signal state_changed(old_state: States, new_state: States)
signal unit_died(unit: Unit)
func _process(delta: float) -> void:
stamina_timer += delta
if stamina_timer > stamina_timer_max:
if stamina_timer >= STAMINA_TICK_SECONDS:
stamina += stamina_regeneration_rate
stamina_timer = 0
stamina_timer = 0.0
func is_dead() -> bool:
return _state == States.dead
func take_damage(damage_amount: int) -> void:
if state == States.dead:
print(unit_name + " cannot take damage -> is already dead!")
elif state == States.blocking:
anim_state.travel("Block_Hit")
else:
health -= damage_amount
health_changed.emit(health, maximum_health)
if health <= 0:
die()
func enough_stamina_available(stamina_needed: int) -> bool:
return stamina_needed <= stamina
if is_dead():
print(_unit_name + " kann keinen Schaden nehmen -> bereits tot!")
return
if _state == States.blocking:
anim_state.travel(ANIM_BLOCK_HIT)
return
health = _health - damage_amount
func can_spend_stamina(stamina_needed: int) -> bool:
return stamina_needed <= _stamina
func spend_stamina(stamina_needed: int) -> void:
stamina = _stamina - stamina_needed
func use_stamina(stamina_needed: int) -> void:
stamina -= stamina_needed
func die() -> void:
if state != States.dead:
if not is_dead():
state = States.dead
unit_died.emit(self)
if deaths.size() > 0:
anim_state.travel(deaths.pick_random())
if death_animations.size() > 0:
anim_state.travel(random_string(death_animations))
else:
print(unit_name + " can't die -> is already dead!")
print(_unit_name + " kann nicht sterben -> bereits tot!")
func remove_unit() -> void:
if self is Player:
var player = self as Player
var player := self as Player
player.player_game_over()
else:
queue_free()
# Hilfsfunktion: zufälliger String aus PackedStringArray
func random_string(arr: PackedStringArray) -> String:
if arr.is_empty():
return ""
if rng.seed == 0:
rng.randomize()
var idx := rng.randi_range(0, arr.size() - 1)
return arr[idx]

@ -1,24 +1,32 @@
class_name WallDoorway extends ItemInteractable
class_name WallDoorway
extends ItemInteractable
func _ready() -> void:
state_changed.connect(_on_state_changed)
interaction_area.interact = Callable(self, "_on_interact")
state = States.closed
apply_state(States.closed)
func _on_interact() -> void:
match state:
States.closed:
animation_player.play("open")
#recalculate_navigation_map.emit(self)
state = States.opened
States.opened:
animation_player.play("close")
#recalculate_navigation_map.emit(self)
state = States.closed
apply_state(_toggled_state(state))
func _on_state_changed(new_state: States) -> void:
match new_state:
States.closed:
interaction_area.action_name = "open door"
States.opened:
interaction_area.action_name = "close door"
_update_action_name(new_state)
func apply_state(new_state: States) -> void:
if new_state == States.closed:
animation_player.play(ANIM_CLOSE)
else:
animation_player.play(ANIM_OPEN)
state = new_state
_update_action_name(new_state)
func _toggled_state(s: States) -> States:
return States.opened if s == States.closed else States.closed
func _update_action_name(s: States) -> void:
interaction_area.action_name = ACTION_OPEN if s == States.closed else ACTION_CLOSE