update
This commit is contained in:
@@ -11,9 +11,12 @@ enum Type { SINGLE, AOE, UTILITY, ULT, PASSIVE }
|
||||
@export var uses_gcd: bool = true
|
||||
@export var aoe_radius: float = 0.0
|
||||
@export var icon: String = ""
|
||||
@export var is_heal: bool = false
|
||||
@export var passive_stat: String = "damage"
|
||||
|
||||
func execute(player: Node, targeting: Node) -> bool:
|
||||
var dmg: float = _get_modified_damage(player, damage)
|
||||
var stat: String = "heal" if is_heal else "damage"
|
||||
var dmg: float = _get_modified_damage(player, damage, stat)
|
||||
match type:
|
||||
Type.SINGLE:
|
||||
return _execute_single(player, targeting, dmg)
|
||||
@@ -25,28 +28,44 @@ func execute(player: Node, targeting: Node) -> bool:
|
||||
return _execute_ult(player, targeting, dmg)
|
||||
return false
|
||||
|
||||
func _get_modified_damage(player: Node, base: float) -> float:
|
||||
func _get_modified_damage(player: Node, base: float, stat: String = "damage") -> float:
|
||||
var combat: Node = player.get_node("Combat")
|
||||
return combat.apply_passive(base)
|
||||
return combat.apply_passive(base, stat)
|
||||
|
||||
func _in_range(player: Node, targeting: Node) -> bool:
|
||||
if ability_range <= 0:
|
||||
return true
|
||||
var target: Node3D = targeting.current_target
|
||||
if not target or not is_instance_valid(target):
|
||||
if is_heal:
|
||||
return true
|
||||
if not is_instance_valid(targeting.current_target):
|
||||
return false
|
||||
var dist: float = player.global_position.distance_to(target.global_position)
|
||||
var dist: float = player.global_position.distance_to(targeting.current_target.global_position)
|
||||
return dist <= ability_range
|
||||
|
||||
func _execute_single(player: Node, targeting: Node, dmg: float) -> bool:
|
||||
if is_heal:
|
||||
EventBus.heal_requested.emit(player, player, dmg)
|
||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg)
|
||||
return true
|
||||
if not _in_range(player, targeting):
|
||||
return false
|
||||
var target: Node3D = targeting.current_target
|
||||
EventBus.damage_requested.emit(player, target, dmg)
|
||||
if not is_instance_valid(targeting.current_target):
|
||||
return false
|
||||
EventBus.damage_requested.emit(player, targeting.current_target, dmg)
|
||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg)
|
||||
return true
|
||||
|
||||
func _execute_aoe(player: Node, dmg: float) -> bool:
|
||||
if is_heal:
|
||||
EventBus.heal_requested.emit(player, player, dmg)
|
||||
var players := player.get_tree().get_nodes_in_group("player")
|
||||
for p in players:
|
||||
if p != player and is_instance_valid(p):
|
||||
var dist: float = player.global_position.distance_to(p.global_position)
|
||||
if dist <= ability_range:
|
||||
EventBus.heal_requested.emit(player, p, dmg)
|
||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg)
|
||||
return true
|
||||
var hit := false
|
||||
var enemies := player.get_tree().get_nodes_in_group("enemies")
|
||||
for enemy in enemies:
|
||||
@@ -61,20 +80,38 @@ func _execute_aoe(player: Node, dmg: float) -> bool:
|
||||
func _execute_utility(player: Node) -> bool:
|
||||
var shield: Node = player.get_node_or_null("Shield")
|
||||
if shield:
|
||||
shield.current_shield = shield.max_shield
|
||||
if damage > 0:
|
||||
shield.current_shield = shield.max_shield * (damage / 100.0)
|
||||
else:
|
||||
if shield.current_shield >= shield.max_shield:
|
||||
return false
|
||||
shield.current_shield = shield.max_shield
|
||||
EventBus.shield_changed.emit(player, shield.current_shield, shield.max_shield)
|
||||
return true
|
||||
return false
|
||||
|
||||
func _execute_ult(player: Node, targeting: Node, dmg: float) -> bool:
|
||||
if is_heal:
|
||||
EventBus.heal_requested.emit(player, player, dmg)
|
||||
var players := player.get_tree().get_nodes_in_group("player")
|
||||
var aoe_range: float = aoe_radius if aoe_radius > 0 else ability_range
|
||||
for p in players:
|
||||
if p != player and is_instance_valid(p):
|
||||
var dist: float = player.global_position.distance_to(p.global_position)
|
||||
if dist <= aoe_range:
|
||||
EventBus.heal_requested.emit(player, p, dmg * 0.4)
|
||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg)
|
||||
return true
|
||||
if not _in_range(player, targeting):
|
||||
return false
|
||||
if not is_instance_valid(targeting.current_target):
|
||||
return false
|
||||
var target: Node3D = targeting.current_target
|
||||
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:
|
||||
if enemy != target and is_instance_valid(enemy):
|
||||
var enemy_dist: float = target.global_position.distance_to(enemy.global_position)
|
||||
if enemy_dist <= aoe_range:
|
||||
EventBus.damage_requested.emit(player, enemy, dmg * 2.0)
|
||||
|
||||
@@ -2,3 +2,6 @@ extends Resource
|
||||
class_name AbilitySet
|
||||
|
||||
@export var abilities: Array[Ability] = []
|
||||
@export var aa_damage: float = 10.0
|
||||
@export var aa_range: float = 10.0
|
||||
@export var aa_is_heal: bool = false
|
||||
|
||||
@@ -10,6 +10,7 @@ func _ready() -> void:
|
||||
health_regen = stats.health_regen
|
||||
current_health = max_health
|
||||
EventBus.damage_requested.connect(_on_damage_requested)
|
||||
EventBus.heal_requested.connect(_on_heal_requested)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if current_health > 0 and current_health < max_health and health_regen > 0:
|
||||
@@ -34,3 +35,11 @@ func take_damage(amount: float, attacker: Node) -> void:
|
||||
EventBus.health_changed.emit(get_parent(), current_health, max_health)
|
||||
if current_health <= 0:
|
||||
EventBus.entity_died.emit(get_parent())
|
||||
|
||||
func heal(amount: float) -> void:
|
||||
current_health = min(current_health + amount, max_health)
|
||||
EventBus.health_changed.emit(get_parent(), current_health, max_health)
|
||||
|
||||
func _on_heal_requested(healer: Node, target: Node, amount: float) -> void:
|
||||
if target == get_parent():
|
||||
heal(amount)
|
||||
|
||||
@@ -7,11 +7,15 @@ var regen_time: float
|
||||
var current_shield: float
|
||||
var regen_timer := 0.0
|
||||
|
||||
var base_max_shield: float
|
||||
|
||||
func _ready() -> void:
|
||||
max_shield = stats.max_shield
|
||||
base_max_shield = max_shield
|
||||
regen_delay = stats.shield_regen_delay
|
||||
regen_time = stats.shield_regen_time
|
||||
current_shield = max_shield
|
||||
EventBus.role_changed.connect(_on_role_changed)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if max_shield <= 0:
|
||||
@@ -25,6 +29,22 @@ func _process(delta: float) -> void:
|
||||
EventBus.shield_regenerated.emit(get_parent())
|
||||
EventBus.shield_changed.emit(get_parent(), current_shield, max_shield)
|
||||
|
||||
func _on_role_changed(_player: Node, _role_type: int) -> void:
|
||||
if get_parent() != _player:
|
||||
return
|
||||
var role: Node = get_parent().get_node_or_null("Role")
|
||||
if not role:
|
||||
return
|
||||
var ability_set: AbilitySet = role.get_ability_set()
|
||||
if not ability_set:
|
||||
return
|
||||
max_shield = base_max_shield
|
||||
for ability in ability_set.abilities:
|
||||
if ability and ability.type == Ability.Type.PASSIVE and ability.passive_stat == "shield":
|
||||
max_shield = base_max_shield * (1.0 + ability.damage / 100.0)
|
||||
current_shield = min(current_shield, max_shield)
|
||||
EventBus.shield_changed.emit(get_parent(), current_shield, max_shield)
|
||||
|
||||
func absorb(amount: float) -> float:
|
||||
if current_shield <= 0:
|
||||
return amount
|
||||
|
||||
16
scripts/dungeon/dungeon_manager.gd
Normal file
16
scripts/dungeon/dungeon_manager.gd
Normal file
@@ -0,0 +1,16 @@
|
||||
extends Node
|
||||
|
||||
func _ready() -> void:
|
||||
var player: Node = get_tree().get_first_node_in_group("player")
|
||||
if player:
|
||||
GameState.restore_player(player)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity.is_in_group("boss"):
|
||||
await get_tree().create_timer(2.0).timeout
|
||||
GameState.dungeon_cleared = true
|
||||
GameState.returning_from_dungeon = false
|
||||
GameState.clear_player()
|
||||
EventBus.dungeon_cleared.emit()
|
||||
get_tree().change_scene_to_file("res://scenes/world.tscn")
|
||||
1
scripts/dungeon/dungeon_manager.gd.uid
Normal file
1
scripts/dungeon/dungeon_manager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://drn4h1lxx5t1j
|
||||
5
scripts/enemy/boss.gd
Normal file
5
scripts/enemy/boss.gd
Normal file
@@ -0,0 +1,5 @@
|
||||
extends "res://scripts/enemy/enemy.gd"
|
||||
|
||||
func _ready() -> void:
|
||||
super._ready()
|
||||
add_to_group("boss")
|
||||
1
scripts/enemy/boss.gd.uid
Normal file
1
scripts/enemy/boss.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bkehq3dqyp2yd
|
||||
@@ -10,6 +10,7 @@ var seconds_outside := 0.0
|
||||
func _ready() -> void:
|
||||
EventBus.damage_dealt.connect(_on_damage_dealt)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
EventBus.heal_requested.connect(_on_heal_requested)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
var outside_portal := false
|
||||
@@ -53,6 +54,12 @@ func _on_damage_dealt(attacker: Node, target: Node, amount: float) -> void:
|
||||
multiplier = 2.0
|
||||
add_aggro(attacker, amount * multiplier)
|
||||
|
||||
func _on_heal_requested(healer: Node, _target: Node, amount: float) -> void:
|
||||
if not healer.is_in_group("player"):
|
||||
return
|
||||
if healer in aggro_table:
|
||||
add_aggro(healer, amount * 0.5)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
aggro_table.erase(entity)
|
||||
|
||||
|
||||
@@ -15,3 +15,6 @@ signal respawn_tick(timer)
|
||||
signal enemy_engaged(enemy, target)
|
||||
signal cooldown_tick(cooldowns, max_cooldowns, gcd_timer)
|
||||
signal portal_spawn(portal, enemies)
|
||||
signal heal_requested(healer, target, amount)
|
||||
signal portal_defeated(portal)
|
||||
signal dungeon_cleared()
|
||||
|
||||
42
scripts/game_state.gd
Normal file
42
scripts/game_state.gd
Normal file
@@ -0,0 +1,42 @@
|
||||
extends Node
|
||||
|
||||
var player_health: float = -1.0
|
||||
var player_max_health: float = -1.0
|
||||
var player_shield: float = -1.0
|
||||
var player_max_shield: float = -1.0
|
||||
var player_role: int = 1
|
||||
var portal_position: Vector3 = Vector3.ZERO
|
||||
var returning_from_dungeon := false
|
||||
var dungeon_cleared := false
|
||||
|
||||
func save_player(player: Node) -> void:
|
||||
var health: Node = player.get_node("Health")
|
||||
var shield: Node = player.get_node("Shield")
|
||||
var role: Node = player.get_node("Role")
|
||||
player_health = health.current_health
|
||||
player_max_health = health.max_health
|
||||
player_shield = shield.current_shield
|
||||
player_max_shield = shield.max_shield
|
||||
player_role = role.current_role
|
||||
|
||||
func restore_player(player: Node) -> void:
|
||||
if player_health < 0:
|
||||
return
|
||||
var health: Node = player.get_node("Health")
|
||||
var shield: Node = player.get_node("Shield")
|
||||
var role: Node = player.get_node("Role")
|
||||
health.current_health = player_health
|
||||
shield.current_shield = player_shield
|
||||
role.set_role(player_role)
|
||||
EventBus.health_changed.emit(player, health.current_health, health.max_health)
|
||||
EventBus.shield_changed.emit(player, shield.current_shield, shield.max_shield)
|
||||
|
||||
func clear_player() -> void:
|
||||
player_health = -1.0
|
||||
player_shield = -1.0
|
||||
|
||||
func clear() -> void:
|
||||
clear_player()
|
||||
portal_position = Vector3.ZERO
|
||||
returning_from_dungeon = false
|
||||
dungeon_cleared = false
|
||||
1
scripts/game_state.gd.uid
Normal file
1
scripts/game_state.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cp2vadwcd12sm
|
||||
@@ -1,9 +1,7 @@
|
||||
extends Node
|
||||
|
||||
const GCD_TIME := 0.5
|
||||
const AA_DAMAGE := 10.0
|
||||
const AA_COOLDOWN := 1.0
|
||||
const AA_RANGE := 20.0
|
||||
const AA_COOLDOWN := 0.5
|
||||
|
||||
@onready var player: CharacterBody3D = get_parent()
|
||||
@onready var targeting: Node = get_parent().get_node("Targeting")
|
||||
@@ -36,12 +34,23 @@ func _auto_attack(delta: float) -> void:
|
||||
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:
|
||||
var ability_set: AbilitySet = role.get_ability_set()
|
||||
if not ability_set:
|
||||
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])
|
||||
var aa_damage: float = ability_set.aa_damage
|
||||
var aa_range: float = ability_set.aa_range
|
||||
var aa_is_heal: bool = ability_set.aa_is_heal
|
||||
var dmg: float = apply_passive(aa_damage, "heal" if aa_is_heal else "damage")
|
||||
if aa_is_heal:
|
||||
EventBus.heal_requested.emit(player, player, dmg)
|
||||
print("AA Heal: %s an %s" % [dmg, player.name])
|
||||
else:
|
||||
var dist := player.global_position.distance_to(targeting.current_target.global_position)
|
||||
if dist > aa_range:
|
||||
return
|
||||
var target_name: String = targeting.current_target.name
|
||||
EventBus.damage_requested.emit(player, targeting.current_target, dmg)
|
||||
print("AA: %s Schaden an %s" % [dmg, target_name])
|
||||
aa_timer = AA_COOLDOWN
|
||||
|
||||
func _load_abilities() -> void:
|
||||
@@ -74,11 +83,11 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||
gcd_timer = GCD_TIME
|
||||
return
|
||||
|
||||
func apply_passive(base_damage: float) -> float:
|
||||
func apply_passive(base: float, stat: String = "damage") -> float:
|
||||
for ability in abilities:
|
||||
if ability and ability.type == Ability.Type.PASSIVE:
|
||||
return base_damage * (1.0 + ability.damage / 100.0)
|
||||
return base_damage
|
||||
if ability and ability.type == Ability.Type.PASSIVE and ability.passive_stat == stat:
|
||||
return base * (1.0 + ability.damage / 100.0)
|
||||
return base
|
||||
|
||||
func _on_role_changed(_player: Node, _role_type: int) -> void:
|
||||
_load_abilities()
|
||||
|
||||
@@ -3,7 +3,9 @@ extends CanvasLayer
|
||||
const GCD_TIME := 0.5
|
||||
|
||||
@onready var health_bar: ProgressBar = $HealthBar
|
||||
@onready var health_label: Label = $HealthBar/HealthLabel
|
||||
@onready var shield_bar: ProgressBar = $ShieldBar
|
||||
@onready var shield_label: Label = $ShieldBar/ShieldLabel
|
||||
@onready var respawn_label: Label = $RespawnTimer
|
||||
@onready var class_icon: Label = $AbilityBar/ClassIcon/Label
|
||||
@onready var ability_panels: Array = [
|
||||
@@ -30,11 +32,13 @@ func _on_health_changed(entity: Node, current: float, max_val: float) -> void:
|
||||
if entity.name == "Player":
|
||||
health_bar.max_value = max_val
|
||||
health_bar.value = current
|
||||
health_label.text = "%d/%d" % [current, max_val]
|
||||
|
||||
func _on_shield_changed(entity: Node, current: float, max_val: float) -> void:
|
||||
if entity.name == "Player":
|
||||
shield_bar.max_value = max_val
|
||||
shield_bar.value = current
|
||||
shield_label.text = "%d/%d" % [current, max_val]
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity.name == "Player":
|
||||
|
||||
@@ -2,3 +2,7 @@ extends CharacterBody3D
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("player")
|
||||
if GameState.returning_from_dungeon:
|
||||
GameState.restore_player(self)
|
||||
global_position = GameState.portal_position + Vector3(0, 1, -5)
|
||||
GameState.returning_from_dungeon = false
|
||||
|
||||
@@ -8,7 +8,7 @@ var spawn_position: Vector3
|
||||
@onready var player: CharacterBody3D = get_parent()
|
||||
|
||||
func _ready() -> void:
|
||||
spawn_position = player.global_position
|
||||
spawn_position = Vector3(0, 1, -5)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
|
||||
34
scripts/portal/gate.gd
Normal file
34
scripts/portal/gate.gd
Normal file
@@ -0,0 +1,34 @@
|
||||
extends StaticBody3D
|
||||
|
||||
@export var target_scene: String = "res://scenes/dungeon/dungeon.tscn"
|
||||
@export var is_exit: bool = false
|
||||
|
||||
var active := false
|
||||
|
||||
func _ready() -> void:
|
||||
if not is_exit:
|
||||
if GameState.dungeon_cleared:
|
||||
queue_free()
|
||||
return
|
||||
get_tree().create_timer(0.5).timeout.connect(_check_overlapping)
|
||||
else:
|
||||
get_tree().create_timer(1.0).timeout.connect(func() -> void: active = true)
|
||||
|
||||
func _check_overlapping() -> void:
|
||||
active = true
|
||||
for body in $GateArea.get_overlapping_bodies():
|
||||
_on_gate_area_body_entered(body)
|
||||
|
||||
func _on_gate_area_body_entered(body: Node3D) -> void:
|
||||
if not active:
|
||||
return
|
||||
if body is CharacterBody3D and body.name == "Player":
|
||||
GameState.save_player(body)
|
||||
if is_exit:
|
||||
GameState.returning_from_dungeon = true
|
||||
else:
|
||||
GameState.portal_position = global_position
|
||||
call_deferred("_change_scene")
|
||||
|
||||
func _change_scene() -> void:
|
||||
get_tree().change_scene_to_file(target_scene)
|
||||
1
scripts/portal/gate.gd.uid
Normal file
1
scripts/portal/gate.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ctci5mc3cd2ck
|
||||
@@ -1,12 +1,23 @@
|
||||
extends StaticBody3D
|
||||
|
||||
const GATE_SCENE: PackedScene = preload("res://scenes/portal/gate.tscn")
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("portals")
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity == self:
|
||||
queue_free()
|
||||
if entity != self:
|
||||
return
|
||||
var gate: Node3D = GATE_SCENE.instantiate()
|
||||
gate.global_position = global_position
|
||||
get_parent().add_child(gate)
|
||||
var enemies := get_tree().get_nodes_in_group("enemies")
|
||||
for enemy in enemies:
|
||||
if is_instance_valid(enemy):
|
||||
enemy.queue_free()
|
||||
EventBus.portal_defeated.emit(self)
|
||||
queue_free()
|
||||
|
||||
func _on_detection_area_body_entered(body: Node3D) -> void:
|
||||
if body is CharacterBody3D and body.name == "Player":
|
||||
|
||||
45
scripts/world/portal_spawner.gd
Normal file
45
scripts/world/portal_spawner.gd
Normal file
@@ -0,0 +1,45 @@
|
||||
extends Node
|
||||
|
||||
const PORTAL_SCENE: PackedScene = preload("res://scenes/portal/portal.tscn")
|
||||
const GATE_SCENE: PackedScene = preload("res://scenes/portal/gate.tscn")
|
||||
const SPAWN_INTERVAL := 30.0
|
||||
const MAX_PORTALS := 3
|
||||
const MIN_DISTANCE := 20.0
|
||||
const MAX_DISTANCE := 40.0
|
||||
|
||||
var portals: Array[Node] = []
|
||||
var timer := 0.0
|
||||
|
||||
func _ready() -> void:
|
||||
if GameState.portal_position != Vector3.ZERO and not GameState.dungeon_cleared:
|
||||
call_deferred("_restore_gate")
|
||||
else:
|
||||
if GameState.dungeon_cleared:
|
||||
GameState.clear()
|
||||
call_deferred("_spawn_portal")
|
||||
|
||||
func _restore_gate() -> void:
|
||||
var gate: Node3D = GATE_SCENE.instantiate()
|
||||
get_parent().add_child(gate)
|
||||
gate.global_position = GameState.portal_position
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
timer += delta
|
||||
if timer >= SPAWN_INTERVAL:
|
||||
timer = 0.0
|
||||
_cleanup_dead()
|
||||
if portals.size() < MAX_PORTALS:
|
||||
_spawn_portal()
|
||||
|
||||
func _spawn_portal() -> void:
|
||||
var angle: float = randf() * TAU
|
||||
var distance: float = randf_range(MIN_DISTANCE, MAX_DISTANCE)
|
||||
var pos := Vector3(cos(angle) * distance, 0, sin(angle) * distance)
|
||||
var portal: Node3D = PORTAL_SCENE.instantiate()
|
||||
get_parent().add_child(portal)
|
||||
portal.global_position = pos
|
||||
portals.append(portal)
|
||||
print("Portal gespawnt bei: %s" % pos)
|
||||
|
||||
func _cleanup_dead() -> void:
|
||||
portals = portals.filter(func(p: Node) -> bool: return is_instance_valid(p))
|
||||
1
scripts/world/portal_spawner.gd.uid
Normal file
1
scripts/world/portal_spawner.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cskx6o07iukwh
|
||||
Reference in New Issue
Block a user