update
This commit is contained in:
@@ -9,6 +9,7 @@ enum Type { SINGLE, AOE, UTILITY, ULT, PASSIVE }
|
||||
@export var ability_range: float = 3.0
|
||||
@export var cooldown: float = 0.0
|
||||
@export var uses_gcd: bool = true
|
||||
@export var aoe_radius: float = 0.0
|
||||
@export var icon: String = ""
|
||||
|
||||
func execute(player: Node, targeting: Node) -> bool:
|
||||
@@ -69,12 +70,13 @@ func _execute_ult(player: Node, targeting: Node, dmg: float) -> bool:
|
||||
if not _in_range(player, targeting):
|
||||
return false
|
||||
var target: Node3D = targeting.current_target
|
||||
EventBus.damage_requested.emit(player, target, dmg * 4.0)
|
||||
EventBus.damage_requested.emit(player, target, dmg * 5.0)
|
||||
var aoe_range: float = aoe_radius if aoe_radius > 0 else ability_range
|
||||
var enemies := player.get_tree().get_nodes_in_group("enemies")
|
||||
for enemy in enemies:
|
||||
if enemy != target:
|
||||
var enemy_dist: float = target.global_position.distance_to(enemy.global_position)
|
||||
if enemy_dist <= ability_range:
|
||||
if enemy_dist <= aoe_range:
|
||||
EventBus.damage_requested.emit(player, enemy, dmg * 2.0)
|
||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg * 4.0)
|
||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg * 5.0)
|
||||
return true
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
extends Node
|
||||
|
||||
@export var max_health := 100.0
|
||||
const HEALTH_REGEN := 1.0
|
||||
@export var stats: EntityStats
|
||||
var max_health: float
|
||||
var health_regen: float
|
||||
var current_health: float
|
||||
|
||||
func _ready() -> void:
|
||||
max_health = stats.max_health
|
||||
health_regen = stats.health_regen
|
||||
current_health = max_health
|
||||
EventBus.damage_requested.connect(_on_damage_requested)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if current_health > 0 and current_health < max_health:
|
||||
current_health = min(current_health + HEALTH_REGEN * delta, max_health)
|
||||
if current_health > 0 and current_health < max_health and health_regen > 0:
|
||||
current_health = min(current_health + health_regen * delta, max_health)
|
||||
EventBus.health_changed.emit(get_parent(), current_health, max_health)
|
||||
|
||||
func _on_damage_requested(attacker: Node, target: Node, amount: float) -> void:
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
extends Node
|
||||
|
||||
@export var max_shield := 50.0
|
||||
const REGEN_DELAY := 3.0
|
||||
const REGEN_TIME := 5.0
|
||||
@export var stats: EntityStats
|
||||
var max_shield: float
|
||||
var regen_delay: float
|
||||
var regen_time: float
|
||||
var current_shield: float
|
||||
var regen_timer := 0.0
|
||||
|
||||
func _ready() -> void:
|
||||
max_shield = stats.max_shield
|
||||
regen_delay = stats.shield_regen_delay
|
||||
regen_time = stats.shield_regen_time
|
||||
current_shield = max_shield
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if max_shield <= 0:
|
||||
return
|
||||
if current_shield < max_shield:
|
||||
regen_timer += delta
|
||||
if regen_timer >= REGEN_DELAY:
|
||||
current_shield += (max_shield / REGEN_TIME) * delta
|
||||
if regen_timer >= regen_delay:
|
||||
current_shield += (max_shield / regen_time) * delta
|
||||
if current_shield >= max_shield:
|
||||
current_shield = max_shield
|
||||
EventBus.shield_regenerated.emit(get_parent())
|
||||
@@ -25,7 +31,6 @@ func absorb(amount: float) -> float:
|
||||
regen_timer = 0.0
|
||||
var absorbed: float = min(amount, current_shield)
|
||||
current_shield -= absorbed
|
||||
print("%s Schild: %s/%s (-%s)" % [get_parent().name, current_shield, max_shield, absorbed])
|
||||
if current_shield <= 0:
|
||||
EventBus.shield_broken.emit(get_parent())
|
||||
EventBus.shield_changed.emit(get_parent(), current_shield, max_shield)
|
||||
|
||||
44
scripts/components/spawner.gd
Normal file
44
scripts/components/spawner.gd
Normal file
@@ -0,0 +1,44 @@
|
||||
extends Node
|
||||
|
||||
@export var spawn_scene: PackedScene
|
||||
@export var spawn_count := 3
|
||||
@export var thresholds: Array[float] = [0.85, 0.70, 0.55, 0.40, 0.25, 0.10]
|
||||
|
||||
var triggered: Array[bool] = []
|
||||
|
||||
@onready var parent: Node3D = get_parent()
|
||||
@onready var health: Node = get_parent().get_node("Health")
|
||||
|
||||
func _ready() -> void:
|
||||
triggered.resize(thresholds.size())
|
||||
triggered.fill(false)
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if not spawn_scene or health.current_health <= 0:
|
||||
return
|
||||
var ratio: float = health.current_health / health.max_health
|
||||
for i in range(thresholds.size()):
|
||||
if not triggered[i] and ratio <= thresholds[i]:
|
||||
triggered[i] = true
|
||||
_spawn()
|
||||
|
||||
func _spawn() -> void:
|
||||
var spawned: Array = []
|
||||
for j in range(spawn_count):
|
||||
var entity: Node = spawn_scene.instantiate()
|
||||
var offset := Vector3(randf_range(-2, 2), 0, randf_range(-2, 2))
|
||||
parent.get_parent().add_child(entity)
|
||||
entity.global_position = parent.global_position + offset
|
||||
if "spawn_position" in entity:
|
||||
entity.spawn_position = parent.global_position
|
||||
if "portal" in entity:
|
||||
entity.portal = parent
|
||||
spawned.append(entity)
|
||||
var player: Node = get_tree().get_first_node_in_group("player")
|
||||
if player:
|
||||
var dist: float = parent.global_position.distance_to(player.global_position)
|
||||
if dist <= 10.0:
|
||||
for entity in spawned:
|
||||
if entity.has_method("_engage"):
|
||||
entity._engage(player)
|
||||
EventBus.portal_spawn.emit(parent, spawned)
|
||||
1
scripts/components/spawner.gd.uid
Normal file
1
scripts/components/spawner.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cm2s3xkmuesey
|
||||
@@ -13,7 +13,6 @@ var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
|
||||
func _ready() -> void:
|
||||
spawn_position = global_position
|
||||
add_to_group("enemies")
|
||||
add_to_group("targetable")
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
|
||||
@@ -27,6 +27,11 @@ func _process(delta: float) -> void:
|
||||
var bonus_decay: float = aggro_table[player] * 0.01 * pow(2, seconds_outside) * delta
|
||||
decay += bonus_decay
|
||||
aggro_table[player] -= decay
|
||||
# Im Portal-Radius: Aggro bleibt bei mindestens 1
|
||||
if not outside_portal and enemy.portal and is_instance_valid(player):
|
||||
var player_dist: float = player.global_position.distance_to(enemy.portal.global_position)
|
||||
if player_dist <= PORTAL_RADIUS and aggro_table[player] < 1.0:
|
||||
aggro_table[player] = 1.0
|
||||
if aggro_table[player] <= 0:
|
||||
aggro_table.erase(player)
|
||||
|
||||
@@ -43,8 +48,8 @@ func _on_damage_dealt(attacker: Node, target: Node, amount: float) -> void:
|
||||
if target != enemy:
|
||||
return
|
||||
var multiplier := 1.0
|
||||
var player_class: Node = attacker.get_node_or_null("PlayerClass")
|
||||
if player_class and player_class.current_class == 0:
|
||||
var role: Node = attacker.get_node_or_null("Role")
|
||||
if role and role.current_role == 0:
|
||||
multiplier = 2.0
|
||||
add_aggro(attacker, amount * multiplier)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ signal shield_broken(entity)
|
||||
signal shield_regenerated(entity)
|
||||
signal target_changed(player, target)
|
||||
signal player_respawned(player)
|
||||
signal class_changed(player, class_type)
|
||||
signal role_changed(player, role_type)
|
||||
signal damage_requested(attacker, target, amount)
|
||||
signal health_changed(entity, current, max_val)
|
||||
signal shield_changed(entity, current, max_val)
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
extends Node
|
||||
|
||||
const GCD_TIME := 0.5
|
||||
const AA_DAMAGE := 10.0
|
||||
const AA_COOLDOWN := 1.0
|
||||
const AA_RANGE := 20.0
|
||||
|
||||
@onready var player: CharacterBody3D = get_parent()
|
||||
@onready var targeting: Node = get_parent().get_node("Targeting")
|
||||
@onready var player_class: Node = get_parent().get_node("PlayerClass")
|
||||
@onready var role: Node = get_parent().get_node("Role")
|
||||
|
||||
var abilities: Array = []
|
||||
var cooldowns: Array[float] = [0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
var max_cooldowns: Array[float] = [0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
var gcd_timer := 0.0
|
||||
var aa_timer := 0.0
|
||||
|
||||
func _ready() -> void:
|
||||
_load_abilities()
|
||||
EventBus.class_changed.connect(_on_class_changed)
|
||||
EventBus.role_changed.connect(_on_role_changed)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if gcd_timer > 0:
|
||||
@@ -22,9 +26,26 @@ func _process(delta: float) -> void:
|
||||
if cooldowns[i] > 0:
|
||||
cooldowns[i] -= delta
|
||||
EventBus.cooldown_tick.emit(cooldowns, max_cooldowns, gcd_timer)
|
||||
_auto_attack(delta)
|
||||
|
||||
func _auto_attack(delta: float) -> void:
|
||||
aa_timer -= delta
|
||||
if aa_timer > 0:
|
||||
return
|
||||
if not targeting.in_combat or not targeting.current_target:
|
||||
return
|
||||
if not is_instance_valid(targeting.current_target):
|
||||
return
|
||||
var dist := player.global_position.distance_to(targeting.current_target.global_position)
|
||||
if dist > AA_RANGE:
|
||||
return
|
||||
var dmg := apply_passive(AA_DAMAGE)
|
||||
EventBus.damage_requested.emit(player, targeting.current_target, dmg)
|
||||
print("AA: %s Schaden an %s" % [dmg, targeting.current_target.name])
|
||||
aa_timer = AA_COOLDOWN
|
||||
|
||||
func _load_abilities() -> void:
|
||||
var ability_set: AbilitySet = player_class.get_ability_set()
|
||||
var ability_set: AbilitySet = role.get_ability_set()
|
||||
if ability_set:
|
||||
abilities = ability_set.abilities
|
||||
else:
|
||||
@@ -59,5 +80,5 @@ func apply_passive(base_damage: float) -> float:
|
||||
return base_damage * (1.0 + ability.damage / 100.0)
|
||||
return base_damage
|
||||
|
||||
func _on_class_changed(_player: Node, _class_type: int) -> void:
|
||||
func _on_role_changed(_player: Node, _role_type: int) -> void:
|
||||
_load_abilities()
|
||||
|
||||
@@ -22,7 +22,7 @@ func _ready() -> void:
|
||||
EventBus.shield_changed.connect(_on_shield_changed)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
EventBus.player_respawned.connect(_on_player_respawned)
|
||||
EventBus.class_changed.connect(_on_class_changed)
|
||||
EventBus.role_changed.connect(_on_role_changed)
|
||||
EventBus.respawn_tick.connect(_on_respawn_tick)
|
||||
EventBus.cooldown_tick.connect(_on_cooldown_tick)
|
||||
|
||||
@@ -46,8 +46,8 @@ func _on_player_respawned(_player: Node) -> void:
|
||||
func _on_respawn_tick(timer: float) -> void:
|
||||
respawn_label.text = str(ceil(timer))
|
||||
|
||||
func _on_class_changed(_player: Node, class_type: int) -> void:
|
||||
match class_type:
|
||||
func _on_role_changed(_player: Node, role_type: int) -> void:
|
||||
match role_type:
|
||||
0: class_icon.text = "T"
|
||||
1: class_icon.text = "D"
|
||||
2: class_icon.text = "H"
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
extends Node
|
||||
|
||||
enum PlayerClass { TANK, DAMAGE, HEALER }
|
||||
|
||||
var current_class: int = PlayerClass.DAMAGE
|
||||
|
||||
@export var tank_set: AbilitySet
|
||||
@export var damage_set: AbilitySet
|
||||
@export var healer_set: AbilitySet
|
||||
|
||||
@onready var player: CharacterBody3D = get_parent()
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("class_tank"):
|
||||
set_class(PlayerClass.TANK)
|
||||
elif event.is_action_pressed("class_damage"):
|
||||
set_class(PlayerClass.DAMAGE)
|
||||
elif event.is_action_pressed("class_healer"):
|
||||
set_class(PlayerClass.HEALER)
|
||||
|
||||
func set_class(new_class: int) -> void:
|
||||
current_class = new_class
|
||||
EventBus.class_changed.emit(player, current_class)
|
||||
|
||||
func get_class_icon() -> String:
|
||||
match current_class:
|
||||
PlayerClass.TANK: return "T"
|
||||
PlayerClass.DAMAGE: return "D"
|
||||
PlayerClass.HEALER: return "H"
|
||||
return ""
|
||||
|
||||
func get_ability_set() -> AbilitySet:
|
||||
match current_class:
|
||||
PlayerClass.TANK: return tank_set
|
||||
PlayerClass.DAMAGE: return damage_set
|
||||
PlayerClass.HEALER: return healer_set
|
||||
return damage_set
|
||||
@@ -1 +0,0 @@
|
||||
uid://rus4umqvvqq4
|
||||
37
scripts/player/role.gd
Normal file
37
scripts/player/role.gd
Normal file
@@ -0,0 +1,37 @@
|
||||
extends Node
|
||||
|
||||
enum Role { TANK, DAMAGE, HEALER }
|
||||
|
||||
var current_role: int = Role.DAMAGE
|
||||
|
||||
@export var tank_set: AbilitySet
|
||||
@export var damage_set: AbilitySet
|
||||
@export var healer_set: AbilitySet
|
||||
|
||||
@onready var player: CharacterBody3D = get_parent()
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("class_tank"):
|
||||
set_role(Role.TANK)
|
||||
elif event.is_action_pressed("class_damage"):
|
||||
set_role(Role.DAMAGE)
|
||||
elif event.is_action_pressed("class_healer"):
|
||||
set_role(Role.HEALER)
|
||||
|
||||
func set_role(new_role: int) -> void:
|
||||
current_role = new_role
|
||||
EventBus.role_changed.emit(player, current_role)
|
||||
|
||||
func get_role_icon() -> String:
|
||||
match current_role:
|
||||
Role.TANK: return "T"
|
||||
Role.DAMAGE: return "D"
|
||||
Role.HEALER: return "H"
|
||||
return ""
|
||||
|
||||
func get_ability_set() -> AbilitySet:
|
||||
match current_role:
|
||||
Role.TANK: return tank_set
|
||||
Role.DAMAGE: return damage_set
|
||||
Role.HEALER: return healer_set
|
||||
return damage_set
|
||||
1
scripts/player/role.gd.uid
Normal file
1
scripts/player/role.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dhomrampxola4
|
||||
@@ -49,7 +49,7 @@ func _try_target_under_mouse(mouse_pos: Vector2) -> void:
|
||||
set_target(null)
|
||||
|
||||
func _cycle_target() -> void:
|
||||
var targets := get_tree().get_nodes_in_group("targetable")
|
||||
var targets := get_tree().get_nodes_in_group("enemies") + get_tree().get_nodes_in_group("portals")
|
||||
if targets.is_empty():
|
||||
set_target(null)
|
||||
return
|
||||
@@ -70,23 +70,26 @@ func _on_enemy_engaged(_enemy: Node, target: Node) -> void:
|
||||
if not in_combat:
|
||||
in_combat = true
|
||||
if current_target == null:
|
||||
_target_nearest()
|
||||
_auto_target()
|
||||
|
||||
func _on_damage_dealt(_attacker: Node, target: Node, _amount: float) -> void:
|
||||
func _on_damage_dealt(attacker: Node, target: Node, _amount: float) -> void:
|
||||
if target == player:
|
||||
combat_timer = COMBAT_TIMEOUT
|
||||
if not in_combat:
|
||||
in_combat = true
|
||||
if current_target == null:
|
||||
_target_nearest()
|
||||
_auto_target()
|
||||
elif attacker == player and in_combat:
|
||||
combat_timer = COMBAT_TIMEOUT
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity == current_target:
|
||||
set_target(null)
|
||||
if in_combat:
|
||||
_target_nearest_except(entity)
|
||||
_auto_target(entity)
|
||||
|
||||
func _target_nearest_except(exclude: Node = null) -> void:
|
||||
func _auto_target(exclude: Node = null) -> void:
|
||||
# Priorität 1: Nächster Gegner
|
||||
var enemies := get_tree().get_nodes_in_group("enemies")
|
||||
var nearest: Node3D = null
|
||||
var nearest_dist: float = INF
|
||||
@@ -98,16 +101,14 @@ func _target_nearest_except(exclude: Node = null) -> void:
|
||||
nearest = enemy
|
||||
if nearest:
|
||||
set_target(nearest)
|
||||
|
||||
func _target_nearest() -> void:
|
||||
var enemies := get_tree().get_nodes_in_group("enemies")
|
||||
var nearest: Node3D = null
|
||||
var nearest_dist: float = INF
|
||||
for enemy in enemies:
|
||||
if is_instance_valid(enemy):
|
||||
var dist: float = player.global_position.distance_to(enemy.global_position)
|
||||
return
|
||||
# Priorität 2: Nächstes Portal
|
||||
var portals := get_tree().get_nodes_in_group("portals")
|
||||
for p in portals:
|
||||
if is_instance_valid(p) and p != exclude:
|
||||
var dist: float = player.global_position.distance_to(p.global_position)
|
||||
if dist < nearest_dist:
|
||||
nearest_dist = dist
|
||||
nearest = enemy
|
||||
nearest = p
|
||||
if nearest:
|
||||
set_target(nearest)
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
extends StaticBody3D
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("targetable")
|
||||
add_to_group("portals")
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity == self:
|
||||
queue_free()
|
||||
|
||||
func _on_detection_area_body_entered(body: Node3D) -> void:
|
||||
if body is CharacterBody3D and body.name == "Player":
|
||||
EventBus.enemy_engaged.emit(self, body)
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
extends Node
|
||||
|
||||
const SPAWN_COUNT := 3
|
||||
var thresholds := [0.85, 0.70, 0.55, 0.40, 0.25, 0.10]
|
||||
var triggered: Array[bool] = [false, false, false, false, false, false]
|
||||
var enemy_scene: PackedScene = preload("res://scenes/enemy/enemy.tscn")
|
||||
|
||||
@onready var portal: StaticBody3D = get_parent()
|
||||
@onready var health: Node = get_parent().get_node("Health")
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if health.current_health <= 0:
|
||||
return
|
||||
var ratio: float = health.current_health / health.max_health
|
||||
for i in range(thresholds.size()):
|
||||
if not triggered[i] and ratio <= thresholds[i]:
|
||||
triggered[i] = true
|
||||
_spawn_enemies()
|
||||
|
||||
func _spawn_enemies() -> void:
|
||||
var spawned: Array = []
|
||||
for j in range(SPAWN_COUNT):
|
||||
var enemy: CharacterBody3D = enemy_scene.instantiate()
|
||||
var offset := Vector3(randf_range(-2, 2), 0, randf_range(-2, 2))
|
||||
portal.get_parent().add_child(enemy)
|
||||
enemy.global_position = portal.global_position + offset
|
||||
enemy.spawn_position = portal.global_position
|
||||
enemy.portal = portal
|
||||
spawned.append(enemy)
|
||||
EventBus.portal_spawn.emit(portal, spawned)
|
||||
@@ -1 +0,0 @@
|
||||
uid://begrg74oh76pu
|
||||
8
scripts/resources/entity_stats.gd
Normal file
8
scripts/resources/entity_stats.gd
Normal file
@@ -0,0 +1,8 @@
|
||||
extends Resource
|
||||
class_name EntityStats
|
||||
|
||||
@export var max_health := 100.0
|
||||
@export var health_regen := 0.0
|
||||
@export var max_shield := 0.0
|
||||
@export var shield_regen_delay := 3.0
|
||||
@export var shield_regen_time := 5.0
|
||||
1
scripts/resources/entity_stats.gd.uid
Normal file
1
scripts/resources/entity_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ij663bdj2cgu
|
||||
Reference in New Issue
Block a user