prototype vibe
This commit is contained in:
@@ -45,7 +45,7 @@ func _apply_passive(base: float, stat: String) -> float:
|
||||
match stat:
|
||||
"damage": mult = PlayerData.buff_damage
|
||||
"heal": mult = PlayerData.buff_heal
|
||||
return base * mult
|
||||
return base * mult * PlayerData.level_scale
|
||||
|
||||
func _in_range(ability: Ability) -> bool:
|
||||
if ability.ability_range <= 0 or ability.is_heal:
|
||||
@@ -77,12 +77,13 @@ func _execute_aoe(player: Node, ability: Ability, dmg: float) -> bool:
|
||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg)
|
||||
return true
|
||||
var hit := false
|
||||
for enemy in get_tree().get_nodes_in_group("enemies"):
|
||||
var dist: float = player.global_position.distance_to(enemy.global_position)
|
||||
var targets: Array = get_tree().get_nodes_in_group("enemies") + get_tree().get_nodes_in_group("portals")
|
||||
for target in targets:
|
||||
var dist: float = player.global_position.distance_to(target.global_position)
|
||||
if dist <= ability.ability_range:
|
||||
EventBus.damage_requested.emit(player, enemy, dmg)
|
||||
EventBus.damage_requested.emit(player, target, dmg)
|
||||
if ability.element != 0:
|
||||
EventBus.element_damage_dealt.emit(player, enemy, dmg, ability.element)
|
||||
EventBus.element_damage_dealt.emit(player, target, dmg, ability.element)
|
||||
hit = true
|
||||
if hit:
|
||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg)
|
||||
@@ -116,12 +117,13 @@ func _execute_ult(player: Node, ability: Ability, dmg: float) -> bool:
|
||||
if ability.element != 0:
|
||||
EventBus.element_damage_dealt.emit(player, target, dmg * 5.0, ability.element)
|
||||
var splash_range: float = ability.aoe_radius if ability.aoe_radius > 0 else ability.ability_range
|
||||
for enemy in get_tree().get_nodes_in_group("enemies"):
|
||||
if enemy != target and is_instance_valid(enemy):
|
||||
var enemy_dist: float = target.global_position.distance_to(enemy.global_position)
|
||||
if enemy_dist <= splash_range:
|
||||
EventBus.damage_requested.emit(player, enemy, dmg * 2.0)
|
||||
var splash_targets: Array = get_tree().get_nodes_in_group("enemies") + get_tree().get_nodes_in_group("portals")
|
||||
for other in splash_targets:
|
||||
if other != target and is_instance_valid(other):
|
||||
var other_dist: float = target.global_position.distance_to(other.global_position)
|
||||
if other_dist <= splash_range:
|
||||
EventBus.damage_requested.emit(player, other, dmg * 2.0)
|
||||
if ability.element != 0:
|
||||
EventBus.element_damage_dealt.emit(player, enemy, dmg * 2.0, ability.element)
|
||||
EventBus.element_damage_dealt.emit(player, other, dmg * 2.0, ability.element)
|
||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg * 5.0)
|
||||
return true
|
||||
|
||||
@@ -11,6 +11,8 @@ func _process_group(delta: float, data_source: Node) -> void:
|
||||
if not is_instance_valid(entity) or not data_source.is_alive(entity):
|
||||
continue
|
||||
var data: Dictionary = data_source.entities[entity]
|
||||
if entity.is_in_group("invasion"):
|
||||
_force_invasion_target(entity, data)
|
||||
var state: int = data["state"]
|
||||
match state:
|
||||
State.IDLE:
|
||||
@@ -23,12 +25,22 @@ func _process_group(delta: float, data_source: Node) -> void:
|
||||
State.RETURN:
|
||||
_return_to_spawn(entity, data, data_source, delta)
|
||||
|
||||
func _force_invasion_target(entity: Node, data: Dictionary) -> void:
|
||||
var tavern: Node = get_tree().get_first_node_in_group("tavern")
|
||||
if not tavern:
|
||||
return
|
||||
data["target"] = tavern
|
||||
if data["state"] == State.IDLE or data["state"] == State.RETURN:
|
||||
data["state"] = State.CHASE
|
||||
|
||||
func _chase(entity: Node, data: Dictionary, data_source: Node) -> void:
|
||||
if not is_instance_valid(data["target"]):
|
||||
data["state"] = State.RETURN
|
||||
return
|
||||
var base: EnemyStats = data_source.get_base(entity)
|
||||
var attack_range: float = base.attack_range
|
||||
if data["target"].is_in_group("tavern"):
|
||||
attack_range += 3.0
|
||||
var dist: float = entity.global_position.distance_to(data["target"].global_position)
|
||||
if dist <= attack_range:
|
||||
data["state"] = State.ATTACK
|
||||
@@ -49,13 +61,17 @@ func _attack(entity: Node, data: Dictionary, data_source: Node, delta: float) ->
|
||||
data["state"] = State.RETURN
|
||||
return
|
||||
var base: EnemyStats = data_source.get_base(entity)
|
||||
var attack_range: float = base.attack_range
|
||||
if data["target"].is_in_group("tavern"):
|
||||
attack_range += 3.0
|
||||
var dist: float = entity.global_position.distance_to(data["target"].global_position)
|
||||
if dist > base.attack_range:
|
||||
if dist > attack_range:
|
||||
data["state"] = State.CHASE
|
||||
return
|
||||
if data["attack_timer"] <= 0:
|
||||
data["attack_timer"] = base.attack_cooldown
|
||||
EventBus.damage_requested.emit(entity, data["target"], base.attack_damage)
|
||||
var scale: float = data.get("scale", 1.0)
|
||||
EventBus.damage_requested.emit(entity, data["target"], base.attack_damage * scale)
|
||||
entity.velocity.x = 0
|
||||
entity.velocity.z = 0
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ func _process(_delta: float) -> void:
|
||||
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 = aa_damage * (PlayerData.buff_heal if aa_is_heal else PlayerData.buff_damage)
|
||||
var dmg: float = aa_damage * (PlayerData.buff_heal if aa_is_heal else PlayerData.buff_damage) * PlayerData.level_scale
|
||||
if aa_is_heal:
|
||||
EventBus.heal_requested.emit(player, player, dmg)
|
||||
else:
|
||||
|
||||
113
systems/audio_system.gd
Normal file
113
systems/audio_system.gd
Normal file
@@ -0,0 +1,113 @@
|
||||
extends Node
|
||||
|
||||
const SFX_PATHS := {
|
||||
"hit": "res://assets/audio/sfx/hit.wav",
|
||||
"death": "res://assets/audio/sfx/death.wav",
|
||||
"level_up": "res://assets/audio/sfx/level_up.wav",
|
||||
"ability_cast": "res://assets/audio/sfx/ability_cast.wav",
|
||||
"portal_spawn": "res://assets/audio/sfx/portal_spawn.wav",
|
||||
"invasion_alarm": "res://assets/audio/sfx/invasion_alarm.wav",
|
||||
"tavern_damage": "res://assets/audio/sfx/tavern_damage.wav",
|
||||
}
|
||||
|
||||
const MUSIC_PATHS := {
|
||||
"tavern": "res://assets/audio/music/tavern.wav",
|
||||
"battle": "res://assets/audio/music/battle.wav",
|
||||
"invasion": "res://assets/audio/music/invasion.wav",
|
||||
}
|
||||
|
||||
const SFX_POOL_SIZE := 8
|
||||
|
||||
var sfx_cache: Dictionary = {}
|
||||
var music_cache: Dictionary = {}
|
||||
var sfx_players: Array[AudioStreamPlayer] = []
|
||||
var music_player: AudioStreamPlayer = null
|
||||
var current_music: String = ""
|
||||
|
||||
func _ready() -> void:
|
||||
for i in range(SFX_POOL_SIZE):
|
||||
var p := AudioStreamPlayer.new()
|
||||
p.volume_db = -6.0
|
||||
add_child(p)
|
||||
sfx_players.append(p)
|
||||
music_player = AudioStreamPlayer.new()
|
||||
music_player.volume_db = -12.0
|
||||
music_player.finished.connect(_on_music_finished)
|
||||
add_child(music_player)
|
||||
_preload_audio()
|
||||
EventBus.attack_executed.connect(_on_attack_executed)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
EventBus.level_up.connect(_on_level_up)
|
||||
EventBus.portal_spawn.connect(_on_portal_spawn)
|
||||
EventBus.tavern_damaged.connect(_on_tavern_damaged)
|
||||
EventBus.invasion_started.connect(_on_invasion_started)
|
||||
EventBus.invasion_ended.connect(_on_invasion_ended)
|
||||
EventBus.wave_started.connect(_on_wave_started)
|
||||
_play_music("tavern")
|
||||
|
||||
func _preload_audio() -> void:
|
||||
for key in SFX_PATHS:
|
||||
var path: String = SFX_PATHS[key]
|
||||
if ResourceLoader.exists(path):
|
||||
var stream: AudioStream = load(path)
|
||||
if stream:
|
||||
sfx_cache[key] = stream
|
||||
for key in MUSIC_PATHS:
|
||||
var path: String = MUSIC_PATHS[key]
|
||||
if ResourceLoader.exists(path):
|
||||
var stream: AudioStream = load(path)
|
||||
if stream is AudioStreamWAV:
|
||||
(stream as AudioStreamWAV).loop_mode = AudioStreamWAV.LOOP_FORWARD
|
||||
(stream as AudioStreamWAV).loop_end = (stream as AudioStreamWAV).data.size() / 2
|
||||
if stream:
|
||||
music_cache[key] = stream
|
||||
|
||||
func play_sfx(key: String) -> void:
|
||||
if not sfx_cache.has(key):
|
||||
return
|
||||
for p in sfx_players:
|
||||
if not p.playing:
|
||||
p.stream = sfx_cache[key]
|
||||
p.play()
|
||||
return
|
||||
|
||||
func _play_music(key: String) -> void:
|
||||
if current_music == key and music_player.playing:
|
||||
return
|
||||
if not music_cache.has(key):
|
||||
return
|
||||
music_player.stream = music_cache[key]
|
||||
music_player.play()
|
||||
current_music = key
|
||||
|
||||
func _on_music_finished() -> void:
|
||||
if current_music != "" and music_cache.has(current_music):
|
||||
music_player.play()
|
||||
|
||||
func _on_attack_executed(_attacker, _pos, _dir, _dmg) -> void:
|
||||
play_sfx("hit")
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity == PlayerData:
|
||||
return
|
||||
play_sfx("death")
|
||||
|
||||
func _on_level_up(_player, _level) -> void:
|
||||
play_sfx("level_up")
|
||||
|
||||
func _on_portal_spawn(_portal, _enemies) -> void:
|
||||
play_sfx("portal_spawn")
|
||||
|
||||
func _on_tavern_damaged(_current, _max_val) -> void:
|
||||
play_sfx("tavern_damage")
|
||||
|
||||
func _on_invasion_started(_enemies) -> void:
|
||||
play_sfx("invasion_alarm")
|
||||
_play_music("invasion")
|
||||
|
||||
func _on_invasion_ended(_success) -> void:
|
||||
_play_music("battle")
|
||||
|
||||
func _on_wave_started(_wave) -> void:
|
||||
if current_music != "invasion":
|
||||
_play_music("battle")
|
||||
1
systems/audio_system.gd.uid
Normal file
1
systems/audio_system.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cbfc1ys0i4svm
|
||||
@@ -5,9 +5,10 @@ func _ready() -> void:
|
||||
|
||||
func _on_damage_requested(attacker: Node, target: Node, amount: float) -> void:
|
||||
var remaining: float = amount
|
||||
var shield_system: Node = get_node_or_null("../ShieldSystem")
|
||||
if shield_system:
|
||||
remaining = shield_system.absorb(target, remaining)
|
||||
if not target.is_in_group("tavern"):
|
||||
var shield_system: Node = get_node_or_null("../ShieldSystem")
|
||||
if shield_system:
|
||||
remaining = shield_system.absorb(target, remaining)
|
||||
EventBus.damage_dealt.emit(attacker, target, amount)
|
||||
if remaining > 0:
|
||||
_apply_damage(target, remaining)
|
||||
@@ -33,6 +34,11 @@ func _apply_damage(entity: Node, amount: float) -> void:
|
||||
if health < 0:
|
||||
health = 0
|
||||
PortalData.set_health(entity, health)
|
||||
elif entity.is_in_group("tavern"):
|
||||
var health: float = TavernData.get_stat(entity, "health") - amount
|
||||
if health < 0:
|
||||
health = 0
|
||||
TavernData.set_health(entity, health)
|
||||
|
||||
func _get_player() -> Node:
|
||||
return get_tree().get_first_node_in_group("player")
|
||||
|
||||
@@ -8,6 +8,12 @@ const MARGIN := 2
|
||||
|
||||
var ability_labels: Array[String] = ["1", "2", "3", "4", "P"]
|
||||
var effect_container: HBoxContainer = null
|
||||
var tavern_bar: ProgressBar = null
|
||||
var tavern_label: Label = null
|
||||
var wave_label: Label = null
|
||||
var level_label: Label = null
|
||||
var xp_bar: ProgressBar = null
|
||||
var xp_label: Label = null
|
||||
|
||||
func _ready() -> void:
|
||||
EventBus.health_changed.connect(_on_health_changed)
|
||||
@@ -19,6 +25,11 @@ func _ready() -> void:
|
||||
EventBus.cooldown_tick.connect(_on_cooldown_tick)
|
||||
EventBus.effect_applied.connect(_on_effect_applied)
|
||||
EventBus.effect_expired.connect(_on_effect_expired)
|
||||
EventBus.tavern_damaged.connect(_on_tavern_damaged)
|
||||
EventBus.wave_started.connect(_on_wave_started)
|
||||
EventBus.wave_timer_tick.connect(_on_wave_timer_tick)
|
||||
EventBus.xp_gained.connect(_on_xp_gained)
|
||||
EventBus.level_up.connect(_on_level_up)
|
||||
_init_hud.call_deferred()
|
||||
|
||||
func _init_hud() -> void:
|
||||
@@ -31,6 +42,121 @@ func _init_hud() -> void:
|
||||
effect_container.position = Vector2(10, 60)
|
||||
effect_container.add_theme_constant_override("separation", 3)
|
||||
hud.add_child(effect_container)
|
||||
_init_tavern_bar(hud)
|
||||
_init_xp_bar(hud)
|
||||
|
||||
func _init_tavern_bar(hud: CanvasLayer) -> void:
|
||||
var container := VBoxContainer.new()
|
||||
container.name = "TavernContainer"
|
||||
container.anchor_left = 0.5
|
||||
container.anchor_right = 0.5
|
||||
container.offset_left = -150
|
||||
container.offset_right = 150
|
||||
container.offset_top = 10
|
||||
container.add_theme_constant_override("separation", 2)
|
||||
wave_label = Label.new()
|
||||
wave_label.text = "Welle 1 — 60:00"
|
||||
wave_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
wave_label.add_theme_font_size_override("font_size", 18)
|
||||
container.add_child(wave_label)
|
||||
var title := Label.new()
|
||||
title.text = "Taverne"
|
||||
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
title.add_theme_font_size_override("font_size", 14)
|
||||
container.add_child(title)
|
||||
tavern_bar = ProgressBar.new()
|
||||
tavern_bar.custom_minimum_size = Vector2(300, 22)
|
||||
tavern_bar.show_percentage = false
|
||||
var bg := StyleBoxFlat.new()
|
||||
bg.bg_color = Color(0.3, 0.1, 0.1, 1)
|
||||
var fill := StyleBoxFlat.new()
|
||||
fill.bg_color = Color(0.9, 0.7, 0.2, 1)
|
||||
tavern_bar.add_theme_stylebox_override("background", bg)
|
||||
tavern_bar.add_theme_stylebox_override("fill", fill)
|
||||
tavern_bar.max_value = 5000.0
|
||||
tavern_bar.value = 5000.0
|
||||
container.add_child(tavern_bar)
|
||||
tavern_label = Label.new()
|
||||
tavern_label.text = "5000/5000"
|
||||
tavern_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
tavern_label.anchor_left = 0.0
|
||||
tavern_label.anchor_right = 1.0
|
||||
tavern_bar.add_child(tavern_label)
|
||||
hud.add_child(container)
|
||||
|
||||
func _on_tavern_damaged(current: float, max_val: float) -> void:
|
||||
if tavern_bar:
|
||||
tavern_bar.max_value = max_val
|
||||
tavern_bar.value = current
|
||||
if tavern_label:
|
||||
tavern_label.text = "%d/%d" % [current, max_val]
|
||||
|
||||
func _init_xp_bar(hud: CanvasLayer) -> void:
|
||||
var container := VBoxContainer.new()
|
||||
container.name = "LevelContainer"
|
||||
container.anchor_left = 1.0
|
||||
container.anchor_top = 1.0
|
||||
container.anchor_right = 1.0
|
||||
container.anchor_bottom = 1.0
|
||||
container.offset_left = -260
|
||||
container.offset_top = -80
|
||||
container.offset_right = -10
|
||||
container.offset_bottom = -10
|
||||
container.add_theme_constant_override("separation", 2)
|
||||
level_label = Label.new()
|
||||
level_label.text = "Level 1"
|
||||
level_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
level_label.add_theme_font_size_override("font_size", 20)
|
||||
container.add_child(level_label)
|
||||
xp_bar = ProgressBar.new()
|
||||
xp_bar.custom_minimum_size = Vector2(250, 22)
|
||||
xp_bar.max_value = 1
|
||||
xp_bar.value = 0
|
||||
xp_bar.show_percentage = false
|
||||
var bg := StyleBoxFlat.new()
|
||||
bg.bg_color = Color(0.1, 0.2, 0.1, 1)
|
||||
var fill := StyleBoxFlat.new()
|
||||
fill.bg_color = Color(0.3, 0.9, 0.3, 1)
|
||||
xp_bar.add_theme_stylebox_override("background", bg)
|
||||
xp_bar.add_theme_stylebox_override("fill", fill)
|
||||
xp_label = Label.new()
|
||||
xp_label.text = "0/1"
|
||||
xp_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
xp_label.anchor_left = 0.0
|
||||
xp_label.anchor_right = 1.0
|
||||
xp_bar.add_child(xp_label)
|
||||
container.add_child(xp_bar)
|
||||
hud.add_child(container)
|
||||
_update_xp_ui()
|
||||
|
||||
func _on_xp_gained(_player: Node, _amount: int) -> void:
|
||||
_update_xp_ui()
|
||||
|
||||
func _on_level_up(_player: Node, _new_level: int) -> void:
|
||||
_update_xp_ui()
|
||||
|
||||
func _update_xp_ui() -> void:
|
||||
if level_label:
|
||||
level_label.text = "Level %d" % PlayerData.level
|
||||
if xp_bar:
|
||||
xp_bar.max_value = PlayerData.xp_to_next
|
||||
xp_bar.value = PlayerData.xp
|
||||
if xp_label:
|
||||
xp_label.text = "%d/%d" % [PlayerData.xp, PlayerData.xp_to_next]
|
||||
|
||||
func _on_wave_started(_wave_number: int) -> void:
|
||||
_update_wave_label()
|
||||
|
||||
func _on_wave_timer_tick(_seconds_remaining: float) -> void:
|
||||
_update_wave_label()
|
||||
|
||||
func _update_wave_label() -> void:
|
||||
if not wave_label:
|
||||
return
|
||||
var secs: int = int(max(0.0, GameState.wave_timer_remaining))
|
||||
var mm: int = secs / 60
|
||||
var ss: int = secs % 60
|
||||
wave_label.text = "Welle %d — %02d:%02d" % [GameState.current_wave, mm, ss]
|
||||
|
||||
func _on_health_changed(entity: Node, current: float, max_val: float) -> void:
|
||||
if entity != PlayerData:
|
||||
|
||||
88
systems/invasion_system.gd
Normal file
88
systems/invasion_system.gd
Normal file
@@ -0,0 +1,88 @@
|
||||
extends Node
|
||||
|
||||
const ENEMY_SCENE: PackedScene = preload("res://scenes/enemy/enemy.tscn")
|
||||
const BOSS_STATS: Resource = preload("res://scenes/enemy/boss_stats.tres")
|
||||
const INVASION_COUNT := 16
|
||||
const SPAWN_RADIUS := 45.0
|
||||
|
||||
var active: bool = false
|
||||
var invasion_enemies: Array[Node] = []
|
||||
|
||||
func _ready() -> void:
|
||||
EventBus.wave_timer_tick.connect(_on_wave_timer_tick)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
EventBus.tavern_destroyed.connect(_on_tavern_destroyed)
|
||||
|
||||
func _on_wave_timer_tick(seconds_remaining: float) -> void:
|
||||
if active:
|
||||
return
|
||||
if seconds_remaining > 0:
|
||||
return
|
||||
var has_alive_red := false
|
||||
for p in get_tree().get_nodes_in_group("red_portal"):
|
||||
if is_instance_valid(p) and PortalData.is_alive(p):
|
||||
has_alive_red = true
|
||||
break
|
||||
if not has_alive_red:
|
||||
return
|
||||
trigger()
|
||||
|
||||
func trigger() -> void:
|
||||
active = true
|
||||
invasion_enemies.clear()
|
||||
var tavern: Node = get_tree().get_first_node_in_group("tavern")
|
||||
if not tavern:
|
||||
active = false
|
||||
return
|
||||
var world: Node = get_tree().current_scene
|
||||
var scale: float = PlayerData.level_scale * 10.0
|
||||
for enemy in get_tree().get_nodes_in_group("red_enemies"):
|
||||
if is_instance_valid(enemy):
|
||||
_convert_to_invasion(enemy, tavern)
|
||||
for i in range(INVASION_COUNT):
|
||||
var angle: float = randf() * TAU
|
||||
var pos := Vector3(cos(angle) * SPAWN_RADIUS, 0, sin(angle) * SPAWN_RADIUS)
|
||||
var enemy: Node = ENEMY_SCENE.instantiate()
|
||||
enemy.spawn_scale = scale
|
||||
world.add_child(enemy)
|
||||
enemy.global_position = pos
|
||||
_convert_to_invasion(enemy, tavern)
|
||||
var boss_angle: float = randf() * TAU
|
||||
var boss_pos := Vector3(cos(boss_angle) * SPAWN_RADIUS, 0, sin(boss_angle) * SPAWN_RADIUS)
|
||||
var boss: Node = ENEMY_SCENE.instantiate()
|
||||
boss.add_to_group("boss")
|
||||
boss.stats = BOSS_STATS
|
||||
boss.spawn_scale = scale
|
||||
world.add_child(boss)
|
||||
boss.global_position = boss_pos
|
||||
_convert_to_invasion(boss, tavern)
|
||||
EventBus.invasion_started.emit(invasion_enemies)
|
||||
|
||||
func _convert_to_invasion(enemy: Node, tavern: Node) -> void:
|
||||
enemy.add_to_group("invasion")
|
||||
var data_source: Node = BossData if enemy.is_in_group("boss") else EnemyData
|
||||
data_source.set_stat(enemy, "target", tavern)
|
||||
data_source.set_stat(enemy, "state", 1)
|
||||
invasion_enemies.append(enemy)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if not active:
|
||||
return
|
||||
if entity not in invasion_enemies:
|
||||
return
|
||||
invasion_enemies.erase(entity)
|
||||
var alive_count := 0
|
||||
for e in invasion_enemies:
|
||||
if is_instance_valid(e):
|
||||
alive_count += 1
|
||||
if alive_count == 0:
|
||||
_end_invasion(true)
|
||||
|
||||
func _end_invasion(success: bool) -> void:
|
||||
active = false
|
||||
invasion_enemies.clear()
|
||||
EventBus.invasion_ended.emit(success)
|
||||
|
||||
func _on_tavern_destroyed() -> void:
|
||||
active = false
|
||||
EventBus.game_over.emit()
|
||||
1
systems/invasion_system.gd.uid
Normal file
1
systems/invasion_system.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://841gb4nrydai
|
||||
@@ -16,8 +16,14 @@ func _ready() -> void:
|
||||
EventBus.effect_applied.connect(_on_effect_applied)
|
||||
EventBus.effect_expired.connect(_on_effect_expired)
|
||||
EventBus.portal_spawn.connect(_on_portal_spawn)
|
||||
EventBus.invasion_started.connect(_on_invasion_started)
|
||||
_init_nameplates.call_deferred()
|
||||
|
||||
func _on_invasion_started(enemies: Array) -> void:
|
||||
for enemy in enemies:
|
||||
if is_instance_valid(enemy):
|
||||
_setup_nameplate.call_deferred(enemy)
|
||||
|
||||
func _init_nameplates() -> void:
|
||||
for enemy in get_tree().get_nodes_in_group("enemies"):
|
||||
_setup_nameplate(enemy)
|
||||
@@ -80,6 +86,8 @@ func _on_shield_changed(entity: Node, current: float, max_val: float) -> void:
|
||||
var nameplate: Sprite3D = entity.get_node_or_null("Healthbar")
|
||||
if not nameplate:
|
||||
return
|
||||
if not nameplate.texture:
|
||||
_setup_nameplate(entity)
|
||||
var bar: ProgressBar = nameplate.get_node_or_null("SubViewport/ShieldBar")
|
||||
if not bar:
|
||||
return
|
||||
@@ -96,12 +104,16 @@ func _on_target_changed(_player: Node, target: Node) -> void:
|
||||
continue
|
||||
var nameplate: Sprite3D = enemy.get_node_or_null("Healthbar")
|
||||
if nameplate:
|
||||
if not nameplate.texture:
|
||||
_setup_nameplate(enemy)
|
||||
nameplate.get_node("SubViewport/Border").visible = (target == enemy)
|
||||
for portal in get_tree().get_nodes_in_group("portals"):
|
||||
if not is_instance_valid(portal):
|
||||
continue
|
||||
var nameplate: Sprite3D = portal.get_node_or_null("Healthbar")
|
||||
if nameplate:
|
||||
if not nameplate.texture:
|
||||
_setup_nameplate(portal)
|
||||
nameplate.get_node("SubViewport/Border").visible = (target == portal)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
|
||||
@@ -14,8 +14,6 @@ func _on_entity_died(entity: Node) -> void:
|
||||
var gate: Node3D = GATE_SCENE.instantiate()
|
||||
entity.get_parent().add_child(gate)
|
||||
gate.global_position = pos
|
||||
for enemy in get_tree().get_nodes_in_group("enemies"):
|
||||
if is_instance_valid(enemy):
|
||||
enemy.queue_free()
|
||||
gate.dungeon_variant = entity.stats.variant
|
||||
EventBus.portal_defeated.emit(entity)
|
||||
entity.queue_free()
|
||||
|
||||
@@ -15,18 +15,21 @@ func _process(delta: float) -> void:
|
||||
_respawn()
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if not entity.is_in_group("player"):
|
||||
if entity != PlayerData:
|
||||
return
|
||||
if is_dead:
|
||||
return
|
||||
is_dead = true
|
||||
respawn_timer = PlayerData.respawn_time
|
||||
entity.velocity = Vector3.ZERO
|
||||
entity.get_node("Mesh").visible = false
|
||||
entity.get_node("CollisionShape3D").disabled = true
|
||||
entity.get_node("Movement").set_physics_process(false)
|
||||
entity.get_node("Ability").set_process_unhandled_input(false)
|
||||
entity.get_node("Targeting").set_process_unhandled_input(false)
|
||||
var player: Node = get_tree().get_first_node_in_group("player")
|
||||
if not player:
|
||||
return
|
||||
player.velocity = Vector3.ZERO
|
||||
player.get_node("Mesh").visible = false
|
||||
player.get_node("CollisionShape3D").disabled = true
|
||||
player.get_node("Movement").set_physics_process(false)
|
||||
player.get_node("Ability").set_process_unhandled_input(false)
|
||||
player.get_node("Targeting").set_process_unhandled_input(false)
|
||||
|
||||
func _respawn() -> void:
|
||||
is_dead = false
|
||||
|
||||
@@ -25,11 +25,18 @@ func _on_health_changed(entity: Node, current: float, max_val: float) -> void:
|
||||
|
||||
func _spawn_enemies(portal: Node, count: int) -> void:
|
||||
var spawned: Array = []
|
||||
var is_red: bool = portal.is_in_group("red_portal")
|
||||
var portal_bonus: float = 10.0 if is_red else 1.0
|
||||
var total_scale: float = PlayerData.level_scale * portal_bonus
|
||||
for j in range(count):
|
||||
var entity: Node = ENEMY_SCENE.instantiate()
|
||||
entity.spawn_scale = total_scale
|
||||
var offset := Vector3(randf_range(-2, 2), 0, randf_range(-2, 2))
|
||||
portal.get_parent().add_child(entity)
|
||||
if is_red:
|
||||
entity.add_to_group("red_enemies")
|
||||
entity.global_position = portal.global_position + offset
|
||||
EnemyData.set_stat(entity, "portal", portal)
|
||||
spawned.append(entity)
|
||||
var player: Node = get_tree().get_first_node_in_group("player")
|
||||
if player:
|
||||
|
||||
@@ -14,6 +14,9 @@ func _process(delta: float) -> void:
|
||||
|
||||
func _on_target_requested(_player: Node, target: Node3D) -> void:
|
||||
PlayerData.set_target(target)
|
||||
if target and (target.is_in_group("enemies") or target.is_in_group("portals")):
|
||||
PlayerData.in_combat = true
|
||||
PlayerData.combat_timer = PlayerData.combat_timeout
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity == PlayerData.target:
|
||||
|
||||
47
systems/wave_system.gd
Normal file
47
systems/wave_system.gd
Normal file
@@ -0,0 +1,47 @@
|
||||
extends Node
|
||||
|
||||
const WAVE_DURATION := 60.0
|
||||
|
||||
var tick_accumulator := 0.0
|
||||
|
||||
func _ready() -> void:
|
||||
EventBus.portal_defeated.connect(_on_portal_defeated)
|
||||
EventBus.invasion_ended.connect(_on_invasion_ended)
|
||||
call_deferred("_start_run")
|
||||
|
||||
func _start_run() -> void:
|
||||
if not GameState.run_initialized:
|
||||
GameState.current_wave = 1
|
||||
GameState.wave_timer_remaining = WAVE_DURATION
|
||||
GameState.run_initialized = true
|
||||
EventBus.run_started.emit(GameState.current_wave)
|
||||
EventBus.wave_started.emit(GameState.current_wave)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if GameState.wave_timer_remaining <= 0:
|
||||
return
|
||||
GameState.wave_timer_remaining -= delta
|
||||
tick_accumulator += delta
|
||||
if tick_accumulator >= 1.0:
|
||||
tick_accumulator -= 1.0
|
||||
EventBus.wave_timer_tick.emit(max(0.0, GameState.wave_timer_remaining))
|
||||
if GameState.wave_timer_remaining <= 0:
|
||||
GameState.wave_timer_remaining = 0.0
|
||||
EventBus.wave_timer_tick.emit(0.0)
|
||||
|
||||
func _on_portal_defeated(portal: Node) -> void:
|
||||
if not portal.is_in_group("red_portal"):
|
||||
return
|
||||
_advance_wave()
|
||||
|
||||
func _on_invasion_ended(success: bool) -> void:
|
||||
if not success:
|
||||
return
|
||||
_advance_wave()
|
||||
|
||||
func _advance_wave() -> void:
|
||||
EventBus.wave_ended.emit(GameState.current_wave, true)
|
||||
GameState.current_wave += 1
|
||||
GameState.wave_timer_remaining = WAVE_DURATION
|
||||
tick_accumulator = 0.0
|
||||
EventBus.wave_started.emit(GameState.current_wave)
|
||||
1
systems/wave_system.gd.uid
Normal file
1
systems/wave_system.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://chfcocmkb0wnp
|
||||
19
systems/xp_system.gd
Normal file
19
systems/xp_system.gd
Normal file
@@ -0,0 +1,19 @@
|
||||
extends Node
|
||||
|
||||
const XP_PER_ENEMY: int = 3
|
||||
const XP_PER_BOSS: int = 30
|
||||
|
||||
func _ready() -> void:
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if not entity or not is_instance_valid(entity):
|
||||
return
|
||||
if not entity.is_in_group("enemies"):
|
||||
return
|
||||
var is_boss: bool = entity.is_in_group("boss")
|
||||
var data_source: Node = BossData if is_boss else EnemyData
|
||||
var scale_val: Variant = data_source.get_stat(entity, "scale")
|
||||
var scale: float = scale_val if scale_val != null else 1.0
|
||||
var base_xp: int = XP_PER_BOSS if is_boss else XP_PER_ENEMY
|
||||
PlayerData.add_xp(int(base_xp * scale))
|
||||
1
systems/xp_system.gd.uid
Normal file
1
systems/xp_system.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://clivdryqcvfmh
|
||||
Reference in New Issue
Block a user