refactor
This commit is contained in:
@@ -1,82 +0,0 @@
|
||||
extends Node
|
||||
|
||||
var entities: Dictionary = {}
|
||||
|
||||
func register(entity: Node, base: EnemyStats, scale: float = 1.0) -> void:
|
||||
var max_hp: float = base.max_health * scale
|
||||
var max_sh: float = base.max_shield * scale
|
||||
entities[entity] = {
|
||||
"base": base,
|
||||
"scale": scale,
|
||||
"health": max_hp,
|
||||
"max_health": max_hp,
|
||||
"health_regen": base.health_regen * scale,
|
||||
"shield": max_sh,
|
||||
"max_shield": max_sh,
|
||||
"shield_regen_delay": base.shield_regen_delay,
|
||||
"shield_regen_time": base.shield_regen_time,
|
||||
"shield_regen_timer": 0.0,
|
||||
"alive": true,
|
||||
"buff_damage": 1.0,
|
||||
"buff_heal": 1.0,
|
||||
"buff_shield": 1.0,
|
||||
"state": 0,
|
||||
"target": null,
|
||||
"spawn_position": Vector3.ZERO,
|
||||
"portal": null,
|
||||
"attack_timer": 0.0,
|
||||
}
|
||||
|
||||
func apply_scale(entity: Node, scale: float) -> void:
|
||||
if entity not in entities:
|
||||
return
|
||||
var data: Dictionary = entities[entity]
|
||||
var base: EnemyStats = data["base"]
|
||||
data["scale"] = scale
|
||||
data["max_health"] = base.max_health * scale
|
||||
data["health"] = data["max_health"]
|
||||
data["health_regen"] = base.health_regen * scale
|
||||
data["max_shield"] = base.max_shield * scale
|
||||
data["shield"] = data["max_shield"]
|
||||
EventBus.health_changed.emit(entity, data["health"], data["max_health"])
|
||||
if base.max_shield > 0:
|
||||
EventBus.shield_changed.emit(entity, data["shield"], data["max_shield"])
|
||||
|
||||
func deregister(entity: Node) -> void:
|
||||
entities.erase(entity)
|
||||
|
||||
func get_stat(entity: Node, key: String) -> Variant:
|
||||
if entity in entities:
|
||||
return entities[entity].get(key)
|
||||
return null
|
||||
|
||||
func set_stat(entity: Node, key: String, value: Variant) -> void:
|
||||
if entity in entities:
|
||||
entities[entity][key] = value
|
||||
|
||||
func get_base(entity: Node) -> EnemyStats:
|
||||
if entity in entities:
|
||||
return entities[entity]["base"]
|
||||
return null
|
||||
|
||||
func is_alive(entity: Node) -> bool:
|
||||
if entity in entities:
|
||||
return entities[entity]["alive"]
|
||||
return false
|
||||
|
||||
func set_health(entity: Node, value: float) -> void:
|
||||
if entity not in entities:
|
||||
return
|
||||
entities[entity]["health"] = value
|
||||
var max_health: float = entities[entity]["max_health"]
|
||||
EventBus.health_changed.emit(entity, value, max_health)
|
||||
if value <= 0 and entities[entity]["alive"]:
|
||||
entities[entity]["alive"] = false
|
||||
EventBus.entity_died.emit(entity)
|
||||
|
||||
func set_shield(entity: Node, value: float) -> void:
|
||||
if entity not in entities:
|
||||
return
|
||||
entities[entity]["shield"] = value
|
||||
var max_shield: float = entities[entity]["max_shield"]
|
||||
EventBus.shield_changed.emit(entity, value, max_shield)
|
||||
@@ -1 +0,0 @@
|
||||
uid://dbr02t7pt4vcn
|
||||
@@ -1,82 +0,0 @@
|
||||
extends Node
|
||||
|
||||
var entities: Dictionary = {}
|
||||
|
||||
func register(entity: Node, base: EnemyStats, scale: float = 1.0) -> void:
|
||||
var max_hp: float = base.max_health * scale
|
||||
var max_sh: float = base.max_shield * scale
|
||||
entities[entity] = {
|
||||
"base": base,
|
||||
"scale": scale,
|
||||
"health": max_hp,
|
||||
"max_health": max_hp,
|
||||
"health_regen": base.health_regen * scale,
|
||||
"shield": max_sh,
|
||||
"max_shield": max_sh,
|
||||
"shield_regen_delay": base.shield_regen_delay,
|
||||
"shield_regen_time": base.shield_regen_time,
|
||||
"shield_regen_timer": 0.0,
|
||||
"alive": true,
|
||||
"buff_damage": 1.0,
|
||||
"buff_heal": 1.0,
|
||||
"buff_shield": 1.0,
|
||||
"state": 0,
|
||||
"target": null,
|
||||
"spawn_position": Vector3.ZERO,
|
||||
"portal": null,
|
||||
"attack_timer": 0.0,
|
||||
}
|
||||
|
||||
func apply_scale(entity: Node, scale: float) -> void:
|
||||
if entity not in entities:
|
||||
return
|
||||
var data: Dictionary = entities[entity]
|
||||
var base: EnemyStats = data["base"]
|
||||
data["scale"] = scale
|
||||
data["max_health"] = base.max_health * scale
|
||||
data["health"] = data["max_health"]
|
||||
data["health_regen"] = base.health_regen * scale
|
||||
data["max_shield"] = base.max_shield * scale
|
||||
data["shield"] = data["max_shield"]
|
||||
EventBus.health_changed.emit(entity, data["health"], data["max_health"])
|
||||
if base.max_shield > 0:
|
||||
EventBus.shield_changed.emit(entity, data["shield"], data["max_shield"])
|
||||
|
||||
func deregister(entity: Node) -> void:
|
||||
entities.erase(entity)
|
||||
|
||||
func get_stat(entity: Node, key: String) -> Variant:
|
||||
if entity in entities:
|
||||
return entities[entity].get(key)
|
||||
return null
|
||||
|
||||
func set_stat(entity: Node, key: String, value: Variant) -> void:
|
||||
if entity in entities:
|
||||
entities[entity][key] = value
|
||||
|
||||
func get_base(entity: Node) -> EnemyStats:
|
||||
if entity in entities:
|
||||
return entities[entity]["base"]
|
||||
return null
|
||||
|
||||
func is_alive(entity: Node) -> bool:
|
||||
if entity in entities:
|
||||
return entities[entity]["alive"]
|
||||
return false
|
||||
|
||||
func set_health(entity: Node, value: float) -> void:
|
||||
if entity not in entities:
|
||||
return
|
||||
entities[entity]["health"] = value
|
||||
var max_health: float = entities[entity]["max_health"]
|
||||
EventBus.health_changed.emit(entity, value, max_health)
|
||||
if value <= 0 and entities[entity]["alive"]:
|
||||
entities[entity]["alive"] = false
|
||||
EventBus.entity_died.emit(entity)
|
||||
|
||||
func set_shield(entity: Node, value: float) -> void:
|
||||
if entity not in entities:
|
||||
return
|
||||
entities[entity]["shield"] = value
|
||||
var max_shield: float = entities[entity]["max_shield"]
|
||||
EventBus.shield_changed.emit(entity, value, max_shield)
|
||||
@@ -1 +0,0 @@
|
||||
uid://bvxn6y15tvidu
|
||||
@@ -1,73 +1,60 @@
|
||||
extends Node
|
||||
|
||||
# Intentionen (Input → System)
|
||||
signal ability_use(player, ability_index)
|
||||
signal role_change_requested(player, role)
|
||||
signal target_requested(player, target)
|
||||
signal enemy_detected(enemy, player)
|
||||
signal enemy_lost(enemy, player)
|
||||
signal portal_entered(portal, player)
|
||||
signal entity_registered(entity: Node)
|
||||
signal entity_deregistered(entity: Node)
|
||||
|
||||
# Kampf
|
||||
signal attack_executed(attacker, position, direction, damage)
|
||||
signal damage_dealt(attacker, target, damage)
|
||||
signal damage_requested(attacker, target, amount)
|
||||
signal heal_requested(healer, target, amount)
|
||||
signal damage_requested(attacker: Node, target: Node, amount: float, element: int)
|
||||
signal heal_requested(healer: Node, target: Node, amount: float)
|
||||
signal damage_dealt(attacker: Node, target: Node, amount: float)
|
||||
signal entity_died(entity: Node)
|
||||
signal entity_respawned(entity: Node)
|
||||
signal health_changed(entity: Node, current: float, max: float)
|
||||
signal shield_changed(entity: Node, current: float, max: float)
|
||||
signal shield_broken(entity: Node)
|
||||
|
||||
# Entity
|
||||
signal entity_died(entity)
|
||||
signal health_changed(entity, current, max_val)
|
||||
signal shield_changed(entity, current, max_val)
|
||||
signal shield_broken(entity)
|
||||
signal shield_regenerated(entity)
|
||||
signal ability_use_requested(player: Node, ability_index: int)
|
||||
signal ability_used(player: Node, ability_index: int, ability: Resource)
|
||||
signal cooldown_tick(entity: Node, cds: PackedFloat32Array, max_cds: PackedFloat32Array, gcd: float)
|
||||
signal role_changed(player: Node, role: int)
|
||||
signal target_changed(player: Node, target: Node)
|
||||
|
||||
# Spieler
|
||||
signal target_changed(player, target)
|
||||
signal player_respawned(player)
|
||||
signal role_changed(player, role_type)
|
||||
signal respawn_tick(timer)
|
||||
signal cooldown_tick(cooldowns, max_cooldowns, gcd_timer)
|
||||
signal effect_applied(target: Node, effect: Resource, source: Node)
|
||||
signal effect_expired(target: Node, effect: Resource)
|
||||
signal element_applied(target: Node, element: int)
|
||||
signal buff_changed(entity: Node, stat: StringName, value: float)
|
||||
|
||||
# Buff
|
||||
signal buff_changed(entity, stat, value)
|
||||
signal enemy_detected(enemy: Node, player: Node)
|
||||
signal enemy_lost(enemy: Node, player: Node)
|
||||
signal enemy_engaged(enemy: Node, target: Node)
|
||||
|
||||
# Gegner
|
||||
signal enemy_engaged(enemy, target)
|
||||
signal gate_destroyed(gate: Node)
|
||||
signal portal_spawned(portal: Node)
|
||||
signal portal_entered(portal: Node, player: Node)
|
||||
signal dungeon_cleared(seed: int)
|
||||
signal boss_defeated(boss: Node)
|
||||
|
||||
# Portal
|
||||
signal portal_spawn(portal, enemies)
|
||||
signal portal_defeated(portal)
|
||||
signal loot_dropped(items: Array, position: Vector3)
|
||||
signal item_picked_up(player: Node, item: Resource)
|
||||
signal inventory_changed(player: Node)
|
||||
signal item_crafted(player: Node, item: Resource)
|
||||
signal building_placed(building: Node)
|
||||
signal building_removed(building: Node)
|
||||
|
||||
# Dungeon
|
||||
signal dungeon_cleared()
|
||||
|
||||
# Effects
|
||||
signal effect_requested(target, effect, source)
|
||||
signal effect_applied(target, effect)
|
||||
signal effect_expired(target, effect)
|
||||
|
||||
# Elements
|
||||
signal element_damage_dealt(attacker, target, amount, element)
|
||||
signal element_applied(target, element)
|
||||
signal element_reaction(target, element_a, element_b, reaction_name)
|
||||
|
||||
# Wave
|
||||
signal run_started(wave_number)
|
||||
signal wave_started(wave_number)
|
||||
signal wave_timer_tick(seconds_remaining)
|
||||
signal wave_ended(wave_number, success)
|
||||
|
||||
# XP / Level
|
||||
signal xp_gained(player, amount)
|
||||
signal level_up(player, new_level)
|
||||
|
||||
# Taverne
|
||||
signal tavern_damaged(current, max_val)
|
||||
signal tavern_destroyed()
|
||||
|
||||
# Invasion
|
||||
signal invasion_started(enemies)
|
||||
signal invasion_ended(success)
|
||||
|
||||
# Game-Over
|
||||
signal wave_started(wave_number: int)
|
||||
signal wave_timer_tick(seconds_remaining: float)
|
||||
signal wave_ended(wave_number: int, success: bool)
|
||||
signal invasion_started()
|
||||
signal invasion_ended(success: bool)
|
||||
signal village_damaged(current: float, max: float)
|
||||
signal village_destroyed()
|
||||
signal game_over()
|
||||
signal run_started(wave_number: int)
|
||||
|
||||
signal xp_gained(player: Node, amount: float)
|
||||
signal level_up(player: Node, new_level: int)
|
||||
|
||||
signal dialog_opened(player: Node, npc: Node)
|
||||
signal dialog_closed(player: Node)
|
||||
signal chat_message(peer_id: int, sender_name: String, text: String)
|
||||
|
||||
signal scene_change_requested(scene_path: String)
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://g7a7xkg1pgb4
|
||||
uid://361x7bdk2j6v
|
||||
|
||||
@@ -1,19 +1,36 @@
|
||||
extends Node
|
||||
|
||||
# Run-Zustand
|
||||
const ROLE_TANK: int = 0
|
||||
const ROLE_DAMAGE: int = 1
|
||||
const ROLE_HEALER: int = 2
|
||||
|
||||
const SCENE_MAIN_MENU: String = "res://scenes/menu/main_menu.tscn"
|
||||
const SCENE_LOBBY: String = "res://scenes/menu/lobby.tscn"
|
||||
const SCENE_WORLD: String = "res://scenes/world/world.tscn"
|
||||
const SCENE_DUNGEON: String = "res://scenes/dungeon/dungeon.tscn"
|
||||
const SCENE_OPTIONS: String = "res://scenes/menu/options_menu.tscn"
|
||||
|
||||
var current_scene: String = SCENE_MAIN_MENU
|
||||
var paused: bool = false
|
||||
var run_seed: int = 0
|
||||
var dungeon_seed: int = 0
|
||||
var dungeon_red: bool = false
|
||||
var current_wave: int = 1
|
||||
var wave_timer_remaining: float = 0.0
|
||||
var run_initialized: bool = false
|
||||
var portal_return_position: Vector3 = Vector3.ZERO
|
||||
|
||||
# Dungeon-Kontext (für XP-Zuordnung nach Clear)
|
||||
var last_dungeon_variant: int = 0
|
||||
|
||||
# Flag für Forced Return (Timer läuft ab während Spieler im Dungeon)
|
||||
var force_return_to_world: bool = false
|
||||
|
||||
func reset() -> void:
|
||||
func reset_run() -> void:
|
||||
run_seed = randi()
|
||||
current_wave = 1
|
||||
wave_timer_remaining = 0.0
|
||||
run_initialized = false
|
||||
last_dungeon_variant = 0
|
||||
force_return_to_world = false
|
||||
dungeon_seed = 0
|
||||
dungeon_red = false
|
||||
paused = false
|
||||
Stats.clear_all()
|
||||
|
||||
func change_scene(path: String) -> void:
|
||||
current_scene = path
|
||||
EventBus.scene_change_requested.emit(path)
|
||||
get_tree().change_scene_to_file(path)
|
||||
|
||||
func set_paused(value: bool) -> void:
|
||||
paused = value
|
||||
get_tree().paused = value
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://c3jq4raqs0onf
|
||||
uid://dettmu50fjtvc
|
||||
|
||||
148
autoloads/net.gd
Normal file
148
autoloads/net.gd
Normal file
@@ -0,0 +1,148 @@
|
||||
extends Node
|
||||
|
||||
const DEFAULT_PORT: int = 7777
|
||||
const MAX_PLAYERS: int = 4
|
||||
|
||||
signal peer_connected(id: int)
|
||||
signal peer_disconnected(id: int)
|
||||
signal connected_to_server()
|
||||
signal connection_failed()
|
||||
signal server_disconnected()
|
||||
signal world_ready()
|
||||
signal peer_world_loaded(peer_id: int)
|
||||
|
||||
var player_names: Dictionary = {}
|
||||
var local_name: String = "Player"
|
||||
var _expected_peers: Array = []
|
||||
var _ready_peers: Array = []
|
||||
|
||||
func _ready() -> void:
|
||||
multiplayer.peer_connected.connect(_on_peer_connected)
|
||||
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
|
||||
multiplayer.connected_to_server.connect(_on_connected_to_server)
|
||||
multiplayer.connection_failed.connect(_on_connection_failed)
|
||||
multiplayer.server_disconnected.connect(_on_server_disconnected)
|
||||
|
||||
func host(port: int = DEFAULT_PORT, max_clients: int = MAX_PLAYERS - 1) -> bool:
|
||||
var peer := ENetMultiplayerPeer.new()
|
||||
var err := peer.create_server(port, max_clients)
|
||||
if err != OK:
|
||||
push_error("Net: failed to host on port %d (err=%d)" % [port, err])
|
||||
return false
|
||||
multiplayer.multiplayer_peer = peer
|
||||
player_names[1] = local_name
|
||||
return true
|
||||
|
||||
func join(address: String, port: int = DEFAULT_PORT) -> bool:
|
||||
var peer := ENetMultiplayerPeer.new()
|
||||
var err := peer.create_client(address, port)
|
||||
if err != OK:
|
||||
push_error("Net: failed to join %s:%d (err=%d)" % [address, port, err])
|
||||
return false
|
||||
multiplayer.multiplayer_peer = peer
|
||||
return true
|
||||
|
||||
func host_singleplayer() -> bool:
|
||||
if multiplayer.multiplayer_peer != null:
|
||||
multiplayer.multiplayer_peer.close()
|
||||
var peer := OfflineMultiplayerPeer.new()
|
||||
multiplayer.multiplayer_peer = peer
|
||||
player_names.clear()
|
||||
player_names[1] = local_name
|
||||
return true
|
||||
|
||||
func disconnect_net() -> void:
|
||||
if multiplayer.multiplayer_peer != null:
|
||||
multiplayer.multiplayer_peer.close()
|
||||
multiplayer.multiplayer_peer = null
|
||||
player_names.clear()
|
||||
|
||||
func is_host() -> bool:
|
||||
return multiplayer.multiplayer_peer == null or multiplayer.is_server()
|
||||
|
||||
func is_authority_for(node: Node) -> bool:
|
||||
return node.is_multiplayer_authority()
|
||||
|
||||
func local_id() -> int:
|
||||
if multiplayer.multiplayer_peer == null:
|
||||
return 1
|
||||
return multiplayer.get_unique_id()
|
||||
|
||||
func _on_peer_connected(id: int) -> void:
|
||||
peer_connected.emit(id)
|
||||
if is_host():
|
||||
_register_name.rpc_id(id, 1, local_name)
|
||||
|
||||
func _on_peer_disconnected(id: int) -> void:
|
||||
player_names.erase(id)
|
||||
peer_disconnected.emit(id)
|
||||
|
||||
func _on_connected_to_server() -> void:
|
||||
player_names[1] = "Host"
|
||||
player_names[multiplayer.get_unique_id()] = local_name
|
||||
_register_name.rpc(multiplayer.get_unique_id(), local_name)
|
||||
connected_to_server.emit()
|
||||
|
||||
func _on_connection_failed() -> void:
|
||||
multiplayer.multiplayer_peer = null
|
||||
connection_failed.emit()
|
||||
|
||||
func _on_server_disconnected() -> void:
|
||||
multiplayer.multiplayer_peer = null
|
||||
player_names.clear()
|
||||
server_disconnected.emit()
|
||||
|
||||
@rpc("any_peer", "reliable", "call_local")
|
||||
func _register_name(peer_id: int, name: String) -> void:
|
||||
player_names[peer_id] = name
|
||||
|
||||
func mark_world_loaded() -> void:
|
||||
if multiplayer.multiplayer_peer == null:
|
||||
_expected_peers = [1]
|
||||
_ready_peers = [1]
|
||||
world_ready.emit()
|
||||
return
|
||||
if is_host():
|
||||
_expected_peers = [1]
|
||||
for p in multiplayer.get_peers():
|
||||
_expected_peers.append(p)
|
||||
_ready_peers = [1]
|
||||
if _all_ready():
|
||||
_broadcast_ready.rpc()
|
||||
else:
|
||||
_client_world_loaded.rpc_id(1, multiplayer.get_unique_id())
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _client_world_loaded(peer_id: int) -> void:
|
||||
if not is_host():
|
||||
return
|
||||
if not peer_id in _expected_peers:
|
||||
_expected_peers.append(peer_id)
|
||||
if not peer_id in _ready_peers:
|
||||
_ready_peers.append(peer_id)
|
||||
peer_world_loaded.emit(peer_id)
|
||||
if _all_ready():
|
||||
_broadcast_ready.rpc()
|
||||
|
||||
@rpc("authority", "reliable")
|
||||
func _load_scene_for_peer(path: String) -> void:
|
||||
GameState.change_scene(path)
|
||||
|
||||
func tell_peer_to_load_scene(peer_id: int, path: String) -> void:
|
||||
if not is_host():
|
||||
return
|
||||
_load_scene_for_peer.rpc_id(peer_id, path)
|
||||
|
||||
@rpc("authority", "reliable", "call_local")
|
||||
func _broadcast_ready() -> void:
|
||||
world_ready.emit()
|
||||
|
||||
func _all_ready() -> bool:
|
||||
for p in _expected_peers:
|
||||
if not p in _ready_peers:
|
||||
return false
|
||||
return true
|
||||
|
||||
func reset_world_ready() -> void:
|
||||
_expected_peers.clear()
|
||||
_ready_peers.clear()
|
||||
1
autoloads/net.gd.uid
Normal file
1
autoloads/net.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://1k1cufc2skfr
|
||||
@@ -1,195 +0,0 @@
|
||||
extends Node
|
||||
|
||||
enum Role { TANK, DAMAGE, HEALER }
|
||||
|
||||
# Basis (aus Resource geladen)
|
||||
var base: PlayerStats
|
||||
var speed := 5.0
|
||||
var jump_velocity := 4.5
|
||||
var target_range := 20.0
|
||||
var combat_timeout := 3.0
|
||||
var respawn_time := 3.0
|
||||
var gcd_time := 0.5
|
||||
var aa_cooldown := 0.5
|
||||
|
||||
# Laufzeit
|
||||
var health := 100.0
|
||||
var max_health := 100.0
|
||||
var health_regen := 0.0
|
||||
var shield := 0.0
|
||||
var max_shield := 0.0
|
||||
var shield_regen_delay := 3.0
|
||||
var shield_regen_time := 5.0
|
||||
var shield_regen_timer := 0.0
|
||||
var alive := true
|
||||
|
||||
# Buffs
|
||||
var buff_damage := 1.0
|
||||
var buff_heal := 1.0
|
||||
var buff_shield := 1.0
|
||||
|
||||
# Level / XP
|
||||
const XP_PER_LEVEL: int = 50
|
||||
var level: int = 1
|
||||
var xp: int = 0
|
||||
var xp_to_next: int = XP_PER_LEVEL
|
||||
var level_scale: float = 1.0
|
||||
|
||||
# Rolle
|
||||
var current_role: int = Role.DAMAGE
|
||||
var ability_set: AbilitySet = null
|
||||
|
||||
# Kampf
|
||||
var target: Node3D = null
|
||||
var in_combat := false
|
||||
var combat_timer := 0.0
|
||||
|
||||
# Cooldowns
|
||||
var cooldowns: Array[float] = []
|
||||
var max_cooldowns: Array[float] = []
|
||||
var gcd := 0.0
|
||||
var aa_timer := 0.0
|
||||
|
||||
# Szenenwechsel
|
||||
var portal_position := Vector3.ZERO
|
||||
var returning_from_dungeon := false
|
||||
var dungeon_cleared := false
|
||||
|
||||
# Cache für Szenenwechsel
|
||||
var _cache: Dictionary = {}
|
||||
|
||||
func init_from_resource(res: PlayerStats) -> void:
|
||||
base = res
|
||||
speed = res.speed
|
||||
jump_velocity = res.jump_velocity
|
||||
target_range = res.target_range
|
||||
combat_timeout = res.combat_timeout
|
||||
respawn_time = res.respawn_time
|
||||
gcd_time = res.gcd_time
|
||||
aa_cooldown = res.aa_cooldown
|
||||
if _cache.is_empty():
|
||||
max_health = res.max_health * level_scale
|
||||
health = max_health
|
||||
health_regen = res.health_regen * level_scale
|
||||
max_shield = res.max_shield * level_scale
|
||||
shield = max_shield
|
||||
shield_regen_delay = res.shield_regen_delay
|
||||
shield_regen_time = res.shield_regen_time
|
||||
shield_regen_timer = 0.0
|
||||
alive = true
|
||||
buff_damage = 1.0
|
||||
buff_heal = 1.0
|
||||
buff_shield = 1.0
|
||||
else:
|
||||
_restore_cache()
|
||||
cooldowns.resize(5)
|
||||
cooldowns.fill(0.0)
|
||||
max_cooldowns.resize(5)
|
||||
max_cooldowns.fill(0.0)
|
||||
gcd = 0.0
|
||||
aa_timer = 0.0
|
||||
|
||||
func set_health(value: float) -> void:
|
||||
health = value
|
||||
EventBus.health_changed.emit(self, health, max_health)
|
||||
if health <= 0 and alive:
|
||||
alive = false
|
||||
EventBus.entity_died.emit(self)
|
||||
|
||||
func set_shield(value: float) -> void:
|
||||
shield = value
|
||||
EventBus.shield_changed.emit(self, shield, max_shield)
|
||||
|
||||
func set_role(role: int) -> void:
|
||||
current_role = role
|
||||
EventBus.role_changed.emit(self, current_role)
|
||||
|
||||
func set_target(new_target: Node3D) -> void:
|
||||
target = new_target
|
||||
EventBus.target_changed.emit(self, target)
|
||||
|
||||
func respawn() -> void:
|
||||
health = max_health
|
||||
shield = max_shield
|
||||
alive = true
|
||||
EventBus.health_changed.emit(self, health, max_health)
|
||||
EventBus.shield_changed.emit(self, shield, max_shield)
|
||||
EventBus.player_respawned.emit(self)
|
||||
|
||||
func save_cache() -> void:
|
||||
_cache = {
|
||||
"health": health,
|
||||
"max_health": max_health,
|
||||
"health_regen": health_regen,
|
||||
"shield": shield,
|
||||
"max_shield": max_shield,
|
||||
"shield_regen_delay": shield_regen_delay,
|
||||
"shield_regen_time": shield_regen_time,
|
||||
"alive": alive,
|
||||
"buff_damage": buff_damage,
|
||||
"buff_heal": buff_heal,
|
||||
"buff_shield": buff_shield,
|
||||
}
|
||||
|
||||
func clear_cache() -> void:
|
||||
_cache.clear()
|
||||
portal_position = Vector3.ZERO
|
||||
returning_from_dungeon = false
|
||||
dungeon_cleared = false
|
||||
|
||||
func reset_run() -> void:
|
||||
clear_cache()
|
||||
level = 1
|
||||
xp = 0
|
||||
xp_to_next = XP_PER_LEVEL
|
||||
level_scale = 1.0
|
||||
|
||||
func add_xp(amount: int) -> void:
|
||||
xp += amount
|
||||
EventBus.xp_gained.emit(self, amount)
|
||||
while xp >= xp_to_next:
|
||||
xp -= xp_to_next
|
||||
level_up()
|
||||
|
||||
func level_up() -> void:
|
||||
level += 1
|
||||
level_scale = float(_fibonacci(level))
|
||||
xp_to_next = XP_PER_LEVEL * _fibonacci(level)
|
||||
if base:
|
||||
max_health = base.max_health * level_scale
|
||||
max_shield = base.max_shield * level_scale
|
||||
else:
|
||||
max_health = 100.0 * level_scale
|
||||
max_shield = 50.0 * level_scale
|
||||
health = max_health
|
||||
shield = max_shield
|
||||
EventBus.health_changed.emit(self, health, max_health)
|
||||
EventBus.shield_changed.emit(self, shield, max_shield)
|
||||
EventBus.level_up.emit(self, level)
|
||||
|
||||
func _fibonacci(n: int) -> int:
|
||||
if n <= 1:
|
||||
return 1
|
||||
if n == 2:
|
||||
return 2
|
||||
var a := 1
|
||||
var b := 2
|
||||
for i in range(3, n + 1):
|
||||
var c := a + b
|
||||
a = b
|
||||
b = c
|
||||
return b
|
||||
|
||||
func _restore_cache() -> void:
|
||||
health = _cache.get("health", max_health)
|
||||
max_health = _cache.get("max_health", max_health)
|
||||
health_regen = _cache.get("health_regen", 0.0)
|
||||
shield = _cache.get("shield", 0.0)
|
||||
max_shield = _cache.get("max_shield", 0.0)
|
||||
shield_regen_delay = _cache.get("shield_regen_delay", 3.0)
|
||||
shield_regen_time = _cache.get("shield_regen_time", 5.0)
|
||||
alive = _cache.get("alive", true)
|
||||
buff_damage = _cache.get("buff_damage", 1.0)
|
||||
buff_heal = _cache.get("buff_heal", 1.0)
|
||||
buff_shield = _cache.get("buff_shield", 1.0)
|
||||
_cache.clear()
|
||||
@@ -1 +0,0 @@
|
||||
uid://blmuqkl3aro5w
|
||||
@@ -1,45 +0,0 @@
|
||||
extends Node
|
||||
|
||||
var entities: Dictionary = {}
|
||||
|
||||
func register(entity: Node, base: PortalStats) -> void:
|
||||
var thresholds: Array[float] = base.thresholds.duplicate()
|
||||
var triggered: Array[bool] = []
|
||||
triggered.resize(thresholds.size())
|
||||
triggered.fill(false)
|
||||
entities[entity] = {
|
||||
"base": base,
|
||||
"health": base.max_health,
|
||||
"max_health": base.max_health,
|
||||
"alive": true,
|
||||
"spawn_count": base.spawn_count,
|
||||
"thresholds": thresholds,
|
||||
"triggered": triggered,
|
||||
}
|
||||
|
||||
func deregister(entity: Node) -> void:
|
||||
entities.erase(entity)
|
||||
|
||||
func get_stat(entity: Node, key: String) -> Variant:
|
||||
if entity in entities:
|
||||
return entities[entity].get(key)
|
||||
return null
|
||||
|
||||
func set_stat(entity: Node, key: String, value: Variant) -> void:
|
||||
if entity in entities:
|
||||
entities[entity][key] = value
|
||||
|
||||
func is_alive(entity: Node) -> bool:
|
||||
if entity in entities:
|
||||
return entities[entity]["alive"]
|
||||
return false
|
||||
|
||||
func set_health(entity: Node, value: float) -> void:
|
||||
if entity not in entities:
|
||||
return
|
||||
entities[entity]["health"] = value
|
||||
var max_health: float = entities[entity]["max_health"]
|
||||
EventBus.health_changed.emit(entity, value, max_health)
|
||||
if value <= 0 and entities[entity]["alive"]:
|
||||
entities[entity]["alive"] = false
|
||||
EventBus.entity_died.emit(entity)
|
||||
@@ -1 +0,0 @@
|
||||
uid://doullpjapcsk1
|
||||
46
autoloads/save_load.gd
Normal file
46
autoloads/save_load.gd
Normal file
@@ -0,0 +1,46 @@
|
||||
extends Node
|
||||
|
||||
const SAVE_DIR: String = "user://saves/"
|
||||
|
||||
func ensure_dir() -> void:
|
||||
DirAccess.make_dir_recursive_absolute(SAVE_DIR)
|
||||
|
||||
func save_path(slot: String) -> String:
|
||||
return SAVE_DIR + slot + ".save"
|
||||
|
||||
func save_run(slot: String, payload: Dictionary) -> bool:
|
||||
ensure_dir()
|
||||
var f := FileAccess.open(save_path(slot), FileAccess.WRITE)
|
||||
if f == null:
|
||||
push_error("SaveLoad: cannot open %s for write" % slot)
|
||||
return false
|
||||
f.store_string(JSON.stringify(payload, " "))
|
||||
f.close()
|
||||
return true
|
||||
|
||||
func load_run(slot: String) -> Dictionary:
|
||||
var path := save_path(slot)
|
||||
if not FileAccess.file_exists(path):
|
||||
return {}
|
||||
var f := FileAccess.open(path, FileAccess.READ)
|
||||
var text := f.get_as_text()
|
||||
f.close()
|
||||
var data: Variant = JSON.parse_string(text)
|
||||
if typeof(data) != TYPE_DICTIONARY:
|
||||
return {}
|
||||
return data
|
||||
|
||||
func list_slots() -> Array[String]:
|
||||
ensure_dir()
|
||||
var out: Array[String] = []
|
||||
var d := DirAccess.open(SAVE_DIR)
|
||||
if d == null:
|
||||
return out
|
||||
d.list_dir_begin()
|
||||
var fname := d.get_next()
|
||||
while fname != "":
|
||||
if not d.current_is_dir() and fname.ends_with(".save"):
|
||||
out.append(fname.trim_suffix(".save"))
|
||||
fname = d.get_next()
|
||||
d.list_dir_end()
|
||||
return out
|
||||
1
autoloads/save_load.gd.uid
Normal file
1
autoloads/save_load.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bstx6urqlutmq
|
||||
124
autoloads/stats.gd
Normal file
124
autoloads/stats.gd
Normal file
@@ -0,0 +1,124 @@
|
||||
extends Node
|
||||
|
||||
const SYNCED_STATS: Array = [
|
||||
"health", "shield", "max_health", "max_shield",
|
||||
"role", "level", "xp", "xp_to_next",
|
||||
"buff_damage", "buff_heal", "buff_shield",
|
||||
]
|
||||
const SYNC_INTERVAL: float = 0.10
|
||||
|
||||
var _entities: Dictionary = {}
|
||||
var _player_cache: Dictionary = {}
|
||||
var _dirty: Dictionary = {}
|
||||
var _accum: float = 0.0
|
||||
|
||||
func _ready() -> void:
|
||||
set_process(true)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
_accum += delta
|
||||
if _accum < SYNC_INTERVAL:
|
||||
return
|
||||
_accum = 0.0
|
||||
if multiplayer.multiplayer_peer == null or multiplayer.multiplayer_peer is OfflineMultiplayerPeer or not multiplayer.is_server():
|
||||
_dirty.clear()
|
||||
return
|
||||
if _dirty.is_empty():
|
||||
return
|
||||
for entity in _dirty.keys():
|
||||
if not is_instance_valid(entity) or not entity.is_inside_tree():
|
||||
continue
|
||||
_sync_stats_batch.rpc(String(entity.get_path()), _dirty[entity])
|
||||
_dirty.clear()
|
||||
|
||||
@rpc("authority", "unreliable_ordered")
|
||||
func _sync_stats_batch(path_str: String, changes: Dictionary) -> void:
|
||||
var entity := get_node_or_null(NodePath(path_str))
|
||||
if entity == null or not entity in _entities:
|
||||
return
|
||||
for stat in changes.keys():
|
||||
_entities[entity][stat] = changes[stat]
|
||||
match stat:
|
||||
"health":
|
||||
EventBus.health_changed.emit(entity, changes[stat], _entities[entity].get("max_health", 100.0))
|
||||
"shield":
|
||||
EventBus.shield_changed.emit(entity, changes[stat], _entities[entity].get("max_shield", 0.0))
|
||||
"role":
|
||||
EventBus.role_changed.emit(entity, changes[stat])
|
||||
"level":
|
||||
EventBus.level_up.emit(entity, changes[stat])
|
||||
"buff_damage", "buff_heal", "buff_shield":
|
||||
EventBus.buff_changed.emit(entity, StringName(stat), changes[stat])
|
||||
|
||||
func register(entity: Node, base_resource: Resource) -> void:
|
||||
if not is_instance_valid(entity):
|
||||
return
|
||||
var data: Dictionary = {}
|
||||
for prop in base_resource.get_property_list():
|
||||
var name: String = prop.name
|
||||
if not (prop.usage & PROPERTY_USAGE_STORAGE):
|
||||
continue
|
||||
if name in ["resource_local_to_scene", "resource_path", "resource_name", "resource_scene_unique_id", "script"]:
|
||||
continue
|
||||
data[name] = base_resource.get(name)
|
||||
data["health"] = data.get("max_health", 100.0)
|
||||
data["shield"] = data.get("max_shield", 0.0)
|
||||
data["shield_regen_timer"] = 0.0
|
||||
data["base_resource"] = base_resource
|
||||
_entities[entity] = data
|
||||
EventBus.entity_registered.emit(entity)
|
||||
|
||||
func deregister(entity: Node) -> void:
|
||||
if entity in _entities:
|
||||
_entities.erase(entity)
|
||||
EventBus.entity_deregistered.emit(entity)
|
||||
|
||||
func has(entity: Node) -> bool:
|
||||
return entity in _entities
|
||||
|
||||
func get_stat(entity: Node, stat: String, default: Variant = 0.0) -> Variant:
|
||||
if entity in _entities:
|
||||
return _entities[entity].get(stat, default)
|
||||
return default
|
||||
|
||||
func set_stat(entity: Node, stat: String, value: Variant) -> void:
|
||||
if not entity in _entities:
|
||||
return
|
||||
var prev: Variant = _entities[entity].get(stat)
|
||||
_entities[entity][stat] = value
|
||||
if stat in SYNCED_STATS and prev != value and multiplayer.multiplayer_peer != null and not (multiplayer.multiplayer_peer is OfflineMultiplayerPeer) and multiplayer.is_server() and is_instance_valid(entity) and entity.is_inside_tree():
|
||||
if not entity in _dirty:
|
||||
_dirty[entity] = {}
|
||||
_dirty[entity][stat] = value
|
||||
|
||||
|
||||
func get_all(entity: Node) -> Dictionary:
|
||||
return _entities.get(entity, {})
|
||||
|
||||
func entities() -> Array:
|
||||
return _entities.keys()
|
||||
|
||||
func entities_in_group(group: StringName) -> Array:
|
||||
var out: Array = []
|
||||
for e in _entities.keys():
|
||||
if is_instance_valid(e) and e.is_in_group(group):
|
||||
out.append(e)
|
||||
return out
|
||||
|
||||
func cache_player(peer_id: int, entity: Node) -> void:
|
||||
if entity in _entities:
|
||||
_player_cache[peer_id] = _entities[entity].duplicate(true)
|
||||
|
||||
func restore_player(peer_id: int, entity: Node) -> void:
|
||||
if peer_id in _player_cache:
|
||||
_entities[entity] = _player_cache[peer_id].duplicate(true)
|
||||
|
||||
func clear_player_cache(peer_id: int = -1) -> void:
|
||||
if peer_id == -1:
|
||||
_player_cache.clear()
|
||||
else:
|
||||
_player_cache.erase(peer_id)
|
||||
|
||||
func clear_all() -> void:
|
||||
_entities.clear()
|
||||
_player_cache.clear()
|
||||
1
autoloads/stats.gd.uid
Normal file
1
autoloads/stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cdrii2l4sefow
|
||||
@@ -1,8 +0,0 @@
|
||||
extends Resource
|
||||
class_name BaseStats
|
||||
|
||||
@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 +0,0 @@
|
||||
uid://cet184f878lb8
|
||||
@@ -1,38 +0,0 @@
|
||||
extends Node
|
||||
|
||||
var entities: Dictionary = {}
|
||||
|
||||
func register(entity: Node, base: Resource) -> void:
|
||||
entities[entity] = {
|
||||
"base": base,
|
||||
"health": base.max_health,
|
||||
"max_health": base.max_health,
|
||||
"alive": true,
|
||||
}
|
||||
|
||||
func deregister(entity: Node) -> void:
|
||||
entities.erase(entity)
|
||||
|
||||
func get_stat(entity: Node, key: String) -> Variant:
|
||||
if entity in entities:
|
||||
return entities[entity].get(key)
|
||||
return null
|
||||
|
||||
func set_stat(entity: Node, key: String, value: Variant) -> void:
|
||||
if entity in entities:
|
||||
entities[entity][key] = value
|
||||
|
||||
func is_alive(entity: Node) -> bool:
|
||||
if entity in entities:
|
||||
return entities[entity]["alive"]
|
||||
return false
|
||||
|
||||
func set_health(entity: Node, value: float) -> void:
|
||||
if entity not in entities:
|
||||
return
|
||||
entities[entity]["health"] = value
|
||||
var max_health: float = entities[entity]["max_health"]
|
||||
EventBus.tavern_damaged.emit(value, max_health)
|
||||
if value <= 0 and entities[entity]["alive"]:
|
||||
entities[entity]["alive"] = false
|
||||
EventBus.tavern_destroyed.emit()
|
||||
@@ -1 +0,0 @@
|
||||
uid://822h8c1pur1a
|
||||
Reference in New Issue
Block a user