refactor
This commit is contained in:
@@ -1,266 +1,133 @@
|
||||
[gd_scene format=3]
|
||||
[gd_scene load_steps=23 format=3 uid="uid://b0dungeon0001"]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://scenes/player/player.tscn" id="player"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/hud/hud.tscn" id="hud"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/enemy/enemy.tscn" id="enemy"]
|
||||
[ext_resource type="Resource" path="res://scenes/enemy/boss_stats.tres" id="boss_stats"]
|
||||
[ext_resource type="Script" path="res://systems/dungeon_system.gd" id="dungeon_system"]
|
||||
[ext_resource type="Script" path="res://scenes/dungeon/dungeon_manager.gd" id="dungeon_manager"]
|
||||
[ext_resource type="Script" path="res://systems/audio_system.gd" id="audio_system"]
|
||||
[ext_resource type="Script" path="res://systems/xp_system.gd" id="xp_system"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/portal/gate.tscn" id="gate"]
|
||||
[ext_resource type="Script" path="res://systems/damage_system.gd" id="damage_system"]
|
||||
[ext_resource type="Script" path="res://systems/health_system.gd" id="health_system"]
|
||||
[ext_resource type="Script" path="res://systems/heal_system.gd" id="heal_system"]
|
||||
[ext_resource type="Script" path="res://systems/shield_system.gd" id="shield_system"]
|
||||
[ext_resource type="Script" path="res://systems/role_system.gd" id="role_system"]
|
||||
[ext_resource type="Script" path="res://systems/ability_system.gd" id="ability_system"]
|
||||
[ext_resource type="Script" path="res://systems/attack_system.gd" id="attack_system"]
|
||||
[ext_resource type="Script" path="res://systems/cooldown_system.gd" id="cooldown_system"]
|
||||
[ext_resource type="Script" path="res://systems/targeting_system.gd" id="targeting_system"]
|
||||
[ext_resource type="Script" path="res://systems/aggro/aggro_system.gd" id="aggro_system"]
|
||||
[ext_resource type="Script" path="res://systems/aggro/aggro_tracker.gd" id="aggro_tracker"]
|
||||
[ext_resource type="Script" path="res://systems/aggro/aggro_decay.gd" id="aggro_decay"]
|
||||
[ext_resource type="Script" path="res://systems/aggro/aggro_events.gd" id="aggro_events"]
|
||||
[ext_resource type="Script" path="res://systems/ai_system.gd" id="ai_system"]
|
||||
[ext_resource type="Script" path="res://systems/respawn_system.gd" id="respawn_system"]
|
||||
[ext_resource type="Script" path="res://systems/spawn_system.gd" id="spawn_system"]
|
||||
[ext_resource type="Script" path="res://systems/aura_system.gd" id="aura_system"]
|
||||
[ext_resource type="Script" path="res://systems/buff_system.gd" id="buff_system"]
|
||||
[ext_resource type="Script" path="res://systems/debuff_system.gd" id="debuff_system"]
|
||||
[ext_resource type="Script" path="res://systems/element_system.gd" id="element_system"]
|
||||
[ext_resource type="Script" path="res://systems/hud_system.gd" id="hud_system"]
|
||||
[ext_resource type="Script" path="res://systems/nameplate_system.gd" id="nameplate_system"]
|
||||
[ext_resource type="Resource" uid="uid://cgxtn7dfs40bh" path="res://scenes/player/role/tank/set.tres" id="tank_set"]
|
||||
[ext_resource type="Resource" uid="uid://beodknb6i1pm4" path="res://scenes/player/role/damage/set.tres" id="damage_set"]
|
||||
[ext_resource type="Resource" uid="uid://kcwuhnqy34mj" path="res://scenes/player/role/healer/set.tres" id="healer_set"]
|
||||
[ext_resource type="Script" path="res://scenes/dungeon/dungeon_manager.gd" id="1"]
|
||||
[ext_resource type="PackedScene" uid="uid://b0player00001" path="res://scenes/entities/player/player.tscn" id="2"]
|
||||
[ext_resource type="Script" path="res://systems/health_system.gd" id="4"]
|
||||
[ext_resource type="Script" path="res://systems/shield_system.gd" id="5"]
|
||||
[ext_resource type="Script" path="res://systems/respawn_system.gd" id="6"]
|
||||
[ext_resource type="Script" path="res://systems/cooldown_system.gd" id="7"]
|
||||
[ext_resource type="Script" path="res://systems/role_system.gd" id="8"]
|
||||
[ext_resource type="Script" path="res://systems/effect_system.gd" id="9"]
|
||||
[ext_resource type="Script" path="res://systems/element_system.gd" id="10"]
|
||||
[ext_resource type="Script" path="res://systems/aggro_system.gd" id="11"]
|
||||
[ext_resource type="Script" path="res://systems/combat/ability_system.gd" id="12"]
|
||||
[ext_resource type="Script" path="res://systems/combat/auto_attack_system.gd" id="13"]
|
||||
[ext_resource type="Script" path="res://systems/spawn_system.gd" id="14"]
|
||||
[ext_resource type="Script" path="res://systems/xp_system.gd" id="17"]
|
||||
[ext_resource type="Script" path="res://systems/loot_system.gd" id="18"]
|
||||
[ext_resource type="Script" path="res://systems/inventory_system.gd" id="19"]
|
||||
[ext_resource type="Script" path="res://systems/chat_system.gd" id="24"]
|
||||
[ext_resource type="Script" path="res://systems/map_system.gd" id="25"]
|
||||
[ext_resource type="PackedScene" uid="uid://b0hud00001" path="res://scenes/hud/hud.tscn" id="26"]
|
||||
[ext_resource type="Script" path="res://systems/audio_system.gd" id="27"]
|
||||
|
||||
[sub_resource type="NavigationMesh" id="NavigationMesh_1"]
|
||||
vertices = PackedVector3Array(-7.0, 0.5, -7.0, -7.0, 0.5, 87.0, 7.0, 0.5, 87.0, 7.0, 0.5, -7.0)
|
||||
polygons = [PackedInt32Array(3, 2, 0), PackedInt32Array(0, 2, 1)]
|
||||
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_1"]
|
||||
sky_top_color = Color(0.1, 0.05, 0.1, 1)
|
||||
sky_horizon_color = Color(0.15, 0.05, 0.05, 1)
|
||||
ground_horizon_color = Color(0.1, 0.05, 0.05, 1)
|
||||
ground_bottom_color = Color(0.0, 0.0, 0.0, 1)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_floor"]
|
||||
albedo_color = Color(0.2, 0.18, 0.15, 1)
|
||||
[sub_resource type="Sky" id="Sky_1"]
|
||||
sky_material = SubResource("ProceduralSkyMaterial_1")
|
||||
|
||||
[sub_resource type="PlaneMesh" id="PlaneMesh_1"]
|
||||
material = SubResource("StandardMaterial3D_floor")
|
||||
size = Vector2(15, 90)
|
||||
|
||||
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_1"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_wall"]
|
||||
albedo_color = Color(0.25, 0.22, 0.2, 1)
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_north_south"]
|
||||
material = SubResource("StandardMaterial3D_wall")
|
||||
size = Vector3(15, 3, 0.5)
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_north_south"]
|
||||
size = Vector3(15, 3, 0.5)
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_east_west"]
|
||||
material = SubResource("StandardMaterial3D_wall")
|
||||
size = Vector3(0.5, 3, 90)
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_east_west"]
|
||||
size = Vector3(0.5, 3, 90)
|
||||
[sub_resource type="Environment" id="Environment_1"]
|
||||
background_mode = 2
|
||||
sky = SubResource("Sky_1")
|
||||
ambient_light_source = 3
|
||||
ambient_light_color = Color(0.2, 0.18, 0.2, 1)
|
||||
ambient_light_energy = 0.4
|
||||
|
||||
[node name="Dungeon" type="Node3D"]
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
||||
environment = SubResource("Environment_1")
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
||||
transform = Transform3D(0.866, -0.354, 0.354, 0, 0.707, 0.707, -0.5, -0.612, 0.612, 0, 30, 0)
|
||||
light_energy = 0.7
|
||||
|
||||
[node name="DungeonGeometry" type="Node3D" parent="."]
|
||||
|
||||
[node name="Systems" type="Node" parent="."]
|
||||
|
||||
[node name="HealthSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("health_system")
|
||||
|
||||
[node name="DamageSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("damage_system")
|
||||
|
||||
[node name="HealSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("heal_system")
|
||||
script = ExtResource("4")
|
||||
|
||||
[node name="ShieldSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("shield_system")
|
||||
|
||||
[node name="RoleSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("role_system")
|
||||
tank_set = ExtResource("tank_set")
|
||||
damage_set = ExtResource("damage_set")
|
||||
healer_set = ExtResource("healer_set")
|
||||
|
||||
[node name="AbilitySystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("ability_system")
|
||||
|
||||
[node name="AttackSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("attack_system")
|
||||
|
||||
[node name="CooldownSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("cooldown_system")
|
||||
|
||||
[node name="TargetingSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("targeting_system")
|
||||
|
||||
[node name="AggroSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("aggro_system")
|
||||
|
||||
[node name="AggroTracker" type="Node" parent="Systems/AggroSystem"]
|
||||
script = ExtResource("aggro_tracker")
|
||||
|
||||
[node name="AggroDecay" type="Node" parent="Systems/AggroSystem"]
|
||||
script = ExtResource("aggro_decay")
|
||||
|
||||
[node name="AggroEvents" type="Node" parent="Systems/AggroSystem"]
|
||||
script = ExtResource("aggro_events")
|
||||
|
||||
[node name="AISystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("ai_system")
|
||||
script = ExtResource("5")
|
||||
|
||||
[node name="RespawnSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("respawn_system")
|
||||
script = ExtResource("6")
|
||||
|
||||
[node name="SpawnSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("spawn_system")
|
||||
[node name="CooldownSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("7")
|
||||
|
||||
[node name="AuraSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("aura_system")
|
||||
[node name="RoleSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("8")
|
||||
|
||||
[node name="BuffSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("buff_system")
|
||||
|
||||
[node name="DebuffSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("debuff_system")
|
||||
[node name="EffectSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("9")
|
||||
|
||||
[node name="ElementSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("element_system")
|
||||
script = ExtResource("10")
|
||||
|
||||
[node name="HudSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("hud_system")
|
||||
[node name="AggroSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("11")
|
||||
|
||||
[node name="NameplateSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("nameplate_system")
|
||||
[node name="AbilitySystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("12")
|
||||
|
||||
[node name="NavigationRegion3D" type="NavigationRegion3D" parent="."]
|
||||
navigation_mesh = SubResource("NavigationMesh_1")
|
||||
[node name="AutoAttackSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("13")
|
||||
|
||||
[node name="Boden" type="MeshInstance3D" parent="NavigationRegion3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 40)
|
||||
mesh = SubResource("PlaneMesh_1")
|
||||
[node name="SpawnSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("14")
|
||||
|
||||
[node name="BodenCollision" type="StaticBody3D" parent="."]
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="BodenCollision"]
|
||||
shape = SubResource("WorldBoundaryShape3D_1")
|
||||
|
||||
[node name="WallSouth" type="StaticBody3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, -5.25)
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="WallSouth"]
|
||||
mesh = SubResource("BoxMesh_north_south")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="WallSouth"]
|
||||
shape = SubResource("BoxShape3D_north_south")
|
||||
|
||||
[node name="WallNorth" type="StaticBody3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 85.25)
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="WallNorth"]
|
||||
mesh = SubResource("BoxMesh_north_south")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="WallNorth"]
|
||||
shape = SubResource("BoxShape3D_north_south")
|
||||
|
||||
[node name="WallEast" type="StaticBody3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.75, 1.5, 40)
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="WallEast"]
|
||||
mesh = SubResource("BoxMesh_east_west")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="WallEast"]
|
||||
shape = SubResource("BoxShape3D_east_west")
|
||||
|
||||
[node name="WallWest" type="StaticBody3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.75, 1.5, 40)
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="WallWest"]
|
||||
mesh = SubResource("BoxMesh_east_west")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="WallWest"]
|
||||
shape = SubResource("BoxShape3D_east_west")
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.707, 0.707, 0, -0.707, 0.707, 0, 10, 40)
|
||||
light_energy = 0.6
|
||||
shadow_enabled = true
|
||||
|
||||
[node name="Player" parent="." instance=ExtResource("player")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, -3)
|
||||
|
||||
[node name="HUD" parent="." instance=ExtResource("hud")]
|
||||
|
||||
[node name="Enemy1a" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 0, 15)
|
||||
|
||||
[node name="Enemy1b" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 15)
|
||||
|
||||
[node name="Enemy1c" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 15)
|
||||
|
||||
[node name="Enemy1d" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3, 0, 15)
|
||||
|
||||
[node name="Enemy2a" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 0, 30)
|
||||
|
||||
[node name="Enemy2b" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 30)
|
||||
|
||||
[node name="Enemy2c" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 30)
|
||||
|
||||
[node name="Enemy2d" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3, 0, 30)
|
||||
|
||||
[node name="Enemy3a" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 0, 45)
|
||||
|
||||
[node name="Enemy3b" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 45)
|
||||
|
||||
[node name="Enemy3c" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 45)
|
||||
|
||||
[node name="Enemy3d" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3, 0, 45)
|
||||
|
||||
[node name="Enemy4a" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 0, 60)
|
||||
|
||||
[node name="Enemy4b" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 60)
|
||||
|
||||
[node name="Enemy4c" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 60)
|
||||
|
||||
[node name="Enemy4d" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3, 0, 60)
|
||||
|
||||
[node name="Boss" parent="." groups=["boss"] instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 75)
|
||||
stats = ExtResource("boss_stats")
|
||||
|
||||
[node name="ExitGate" parent="." instance=ExtResource("gate")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0, -4)
|
||||
target_scene = "res://scenes/world/world.tscn"
|
||||
is_exit = true
|
||||
|
||||
[node name="DungeonSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("dungeon_system")
|
||||
|
||||
[node name="AudioSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("audio_system")
|
||||
[node name="LootSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("18")
|
||||
|
||||
[node name="XpSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("xp_system")
|
||||
script = ExtResource("17")
|
||||
|
||||
[node name="DungeonManager" type="Node" parent="."]
|
||||
script = ExtResource("dungeon_manager")
|
||||
[node name="InventorySystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("19")
|
||||
|
||||
[node name="ChatSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("24")
|
||||
|
||||
[node name="MapSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("25")
|
||||
|
||||
[node name="AudioSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("27")
|
||||
|
||||
[node name="EntityRoot" type="Node3D" parent="."]
|
||||
|
||||
[node name="Players" type="Node3D" parent="EntityRoot"]
|
||||
|
||||
[node name="Enemies" type="Node3D" parent="EntityRoot"]
|
||||
|
||||
[node name="Loot" type="Node3D" parent="EntityRoot"]
|
||||
|
||||
[node name="Portals" type="Node3D" parent="EntityRoot"]
|
||||
|
||||
[node name="Buildings" type="Node3D" parent="EntityRoot"]
|
||||
|
||||
[node name="Gates" type="Node3D" parent="EntityRoot"]
|
||||
|
||||
[node name="Npcs" type="Node3D" parent="EntityRoot"]
|
||||
|
||||
[node name="PlayerSpawner" type="MultiplayerSpawner" parent="."]
|
||||
_spawnable_scenes = PackedStringArray("res://scenes/entities/player/player.tscn")
|
||||
spawn_path = NodePath("../EntityRoot/Players")
|
||||
|
||||
[node name="EnemySpawner" type="MultiplayerSpawner" parent="."]
|
||||
_spawnable_scenes = PackedStringArray("res://scenes/entities/enemy/enemy.tscn")
|
||||
spawn_path = NodePath("../EntityRoot/Enemies")
|
||||
|
||||
[node name="LootSpawner" type="MultiplayerSpawner" parent="."]
|
||||
_spawnable_scenes = PackedStringArray("res://scenes/entities/loot/loot_drop.tscn")
|
||||
spawn_path = NodePath("../EntityRoot/Loot")
|
||||
|
||||
[node name="HUD" parent="." instance=ExtResource("26")]
|
||||
|
||||
93
scenes/dungeon/dungeon_generator.gd
Normal file
93
scenes/dungeon/dungeon_generator.gd
Normal file
@@ -0,0 +1,93 @@
|
||||
extends Node
|
||||
|
||||
const ROOM_HEIGHT: float = 4.0
|
||||
const WALL_THICKNESS: float = 0.4
|
||||
const CORRIDOR_WIDTH: float = 4.0
|
||||
|
||||
var rng: RandomNumberGenerator
|
||||
var rooms: Array = []
|
||||
|
||||
func generate(parent: Node3D, seed: int, scale_difficulty: float = 1.0) -> Dictionary:
|
||||
rng = RandomNumberGenerator.new()
|
||||
rng.seed = seed
|
||||
rooms.clear()
|
||||
var room_count: int = rng.randi_range(5, 8)
|
||||
var pos := Vector3(0, 0, 0)
|
||||
var dir := Vector3(0, 0, -1)
|
||||
for i in range(room_count):
|
||||
var w: float = rng.randf_range(8.0, 14.0)
|
||||
var d: float = rng.randf_range(8.0, 14.0)
|
||||
rooms.append({"pos": pos, "size": Vector3(w, ROOM_HEIGHT, d)})
|
||||
if i == room_count - 1:
|
||||
break
|
||||
var corridor_len: float = rng.randf_range(4.0, 8.0)
|
||||
var step: Vector3 = dir * (max(w, d) * 0.5 + corridor_len + 4.0)
|
||||
pos += step
|
||||
if rng.randf() < 0.5:
|
||||
var rotate_left: bool = rng.randf() < 0.5
|
||||
dir = dir.rotated(Vector3.UP, PI * 0.5 * (1 if rotate_left else -1))
|
||||
_build_geometry(parent)
|
||||
return {"rooms": rooms, "spawn": rooms[0].pos + Vector3(0, 1, 0), "boss": rooms[-1].pos}
|
||||
|
||||
func _build_geometry(parent: Node3D) -> void:
|
||||
for r in rooms:
|
||||
_build_room(parent, r.pos, r.size)
|
||||
for i in range(rooms.size() - 1):
|
||||
_build_corridor(parent, rooms[i].pos, rooms[i + 1].pos)
|
||||
|
||||
func _build_room(parent: Node3D, center: Vector3, size: Vector3) -> void:
|
||||
_add_floor(parent, center, Vector2(size.x, size.z))
|
||||
var hw: float = size.x * 0.5
|
||||
var hd: float = size.z * 0.5
|
||||
_add_wall(parent, center + Vector3(0, ROOM_HEIGHT * 0.5, -hd), Vector3(size.x, ROOM_HEIGHT, WALL_THICKNESS))
|
||||
_add_wall(parent, center + Vector3(0, ROOM_HEIGHT * 0.5, hd), Vector3(size.x, ROOM_HEIGHT, WALL_THICKNESS))
|
||||
_add_wall(parent, center + Vector3(-hw, ROOM_HEIGHT * 0.5, 0), Vector3(WALL_THICKNESS, ROOM_HEIGHT, size.z))
|
||||
_add_wall(parent, center + Vector3(hw, ROOM_HEIGHT * 0.5, 0), Vector3(WALL_THICKNESS, ROOM_HEIGHT, size.z))
|
||||
|
||||
func _build_corridor(parent: Node3D, from: Vector3, to: Vector3) -> void:
|
||||
var mid: Vector3 = (from + to) * 0.5
|
||||
var d: float = from.distance_to(to)
|
||||
var dir: Vector3 = (to - from).normalized()
|
||||
_add_floor(parent, Vector3(mid.x, 0, mid.z), Vector2(d, CORRIDOR_WIDTH) if abs(dir.x) > abs(dir.z) else Vector2(CORRIDOR_WIDTH, d))
|
||||
|
||||
func _add_floor(parent: Node3D, center: Vector3, size: Vector2) -> void:
|
||||
var body := StaticBody3D.new()
|
||||
body.collision_layer = 1
|
||||
body.collision_mask = 0
|
||||
var mesh := MeshInstance3D.new()
|
||||
var box := BoxMesh.new()
|
||||
box.size = Vector3(size.x, 0.4, size.y)
|
||||
mesh.mesh = box
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.albedo_color = Color(0.25, 0.22, 0.2)
|
||||
mesh.material_override = mat
|
||||
mesh.position = Vector3(0, -0.2, 0)
|
||||
var col := CollisionShape3D.new()
|
||||
var shape := BoxShape3D.new()
|
||||
shape.size = Vector3(size.x, 0.4, size.y)
|
||||
col.shape = shape
|
||||
col.position = Vector3(0, -0.2, 0)
|
||||
body.add_child(mesh)
|
||||
body.add_child(col)
|
||||
body.position = center
|
||||
parent.add_child(body)
|
||||
|
||||
func _add_wall(parent: Node3D, center: Vector3, size: Vector3) -> void:
|
||||
var body := StaticBody3D.new()
|
||||
body.collision_layer = 1
|
||||
body.collision_mask = 0
|
||||
var mesh := MeshInstance3D.new()
|
||||
var box := BoxMesh.new()
|
||||
box.size = size
|
||||
mesh.mesh = box
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.albedo_color = Color(0.35, 0.32, 0.28)
|
||||
mesh.material_override = mat
|
||||
var col := CollisionShape3D.new()
|
||||
var shape := BoxShape3D.new()
|
||||
shape.size = size
|
||||
col.shape = shape
|
||||
body.add_child(mesh)
|
||||
body.add_child(col)
|
||||
body.position = center
|
||||
parent.add_child(body)
|
||||
1
scenes/dungeon/dungeon_generator.gd.uid
Normal file
1
scenes/dungeon/dungeon_generator.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d1u4odursm3m4
|
||||
@@ -1,16 +1,82 @@
|
||||
extends Node
|
||||
extends Node3D
|
||||
|
||||
const PLAYER_SCENE: PackedScene = preload("res://scenes/entities/player/player.tscn")
|
||||
const ENEMY_SCENE: PackedScene = preload("res://scenes/entities/enemy/enemy.tscn")
|
||||
const GENERATOR: GDScript = preload("res://scenes/dungeon/dungeon_generator.gd")
|
||||
|
||||
@onready var players_root: Node3D = $EntityRoot/Players
|
||||
@onready var dungeon_root: Node3D = $DungeonGeometry
|
||||
@onready var spawn_system: Node = $Systems/SpawnSystem
|
||||
|
||||
var generator: Node
|
||||
var data: Dictionary
|
||||
|
||||
func _ready() -> void:
|
||||
call_deferred("_scale_dungeon")
|
||||
add_to_group("dungeon")
|
||||
generator = GENERATOR.new()
|
||||
add_child(generator)
|
||||
data = generator.generate(dungeon_root, GameState.dungeon_seed, GameState.current_wave * (5.0 if GameState.dungeon_red else 1.0))
|
||||
EventBus.boss_defeated.connect(_on_boss_defeated)
|
||||
Net.world_ready.connect(_on_world_ready)
|
||||
Net.peer_world_loaded.connect(_on_peer_world_loaded)
|
||||
if Net.is_host():
|
||||
multiplayer.peer_connected.connect(_on_peer_connected)
|
||||
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
|
||||
Net.reset_world_ready()
|
||||
Net.mark_world_loaded()
|
||||
|
||||
func _scale_dungeon() -> void:
|
||||
var variant_multiplier: float = 10.0 if GameState.last_dungeon_variant == 1 else 1.0
|
||||
var total_scale: float = PlayerData.level_scale * variant_multiplier
|
||||
var parent: Node = get_parent()
|
||||
for child in parent.get_children():
|
||||
if not child.is_in_group("enemies"):
|
||||
continue
|
||||
if child.is_in_group("boss"):
|
||||
BossData.apply_scale(child, total_scale)
|
||||
else:
|
||||
EnemyData.apply_scale(child, total_scale)
|
||||
func _exit_tree() -> void:
|
||||
if Net.world_ready.is_connected(_on_world_ready):
|
||||
Net.world_ready.disconnect(_on_world_ready)
|
||||
if Net.peer_world_loaded.is_connected(_on_peer_world_loaded):
|
||||
Net.peer_world_loaded.disconnect(_on_peer_world_loaded)
|
||||
|
||||
func _on_world_ready() -> void:
|
||||
if not Net.is_host():
|
||||
return
|
||||
_spawn_player(1)
|
||||
for id in multiplayer.get_peers():
|
||||
_spawn_player(id)
|
||||
_populate_dungeon()
|
||||
|
||||
func _populate_dungeon() -> void:
|
||||
var difficulty: float = GameState.current_wave * (3.0 if GameState.dungeon_red else 1.0)
|
||||
for i in range(data.rooms.size() - 1):
|
||||
var room: Dictionary = data.rooms[i]
|
||||
var n: int = 2 + (1 if GameState.dungeon_red else 0)
|
||||
for j in range(n):
|
||||
var off := Vector3(randf_range(-room.size.x * 0.3, room.size.x * 0.3), 0.5, randf_range(-room.size.z * 0.3, room.size.z * 0.3))
|
||||
spawn_system.spawn_enemy_at(room.pos + off, GameState.dungeon_red, difficulty * 0.5)
|
||||
spawn_system.spawn_boss_at(data.boss + Vector3(0, 0.5, 0), difficulty)
|
||||
|
||||
func _spawn_player(peer_id: int) -> void:
|
||||
if players_root.get_node_or_null(str(peer_id)) != null:
|
||||
return
|
||||
var p: CharacterBody3D = PLAYER_SCENE.instantiate()
|
||||
p.name = str(peer_id)
|
||||
players_root.add_child(p, true)
|
||||
p.global_position = data.spawn + Vector3(randf_range(-1, 1), 0, randf_range(-1, 1))
|
||||
|
||||
func _on_peer_connected(id: int) -> void:
|
||||
Net.tell_peer_to_load_scene(id, GameState.SCENE_DUNGEON)
|
||||
|
||||
func _on_peer_world_loaded(peer_id: int) -> void:
|
||||
if not Net.is_host():
|
||||
return
|
||||
_spawn_player(peer_id)
|
||||
|
||||
func _on_peer_disconnected(id: int) -> void:
|
||||
var node := players_root.get_node_or_null(str(id))
|
||||
if node:
|
||||
node.queue_free()
|
||||
|
||||
func _on_boss_defeated(_b: Node) -> void:
|
||||
if Net.is_host():
|
||||
var t := get_tree().create_timer(2.0)
|
||||
t.timeout.connect(func():
|
||||
GameState.dungeon_seed = 0
|
||||
_return.rpc())
|
||||
|
||||
@rpc("authority", "reliable", "call_local")
|
||||
func _return() -> void:
|
||||
GameState.change_scene(GameState.SCENE_WORLD)
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://bfkxrflfn5qx4
|
||||
uid://civwsilci7nlx
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
extends EnemyStats
|
||||
class_name BossStats
|
||||
@@ -1 +0,0 @@
|
||||
uid://dlawq281oesnf
|
||||
@@ -1,20 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="BossStats" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/enemy/boss_stats.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
max_health = 500.0
|
||||
health_regen = 0.0
|
||||
max_shield = 100.0
|
||||
shield_regen_delay = 5.0
|
||||
shield_regen_time = 8.0
|
||||
speed = 3.0
|
||||
attack_range = 2.0
|
||||
attack_cooldown = 1.5
|
||||
attack_damage = 5.0
|
||||
regen_fast = 0.1
|
||||
regen_slow = 0.01
|
||||
aggro_decay = 1.0
|
||||
portal_radius = 10.0
|
||||
alert_radius = 10.0
|
||||
@@ -1,11 +0,0 @@
|
||||
extends Node
|
||||
|
||||
@onready var entity: CharacterBody3D = get_parent()
|
||||
|
||||
func _on_detection_area_body_entered(body: Node3D) -> void:
|
||||
if body is CharacterBody3D and body.name == "Player":
|
||||
EventBus.enemy_detected.emit(entity, body)
|
||||
|
||||
func _on_detection_area_body_exited(body: Node3D) -> void:
|
||||
if body is CharacterBody3D and body.name == "Player":
|
||||
EventBus.enemy_lost.emit(entity, body)
|
||||
@@ -1 +0,0 @@
|
||||
uid://b07aajhufqvb3
|
||||
@@ -1,96 +0,0 @@
|
||||
[gd_scene format=3 uid="uid://db8pa55ev4l4a"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://vy6hyqok0p8b" path="res://scenes/enemy/init.gd" id="1"]
|
||||
[ext_resource type="Script" uid="uid://b07aajhufqvb3" path="res://scenes/enemy/detection.gd" id="2"]
|
||||
[ext_resource type="Resource" uid="uid://cj1shmjwf0xeo" path="res://scenes/enemy/enemy_stats.tres" id="8"]
|
||||
[ext_resource type="PackedScene" path="res://assets/models/characters/Skeleton_Minion.glb" id="9"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
|
||||
radius = 0.4
|
||||
height = 1.5
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_2"]
|
||||
radius = 0.4
|
||||
height = 1.5
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_1"]
|
||||
radius = 10.0
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_bg"]
|
||||
bg_color = Color(0.3, 0.1, 0.1, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_fill"]
|
||||
bg_color = Color(0.2, 0.8, 0.2, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_bg"]
|
||||
bg_color = Color(0.1, 0.1, 0.3, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_fill"]
|
||||
bg_color = Color(0.2, 0.5, 0.9, 1)
|
||||
|
||||
[node name="Enemy" type="CharacterBody3D" unique_id=1724620529]
|
||||
script = ExtResource("1")
|
||||
stats = ExtResource("8")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1011138038]
|
||||
shape = SubResource("CapsuleShape3D_1")
|
||||
|
||||
[node name="Mesh" type="Node3D" parent="." unique_id=1598094615]
|
||||
|
||||
[node name="Model" parent="Mesh" instance=ExtResource("9")]
|
||||
transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, -0.75, 0)
|
||||
|
||||
[node name="HitArea" type="Area3D" parent="." unique_id=893463784]
|
||||
collision_layer = 4
|
||||
collision_mask = 0
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="HitArea" unique_id=984781962]
|
||||
shape = SubResource("CapsuleShape3D_2")
|
||||
|
||||
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="." unique_id=440641945]
|
||||
|
||||
[node name="Detection" type="Node" parent="." unique_id=534240144]
|
||||
script = ExtResource("2")
|
||||
|
||||
[node name="DetectionArea" type="Area3D" parent="." unique_id=1955178598]
|
||||
collision_layer = 0
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="DetectionArea" unique_id=557461347]
|
||||
shape = SubResource("SphereShape3D_1")
|
||||
|
||||
[node name="Healthbar" type="Sprite3D" parent="." unique_id=1008728031]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
|
||||
billboard = 1
|
||||
|
||||
[node name="SubViewport" type="SubViewport" parent="Healthbar" unique_id=1219060718]
|
||||
transparent_bg = true
|
||||
size = Vector2i(104, 29)
|
||||
|
||||
[node name="Border" type="ColorRect" parent="Healthbar/SubViewport" unique_id=848146848]
|
||||
offset_right = 104.0
|
||||
offset_bottom = 29.0
|
||||
color = Color(1, 0.9, 0.2, 1)
|
||||
|
||||
[node name="HealthBar" type="ProgressBar" parent="Healthbar/SubViewport" unique_id=1206434403]
|
||||
offset_left = 2.0
|
||||
offset_top = 2.0
|
||||
offset_right = 102.0
|
||||
offset_bottom = 12.0
|
||||
theme_override_styles/background = SubResource("StyleBoxFlat_health_bg")
|
||||
theme_override_styles/fill = SubResource("StyleBoxFlat_health_fill")
|
||||
value = 100.0
|
||||
show_percentage = false
|
||||
|
||||
[node name="ShieldBar" type="ProgressBar" parent="Healthbar/SubViewport" unique_id=1891108036]
|
||||
offset_left = 2.0
|
||||
offset_top = 15.0
|
||||
offset_right = 102.0
|
||||
offset_bottom = 27.0
|
||||
theme_override_styles/background = SubResource("StyleBoxFlat_shield_bg")
|
||||
theme_override_styles/fill = SubResource("StyleBoxFlat_shield_fill")
|
||||
max_value = 50.0
|
||||
value = 50.0
|
||||
show_percentage = false
|
||||
|
||||
[connection signal="body_entered" from="DetectionArea" to="Detection" method="_on_detection_area_body_entered"]
|
||||
[connection signal="body_exited" from="DetectionArea" to="Detection" method="_on_detection_area_body_exited"]
|
||||
@@ -1,12 +0,0 @@
|
||||
extends BaseStats
|
||||
class_name EnemyStats
|
||||
|
||||
@export var speed := 3.0
|
||||
@export var attack_range := 2.0
|
||||
@export var attack_cooldown := 1.5
|
||||
@export var attack_damage := 5.0
|
||||
@export var regen_fast := 0.10
|
||||
@export var regen_slow := 0.01
|
||||
@export var aggro_decay := 1.0
|
||||
@export var portal_radius := 10.0
|
||||
@export var alert_radius := 10.0
|
||||
@@ -1 +0,0 @@
|
||||
uid://bh2uuuvl30y0x
|
||||
@@ -1,7 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="EnemyStats" format=3 uid="uid://cj1shmjwf0xeo"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bh2uuuvl30y0x" path="res://scenes/enemy/enemy_stats.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
max_shield = 50.0
|
||||
@@ -1,101 +0,0 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
const SKELETON_WARRIOR: PackedScene = preload("res://assets/models/characters/Skeleton_Warrior.glb")
|
||||
const SKELETON_MAGE: PackedScene = preload("res://assets/models/characters/Skeleton_Mage.glb")
|
||||
|
||||
@export var stats: EnemyStats
|
||||
|
||||
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
|
||||
var spawn_scale: float = 1.0
|
||||
var anim_player: AnimationPlayer = null
|
||||
var current_anim: String = ""
|
||||
var attack_lock_until: float = 0.0
|
||||
var is_dying: bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("enemies")
|
||||
if is_in_group("boss"):
|
||||
BossData.register(self, stats, spawn_scale)
|
||||
BossData.set_stat(self, "spawn_position", global_position)
|
||||
_swap_model(SKELETON_MAGE, 1.3)
|
||||
else:
|
||||
EnemyData.register(self, stats, spawn_scale)
|
||||
EnemyData.set_stat(self, "spawn_position", global_position)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
EventBus.attack_executed.connect(_on_attack_executed)
|
||||
call_deferred("_check_variant")
|
||||
call_deferred("_init_anim")
|
||||
|
||||
func _init_anim() -> void:
|
||||
anim_player = get_node_or_null("Mesh/Model/AnimationPlayer")
|
||||
_play_anim("Idle")
|
||||
|
||||
func _check_variant() -> void:
|
||||
if is_in_group("boss"):
|
||||
return
|
||||
if is_in_group("red_enemies") or is_in_group("invasion"):
|
||||
_swap_model(SKELETON_WARRIOR, 1.0)
|
||||
anim_player = get_node_or_null("Mesh/Model/AnimationPlayer")
|
||||
_play_anim("Idle")
|
||||
|
||||
func _swap_model(new_scene: PackedScene, scale_factor: float = 1.0) -> void:
|
||||
var mesh: Node3D = get_node_or_null("Mesh")
|
||||
if not mesh:
|
||||
return
|
||||
var old: Node = mesh.get_node_or_null("Model")
|
||||
if old:
|
||||
old.queue_free()
|
||||
var new_model: Node3D = new_scene.instantiate()
|
||||
new_model.name = "Model"
|
||||
new_model.scale = Vector3(scale_factor, scale_factor, scale_factor)
|
||||
new_model.position = Vector3(0, -0.75, 0)
|
||||
new_model.rotation.y = PI
|
||||
mesh.add_child(new_model)
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if is_in_group("boss"):
|
||||
BossData.deregister(self)
|
||||
else:
|
||||
EnemyData.deregister(self)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity != self:
|
||||
return
|
||||
is_dying = true
|
||||
_play_anim("Death_A", false)
|
||||
get_tree().create_timer(1.0).timeout.connect(queue_free)
|
||||
|
||||
func _on_attack_executed(attacker: Node, _pos: Vector3, _dir: Vector3, _damage: float) -> void:
|
||||
if attacker != self:
|
||||
return
|
||||
_play_anim("1H_Melee_Attack_Chop", false)
|
||||
attack_lock_until = Time.get_ticks_msec() / 1000.0 + 0.5
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if is_dying:
|
||||
return
|
||||
var now: float = Time.get_ticks_msec() / 1000.0
|
||||
if now < attack_lock_until:
|
||||
return
|
||||
if velocity.length() > 0.1:
|
||||
_play_anim("Running_A")
|
||||
else:
|
||||
_play_anim("Idle")
|
||||
|
||||
func _play_anim(anim_name: String, loop: bool = true) -> void:
|
||||
if not anim_player:
|
||||
return
|
||||
if current_anim == anim_name:
|
||||
return
|
||||
if not anim_player.has_animation(anim_name):
|
||||
return
|
||||
var anim: Animation = anim_player.get_animation(anim_name)
|
||||
if anim:
|
||||
anim.loop_mode = Animation.LOOP_LINEAR if loop else Animation.LOOP_NONE
|
||||
anim_player.play(anim_name)
|
||||
current_anim = anim_name
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not is_on_floor():
|
||||
velocity.y -= gravity * delta
|
||||
move_and_slide()
|
||||
@@ -1 +0,0 @@
|
||||
uid://vy6hyqok0p8b
|
||||
36
scenes/entities/building/building.gd
Normal file
36
scenes/entities/building/building.gd
Normal file
@@ -0,0 +1,36 @@
|
||||
extends StaticBody3D
|
||||
|
||||
@export var building_id: StringName = &""
|
||||
|
||||
@onready var mesh: MeshInstance3D = $Mesh
|
||||
@onready var collision: CollisionShape3D = $Collision
|
||||
|
||||
func _enter_tree() -> void:
|
||||
set_multiplayer_authority(1)
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("buildings")
|
||||
apply_building(building_id)
|
||||
|
||||
func apply_building(id: StringName) -> void:
|
||||
building_id = id
|
||||
var data: Building = _load_building(id)
|
||||
if data == null:
|
||||
return
|
||||
var box: BoxMesh = mesh.mesh as BoxMesh
|
||||
if box:
|
||||
box.size = data.size
|
||||
var shape: BoxShape3D = collision.shape as BoxShape3D
|
||||
if shape:
|
||||
shape.size = data.size
|
||||
collision.position = Vector3(0.0, data.size.y * 0.5, 0.0)
|
||||
mesh.position = Vector3(0.0, data.size.y * 0.5, 0.0)
|
||||
var mat: StandardMaterial3D = StandardMaterial3D.new()
|
||||
mat.albedo_color = data.color
|
||||
mesh.material_override = mat
|
||||
|
||||
static func _load_building(id: StringName) -> Building:
|
||||
var path := "res://resources/buildings/%s.tres" % str(id)
|
||||
if ResourceLoader.exists(path):
|
||||
return load(path) as Building
|
||||
return null
|
||||
1
scenes/entities/building/building.gd.uid
Normal file
1
scenes/entities/building/building.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cq63wtaqo3ebt
|
||||
22
scenes/entities/building/building.tscn
Normal file
22
scenes/entities/building/building.tscn
Normal file
@@ -0,0 +1,22 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://b0building001"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/entities/building/building.gd" id="1"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_1"]
|
||||
size = Vector3(1, 1, 1)
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_1"]
|
||||
size = Vector3(1, 1, 1)
|
||||
|
||||
[node name="Building" type="StaticBody3D"]
|
||||
collision_layer = 16
|
||||
collision_mask = 0
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Collision" type="CollisionShape3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
|
||||
shape = SubResource("BoxShape3D_1")
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
|
||||
mesh = SubResource("BoxMesh_1")
|
||||
170
scenes/entities/enemy/enemy.gd
Normal file
170
scenes/entities/enemy/enemy.gd
Normal file
@@ -0,0 +1,170 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
const GRAVITY: float = 18.0
|
||||
|
||||
@export var stats_resource: EnemyStats
|
||||
@export var is_boss: bool = false
|
||||
|
||||
@onready var nav: NavigationAgent3D = $NavAgent
|
||||
@onready var mesh_holder: Node3D = $MeshHolder
|
||||
@onready var collision: CollisionShape3D = $Collision
|
||||
@onready var detection: Area3D = $DetectionArea
|
||||
@onready var sync: MultiplayerSynchronizer = $Synchronizer
|
||||
@onready var healthbar: MeshInstance3D = $Healthbar
|
||||
@onready var name_label: Label3D = $NameLabel
|
||||
|
||||
var origin: Vector3 = Vector3.ZERO
|
||||
var attack_cd: float = 0.0
|
||||
var dead: bool = false
|
||||
var invasion_target: Node = null
|
||||
|
||||
@export var sync_position: Vector3 = Vector3.ZERO
|
||||
@export var sync_yaw: float = 0.0
|
||||
|
||||
func _enter_tree() -> void:
|
||||
set_multiplayer_authority(1)
|
||||
|
||||
func _ready() -> void:
|
||||
if is_boss:
|
||||
add_to_group("boss")
|
||||
add_to_group("enemies")
|
||||
if stats_resource == null:
|
||||
stats_resource = EnemyStats.new()
|
||||
Stats.register(self, stats_resource)
|
||||
origin = global_position
|
||||
detection.body_entered.connect(_on_body_entered)
|
||||
EventBus.health_changed.connect(_on_health_changed)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
name_label.text = "Boss" if is_boss else "Enemy"
|
||||
if is_boss:
|
||||
name_label.text = "Boss"
|
||||
var mesh: MeshInstance3D = mesh_holder.get_node("Mesh")
|
||||
mesh.scale = Vector3(1.5, 1.5, 1.5)
|
||||
var mat: StandardMaterial3D = StandardMaterial3D.new()
|
||||
mat.albedo_color = Color(0.6, 0.2, 0.8)
|
||||
mesh.material_override = mat
|
||||
|
||||
func _exit_tree() -> void:
|
||||
Stats.deregister(self)
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not is_multiplayer_authority():
|
||||
global_position = global_position.lerp(sync_position, clamp(delta * 20.0, 0.0, 1.0))
|
||||
rotation.y = lerp_angle(rotation.y, sync_yaw, clamp(delta * 20.0, 0.0, 1.0))
|
||||
return
|
||||
if dead:
|
||||
return
|
||||
if not is_on_floor():
|
||||
velocity.y -= GRAVITY * delta
|
||||
attack_cd = max(0.0, attack_cd - delta)
|
||||
var target: Node = _get_target()
|
||||
if target == null:
|
||||
_return_to_origin(delta)
|
||||
else:
|
||||
_chase_or_attack(target, delta)
|
||||
move_and_slide()
|
||||
sync_position = global_position
|
||||
sync_yaw = rotation.y
|
||||
|
||||
func _get_target() -> Node:
|
||||
var aggro: Node = get_node_or_null("/root/World/Systems/AggroSystem")
|
||||
if aggro == null:
|
||||
aggro = get_node_or_null("/root/Dungeon/Systems/AggroSystem")
|
||||
if aggro and aggro.has_method("target_for"):
|
||||
var t: Node = aggro.target_for(self)
|
||||
if t:
|
||||
return t
|
||||
if invasion_target and is_instance_valid(invasion_target):
|
||||
return invasion_target
|
||||
return null
|
||||
|
||||
func _chase_or_attack(target: Node, delta: float) -> void:
|
||||
var t_pos: Vector3 = (target as Node3D).global_position
|
||||
var d: float = global_position.distance_to(t_pos)
|
||||
var attack_range: float = float(Stats.get_stat(self, "attack_range", 2.0))
|
||||
if d <= attack_range:
|
||||
velocity.x = move_toward(velocity.x, 0.0, 20.0 * delta)
|
||||
velocity.z = move_toward(velocity.z, 0.0, 20.0 * delta)
|
||||
var look := Vector3(t_pos.x - global_position.x, 0.0, t_pos.z - global_position.z)
|
||||
if look.length() > 0.01:
|
||||
rotation.y = atan2(look.x, look.z)
|
||||
if attack_cd <= 0.0:
|
||||
attack_cd = float(Stats.get_stat(self, "attack_cooldown", 1.5))
|
||||
var dmg: float = float(Stats.get_stat(self, "attack_damage", 5.0))
|
||||
EventBus.damage_requested.emit(self, target, dmg, Element.NONE)
|
||||
else:
|
||||
var dir := Vector3(t_pos.x - global_position.x, 0.0, t_pos.z - global_position.z).normalized()
|
||||
var speed: float = float(Stats.get_stat(self, "speed", 3.0))
|
||||
velocity.x = dir.x * speed
|
||||
velocity.z = dir.z * speed
|
||||
if dir.length() > 0.01:
|
||||
rotation.y = atan2(dir.x, dir.z)
|
||||
|
||||
func _return_to_origin(delta: float) -> void:
|
||||
var d: float = global_position.distance_to(origin)
|
||||
if d < 0.5:
|
||||
velocity.x = move_toward(velocity.x, 0.0, 20.0 * delta)
|
||||
velocity.z = move_toward(velocity.z, 0.0, 20.0 * delta)
|
||||
var max_hp: float = float(Stats.get_stat(self, "max_health", 100.0))
|
||||
var hp: float = float(Stats.get_stat(self, "health", 0.0))
|
||||
if hp > 0.0 and hp < max_hp:
|
||||
Stats.set_stat(self, "health", min(max_hp, hp + max_hp * 0.20 * delta))
|
||||
EventBus.health_changed.emit(self, Stats.get_stat(self, "health"), max_hp)
|
||||
else:
|
||||
var dir: Vector3 = Vector3(origin.x - global_position.x, 0.0, origin.z - global_position.z).normalized()
|
||||
var speed: float = float(Stats.get_stat(self, "speed", 3.0)) * 1.5
|
||||
velocity.x = dir.x * speed
|
||||
velocity.z = dir.z * speed
|
||||
if dir.length() > 0.01:
|
||||
rotation.y = atan2(dir.x, dir.z)
|
||||
|
||||
func _on_body_entered(body: Node) -> void:
|
||||
if not multiplayer.is_server() and multiplayer.multiplayer_peer != null:
|
||||
return
|
||||
if body.is_in_group("player") and not dead:
|
||||
EventBus.enemy_detected.emit(self, body)
|
||||
|
||||
func _on_health_changed(entity: Node, current: float, max: float) -> void:
|
||||
if entity != self:
|
||||
return
|
||||
var ratio: float = clamp(current / max if max > 0 else 0.0, 0.0, 1.0)
|
||||
if healthbar:
|
||||
healthbar.scale.x = max(0.01, ratio)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity != self or dead:
|
||||
return
|
||||
dead = true
|
||||
if multiplayer.is_server() or multiplayer.multiplayer_peer == null:
|
||||
_on_death.rpc()
|
||||
var loot: Node = get_node_or_null("/root/World/Systems/LootSystem")
|
||||
if loot == null:
|
||||
loot = get_node_or_null("/root/Dungeon/Systems/LootSystem")
|
||||
if loot and loot.has_method("drop_loot_for"):
|
||||
loot.drop_loot_for(self, global_position)
|
||||
var xp_sys: Node = get_node_or_null("/root/World/Systems/XpSystem")
|
||||
if xp_sys == null:
|
||||
xp_sys = get_node_or_null("/root/Dungeon/Systems/XpSystem")
|
||||
if xp_sys and xp_sys.has_method("award_for_enemy"):
|
||||
xp_sys.award_for_enemy(self)
|
||||
if is_boss:
|
||||
EventBus.boss_defeated.emit(self)
|
||||
var t := get_tree().create_timer(2.0)
|
||||
t.timeout.connect(func():
|
||||
if is_instance_valid(self):
|
||||
queue_free())
|
||||
|
||||
@rpc("any_peer", "reliable", "call_local")
|
||||
func _on_death() -> void:
|
||||
if multiplayer.multiplayer_peer != null and not (multiplayer.multiplayer_peer is OfflineMultiplayerPeer) and multiplayer.get_remote_sender_id() != 0 and multiplayer.get_remote_sender_id() != 1:
|
||||
return
|
||||
dead = true
|
||||
collision.disabled = true
|
||||
modulate_alpha(0.4)
|
||||
|
||||
func modulate_alpha(a: float) -> void:
|
||||
for child in mesh_holder.get_children():
|
||||
if child is MeshInstance3D and child.material_override:
|
||||
var c: Color = child.material_override.albedo_color
|
||||
c.a = a
|
||||
child.material_override.albedo_color = c
|
||||
1
scenes/entities/enemy/enemy.gd.uid
Normal file
1
scenes/entities/enemy/enemy.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://t28sckpnef15
|
||||
76
scenes/entities/enemy/enemy.tscn
Normal file
76
scenes/entities/enemy/enemy.tscn
Normal file
@@ -0,0 +1,76 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://b0enemy00001"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/entities/enemy/enemy.gd" id="1"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
|
||||
height = 1.6
|
||||
radius = 0.4
|
||||
|
||||
[sub_resource type="CapsuleMesh" id="CapsuleMesh_1"]
|
||||
height = 1.6
|
||||
radius = 0.4
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Mat_1"]
|
||||
albedo_color = Color(0.6, 0.6, 0.6, 1)
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_1"]
|
||||
radius = 12.0
|
||||
|
||||
[sub_resource type="QuadMesh" id="QuadMesh_1"]
|
||||
size = Vector2(1.0, 0.12)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="HBMat"]
|
||||
shading_mode = 0
|
||||
no_depth_test = true
|
||||
albedo_color = Color(0.9, 0.2, 0.2, 1)
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_1"]
|
||||
properties/0/path = NodePath(".:sync_position")
|
||||
properties/0/spawn = true
|
||||
properties/0/replication_mode = 1
|
||||
properties/1/path = NodePath(".:sync_yaw")
|
||||
properties/1/spawn = true
|
||||
properties/1/replication_mode = 1
|
||||
|
||||
[node name="Enemy" type="CharacterBody3D"]
|
||||
collision_layer = 4
|
||||
collision_mask = 1
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Collision" type="CollisionShape3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.8, 0)
|
||||
shape = SubResource("CapsuleShape3D_1")
|
||||
|
||||
[node name="MeshHolder" type="Node3D" parent="."]
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="MeshHolder"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.8, 0)
|
||||
mesh = SubResource("CapsuleMesh_1")
|
||||
material_override = SubResource("Mat_1")
|
||||
|
||||
[node name="NameLabel" type="Label3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.2, 0)
|
||||
billboard = 1
|
||||
text = "Enemy"
|
||||
font_size = 20
|
||||
outline_size = 3
|
||||
|
||||
[node name="Healthbar" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.95, 0)
|
||||
mesh = SubResource("QuadMesh_1")
|
||||
material_override = SubResource("HBMat")
|
||||
gi_mode = 0
|
||||
|
||||
[node name="DetectionArea" type="Area3D" parent="."]
|
||||
collision_layer = 0
|
||||
collision_mask = 2
|
||||
|
||||
[node name="DetectionShape" type="CollisionShape3D" parent="DetectionArea"]
|
||||
shape = SubResource("SphereShape3D_1")
|
||||
|
||||
[node name="NavAgent" type="NavigationAgent3D" parent="."]
|
||||
path_desired_distance = 0.5
|
||||
target_desired_distance = 0.5
|
||||
|
||||
[node name="Synchronizer" type="MultiplayerSynchronizer" parent="."]
|
||||
replication_config = SubResource("SceneReplicationConfig_1")
|
||||
80
scenes/entities/gate/gate.gd
Normal file
80
scenes/entities/gate/gate.gd
Normal file
@@ -0,0 +1,80 @@
|
||||
extends StaticBody3D
|
||||
|
||||
@export var stats_resource: GateStats
|
||||
@export var is_red: bool = false
|
||||
|
||||
@onready var mesh: MeshInstance3D = $Mesh
|
||||
@onready var healthbar: MeshInstance3D = $Healthbar
|
||||
@onready var name_label: Label3D = $NameLabel
|
||||
@onready var spawn_point: Node3D = $SpawnPoint
|
||||
|
||||
var spawn_timer: float = 0.0
|
||||
var spawned_count: int = 0
|
||||
var dead: bool = false
|
||||
|
||||
func _enter_tree() -> void:
|
||||
set_multiplayer_authority(1)
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("gates")
|
||||
if is_red:
|
||||
add_to_group("red_gate")
|
||||
if stats_resource == null:
|
||||
stats_resource = GateStats.new()
|
||||
if is_red:
|
||||
stats_resource.max_health = 600.0
|
||||
stats_resource.spawn_count = 8
|
||||
else:
|
||||
stats_resource.max_health = 200.0
|
||||
stats_resource.spawn_count = 5
|
||||
stats_resource.is_red = is_red
|
||||
Stats.register(self, stats_resource)
|
||||
EventBus.health_changed.connect(_on_health_changed)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
if is_red:
|
||||
var mat: StandardMaterial3D = StandardMaterial3D.new()
|
||||
mat.albedo_color = Color(0.95, 0.2, 0.15)
|
||||
mat.emission_enabled = true
|
||||
mat.emission = Color(1.0, 0.4, 0.2)
|
||||
mat.emission_energy_multiplier = 0.6
|
||||
mesh.material_override = mat
|
||||
name_label.text = "Red Gate"
|
||||
else:
|
||||
name_label.text = "Gate"
|
||||
set_physics_process(true)
|
||||
|
||||
func _exit_tree() -> void:
|
||||
Stats.deregister(self)
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if dead:
|
||||
return
|
||||
if not multiplayer.is_server() and multiplayer.multiplayer_peer != null:
|
||||
return
|
||||
spawn_timer = max(0.0, spawn_timer - delta)
|
||||
if spawn_timer <= 0.0 and spawned_count < int(Stats.get_stat(self, "spawn_count", 5)):
|
||||
var spawn_sys: Node = get_node_or_null("/root/World/Systems/SpawnSystem")
|
||||
if spawn_sys and spawn_sys.has_method("spawn_enemy_at"):
|
||||
spawn_sys.spawn_enemy_at(spawn_point.global_position + Vector3(randf_range(-1.5, 1.5), 0.0, randf_range(-1.5, 1.5)), is_red)
|
||||
spawned_count += 1
|
||||
spawn_timer = float(Stats.get_stat(self, "spawn_interval", 4.0))
|
||||
|
||||
func _on_health_changed(entity: Node, current: float, max: float) -> void:
|
||||
if entity != self:
|
||||
return
|
||||
var ratio: float = clamp(current / max if max > 0 else 0.0, 0.0, 1.0)
|
||||
healthbar.scale.x = max(0.01, ratio * 2.0)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity != self or dead:
|
||||
return
|
||||
dead = true
|
||||
if multiplayer.is_server() or multiplayer.multiplayer_peer == null:
|
||||
var spawn_sys: Node = get_node_or_null("/root/World/Systems/SpawnSystem")
|
||||
if spawn_sys and spawn_sys.has_method("spawn_portal_at"):
|
||||
spawn_sys.spawn_portal_at(global_position, is_red)
|
||||
EventBus.gate_destroyed.emit(self)
|
||||
var t := get_tree().create_timer(0.5)
|
||||
t.timeout.connect(func():
|
||||
if is_instance_valid(self):
|
||||
queue_free())
|
||||
1
scenes/entities/gate/gate.gd.uid
Normal file
1
scenes/entities/gate/gate.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://yrf2o25mrihx
|
||||
53
scenes/entities/gate/gate.tscn
Normal file
53
scenes/entities/gate/gate.tscn
Normal file
@@ -0,0 +1,53 @@
|
||||
[gd_scene load_steps=7 format=3 uid="uid://b0gate0001"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/entities/gate/gate.gd" id="1"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_1"]
|
||||
size = Vector3(2, 3, 2)
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_1"]
|
||||
size = Vector3(2, 3, 2)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="GateMat"]
|
||||
albedo_color = Color(0.4, 0.4, 0.55, 1)
|
||||
emission_enabled = true
|
||||
emission = Color(0.2, 0.2, 0.4, 1)
|
||||
emission_energy_multiplier = 0.4
|
||||
|
||||
[sub_resource type="QuadMesh" id="QuadMesh_HB"]
|
||||
size = Vector2(1.0, 0.18)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="HBMat"]
|
||||
shading_mode = 0
|
||||
no_depth_test = true
|
||||
albedo_color = Color(0.95, 0.4, 0.2, 1)
|
||||
|
||||
[node name="Gate" type="StaticBody3D"]
|
||||
collision_layer = 4
|
||||
collision_mask = 0
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Collision" type="CollisionShape3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
|
||||
shape = SubResource("BoxShape3D_1")
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
|
||||
mesh = SubResource("BoxMesh_1")
|
||||
material_override = SubResource("GateMat")
|
||||
|
||||
[node name="NameLabel" type="Label3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4.0, 0)
|
||||
billboard = 1
|
||||
text = "Gate"
|
||||
font_size = 22
|
||||
outline_size = 3
|
||||
|
||||
[node name="Healthbar" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3.5, 0)
|
||||
mesh = SubResource("QuadMesh_HB")
|
||||
material_override = SubResource("HBMat")
|
||||
gi_mode = 0
|
||||
|
||||
[node name="SpawnPoint" type="Node3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3)
|
||||
39
scenes/entities/loot/loot_drop.gd
Normal file
39
scenes/entities/loot/loot_drop.gd
Normal file
@@ -0,0 +1,39 @@
|
||||
extends Area3D
|
||||
|
||||
@onready var mesh: MeshInstance3D = $Mesh
|
||||
@onready var label: Label3D = $Label
|
||||
|
||||
@export var item_id: StringName = &""
|
||||
@export var amount: int = 1
|
||||
|
||||
var rotation_speed: float = 1.5
|
||||
var bob: float = 0.0
|
||||
|
||||
func _enter_tree() -> void:
|
||||
set_multiplayer_authority(1)
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("loot")
|
||||
body_entered.connect(_on_body_entered)
|
||||
if item_id != &"":
|
||||
label.text = "%s x%d" % [str(item_id), amount]
|
||||
else:
|
||||
label.text = "Loot"
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
bob += delta
|
||||
mesh.rotation.y += rotation_speed * delta
|
||||
mesh.position.y = 0.5 + sin(bob * 2.0) * 0.1
|
||||
|
||||
func _on_body_entered(body: Node) -> void:
|
||||
if not multiplayer.is_server() and multiplayer.multiplayer_peer != null:
|
||||
return
|
||||
if not body.is_in_group("player"):
|
||||
return
|
||||
var inv: Node = get_node_or_null("/root/World/Systems/InventorySystem")
|
||||
if inv == null:
|
||||
inv = get_node_or_null("/root/Dungeon/Systems/InventorySystem")
|
||||
if inv and inv.has_method("add_item"):
|
||||
inv.add_item(body, item_id, amount)
|
||||
EventBus.item_picked_up.emit(body, item_id)
|
||||
queue_free()
|
||||
1
scenes/entities/loot/loot_drop.gd.uid
Normal file
1
scenes/entities/loot/loot_drop.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://begk6dfrigj12
|
||||
35
scenes/entities/loot/loot_drop.tscn
Normal file
35
scenes/entities/loot/loot_drop.tscn
Normal file
@@ -0,0 +1,35 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://b0loot0001"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/entities/loot/loot_drop.gd" id="1"]
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_1"]
|
||||
size = Vector3(0.4, 0.4, 0.4)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="LootMat"]
|
||||
albedo_color = Color(0.95, 0.85, 0.2, 1)
|
||||
emission_enabled = true
|
||||
emission = Color(1.0, 0.9, 0.4, 1)
|
||||
emission_energy_multiplier = 0.6
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_1"]
|
||||
radius = 1.2
|
||||
|
||||
[node name="LootDrop" type="Area3D"]
|
||||
collision_layer = 0
|
||||
collision_mask = 2
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
|
||||
mesh = SubResource("BoxMesh_1")
|
||||
material_override = SubResource("LootMat")
|
||||
|
||||
[node name="Label" type="Label3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
|
||||
billboard = 1
|
||||
text = "Loot"
|
||||
font_size = 18
|
||||
outline_size = 3
|
||||
|
||||
[node name="PickupShape" type="CollisionShape3D" parent="."]
|
||||
shape = SubResource("SphereShape3D_1")
|
||||
52
scenes/entities/npc/npc.gd
Normal file
52
scenes/entities/npc/npc.gd
Normal file
@@ -0,0 +1,52 @@
|
||||
extends StaticBody3D
|
||||
|
||||
@export var profile: NpcProfile
|
||||
@export var profile_id: StringName = &""
|
||||
|
||||
@onready var mesh: MeshInstance3D = $Mesh
|
||||
@onready var label: Label3D = $Label
|
||||
@onready var prompt: Label3D = $Prompt
|
||||
@onready var interact_area: Area3D = $InteractArea
|
||||
|
||||
var nearby_player: Node = null
|
||||
|
||||
func _enter_tree() -> void:
|
||||
set_multiplayer_authority(1)
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("npc")
|
||||
if profile == null and profile_id != &"":
|
||||
var path := "res://resources/npcs/%s.tres" % str(profile_id)
|
||||
if ResourceLoader.exists(path):
|
||||
profile = load(path) as NpcProfile
|
||||
if profile == null:
|
||||
profile = NpcProfile.new()
|
||||
profile.display_name = "Villager"
|
||||
profile.lore = "A simple villager."
|
||||
profile.personality = "Friendly, curious."
|
||||
profile.fallback_text = "Hello there!"
|
||||
label.text = profile.display_name
|
||||
prompt.visible = false
|
||||
if profile.color != Color.WHITE:
|
||||
var mat: StandardMaterial3D = StandardMaterial3D.new()
|
||||
mat.albedo_color = profile.color
|
||||
mesh.material_override = mat
|
||||
interact_area.body_entered.connect(_on_player_near)
|
||||
interact_area.body_exited.connect(_on_player_left)
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if nearby_player and nearby_player.is_multiplayer_authority():
|
||||
prompt.visible = true
|
||||
if Input.is_action_just_pressed("interact"):
|
||||
EventBus.dialog_opened.emit(nearby_player, self)
|
||||
else:
|
||||
prompt.visible = false
|
||||
|
||||
func _on_player_near(body: Node) -> void:
|
||||
if body.is_in_group("player"):
|
||||
if body.is_multiplayer_authority():
|
||||
nearby_player = body
|
||||
|
||||
func _on_player_left(body: Node) -> void:
|
||||
if body == nearby_player:
|
||||
nearby_player = null
|
||||
1
scenes/entities/npc/npc.gd.uid
Normal file
1
scenes/entities/npc/npc.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://chul3jysytie2
|
||||
53
scenes/entities/npc/npc.tscn
Normal file
53
scenes/entities/npc/npc.tscn
Normal file
@@ -0,0 +1,53 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://b0npc0001"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/entities/npc/npc.gd" id="1"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
|
||||
height = 1.7
|
||||
radius = 0.35
|
||||
|
||||
[sub_resource type="CapsuleMesh" id="CapsuleMesh_1"]
|
||||
height = 1.7
|
||||
radius = 0.35
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="NpcMat"]
|
||||
albedo_color = Color(0.85, 0.7, 0.5, 1)
|
||||
|
||||
[sub_resource type="SphereShape3D" id="InteractShape"]
|
||||
radius = 2.5
|
||||
|
||||
[node name="Npc" type="StaticBody3D"]
|
||||
collision_layer = 0
|
||||
collision_mask = 0
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Collision" type="CollisionShape3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.85, 0)
|
||||
shape = SubResource("CapsuleShape3D_1")
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.85, 0)
|
||||
mesh = SubResource("CapsuleMesh_1")
|
||||
material_override = SubResource("NpcMat")
|
||||
|
||||
[node name="Label" type="Label3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.2, 0)
|
||||
billboard = 1
|
||||
text = "Villager"
|
||||
font_size = 22
|
||||
outline_size = 3
|
||||
modulate = Color(0.95, 0.85, 0.5, 1)
|
||||
|
||||
[node name="Prompt" type="Label3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.55, 0)
|
||||
billboard = 1
|
||||
text = "[E] Talk"
|
||||
font_size = 16
|
||||
outline_size = 2
|
||||
|
||||
[node name="InteractArea" type="Area3D" parent="."]
|
||||
collision_layer = 0
|
||||
collision_mask = 2
|
||||
|
||||
[node name="InteractShape" type="CollisionShape3D" parent="InteractArea"]
|
||||
shape = SubResource("InteractShape")
|
||||
242
scenes/entities/player/player.gd
Normal file
242
scenes/entities/player/player.gd
Normal file
@@ -0,0 +1,242 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
const GRAVITY: float = 18.0
|
||||
const MOUSE_SENS: float = 0.0035
|
||||
|
||||
@export var stats_resource: PlayerStats
|
||||
|
||||
@onready var pivot: Node3D = $Pivot
|
||||
@onready var pitch_pivot: Node3D = $Pivot/PitchPivot
|
||||
@onready var camera: Camera3D = $Pivot/PitchPivot/Camera
|
||||
@onready var mesh_holder: Node3D = $MeshHolder
|
||||
@onready var collision: CollisionShape3D = $Collision
|
||||
@onready var sync: MultiplayerSynchronizer = $Synchronizer
|
||||
@onready var name_label: Label3D = $NameLabel
|
||||
|
||||
var peer_id: int = 1
|
||||
var role: int = GameState.ROLE_DAMAGE
|
||||
var look_dragging: bool = false
|
||||
var current_target: Node = null
|
||||
var dead: bool = false
|
||||
var ui_capturing: bool = false
|
||||
var build_mode: bool = false
|
||||
|
||||
@export var sync_position: Vector3 = Vector3.ZERO
|
||||
@export var sync_velocity: Vector3 = Vector3.ZERO
|
||||
@export var sync_yaw: float = 0.0
|
||||
@export var sync_role: int = GameState.ROLE_DAMAGE
|
||||
|
||||
func _enter_tree() -> void:
|
||||
peer_id = name.to_int()
|
||||
set_multiplayer_authority(peer_id)
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("player")
|
||||
if stats_resource == null:
|
||||
stats_resource = PlayerStats.new()
|
||||
Stats.register(self, stats_resource)
|
||||
Stats.set_stat(self, "role", role)
|
||||
name_label.text = Net.player_names.get(peer_id, "P%d" % peer_id)
|
||||
EventBus.entity_died.connect(_on_entity_died_clear_target)
|
||||
if is_multiplayer_authority():
|
||||
camera.current = true
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
else:
|
||||
camera.current = false
|
||||
_apply_role_visual(role)
|
||||
|
||||
func _on_entity_died_clear_target(entity: Node) -> void:
|
||||
if entity == current_target:
|
||||
current_target = null
|
||||
|
||||
func _set_target(t: Node) -> void:
|
||||
current_target = t
|
||||
EventBus.target_changed.emit(self, t)
|
||||
if multiplayer.multiplayer_peer != null and not (multiplayer.multiplayer_peer is OfflineMultiplayerPeer) and not multiplayer.is_server():
|
||||
_request_target.rpc_id(1, String(t.get_path()) if t else "")
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _request_target(path_str: String) -> void:
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
var t: Node = get_node_or_null(NodePath(path_str)) if path_str != "" else null
|
||||
current_target = t
|
||||
|
||||
func _exit_tree() -> void:
|
||||
Stats.deregister(self)
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if not is_multiplayer_authority():
|
||||
return
|
||||
if ui_capturing:
|
||||
return
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT:
|
||||
look_dragging = event.pressed
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if look_dragging else Input.MOUSE_MODE_VISIBLE
|
||||
elif event is InputEventMouseMotion and look_dragging:
|
||||
pivot.rotate_y(-event.relative.x * MOUSE_SENS)
|
||||
pitch_pivot.rotate_x(-event.relative.y * MOUSE_SENS)
|
||||
pitch_pivot.rotation.x = clamp(pitch_pivot.rotation.x, -1.2, 0.2)
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if not is_multiplayer_authority():
|
||||
return
|
||||
if dead:
|
||||
return
|
||||
if build_mode:
|
||||
return
|
||||
if event.is_action_pressed("class_tank"):
|
||||
_request_role(GameState.ROLE_TANK)
|
||||
elif event.is_action_pressed("class_damage"):
|
||||
_request_role(GameState.ROLE_DAMAGE)
|
||||
elif event.is_action_pressed("class_healer"):
|
||||
_request_role(GameState.ROLE_HEALER)
|
||||
elif event.is_action_pressed("ability_1"):
|
||||
EventBus.ability_use_requested.emit(self, 0)
|
||||
elif event.is_action_pressed("ability_2"):
|
||||
EventBus.ability_use_requested.emit(self, 1)
|
||||
elif event.is_action_pressed("ability_3"):
|
||||
EventBus.ability_use_requested.emit(self, 2)
|
||||
elif event.is_action_pressed("ability_4"):
|
||||
EventBus.ability_use_requested.emit(self, 3)
|
||||
elif event.is_action_pressed("target_next"):
|
||||
var nt := _cycle_target()
|
||||
_set_target(nt)
|
||||
elif event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed and not look_dragging:
|
||||
var t := _pick_target_under_mouse()
|
||||
if t:
|
||||
_set_target(t)
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if is_multiplayer_authority():
|
||||
if not dead:
|
||||
_process_local(delta)
|
||||
sync_position = global_position
|
||||
sync_velocity = velocity
|
||||
sync_yaw = pivot.rotation.y
|
||||
sync_role = role
|
||||
else:
|
||||
global_position = global_position.lerp(sync_position, clamp(delta * 20.0, 0.0, 1.0))
|
||||
pivot.rotation.y = lerp_angle(pivot.rotation.y, sync_yaw, clamp(delta * 20.0, 0.0, 1.0))
|
||||
if sync_role != role:
|
||||
role = sync_role
|
||||
_apply_role_visual(role)
|
||||
|
||||
func _process_local(delta: float) -> void:
|
||||
if not is_on_floor():
|
||||
velocity.y -= GRAVITY * delta
|
||||
var move_dir: Vector2 = Input.get_vector("move_left", "move_right", "move_forward", "move_back") if not ui_capturing else Vector2.ZERO
|
||||
var speed: float = float(Stats.get_stat(self, "speed", 5.0))
|
||||
var basis_y := Basis(Vector3.UP, pivot.rotation.y)
|
||||
var direction := basis_y * Vector3(move_dir.x, 0.0, move_dir.y)
|
||||
if direction.length() > 0.01:
|
||||
velocity.x = direction.x * speed
|
||||
velocity.z = direction.z * speed
|
||||
var look_dir := Vector3(velocity.x, 0.0, velocity.z).normalized()
|
||||
var target_basis := Basis.looking_at(look_dir, Vector3.UP)
|
||||
mesh_holder.basis = mesh_holder.basis.slerp(target_basis, clamp(delta * 12.0, 0.0, 1.0))
|
||||
else:
|
||||
velocity.x = move_toward(velocity.x, 0.0, speed * 6.0 * delta)
|
||||
velocity.z = move_toward(velocity.z, 0.0, speed * 6.0 * delta)
|
||||
if Input.is_action_just_pressed("jump") and is_on_floor() and not ui_capturing:
|
||||
velocity.y = float(Stats.get_stat(self, "jump_velocity", 4.5))
|
||||
move_and_slide()
|
||||
|
||||
func _request_role(new_role: int) -> void:
|
||||
_set_role.rpc_id(1, new_role)
|
||||
|
||||
@rpc("any_peer", "reliable", "call_local")
|
||||
func _set_role(new_role: int) -> void:
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
if new_role == role:
|
||||
return
|
||||
role = new_role
|
||||
Stats.set_stat(self, "role", role)
|
||||
_apply_role.rpc(role)
|
||||
|
||||
@rpc("any_peer", "reliable", "call_local")
|
||||
func _apply_role(new_role: int) -> void:
|
||||
if multiplayer.multiplayer_peer != null and not (multiplayer.multiplayer_peer is OfflineMultiplayerPeer) and multiplayer.get_remote_sender_id() != 0 and multiplayer.get_remote_sender_id() != 1:
|
||||
return
|
||||
role = new_role
|
||||
if Stats.has(self):
|
||||
Stats.set_stat(self, "role", role)
|
||||
_apply_role_visual(role)
|
||||
EventBus.role_changed.emit(self, role)
|
||||
|
||||
func _apply_role_visual(r: int) -> void:
|
||||
var mesh: MeshInstance3D = mesh_holder.get_node("Mesh")
|
||||
var mat: StandardMaterial3D = mesh.get_active_material(0).duplicate() if mesh.get_active_material(0) else StandardMaterial3D.new()
|
||||
match r:
|
||||
GameState.ROLE_TANK:
|
||||
mat.albedo_color = Color(0.3, 0.5, 0.95)
|
||||
GameState.ROLE_DAMAGE:
|
||||
mat.albedo_color = Color(0.95, 0.3, 0.3)
|
||||
GameState.ROLE_HEALER:
|
||||
mat.albedo_color = Color(0.4, 0.85, 0.4)
|
||||
mesh.material_override = mat
|
||||
|
||||
@rpc("any_peer", "reliable", "call_local")
|
||||
func set_dead(value: bool) -> void:
|
||||
if multiplayer.multiplayer_peer != null and not (multiplayer.multiplayer_peer is OfflineMultiplayerPeer) and multiplayer.get_remote_sender_id() != 0 and multiplayer.get_remote_sender_id() != 1:
|
||||
return
|
||||
dead = value
|
||||
visible = not value
|
||||
collision.disabled = value
|
||||
if value:
|
||||
velocity = Vector3.ZERO
|
||||
|
||||
@rpc("any_peer", "reliable", "call_local")
|
||||
func teleport_to(pos: Vector3) -> void:
|
||||
if multiplayer.multiplayer_peer != null and not (multiplayer.multiplayer_peer is OfflineMultiplayerPeer) and multiplayer.get_remote_sender_id() != 0 and multiplayer.get_remote_sender_id() != 1:
|
||||
return
|
||||
global_position = pos
|
||||
sync_position = pos
|
||||
|
||||
func set_ui_capturing(v: bool) -> void:
|
||||
ui_capturing = v
|
||||
|
||||
func set_build_mode(v: bool) -> void:
|
||||
build_mode = v
|
||||
|
||||
func _cycle_target() -> Node:
|
||||
if current_target != null and not is_instance_valid(current_target):
|
||||
current_target = null
|
||||
var candidates: Array = []
|
||||
for n in get_tree().get_nodes_in_group("enemies"):
|
||||
if is_instance_valid(n):
|
||||
candidates.append(n)
|
||||
for n in get_tree().get_nodes_in_group("portals"):
|
||||
if is_instance_valid(n):
|
||||
candidates.append(n)
|
||||
for n in get_tree().get_nodes_in_group("gates"):
|
||||
if is_instance_valid(n):
|
||||
candidates.append(n)
|
||||
if candidates.is_empty():
|
||||
current_target = null
|
||||
return null
|
||||
candidates.sort_custom(func(a, b):
|
||||
return (a as Node3D).global_position.distance_to(global_position) < (b as Node3D).global_position.distance_to(global_position))
|
||||
if current_target == null or not current_target in candidates:
|
||||
current_target = candidates[0]
|
||||
else:
|
||||
var idx: int = candidates.find(current_target)
|
||||
current_target = candidates[(idx + 1) % candidates.size()]
|
||||
return current_target
|
||||
|
||||
func _pick_target_under_mouse() -> Node:
|
||||
var mouse := get_viewport().get_mouse_position()
|
||||
var from := camera.project_ray_origin(mouse)
|
||||
var to := from + camera.project_ray_normal(mouse) * 100.0
|
||||
var space := get_world_3d().direct_space_state
|
||||
var query := PhysicsRayQueryParameters3D.create(from, to)
|
||||
query.collision_mask = 0xFFFFFFFF
|
||||
query.exclude = [self]
|
||||
var hit := space.intersect_ray(query)
|
||||
if hit.is_empty():
|
||||
return null
|
||||
var n: Node = hit.collider
|
||||
while n != null and not (n.is_in_group("enemies") or n.is_in_group("portals") or n.is_in_group("gates") or n.is_in_group("npc")):
|
||||
n = n.get_parent()
|
||||
return n
|
||||
1
scenes/entities/player/player.gd.uid
Normal file
1
scenes/entities/player/player.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://w43lhoat7ccq
|
||||
58
scenes/entities/player/player.tscn
Normal file
58
scenes/entities/player/player.tscn
Normal file
@@ -0,0 +1,58 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://b0player00001"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/entities/player/player.gd" id="1"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
|
||||
height = 1.8
|
||||
radius = 0.35
|
||||
|
||||
[sub_resource type="CapsuleMesh" id="CapsuleMesh_1"]
|
||||
height = 1.8
|
||||
radius = 0.35
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
|
||||
albedo_color = Color(0.3, 0.55, 0.95, 1)
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_1"]
|
||||
properties/0/path = NodePath(".:sync_position")
|
||||
properties/0/spawn = true
|
||||
properties/0/replication_mode = 1
|
||||
properties/1/path = NodePath(".:sync_yaw")
|
||||
properties/1/spawn = true
|
||||
properties/1/replication_mode = 1
|
||||
|
||||
[node name="Player" type="CharacterBody3D"]
|
||||
collision_layer = 2
|
||||
collision_mask = 1
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Collision" type="CollisionShape3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0)
|
||||
shape = SubResource("CapsuleShape3D_1")
|
||||
|
||||
[node name="MeshHolder" type="Node3D" parent="."]
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="MeshHolder"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0)
|
||||
mesh = SubResource("CapsuleMesh_1")
|
||||
surface_material_override/0 = SubResource("StandardMaterial3D_1")
|
||||
|
||||
[node name="NameLabel" type="Label3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.0, 0)
|
||||
billboard = 1
|
||||
text = "Player"
|
||||
font_size = 24
|
||||
outline_size = 4
|
||||
|
||||
[node name="Pivot" type="Node3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
|
||||
|
||||
[node name="PitchPivot" type="Node3D" parent="Pivot"]
|
||||
|
||||
[node name="Camera" type="Camera3D" parent="Pivot/PitchPivot"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 5)
|
||||
current = false
|
||||
fov = 70.0
|
||||
|
||||
[node name="Synchronizer" type="MultiplayerSynchronizer" parent="."]
|
||||
replication_config = SubResource("SceneReplicationConfig_1")
|
||||
62
scenes/entities/portal/portal.gd
Normal file
62
scenes/entities/portal/portal.gd
Normal file
@@ -0,0 +1,62 @@
|
||||
extends StaticBody3D
|
||||
|
||||
@export var stats_resource: PortalStats
|
||||
@export var is_red: bool = false
|
||||
|
||||
@onready var mesh: MeshInstance3D = $Mesh
|
||||
@onready var name_label: Label3D = $NameLabel
|
||||
@onready var enter_area: Area3D = $EnterArea
|
||||
|
||||
var triggered: bool = false
|
||||
|
||||
func _enter_tree() -> void:
|
||||
set_multiplayer_authority(1)
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("portals")
|
||||
if is_red:
|
||||
add_to_group("red_portal")
|
||||
if stats_resource == null:
|
||||
stats_resource = PortalStats.new()
|
||||
stats_resource.max_health = 1.0
|
||||
stats_resource.is_red = is_red
|
||||
Stats.register(self, stats_resource)
|
||||
enter_area.body_entered.connect(_on_body_entered)
|
||||
if is_red:
|
||||
var mat: StandardMaterial3D = StandardMaterial3D.new()
|
||||
mat.albedo_color = Color(0.95, 0.2, 0.2)
|
||||
mat.emission_enabled = true
|
||||
mat.emission = Color(1.0, 0.4, 0.3)
|
||||
mat.emission_energy_multiplier = 1.5
|
||||
mesh.material_override = mat
|
||||
name_label.text = "Red Portal"
|
||||
else:
|
||||
name_label.text = "Portal"
|
||||
|
||||
func _exit_tree() -> void:
|
||||
Stats.deregister(self)
|
||||
|
||||
func _on_body_entered(body: Node) -> void:
|
||||
if not body.is_in_group("player"):
|
||||
return
|
||||
if not body.is_multiplayer_authority():
|
||||
return
|
||||
if triggered:
|
||||
return
|
||||
triggered = true
|
||||
EventBus.portal_entered.emit(self, body)
|
||||
_request_enter.rpc_id(1, is_red, global_position)
|
||||
|
||||
@rpc("any_peer", "reliable", "call_local")
|
||||
func _request_enter(red: bool, return_pos: Vector3) -> void:
|
||||
if not multiplayer.is_server() and multiplayer.multiplayer_peer != null:
|
||||
return
|
||||
var seed: int = randi()
|
||||
_do_enter.rpc(seed, red, return_pos)
|
||||
|
||||
@rpc("authority", "reliable", "call_local")
|
||||
func _do_enter(seed: int, red: bool, return_pos: Vector3) -> void:
|
||||
GameState.dungeon_seed = seed
|
||||
GameState.dungeon_red = red
|
||||
GameState.portal_return_position = return_pos
|
||||
GameState.change_scene(GameState.SCENE_DUNGEON)
|
||||
1
scenes/entities/portal/portal.gd.uid
Normal file
1
scenes/entities/portal/portal.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cny0k5s884uco
|
||||
49
scenes/entities/portal/portal.tscn
Normal file
49
scenes/entities/portal/portal.tscn
Normal file
@@ -0,0 +1,49 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://b0portal0001"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/entities/portal/portal.gd" id="1"]
|
||||
|
||||
[sub_resource type="CylinderShape3D" id="CylShape_1"]
|
||||
height = 0.4
|
||||
radius = 1.5
|
||||
|
||||
[sub_resource type="CylinderMesh" id="CylMesh_1"]
|
||||
top_radius = 1.5
|
||||
bottom_radius = 1.5
|
||||
height = 3.0
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="PortalMat"]
|
||||
albedo_color = Color(0.3, 0.5, 0.95, 1)
|
||||
emission_enabled = true
|
||||
emission = Color(0.5, 0.7, 1.0, 1)
|
||||
emission_energy_multiplier = 1.5
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereEnter"]
|
||||
radius = 1.8
|
||||
|
||||
[node name="Portal" type="StaticBody3D"]
|
||||
collision_layer = 8
|
||||
collision_mask = 0
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Collision" type="CollisionShape3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.2, 0)
|
||||
shape = SubResource("CylShape_1")
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
|
||||
mesh = SubResource("CylMesh_1")
|
||||
material_override = SubResource("PortalMat")
|
||||
|
||||
[node name="NameLabel" type="Label3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4.0, 0)
|
||||
billboard = 1
|
||||
text = "Portal"
|
||||
font_size = 22
|
||||
outline_size = 3
|
||||
|
||||
[node name="EnterArea" type="Area3D" parent="."]
|
||||
collision_layer = 0
|
||||
collision_mask = 2
|
||||
|
||||
[node name="EnterShape" type="CollisionShape3D" parent="EnterArea"]
|
||||
shape = SubResource("SphereEnter")
|
||||
36
scenes/entities/village/village.gd
Normal file
36
scenes/entities/village/village.gd
Normal file
@@ -0,0 +1,36 @@
|
||||
extends StaticBody3D
|
||||
|
||||
@export var stats_resource: VillageStats
|
||||
|
||||
@onready var mesh: MeshInstance3D = $Mesh
|
||||
@onready var label: Label3D = $Label
|
||||
@onready var healthbar: MeshInstance3D = $Healthbar
|
||||
|
||||
func _enter_tree() -> void:
|
||||
set_multiplayer_authority(1)
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("village")
|
||||
if stats_resource == null:
|
||||
stats_resource = VillageStats.new()
|
||||
stats_resource.max_health = 1000.0
|
||||
Stats.register(self, stats_resource)
|
||||
EventBus.health_changed.connect(_on_health_changed)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
label.text = "Village"
|
||||
|
||||
func _exit_tree() -> void:
|
||||
Stats.deregister(self)
|
||||
|
||||
func _on_health_changed(entity: Node, current: float, max: float) -> void:
|
||||
if entity != self:
|
||||
return
|
||||
EventBus.village_damaged.emit(current, max)
|
||||
var ratio: float = clamp(current / max if max > 0 else 0.0, 0.0, 1.0)
|
||||
healthbar.scale.x = max(0.01, ratio * 4.0)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity != self:
|
||||
return
|
||||
EventBus.village_destroyed.emit()
|
||||
EventBus.game_over.emit()
|
||||
1
scenes/entities/village/village.gd.uid
Normal file
1
scenes/entities/village/village.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://n5yrsrsav4yx
|
||||
47
scenes/entities/village/village.tscn
Normal file
47
scenes/entities/village/village.tscn
Normal file
@@ -0,0 +1,47 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://b0village001"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/entities/village/village.gd" id="1"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape_1"]
|
||||
size = Vector3(6, 4, 6)
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_1"]
|
||||
size = Vector3(6, 4, 6)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="VillageMat"]
|
||||
albedo_color = Color(0.55, 0.4, 0.25, 1)
|
||||
|
||||
[sub_resource type="QuadMesh" id="QuadMesh_HB"]
|
||||
size = Vector2(1.0, 0.25)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="HBMat"]
|
||||
shading_mode = 0
|
||||
no_depth_test = true
|
||||
albedo_color = Color(0.4, 0.85, 0.4, 1)
|
||||
|
||||
[node name="Village" type="StaticBody3D"]
|
||||
collision_layer = 1
|
||||
collision_mask = 0
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Collision" type="CollisionShape3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
|
||||
shape = SubResource("BoxShape_1")
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
|
||||
mesh = SubResource("BoxMesh_1")
|
||||
material_override = SubResource("VillageMat")
|
||||
|
||||
[node name="Label" type="Label3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5.5, 0)
|
||||
billboard = 1
|
||||
text = "Village"
|
||||
font_size = 28
|
||||
outline_size = 4
|
||||
|
||||
[node name="Healthbar" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5.0, 0)
|
||||
mesh = SubResource("QuadMesh_HB")
|
||||
material_override = SubResource("HBMat")
|
||||
gi_mode = 0
|
||||
497
scenes/hud/hud.gd
Normal file
497
scenes/hud/hud.gd
Normal file
@@ -0,0 +1,497 @@
|
||||
extends CanvasLayer
|
||||
|
||||
@onready var hp_bar: ProgressBar = %HpBar
|
||||
@onready var hp_label: Label = %HpLabel
|
||||
@onready var shield_bar: ProgressBar = %ShieldBar
|
||||
@onready var shield_label: Label = %ShieldLabel
|
||||
@onready var xp_bar: ProgressBar = %XpBar
|
||||
@onready var level_label: Label = %LevelLabel
|
||||
@onready var wave_label: Label = %WaveLabel
|
||||
@onready var timer_label: Label = %WaveTimer
|
||||
@onready var village_bar: ProgressBar = %VillageBar
|
||||
@onready var role_icon: Panel = %RoleIcon
|
||||
@onready var role_label: Label = %RoleLabel
|
||||
@onready var ability_box: HBoxContainer = %AbilityBox
|
||||
@onready var death_overlay: Control = %DeathOverlay
|
||||
@onready var death_label: Label = %DeathLabel
|
||||
@onready var chat_log: RichTextLabel = %ChatLog
|
||||
@onready var chat_input: LineEdit = %ChatInput
|
||||
@onready var inventory_panel: Control = %InventoryPanel
|
||||
@onready var inventory_list: VBoxContainer = %InventoryList
|
||||
@onready var crafting_panel: Control = %CraftingPanel
|
||||
@onready var crafting_list: VBoxContainer = %CraftingList
|
||||
@onready var build_panel: Control = %BuildPanel
|
||||
@onready var build_list: HBoxContainer = %BuildList
|
||||
@onready var dialog_panel: Control = %DialogPanel
|
||||
@onready var dialog_npc: Label = %DialogNpc
|
||||
@onready var dialog_log: RichTextLabel = %DialogLog
|
||||
@onready var dialog_input: LineEdit = %DialogInput
|
||||
@onready var map_panel: Control = %MapPanel
|
||||
@onready var map_canvas: Control = %MapCanvas
|
||||
@onready var minimap_canvas: Control = %MinimapCanvas
|
||||
@onready var pause_panel: Control = %PausePanel
|
||||
@onready var game_over_overlay: Control = %GameOverOverlay
|
||||
@onready var ability_buttons: Array = []
|
||||
|
||||
var local_player: Node = null
|
||||
var dialog_npc_node: Node = null
|
||||
var build_selected: int = 0
|
||||
var build_rotation: float = 0.0
|
||||
var build_preview: MeshInstance3D = null
|
||||
var build_active: bool = false
|
||||
var _minimap_accum: float = 0.0
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("dialog_ui")
|
||||
EventBus.health_changed.connect(_on_health_changed)
|
||||
EventBus.shield_changed.connect(_on_shield_changed)
|
||||
EventBus.cooldown_tick.connect(_on_cooldown_tick)
|
||||
EventBus.role_changed.connect(_on_role_changed)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
EventBus.entity_respawned.connect(_on_respawned)
|
||||
EventBus.wave_started.connect(_on_wave_started)
|
||||
EventBus.wave_timer_tick.connect(_on_wave_tick)
|
||||
EventBus.village_damaged.connect(_on_village_damaged)
|
||||
EventBus.village_destroyed.connect(_on_village_destroyed)
|
||||
EventBus.invasion_started.connect(_on_invasion_started)
|
||||
EventBus.xp_gained.connect(_on_xp_gained)
|
||||
EventBus.level_up.connect(_on_level_up)
|
||||
EventBus.dialog_opened.connect(_on_dialog_opened)
|
||||
EventBus.inventory_changed.connect(_on_inventory_changed)
|
||||
EventBus.chat_message.connect(_on_chat)
|
||||
chat_input.text_submitted.connect(_on_chat_submitted)
|
||||
dialog_input.text_submitted.connect(_on_dialog_submitted)
|
||||
death_overlay.visible = false
|
||||
inventory_panel.visible = false
|
||||
crafting_panel.visible = false
|
||||
build_panel.visible = false
|
||||
dialog_panel.visible = false
|
||||
map_panel.visible = false
|
||||
pause_panel.visible = false
|
||||
game_over_overlay.visible = false
|
||||
set_process(true)
|
||||
_wire_ability_buttons()
|
||||
call_deferred("_populate_build_list")
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if local_player == null:
|
||||
local_player = _find_local_player()
|
||||
if local_player:
|
||||
var role: int = int(Stats.get_stat(local_player, "role", GameState.ROLE_DAMAGE))
|
||||
_on_role_changed(local_player, role)
|
||||
_refresh_vitals()
|
||||
if build_active:
|
||||
_update_build_preview()
|
||||
_update_minimap()
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("inventory"):
|
||||
_toggle_panel(inventory_panel)
|
||||
_refresh_inventory()
|
||||
elif event.is_action_pressed("crafting"):
|
||||
_toggle_panel(crafting_panel)
|
||||
_refresh_crafting()
|
||||
elif event.is_action_pressed("build_mode"):
|
||||
_toggle_build_mode()
|
||||
elif event.is_action_pressed("map"):
|
||||
_toggle_panel(map_panel)
|
||||
elif event.is_action_pressed("chat"):
|
||||
chat_input.grab_focus()
|
||||
_capture_ui(true)
|
||||
elif event.is_action_pressed("pause"):
|
||||
if dialog_panel.visible:
|
||||
dialog_panel.visible = false
|
||||
_capture_ui(false)
|
||||
elif build_active:
|
||||
_toggle_build_mode()
|
||||
elif inventory_panel.visible or crafting_panel.visible or map_panel.visible:
|
||||
inventory_panel.visible = false
|
||||
crafting_panel.visible = false
|
||||
map_panel.visible = false
|
||||
_capture_ui(false)
|
||||
else:
|
||||
_toggle_pause()
|
||||
elif build_active:
|
||||
if event.is_action_pressed("rotate_build"):
|
||||
build_rotation = wrapf(build_rotation + PI * 0.5, 0.0, TAU)
|
||||
elif event.is_action_pressed("ability_1"):
|
||||
_select_build(0)
|
||||
elif event.is_action_pressed("ability_2"):
|
||||
_select_build(1)
|
||||
elif event.is_action_pressed("ability_3"):
|
||||
_select_build(2)
|
||||
elif event.is_action_pressed("ability_4"):
|
||||
_select_build(3)
|
||||
elif event is InputEventMouseButton and event.pressed:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT:
|
||||
_try_place()
|
||||
elif event.button_index == MOUSE_BUTTON_MIDDLE:
|
||||
_try_remove()
|
||||
|
||||
func _wire_ability_buttons() -> void:
|
||||
for c in ability_box.get_children():
|
||||
if c is Button:
|
||||
ability_buttons.append(c)
|
||||
|
||||
func _toggle_panel(panel: Control) -> void:
|
||||
panel.visible = not panel.visible
|
||||
_capture_ui(_any_panel_visible())
|
||||
|
||||
func _any_panel_visible() -> bool:
|
||||
return inventory_panel.visible or crafting_panel.visible or dialog_panel.visible or pause_panel.visible
|
||||
|
||||
func _capture_ui(v: bool) -> void:
|
||||
if local_player and local_player.has_method("set_ui_capturing"):
|
||||
local_player.set_ui_capturing(v)
|
||||
|
||||
func _find_local_player() -> Node:
|
||||
for p in get_tree().get_nodes_in_group("player"):
|
||||
if p.is_multiplayer_authority():
|
||||
return p
|
||||
return null
|
||||
|
||||
func _refresh_vitals() -> void:
|
||||
if local_player == null:
|
||||
return
|
||||
var hp: float = float(Stats.get_stat(local_player, "health", 0.0))
|
||||
var max_hp: float = float(Stats.get_stat(local_player, "max_health", 1.0))
|
||||
_on_health_changed(local_player, hp, max_hp)
|
||||
var shield: float = float(Stats.get_stat(local_player, "shield", 0.0))
|
||||
var max_shield: float = float(Stats.get_stat(local_player, "max_shield", 0.0))
|
||||
_on_shield_changed(local_player, shield, max_shield)
|
||||
var xp: float = float(Stats.get_stat(local_player, "xp", 0.0))
|
||||
var to_next: float = float(Stats.get_stat(local_player, "xp_to_next", 50.0))
|
||||
xp_bar.max_value = to_next
|
||||
xp_bar.value = xp
|
||||
level_label.text = "Lv %d" % int(Stats.get_stat(local_player, "level", 1))
|
||||
|
||||
func _on_health_changed(entity: Node, current: float, max: float) -> void:
|
||||
if entity != local_player:
|
||||
return
|
||||
hp_bar.max_value = max
|
||||
hp_bar.value = current
|
||||
hp_label.text = "%d / %d" % [int(current), int(max)]
|
||||
|
||||
func _on_shield_changed(entity: Node, current: float, max: float) -> void:
|
||||
if entity != local_player:
|
||||
return
|
||||
shield_bar.max_value = max if max > 0 else 1
|
||||
shield_bar.value = current
|
||||
shield_label.text = "%d / %d" % [int(current), int(max)]
|
||||
|
||||
func _on_cooldown_tick(entity: Node, cds: PackedFloat32Array, _max_cds: PackedFloat32Array, gcd: float) -> void:
|
||||
if entity != local_player:
|
||||
return
|
||||
for i in range(min(ability_buttons.size(), cds.size())):
|
||||
var btn: Button = ability_buttons[i]
|
||||
if cds[i] > 0.0:
|
||||
btn.text = "%d\n%.1f" % [i + 1, cds[i]]
|
||||
btn.disabled = true
|
||||
elif gcd > 0.0:
|
||||
btn.text = "%d\nGCD" % (i + 1)
|
||||
btn.disabled = true
|
||||
else:
|
||||
btn.text = "%d" % (i + 1)
|
||||
btn.disabled = false
|
||||
|
||||
func _on_role_changed(player: Node, role: int) -> void:
|
||||
if player != local_player and player != _find_local_player():
|
||||
return
|
||||
if local_player == null:
|
||||
local_player = player
|
||||
match role:
|
||||
GameState.ROLE_TANK:
|
||||
role_label.text = "T"
|
||||
role_icon.modulate = Color(0.3, 0.5, 0.95)
|
||||
GameState.ROLE_DAMAGE:
|
||||
role_label.text = "D"
|
||||
role_icon.modulate = Color(0.95, 0.3, 0.3)
|
||||
GameState.ROLE_HEALER:
|
||||
role_label.text = "H"
|
||||
role_icon.modulate = Color(0.4, 0.85, 0.4)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity != local_player:
|
||||
return
|
||||
death_overlay.visible = true
|
||||
death_label.text = "Respawning..."
|
||||
|
||||
func _on_respawned(entity: Node) -> void:
|
||||
if entity != local_player:
|
||||
return
|
||||
death_overlay.visible = false
|
||||
|
||||
func _on_wave_started(wave: int) -> void:
|
||||
wave_label.text = "Wave %d" % wave
|
||||
|
||||
func _on_wave_tick(seconds: float) -> void:
|
||||
var m := int(seconds) / 60
|
||||
var s := int(seconds) % 60
|
||||
timer_label.text = "%02d:%02d" % [m, s]
|
||||
|
||||
func _on_village_damaged(current: float, max: float) -> void:
|
||||
village_bar.max_value = max
|
||||
village_bar.value = current
|
||||
|
||||
func _on_village_destroyed() -> void:
|
||||
game_over_overlay.visible = true
|
||||
|
||||
func _on_invasion_started() -> void:
|
||||
timer_label.modulate = Color(1.0, 0.4, 0.3)
|
||||
|
||||
func _on_xp_gained(player: Node, _amount: float) -> void:
|
||||
if player != local_player:
|
||||
return
|
||||
var xp: float = float(Stats.get_stat(player, "xp", 0.0))
|
||||
var to_next: float = float(Stats.get_stat(player, "xp_to_next", 50.0))
|
||||
xp_bar.max_value = to_next
|
||||
xp_bar.value = xp
|
||||
|
||||
func _on_level_up(player: Node, new_level: int) -> void:
|
||||
if player != local_player:
|
||||
return
|
||||
level_label.text = "Lv %d" % new_level
|
||||
_refresh_vitals()
|
||||
|
||||
func _on_dialog_opened(player: Node, npc: Node) -> void:
|
||||
if player != local_player:
|
||||
return
|
||||
dialog_panel.visible = true
|
||||
dialog_npc_node = npc
|
||||
dialog_npc.text = npc.profile.display_name
|
||||
dialog_log.text = "[i]" + npc.profile.greeting + "[/i]\n"
|
||||
dialog_input.text = ""
|
||||
dialog_input.grab_focus()
|
||||
_capture_ui(true)
|
||||
|
||||
func _on_dialog_submitted(text: String) -> void:
|
||||
if dialog_npc_node == null:
|
||||
return
|
||||
var dialog_sys: Node = _find_system("DialogSystem")
|
||||
if dialog_sys == null:
|
||||
return
|
||||
dialog_log.append_text("[b]Du:[/b] " + text + "\n[i]...[/i]\n")
|
||||
dialog_input.text = ""
|
||||
dialog_sys.ask(dialog_npc_node, local_player, text)
|
||||
|
||||
func show_answer(text: String) -> void:
|
||||
if dialog_npc_node == null:
|
||||
return
|
||||
dialog_log.text = dialog_log.text.replace("[i]...[/i]\n", "")
|
||||
dialog_log.append_text("[b]" + dialog_npc_node.profile.display_name + ":[/b] " + text + "\n")
|
||||
|
||||
func _on_inventory_changed(player: Node) -> void:
|
||||
if player != local_player:
|
||||
return
|
||||
_refresh_inventory()
|
||||
|
||||
func _refresh_inventory() -> void:
|
||||
for c in inventory_list.get_children():
|
||||
c.queue_free()
|
||||
if local_player == null:
|
||||
return
|
||||
var inv_sys: Node = _find_system("InventorySystem")
|
||||
if inv_sys == null:
|
||||
return
|
||||
var inv: Dictionary = inv_sys.get_inventory(local_player)
|
||||
if inv.is_empty():
|
||||
var lbl := Label.new()
|
||||
lbl.text = "(empty)"
|
||||
inventory_list.add_child(lbl)
|
||||
return
|
||||
for k in inv.keys():
|
||||
var lbl := Label.new()
|
||||
lbl.text = "%s: %d" % [str(k), inv[k]]
|
||||
inventory_list.add_child(lbl)
|
||||
|
||||
func _refresh_crafting() -> void:
|
||||
for c in crafting_list.get_children():
|
||||
c.queue_free()
|
||||
if local_player == null:
|
||||
return
|
||||
var c_sys: Node = _find_system("CraftingSystem")
|
||||
var inv_sys: Node = _find_system("InventorySystem")
|
||||
if c_sys == null or inv_sys == null:
|
||||
return
|
||||
for r in c_sys.get_recipes():
|
||||
var btn := Button.new()
|
||||
var inputs_str: String = ""
|
||||
for k in r.inputs.keys():
|
||||
inputs_str += "%s x%d " % [str(k), r.inputs[k]]
|
||||
btn.text = "%s (%s)" % [r.name, inputs_str.strip_edges()]
|
||||
btn.disabled = not c_sys.can_craft(local_player, r)
|
||||
btn.pressed.connect(func(): c_sys.craft(local_player, r.id); _refresh_crafting())
|
||||
crafting_list.add_child(btn)
|
||||
|
||||
func _populate_build_list() -> void:
|
||||
for c in build_list.get_children():
|
||||
c.queue_free()
|
||||
var b_sys: Node = _find_system("BuildingSystem")
|
||||
if b_sys == null:
|
||||
return
|
||||
var bps: Array = b_sys.get_blueprints()
|
||||
for i in range(bps.size()):
|
||||
var btn := Button.new()
|
||||
btn.text = "%d %s\n%s x%d" % [i + 1, bps[i].name, str(bps[i].material), bps[i].cost]
|
||||
btn.toggle_mode = true
|
||||
btn.button_pressed = (i == 0)
|
||||
btn.pressed.connect(func(): _select_build(i))
|
||||
build_list.add_child(btn)
|
||||
|
||||
func _select_build(idx: int) -> void:
|
||||
build_selected = idx
|
||||
var btns := build_list.get_children()
|
||||
for i in range(btns.size()):
|
||||
if btns[i] is Button:
|
||||
(btns[i] as Button).button_pressed = (i == idx)
|
||||
if build_preview:
|
||||
_update_preview_mesh()
|
||||
|
||||
func _toggle_build_mode() -> void:
|
||||
build_active = not build_active
|
||||
build_panel.visible = build_active
|
||||
if local_player and local_player.has_method("set_build_mode"):
|
||||
local_player.set_build_mode(build_active)
|
||||
if build_active:
|
||||
_create_build_preview()
|
||||
elif build_preview:
|
||||
build_preview.queue_free()
|
||||
build_preview = null
|
||||
|
||||
func _create_build_preview() -> void:
|
||||
if build_preview:
|
||||
build_preview.queue_free()
|
||||
var world: Node = get_tree().current_scene
|
||||
if world == null:
|
||||
return
|
||||
build_preview = MeshInstance3D.new()
|
||||
build_preview.cast_shadow = MeshInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
world.add_child(build_preview)
|
||||
_update_preview_mesh()
|
||||
|
||||
func _update_preview_mesh() -> void:
|
||||
if build_preview == null:
|
||||
return
|
||||
var b_sys: Node = _find_system("BuildingSystem")
|
||||
if b_sys == null:
|
||||
return
|
||||
var bp: Dictionary = b_sys.get_blueprints()[build_selected]
|
||||
var box := BoxMesh.new()
|
||||
box.size = bp.size
|
||||
build_preview.mesh = box
|
||||
var mat := StandardMaterial3D.new()
|
||||
var c: Color = bp.color
|
||||
c.a = 0.5
|
||||
mat.albedo_color = c
|
||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
mat.flags_unshaded = true
|
||||
build_preview.material_override = mat
|
||||
|
||||
func _update_build_preview() -> void:
|
||||
if build_preview == null or local_player == null:
|
||||
return
|
||||
var b_sys: Node = _find_system("BuildingSystem")
|
||||
if b_sys == null:
|
||||
return
|
||||
var pos: Vector3 = _ground_under_cursor()
|
||||
var snapped: Vector3 = b_sys.snap_position(pos)
|
||||
var bp: Dictionary = b_sys.get_blueprints()[build_selected]
|
||||
build_preview.global_position = snapped + Vector3(0, bp.size.y * 0.5, 0)
|
||||
build_preview.rotation.y = build_rotation
|
||||
|
||||
func _ground_under_cursor() -> Vector3:
|
||||
var cam: Camera3D = local_player.camera if local_player.has_method("get") else null
|
||||
if cam == null:
|
||||
return Vector3.ZERO
|
||||
var mouse := get_viewport().get_mouse_position()
|
||||
var from := cam.project_ray_origin(mouse)
|
||||
var dir := cam.project_ray_normal(mouse)
|
||||
if abs(dir.y) < 0.001:
|
||||
return from
|
||||
var t: float = -from.y / dir.y
|
||||
if t <= 0:
|
||||
return from
|
||||
return from + dir * t
|
||||
|
||||
func _try_place() -> void:
|
||||
if local_player == null:
|
||||
return
|
||||
var b_sys: Node = _find_system("BuildingSystem")
|
||||
if b_sys == null:
|
||||
return
|
||||
var bps: Array = b_sys.get_blueprints()
|
||||
var bp: Dictionary = bps[build_selected]
|
||||
var pos: Vector3 = _ground_under_cursor()
|
||||
b_sys.place(local_player, bp.id, pos, build_rotation)
|
||||
|
||||
func _try_remove() -> void:
|
||||
if local_player == null:
|
||||
return
|
||||
var cam: Camera3D = local_player.camera if local_player.has_method("get") else null
|
||||
if cam == null:
|
||||
return
|
||||
var mouse := get_viewport().get_mouse_position()
|
||||
var from := cam.project_ray_origin(mouse)
|
||||
var to := from + cam.project_ray_normal(mouse) * 100.0
|
||||
var space: PhysicsDirectSpaceState3D = local_player.get_world_3d().direct_space_state
|
||||
var query := PhysicsRayQueryParameters3D.create(from, to)
|
||||
query.collision_mask = 16
|
||||
var hit: Dictionary = space.intersect_ray(query)
|
||||
if hit.is_empty():
|
||||
return
|
||||
var node: Node = hit.collider
|
||||
while node and not node.is_in_group("buildings"):
|
||||
node = node.get_parent()
|
||||
if node:
|
||||
var b_sys: Node = _find_system("BuildingSystem")
|
||||
if b_sys:
|
||||
b_sys.remove(local_player, node.get_path())
|
||||
|
||||
func _on_chat(_peer_id: int, sender: String, text: String) -> void:
|
||||
chat_log.append_text("[b]%s:[/b] %s\n" % [sender, text])
|
||||
|
||||
func _on_chat_submitted(text: String) -> void:
|
||||
var c_sys: Node = _find_system("ChatSystem")
|
||||
if c_sys:
|
||||
c_sys.send(text)
|
||||
chat_input.text = ""
|
||||
chat_input.release_focus()
|
||||
_capture_ui(_any_panel_visible())
|
||||
|
||||
func _toggle_pause() -> void:
|
||||
pause_panel.visible = not pause_panel.visible
|
||||
if pause_panel.visible and multiplayer.multiplayer_peer is OfflineMultiplayerPeer:
|
||||
GameState.set_paused(true)
|
||||
else:
|
||||
GameState.set_paused(false)
|
||||
_capture_ui(_any_panel_visible())
|
||||
|
||||
func _on_resume_pressed() -> void:
|
||||
pause_panel.visible = false
|
||||
GameState.set_paused(false)
|
||||
_capture_ui(_any_panel_visible())
|
||||
|
||||
func _on_quit_pressed() -> void:
|
||||
Net.disconnect_net()
|
||||
GameState.set_paused(false)
|
||||
GameState.change_scene(GameState.SCENE_MAIN_MENU)
|
||||
|
||||
func _on_game_over_restart() -> void:
|
||||
Net.disconnect_net()
|
||||
GameState.set_paused(false)
|
||||
GameState.change_scene(GameState.SCENE_MAIN_MENU)
|
||||
|
||||
func _update_minimap() -> void:
|
||||
_minimap_accum += get_process_delta_time()
|
||||
if _minimap_accum < 0.20:
|
||||
return
|
||||
_minimap_accum = 0.0
|
||||
minimap_canvas.queue_redraw()
|
||||
if map_panel.visible:
|
||||
map_canvas.queue_redraw()
|
||||
|
||||
func _find_system(name: String) -> Node:
|
||||
var n: Node = get_tree().root.get_node_or_null("World/Systems/" + name)
|
||||
if n == null:
|
||||
n = get_tree().root.get_node_or_null("Dungeon/Systems/" + name)
|
||||
return n
|
||||
1
scenes/hud/hud.gd.uid
Normal file
1
scenes/hud/hud.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dimp3y1yydhw2
|
||||
@@ -1,237 +1,411 @@
|
||||
[gd_scene format=3]
|
||||
[gd_scene load_steps=3 format=3 uid="uid://b0hud00001"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ability_active"]
|
||||
bg_color = Color(0.2, 0.2, 0.2, 0.8)
|
||||
border_width_bottom = 2
|
||||
border_width_left = 2
|
||||
border_width_right = 2
|
||||
border_width_top = 2
|
||||
border_color = Color(0.8, 0.8, 0.8, 1)
|
||||
[ext_resource type="Script" path="res://scenes/hud/hud.gd" id="1"]
|
||||
[ext_resource type="Script" path="res://scenes/hud/minimap.gd" id="2"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ability_round"]
|
||||
bg_color = Color(0.2, 0.2, 0.2, 0.8)
|
||||
border_width_bottom = 2
|
||||
border_width_left = 2
|
||||
border_width_right = 2
|
||||
border_width_top = 2
|
||||
border_color = Color(0.8, 0.8, 0.8, 1)
|
||||
corner_radius_top_left = 22
|
||||
corner_radius_top_right = 22
|
||||
corner_radius_bottom_right = 22
|
||||
corner_radius_bottom_left = 22
|
||||
[node name="HUD" type="CanvasLayer"]
|
||||
script = ExtResource("1")
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_bg"]
|
||||
bg_color = Color(0.3, 0.1, 0.1, 1)
|
||||
[node name="VitalsPanel" type="PanelContainer" parent="."]
|
||||
offset_left = 12.0
|
||||
offset_top = 12.0
|
||||
offset_right = 312.0
|
||||
offset_bottom = 130.0
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_fill"]
|
||||
bg_color = Color(0.2, 0.8, 0.2, 1)
|
||||
[node name="VBox" type="VBoxContainer" parent="VitalsPanel"]
|
||||
layout_mode = 2
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_bg"]
|
||||
bg_color = Color(0.1, 0.1, 0.3, 1)
|
||||
[node name="HpRow" type="HBoxContainer" parent="VitalsPanel/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_fill"]
|
||||
bg_color = Color(0.2, 0.5, 0.9, 1)
|
||||
|
||||
[node name="HUD" type="CanvasLayer" groups=["hud"]]
|
||||
|
||||
[node name="HealthBar" type="ProgressBar" parent="."]
|
||||
offset_left = 10.0
|
||||
offset_top = 10.0
|
||||
offset_right = 210.0
|
||||
offset_bottom = 30.0
|
||||
theme_override_styles/background = SubResource("StyleBoxFlat_health_bg")
|
||||
theme_override_styles/fill = SubResource("StyleBoxFlat_health_fill")
|
||||
[node name="HpBar" type="ProgressBar" parent="VitalsPanel/VBox/HpRow"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(220, 18)
|
||||
layout_mode = 2
|
||||
max_value = 100.0
|
||||
value = 100.0
|
||||
show_percentage = false
|
||||
|
||||
[node name="HealthLabel" type="Label" parent="HealthBar"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_font_sizes/font_size = 12
|
||||
[node name="HpLabel" type="Label" parent="VitalsPanel/VBox/HpRow"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "100/100"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="ShieldBar" type="ProgressBar" parent="."]
|
||||
offset_left = 10.0
|
||||
offset_top = 35.0
|
||||
offset_right = 210.0
|
||||
offset_bottom = 55.0
|
||||
theme_override_styles/background = SubResource("StyleBoxFlat_shield_bg")
|
||||
theme_override_styles/fill = SubResource("StyleBoxFlat_shield_fill")
|
||||
[node name="ShieldRow" type="HBoxContainer" parent="VitalsPanel/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ShieldBar" type="ProgressBar" parent="VitalsPanel/VBox/ShieldRow"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(220, 14)
|
||||
layout_mode = 2
|
||||
max_value = 100.0
|
||||
value = 0.0
|
||||
|
||||
[node name="ShieldLabel" type="Label" parent="VitalsPanel/VBox/ShieldRow"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "0/0"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="XpRow" type="HBoxContainer" parent="VitalsPanel/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LevelLabel" type="Label" parent="VitalsPanel/VBox/XpRow"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Lv 1"
|
||||
|
||||
[node name="XpBar" type="ProgressBar" parent="VitalsPanel/VBox/XpRow"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(180, 12)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
max_value = 50.0
|
||||
value = 50.0
|
||||
show_percentage = false
|
||||
value = 0.0
|
||||
|
||||
[node name="ShieldLabel" type="Label" parent="ShieldBar"]
|
||||
[node name="VillageRow" type="HBoxContainer" parent="VitalsPanel/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VillageLabelStatic" type="Label" parent="VitalsPanel/VBox/VillageRow"]
|
||||
layout_mode = 2
|
||||
text = "Village"
|
||||
|
||||
[node name="VillageBar" type="ProgressBar" parent="VitalsPanel/VBox/VillageRow"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(180, 12)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
max_value = 1000.0
|
||||
value = 1000.0
|
||||
|
||||
[node name="WaveBox" type="PanelContainer" parent="."]
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -120.0
|
||||
offset_top = 12.0
|
||||
offset_right = 120.0
|
||||
offset_bottom = 64.0
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="WaveBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="WaveLabel" type="Label" parent="WaveBox/VBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
text = "Wave 1"
|
||||
theme_override_font_sizes/font_size = 18
|
||||
|
||||
[node name="WaveTimer" type="Label" parent="WaveBox/VBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
text = "10:00"
|
||||
|
||||
[node name="MinimapPanel" type="PanelContainer" parent="."]
|
||||
anchor_left = 1.0
|
||||
anchor_right = 1.0
|
||||
offset_left = -212.0
|
||||
offset_top = 12.0
|
||||
offset_right = -12.0
|
||||
offset_bottom = 212.0
|
||||
|
||||
[node name="MinimapCanvas" type="Control" parent="MinimapPanel"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
script = ExtResource("2")
|
||||
world_size = 80.0
|
||||
|
||||
[node name="AbilityBox" type="HBoxContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_top = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -200.0
|
||||
offset_top = -76.0
|
||||
offset_right = 280.0
|
||||
offset_bottom = -12.0
|
||||
theme_override_constants/separation = 6
|
||||
|
||||
[node name="RoleIcon" type="Panel" parent="AbilityBox"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(56, 56)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="RoleLabel" type="Label" parent="AbilityBox/RoleIcon"]
|
||||
unique_name_in_owner = true
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
text = "D"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
theme_override_font_sizes/font_size = 28
|
||||
|
||||
[node name="A1" type="Button" parent="AbilityBox"]
|
||||
custom_minimum_size = Vector2(56, 56)
|
||||
layout_mode = 2
|
||||
text = "1"
|
||||
|
||||
[node name="A2" type="Button" parent="AbilityBox"]
|
||||
custom_minimum_size = Vector2(56, 56)
|
||||
layout_mode = 2
|
||||
text = "2"
|
||||
|
||||
[node name="A3" type="Button" parent="AbilityBox"]
|
||||
custom_minimum_size = Vector2(56, 56)
|
||||
layout_mode = 2
|
||||
text = "3"
|
||||
|
||||
[node name="A4" type="Button" parent="AbilityBox"]
|
||||
custom_minimum_size = Vector2(56, 56)
|
||||
layout_mode = 2
|
||||
text = "4"
|
||||
|
||||
[node name="ChatPanel" type="PanelContainer" parent="."]
|
||||
anchor_top = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 12.0
|
||||
offset_top = -200.0
|
||||
offset_right = 360.0
|
||||
offset_bottom = -12.0
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="ChatPanel"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ChatLog" type="RichTextLabel" parent="ChatPanel/VBox"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 140)
|
||||
layout_mode = 2
|
||||
bbcode_enabled = true
|
||||
scroll_following = true
|
||||
fit_content = true
|
||||
|
||||
[node name="ChatInput" type="LineEdit" parent="ChatPanel/VBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
placeholder_text = "Press Y to chat, Enter to send"
|
||||
|
||||
[node name="DeathOverlay" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="Background" type="ColorRect" parent="DeathOverlay"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "50/50"
|
||||
color = Color(0.0, 0.0, 0.0, 0.5)
|
||||
|
||||
[node name="DeathLabel" type="Label" parent="DeathOverlay"]
|
||||
unique_name_in_owner = true
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -200.0
|
||||
offset_top = -40.0
|
||||
offset_right = 200.0
|
||||
offset_bottom = 40.0
|
||||
text = "Respawning..."
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
theme_override_font_sizes/font_size = 32
|
||||
|
||||
[node name="RespawnTimer" type="Label" parent="."]
|
||||
anchors_preset = 8
|
||||
[node name="InventoryPanel" type="PanelContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -50.0
|
||||
offset_top = -30.0
|
||||
offset_right = 50.0
|
||||
offset_bottom = 30.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_font_sizes/font_size = 48
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
offset_left = -200.0
|
||||
offset_top = -200.0
|
||||
offset_right = 200.0
|
||||
offset_bottom = 200.0
|
||||
|
||||
[node name="AbilityBar" type="HBoxContainer" parent="."]
|
||||
anchors_preset = 7
|
||||
[node name="VBox" type="VBoxContainer" parent="InventoryPanel"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Title" type="Label" parent="InventoryPanel/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Inventory (I)"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="InventoryList" type="VBoxContainer" parent="InventoryPanel/VBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CraftingPanel" type="PanelContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
anchor_left = 0.5
|
||||
anchor_top = 1.0
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -260.0
|
||||
offset_top = -240.0
|
||||
offset_right = 260.0
|
||||
offset_bottom = 240.0
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="CraftingPanel"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Title" type="Label" parent="CraftingPanel/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Crafting (C)"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="CraftingList" type="VBoxContainer" parent="CraftingPanel/VBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="BuildPanel" type="PanelContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
anchor_top = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -130.0
|
||||
offset_top = -60.0
|
||||
offset_right = 130.0
|
||||
offset_bottom = -10.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
theme_override_constants/separation = 5
|
||||
offset_left = 12.0
|
||||
offset_top = -260.0
|
||||
offset_right = 360.0
|
||||
offset_bottom = -210.0
|
||||
|
||||
[node name="ClassIcon" type="Panel" parent="AbilityBar"]
|
||||
custom_minimum_size = Vector2(45, 45)
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_ability_round")
|
||||
[node name="VBox" type="VBoxContainer" parent="BuildPanel"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="AbilityBar/ClassIcon"]
|
||||
[node name="Title" type="Label" parent="BuildPanel/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Build Mode (B)"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="BuildList" type="HBoxContainer" parent="BuildPanel/VBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="DialogPanel" type="PanelContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -300.0
|
||||
offset_top = -200.0
|
||||
offset_right = 300.0
|
||||
offset_bottom = 200.0
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="DialogPanel"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="DialogNpc" type="Label" parent="DialogPanel/VBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "NPC"
|
||||
horizontal_alignment = 1
|
||||
theme_override_font_sizes/font_size = 22
|
||||
|
||||
[node name="DialogLog" type="RichTextLabel" parent="DialogPanel/VBox"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 280)
|
||||
layout_mode = 2
|
||||
bbcode_enabled = true
|
||||
scroll_following = true
|
||||
fit_content = true
|
||||
|
||||
[node name="DialogInput" type="LineEdit" parent="DialogPanel/VBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
placeholder_text = "Frage stellen, Enter senden"
|
||||
|
||||
[node name="MapPanel" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="Background" type="ColorRect" parent="MapPanel"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_font_sizes/font_size = 20
|
||||
text = "D"
|
||||
color = Color(0.0, 0.0, 0.0, 0.7)
|
||||
|
||||
[node name="MapCanvas" type="Control" parent="MapPanel"]
|
||||
unique_name_in_owner = true
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -300.0
|
||||
offset_top = -300.0
|
||||
offset_right = 300.0
|
||||
offset_bottom = 300.0
|
||||
script = ExtResource("2")
|
||||
world_size = 200.0
|
||||
draw_labels = true
|
||||
|
||||
[node name="PausePanel" type="PanelContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -150.0
|
||||
offset_top = -120.0
|
||||
offset_right = 150.0
|
||||
offset_bottom = 120.0
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="PausePanel"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Title" type="Label" parent="PausePanel/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Paused"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
theme_override_font_sizes/font_size = 28
|
||||
|
||||
[node name="Ability1" type="Panel" parent="AbilityBar"]
|
||||
custom_minimum_size = Vector2(45, 45)
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_ability_active")
|
||||
[node name="Resume" type="Button" parent="PausePanel/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Resume"
|
||||
|
||||
[node name="CooldownOverlay" type="ColorRect" parent="AbilityBar/Ability1"]
|
||||
layout_mode = 1
|
||||
[node name="QuitToMenu" type="Button" parent="PausePanel/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Quit to Menu"
|
||||
|
||||
[node name="GameOverOverlay" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0, 0, 0, 0.6)
|
||||
visible = false
|
||||
|
||||
[node name="Label" type="Label" parent="AbilityBar/Ability1"]
|
||||
[node name="Background" type="ColorRect" parent="GameOverOverlay"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "1"
|
||||
color = Color(0.2, 0.0, 0.0, 0.8)
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="GameOverOverlay"]
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -200.0
|
||||
offset_top = -100.0
|
||||
offset_right = 200.0
|
||||
offset_bottom = 100.0
|
||||
|
||||
[node name="Title" type="Label" parent="GameOverOverlay/VBox"]
|
||||
layout_mode = 2
|
||||
text = "GAME OVER"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
theme_override_font_sizes/font_size = 48
|
||||
|
||||
[node name="Ability2" type="Panel" parent="AbilityBar"]
|
||||
custom_minimum_size = Vector2(45, 45)
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_ability_active")
|
||||
[node name="Restart" type="Button" parent="GameOverOverlay/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Back to Main Menu"
|
||||
|
||||
[node name="CooldownOverlay" type="ColorRect" parent="AbilityBar/Ability2"]
|
||||
layout_mode = 1
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0, 0, 0, 0.6)
|
||||
visible = false
|
||||
|
||||
[node name="Label" type="Label" parent="AbilityBar/Ability2"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "2"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="Ability3" type="Panel" parent="AbilityBar"]
|
||||
custom_minimum_size = Vector2(45, 45)
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_ability_active")
|
||||
|
||||
[node name="CooldownOverlay" type="ColorRect" parent="AbilityBar/Ability3"]
|
||||
layout_mode = 1
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0, 0, 0, 0.6)
|
||||
visible = false
|
||||
|
||||
[node name="Label" type="Label" parent="AbilityBar/Ability3"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "3"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="Ability4" type="Panel" parent="AbilityBar"]
|
||||
custom_minimum_size = Vector2(45, 45)
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_ability_active")
|
||||
|
||||
[node name="CooldownOverlay" type="ColorRect" parent="AbilityBar/Ability4"]
|
||||
layout_mode = 1
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0, 0, 0, 0.6)
|
||||
visible = false
|
||||
|
||||
[node name="Label" type="Label" parent="AbilityBar/Ability4"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "4"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="Ability5" type="Panel" parent="AbilityBar"]
|
||||
custom_minimum_size = Vector2(45, 45)
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_ability_round")
|
||||
|
||||
[node name="CooldownOverlay" type="ColorRect" parent="AbilityBar/Ability5"]
|
||||
layout_mode = 1
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0, 0, 0, 0.6)
|
||||
visible = false
|
||||
|
||||
[node name="Label" type="Label" parent="AbilityBar/Ability5"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "P"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
[connection signal="pressed" from="PausePanel/VBox/Resume" to="." method="_on_resume_pressed"]
|
||||
[connection signal="pressed" from="PausePanel/VBox/QuitToMenu" to="." method="_on_quit_pressed"]
|
||||
[connection signal="pressed" from="GameOverOverlay/VBox/Restart" to="." method="_on_game_over_restart"]
|
||||
|
||||
40
scenes/hud/minimap.gd
Normal file
40
scenes/hud/minimap.gd
Normal file
@@ -0,0 +1,40 @@
|
||||
extends Control
|
||||
|
||||
@export var world_size: float = 200.0
|
||||
@export var label_text: String = ""
|
||||
@export var draw_labels: bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
set_process(false)
|
||||
|
||||
func _draw() -> void:
|
||||
var view_size: Vector2 = get_size()
|
||||
draw_rect(Rect2(Vector2.ZERO, view_size), Color(0.05, 0.05, 0.08, 0.85))
|
||||
draw_rect(Rect2(Vector2.ZERO, view_size), Color(0.6, 0.6, 0.6, 0.5), false, 2.0)
|
||||
var local: Node = _local_player()
|
||||
var center_offset: Vector2 = Vector2.ZERO
|
||||
if local:
|
||||
center_offset = Vector2((local as Node3D).global_position.x, (local as Node3D).global_position.z)
|
||||
var map_sys: Node = get_tree().root.get_node_or_null("World/Systems/MapSystem")
|
||||
if map_sys == null:
|
||||
return
|
||||
for entry in map_sys.get_marker_data():
|
||||
var pos: Vector3 = entry.pos
|
||||
var px: Vector2 = _to_pixel(Vector2(pos.x, pos.z), view_size, center_offset)
|
||||
if px.x < 0 or px.x > view_size.x or px.y < 0 or px.y > view_size.y:
|
||||
continue
|
||||
draw_circle(px, 3.0, entry.color)
|
||||
if draw_labels and entry.label != "":
|
||||
var f: Font = ThemeDB.fallback_font
|
||||
draw_string(f, px + Vector2(5, -5), entry.label, HORIZONTAL_ALIGNMENT_LEFT, -1, 12, Color.WHITE)
|
||||
|
||||
func _to_pixel(world: Vector2, view_size: Vector2, center: Vector2) -> Vector2:
|
||||
var rel: Vector2 = world - center
|
||||
var s: float = view_size.x / world_size
|
||||
return view_size * 0.5 + rel * s
|
||||
|
||||
func _local_player() -> Node:
|
||||
for p in get_tree().get_nodes_in_group("player"):
|
||||
if p.is_multiplayer_authority():
|
||||
return p
|
||||
return null
|
||||
1
scenes/hud/minimap.gd.uid
Normal file
1
scenes/hud/minimap.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bt0i0umsod51f
|
||||
@@ -1,21 +0,0 @@
|
||||
extends CanvasLayer
|
||||
|
||||
@onready var label: Label = $Center/VBox/Label
|
||||
@onready var button: Button = $Center/VBox/Button
|
||||
|
||||
func _ready() -> void:
|
||||
visible = false
|
||||
button.pressed.connect(_on_button)
|
||||
|
||||
func show_overlay(wave: int) -> void:
|
||||
label.text = "GAME OVER — Welle %d erreicht" % wave
|
||||
visible = true
|
||||
|
||||
func _on_button() -> void:
|
||||
GameState.reset()
|
||||
PlayerData.reset_run()
|
||||
EnemyData.entities.clear()
|
||||
BossData.entities.clear()
|
||||
PortalData.entities.clear()
|
||||
TavernData.entities.clear()
|
||||
get_tree().change_scene_to_file("res://scenes/menu/main_menu.tscn")
|
||||
@@ -1 +0,0 @@
|
||||
uid://dm00anoh5wtyu
|
||||
@@ -1,41 +0,0 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/menu/game_over_overlay.gd" id="1"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_button"]
|
||||
bg_color = Color(0.2, 0.2, 0.25, 0.9)
|
||||
border_width_bottom = 2
|
||||
border_width_left = 2
|
||||
border_width_right = 2
|
||||
border_width_top = 2
|
||||
border_color = Color(0.7, 0.7, 0.7, 1)
|
||||
|
||||
[node name="GameOverOverlay" type="CanvasLayer"]
|
||||
layer = 10
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Background" type="ColorRect" parent="."]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0, 0, 0, 0.75)
|
||||
|
||||
[node name="Center" type="CenterContainer" parent="."]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="Center"]
|
||||
custom_minimum_size = Vector2(400, 0)
|
||||
theme_override_constants/separation = 30
|
||||
|
||||
[node name="Label" type="Label" parent="Center/VBox"]
|
||||
text = "GAME OVER"
|
||||
horizontal_alignment = 1
|
||||
theme_override_font_sizes/font_size = 48
|
||||
theme_override_colors/font_color = Color(1, 0.3, 0.3, 1)
|
||||
|
||||
[node name="Button" type="Button" parent="Center/VBox"]
|
||||
custom_minimum_size = Vector2(0, 48)
|
||||
text = "Zurück zum Menü"
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_button")
|
||||
55
scenes/menu/lobby.gd
Normal file
55
scenes/menu/lobby.gd
Normal file
@@ -0,0 +1,55 @@
|
||||
extends Control
|
||||
|
||||
@onready var player_list: ItemList = %PlayerList
|
||||
@onready var status_label: Label = %StatusLabel
|
||||
@onready var start_button: Button = %StartButton
|
||||
|
||||
func _ready() -> void:
|
||||
Net.peer_connected.connect(_refresh)
|
||||
Net.peer_disconnected.connect(_refresh)
|
||||
Net.server_disconnected.connect(_on_disconnect)
|
||||
start_button.visible = Net.is_host()
|
||||
if Net.is_host():
|
||||
status_label.text = "Hosting on port %d — press Start when ready" % Net.DEFAULT_PORT
|
||||
else:
|
||||
status_label.text = "Joined — waiting for host to start"
|
||||
_refresh(0)
|
||||
|
||||
func _refresh(_id: int) -> void:
|
||||
player_list.clear()
|
||||
var ids: Array = []
|
||||
if multiplayer.multiplayer_peer == null:
|
||||
ids.append(1)
|
||||
else:
|
||||
ids.append(multiplayer.get_unique_id())
|
||||
for p in multiplayer.get_peers():
|
||||
if not p in ids:
|
||||
ids.append(p)
|
||||
ids.sort()
|
||||
for id in ids:
|
||||
var n: String = Net.player_names.get(id, "Player %d" % id)
|
||||
player_list.add_item("[%d] %s" % [id, n])
|
||||
|
||||
func _on_start_pressed() -> void:
|
||||
if not Net.is_host():
|
||||
return
|
||||
_start_world.rpc()
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func _start_world() -> void:
|
||||
GameState.change_scene(GameState.SCENE_WORLD)
|
||||
|
||||
var _leaving: bool = false
|
||||
|
||||
func _on_back_pressed() -> void:
|
||||
if _leaving:
|
||||
return
|
||||
_leaving = true
|
||||
Net.disconnect_net()
|
||||
GameState.change_scene(GameState.SCENE_MAIN_MENU)
|
||||
|
||||
func _on_disconnect() -> void:
|
||||
if _leaving:
|
||||
return
|
||||
_leaving = true
|
||||
GameState.change_scene(GameState.SCENE_MAIN_MENU)
|
||||
1
scenes/menu/lobby.gd.uid
Normal file
1
scenes/menu/lobby.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d3gurtlgr2a6o
|
||||
58
scenes/menu/lobby.tscn
Normal file
58
scenes/menu/lobby.tscn
Normal file
@@ -0,0 +1,58 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://b0lobby0001"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/menu/lobby.gd" id="1"]
|
||||
|
||||
[node name="Lobby" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Background" type="ColorRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0.08, 0.08, 0.12, 1)
|
||||
|
||||
[node name="Center" type="CenterContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="Center"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(420, 0)
|
||||
|
||||
[node name="Title" type="Label" parent="Center/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Lobby"
|
||||
horizontal_alignment = 1
|
||||
theme_override_font_sizes/font_size = 36
|
||||
|
||||
[node name="StatusLabel" type="Label" parent="Center/VBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
text = ""
|
||||
|
||||
[node name="PlayerList" type="ItemList" parent="Center/VBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(0, 180)
|
||||
|
||||
[node name="StartButton" type="Button" parent="Center/VBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Start"
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
|
||||
[node name="BackButton" type="Button" parent="Center/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Back"
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
|
||||
[connection signal="pressed" from="Center/VBox/StartButton" to="." method="_on_start_pressed"]
|
||||
[connection signal="pressed" from="Center/VBox/BackButton" to="." method="_on_back_pressed"]
|
||||
@@ -1,32 +1,47 @@
|
||||
extends CanvasLayer
|
||||
extends Control
|
||||
|
||||
@onready var singleplayer_button: Button = $Center/VBox/SingleplayerButton
|
||||
@onready var host_button: Button = $Center/VBox/HostButton
|
||||
@onready var join_button: Button = $Center/VBox/JoinButton
|
||||
@onready var quit_button: Button = $Center/VBox/QuitButton
|
||||
@onready var name_input: LineEdit = %NameInput
|
||||
@onready var ip_input: LineEdit = %IpInput
|
||||
@onready var status_label: Label = %StatusLabel
|
||||
|
||||
func _ready() -> void:
|
||||
singleplayer_button.pressed.connect(_on_singleplayer)
|
||||
host_button.pressed.connect(_on_host)
|
||||
join_button.pressed.connect(_on_join)
|
||||
quit_button.pressed.connect(_on_quit)
|
||||
host_button.disabled = true
|
||||
join_button.disabled = true
|
||||
name_input.text = Net.local_name
|
||||
Net.connected_to_server.connect(_on_connected)
|
||||
Net.connection_failed.connect(_on_failed)
|
||||
|
||||
func _on_singleplayer() -> void:
|
||||
GameState.reset()
|
||||
PlayerData.reset_run()
|
||||
EnemyData.entities.clear()
|
||||
BossData.entities.clear()
|
||||
PortalData.entities.clear()
|
||||
TavernData.entities.clear()
|
||||
get_tree().change_scene_to_file("res://scenes/world/world.tscn")
|
||||
func _on_singleplayer_pressed() -> void:
|
||||
Net.local_name = name_input.text.strip_edges() if name_input.text.strip_edges() != "" else "Player"
|
||||
GameState.reset_run()
|
||||
if Net.host_singleplayer():
|
||||
GameState.change_scene(GameState.SCENE_WORLD)
|
||||
|
||||
func _on_host() -> void:
|
||||
pass
|
||||
func _on_host_pressed() -> void:
|
||||
Net.local_name = name_input.text.strip_edges() if name_input.text.strip_edges() != "" else "Host"
|
||||
GameState.reset_run()
|
||||
if Net.host():
|
||||
status_label.text = "Hosting on port %d" % Net.DEFAULT_PORT
|
||||
GameState.change_scene(GameState.SCENE_LOBBY)
|
||||
else:
|
||||
status_label.text = "Hosting failed."
|
||||
|
||||
func _on_join() -> void:
|
||||
pass
|
||||
func _on_join_pressed() -> void:
|
||||
Net.local_name = name_input.text.strip_edges() if name_input.text.strip_edges() != "" else "Client"
|
||||
var addr := ip_input.text.strip_edges()
|
||||
if addr == "":
|
||||
addr = "127.0.0.1"
|
||||
status_label.text = "Connecting to %s..." % addr
|
||||
if not Net.join(addr):
|
||||
status_label.text = "Join failed."
|
||||
|
||||
func _on_quit() -> void:
|
||||
func _on_options_pressed() -> void:
|
||||
GameState.change_scene(GameState.SCENE_OPTIONS)
|
||||
|
||||
func _on_quit_pressed() -> void:
|
||||
get_tree().quit()
|
||||
|
||||
func _on_connected() -> void:
|
||||
status_label.text = "Connected."
|
||||
GameState.change_scene(GameState.SCENE_LOBBY)
|
||||
|
||||
func _on_failed() -> void:
|
||||
status_label.text = "Connection failed."
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://b4m6byh4k2mg7
|
||||
uid://d0k5a5qreg1uu
|
||||
|
||||
@@ -1,57 +1,115 @@
|
||||
[gd_scene format=3]
|
||||
[gd_scene format=3 uid="uid://bm5gsrlveyvnk"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/menu/main_menu.gd" id="1"]
|
||||
[ext_resource type="Script" uid="uid://d0k5a5qreg1uu" path="res://scenes/menu/main_menu.gd" id="1"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_button"]
|
||||
bg_color = Color(0.2, 0.2, 0.25, 0.9)
|
||||
border_width_bottom = 2
|
||||
border_width_left = 2
|
||||
border_width_right = 2
|
||||
border_width_top = 2
|
||||
border_color = Color(0.7, 0.7, 0.7, 1)
|
||||
|
||||
[node name="MainMenu" type="CanvasLayer"]
|
||||
[node name="MainMenu" type="Control" unique_id=282057399]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Background" type="ColorRect" parent="."]
|
||||
[node name="Background" type="ColorRect" parent="." unique_id=1892952438]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0.08, 0.08, 0.12, 1)
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
color = Color(0.1, 0.1, 0.15, 1)
|
||||
|
||||
[node name="Center" type="CenterContainer" parent="."]
|
||||
[node name="Center" type="CenterContainer" parent="." unique_id=303933157]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="Center"]
|
||||
custom_minimum_size = Vector2(320, 0)
|
||||
theme_override_constants/separation = 20
|
||||
[node name="VBox" type="VBoxContainer" parent="Center" unique_id=576802246]
|
||||
custom_minimum_size = Vector2(420, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Title" type="Label" parent="Center/VBox"]
|
||||
[node name="Title" type="Label" parent="Center/VBox" unique_id=1058778255]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 56
|
||||
text = "MMO"
|
||||
horizontal_alignment = 1
|
||||
theme_override_font_sizes/font_size = 64
|
||||
|
||||
[node name="Spacer" type="Control" parent="Center/VBox"]
|
||||
[node name="Spacer1" type="Control" parent="Center/VBox" unique_id=707057681]
|
||||
custom_minimum_size = Vector2(0, 24)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NameRow" type="HBoxContainer" parent="Center/VBox" unique_id=1693567332]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NameLabel" type="Label" parent="Center/VBox/NameRow" unique_id=1012504876]
|
||||
custom_minimum_size = Vector2(80, 0)
|
||||
layout_mode = 2
|
||||
text = "Name:"
|
||||
|
||||
[node name="NameInput" type="LineEdit" parent="Center/VBox/NameRow" unique_id=318387796]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Player"
|
||||
|
||||
[node name="IpRow" type="HBoxContainer" parent="Center/VBox" unique_id=674693362]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="IpLabel" type="Label" parent="Center/VBox/IpRow" unique_id=222908556]
|
||||
custom_minimum_size = Vector2(80, 0)
|
||||
layout_mode = 2
|
||||
text = "Server IP:"
|
||||
|
||||
[node name="IpInput" type="LineEdit" parent="Center/VBox/IpRow" unique_id=99390081]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "127.0.0.1"
|
||||
|
||||
[node name="Spacer2" type="Control" parent="Center/VBox" unique_id=979762154]
|
||||
custom_minimum_size = Vector2(0, 16)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Singleplayer" type="Button" parent="Center/VBox" unique_id=156537849]
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
|
||||
[node name="SingleplayerButton" type="Button" parent="Center/VBox"]
|
||||
custom_minimum_size = Vector2(0, 48)
|
||||
layout_mode = 2
|
||||
text = "Singleplayer"
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_button")
|
||||
|
||||
[node name="HostButton" type="Button" parent="Center/VBox"]
|
||||
custom_minimum_size = Vector2(0, 48)
|
||||
text = "Host (bald)"
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_button")
|
||||
[node name="Host" type="Button" parent="Center/VBox" unique_id=1117468477]
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
layout_mode = 2
|
||||
text = "Host"
|
||||
|
||||
[node name="JoinButton" type="Button" parent="Center/VBox"]
|
||||
custom_minimum_size = Vector2(0, 48)
|
||||
text = "Join (bald)"
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_button")
|
||||
[node name="Join" type="Button" parent="Center/VBox" unique_id=1669660415]
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
layout_mode = 2
|
||||
text = "Join"
|
||||
|
||||
[node name="QuitButton" type="Button" parent="Center/VBox"]
|
||||
custom_minimum_size = Vector2(0, 48)
|
||||
[node name="Options" type="Button" parent="Center/VBox" unique_id=1739276223]
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
layout_mode = 2
|
||||
text = "Options"
|
||||
|
||||
[node name="Quit" type="Button" parent="Center/VBox" unique_id=182300116]
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
layout_mode = 2
|
||||
text = "Quit"
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_button")
|
||||
|
||||
[node name="Spacer3" type="Control" parent="Center/VBox" unique_id=1580062158]
|
||||
custom_minimum_size = Vector2(0, 16)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="StatusLabel" type="Label" parent="Center/VBox" unique_id=643981255]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[connection signal="pressed" from="Center/VBox/Singleplayer" to="." method="_on_singleplayer_pressed"]
|
||||
[connection signal="pressed" from="Center/VBox/Host" to="." method="_on_host_pressed"]
|
||||
[connection signal="pressed" from="Center/VBox/Join" to="." method="_on_join_pressed"]
|
||||
[connection signal="pressed" from="Center/VBox/Options" to="." method="_on_options_pressed"]
|
||||
[connection signal="pressed" from="Center/VBox/Quit" to="." method="_on_quit_pressed"]
|
||||
|
||||
49
scenes/menu/options_menu.gd
Normal file
49
scenes/menu/options_menu.gd
Normal file
@@ -0,0 +1,49 @@
|
||||
extends Control
|
||||
|
||||
const SETTINGS_PATH: String = "user://settings.cfg"
|
||||
|
||||
@onready var master: HSlider = %MasterSlider
|
||||
@onready var music: HSlider = %MusicSlider
|
||||
@onready var sfx: HSlider = %SfxSlider
|
||||
@onready var sens: HSlider = %SensSlider
|
||||
|
||||
func _ready() -> void:
|
||||
load_settings()
|
||||
master.value_changed.connect(_on_master_changed)
|
||||
music.value_changed.connect(_on_music_changed)
|
||||
sfx.value_changed.connect(_on_sfx_changed)
|
||||
|
||||
func load_settings() -> void:
|
||||
var cfg := ConfigFile.new()
|
||||
if cfg.load(SETTINGS_PATH) == OK:
|
||||
master.value = cfg.get_value("audio", "master", 1.0)
|
||||
music.value = cfg.get_value("audio", "music", 1.0)
|
||||
sfx.value = cfg.get_value("audio", "sfx", 1.0)
|
||||
sens.value = cfg.get_value("input", "sens", 1.0)
|
||||
_apply_audio()
|
||||
|
||||
func save_settings() -> void:
|
||||
var cfg := ConfigFile.new()
|
||||
cfg.set_value("audio", "master", master.value)
|
||||
cfg.set_value("audio", "music", music.value)
|
||||
cfg.set_value("audio", "sfx", sfx.value)
|
||||
cfg.set_value("input", "sens", sens.value)
|
||||
cfg.save(SETTINGS_PATH)
|
||||
|
||||
func _on_master_changed(_v: float) -> void:
|
||||
_apply_audio()
|
||||
save_settings()
|
||||
|
||||
func _on_music_changed(_v: float) -> void:
|
||||
_apply_audio()
|
||||
save_settings()
|
||||
|
||||
func _on_sfx_changed(_v: float) -> void:
|
||||
_apply_audio()
|
||||
save_settings()
|
||||
|
||||
func _apply_audio() -> void:
|
||||
AudioServer.set_bus_volume_db(0, linear_to_db(max(0.001, master.value)))
|
||||
|
||||
func _on_back_pressed() -> void:
|
||||
GameState.change_scene(GameState.SCENE_MAIN_MENU)
|
||||
1
scenes/menu/options_menu.gd.uid
Normal file
1
scenes/menu/options_menu.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://fpukylp3yy1c
|
||||
108
scenes/menu/options_menu.tscn
Normal file
108
scenes/menu/options_menu.tscn
Normal file
@@ -0,0 +1,108 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://b0options0001"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/menu/options_menu.gd" id="1"]
|
||||
|
||||
[node name="OptionsMenu" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Background" type="ColorRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0.1, 0.1, 0.15, 1)
|
||||
|
||||
[node name="Center" type="CenterContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="Center"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(420, 0)
|
||||
|
||||
[node name="Title" type="Label" parent="Center/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Options"
|
||||
horizontal_alignment = 1
|
||||
theme_override_font_sizes/font_size = 36
|
||||
|
||||
[node name="MasterRow" type="HBoxContainer" parent="Center/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="MasterLabel" type="Label" parent="Center/VBox/MasterRow"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(110, 0)
|
||||
text = "Master"
|
||||
|
||||
[node name="MasterSlider" type="HSlider" parent="Center/VBox/MasterRow"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
min_value = 0.0
|
||||
max_value = 1.0
|
||||
step = 0.05
|
||||
value = 1.0
|
||||
|
||||
[node name="MusicRow" type="HBoxContainer" parent="Center/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="MusicLabel" type="Label" parent="Center/VBox/MusicRow"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(110, 0)
|
||||
text = "Music"
|
||||
|
||||
[node name="MusicSlider" type="HSlider" parent="Center/VBox/MusicRow"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
min_value = 0.0
|
||||
max_value = 1.0
|
||||
step = 0.05
|
||||
value = 1.0
|
||||
|
||||
[node name="SfxRow" type="HBoxContainer" parent="Center/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SfxLabel" type="Label" parent="Center/VBox/SfxRow"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(110, 0)
|
||||
text = "SFX"
|
||||
|
||||
[node name="SfxSlider" type="HSlider" parent="Center/VBox/SfxRow"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
min_value = 0.0
|
||||
max_value = 1.0
|
||||
step = 0.05
|
||||
value = 1.0
|
||||
|
||||
[node name="SensRow" type="HBoxContainer" parent="Center/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SensLabel" type="Label" parent="Center/VBox/SensRow"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(110, 0)
|
||||
text = "Mouse Sens"
|
||||
|
||||
[node name="SensSlider" type="HSlider" parent="Center/VBox/SensRow"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
min_value = 0.1
|
||||
max_value = 3.0
|
||||
step = 0.1
|
||||
value = 1.0
|
||||
|
||||
[node name="Back" type="Button" parent="Center/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Back"
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
|
||||
[connection signal="pressed" from="Center/VBox/Back" to="." method="_on_back_pressed"]
|
||||
@@ -1,9 +0,0 @@
|
||||
extends Node
|
||||
|
||||
@onready var player: CharacterBody3D = get_parent()
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
for i in range(5):
|
||||
if event.is_action_pressed("ability_%s" % (i + 1)):
|
||||
EventBus.ability_use.emit(player, i)
|
||||
return
|
||||
@@ -1 +0,0 @@
|
||||
uid://hh5yw7vcjdqr
|
||||
@@ -1,30 +0,0 @@
|
||||
extends Node3D
|
||||
|
||||
const SENSITIVITY := 0.003
|
||||
const PITCH_MIN := -80.0
|
||||
const PITCH_MAX := 80.0
|
||||
|
||||
var pitch := -0.3
|
||||
var camera_yaw := 0.0
|
||||
var player_yaw := -2.356
|
||||
|
||||
func _ready() -> void:
|
||||
get_parent().rotation.y = player_yaw
|
||||
rotation = Vector3(pitch, camera_yaw, 0)
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseMotion:
|
||||
var lmb := Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)
|
||||
var rmb := Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT)
|
||||
|
||||
if lmb or rmb:
|
||||
pitch -= event.relative.y * SENSITIVITY
|
||||
pitch = clamp(pitch, deg_to_rad(PITCH_MIN), deg_to_rad(PITCH_MAX))
|
||||
|
||||
if rmb:
|
||||
player_yaw -= event.relative.x * SENSITIVITY
|
||||
get_parent().rotation.y = player_yaw
|
||||
else:
|
||||
camera_yaw -= event.relative.x * SENSITIVITY
|
||||
|
||||
rotation = Vector3(pitch, camera_yaw, 0)
|
||||
@@ -1 +0,0 @@
|
||||
uid://cohjyjge1kqxb
|
||||
@@ -1,59 +0,0 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
@export var stats: PlayerStats
|
||||
|
||||
var anim_player: AnimationPlayer = null
|
||||
var current_anim: String = ""
|
||||
var attack_lock_until: float = 0.0
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("player")
|
||||
PlayerData.init_from_resource(stats)
|
||||
if PlayerData.returning_from_dungeon:
|
||||
global_position = PlayerData.portal_position + Vector3(0, 1, -5)
|
||||
PlayerData.returning_from_dungeon = false
|
||||
elif PlayerData.dungeon_cleared:
|
||||
PlayerData.clear_cache()
|
||||
anim_player = get_node_or_null("Mesh/Model/AnimationPlayer")
|
||||
_play_anim("Idle")
|
||||
EventBus.attack_executed.connect(_on_attack_executed)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
EventBus.player_respawned.connect(_on_player_respawned)
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if not PlayerData.alive:
|
||||
return
|
||||
var now: float = Time.get_ticks_msec() / 1000.0
|
||||
if now < attack_lock_until:
|
||||
return
|
||||
if velocity.length() > 0.1:
|
||||
_play_anim("Running_A")
|
||||
else:
|
||||
_play_anim("Idle")
|
||||
|
||||
func _play_anim(anim_name: String, loop: bool = true) -> void:
|
||||
if not anim_player:
|
||||
return
|
||||
if current_anim == anim_name:
|
||||
return
|
||||
if not anim_player.has_animation(anim_name):
|
||||
return
|
||||
var anim: Animation = anim_player.get_animation(anim_name)
|
||||
if anim:
|
||||
anim.loop_mode = Animation.LOOP_LINEAR if loop else Animation.LOOP_NONE
|
||||
anim_player.play(anim_name)
|
||||
current_anim = anim_name
|
||||
|
||||
func _on_attack_executed(attacker: Node, _pos: Vector3, _dir: Vector3, _damage: float) -> void:
|
||||
if attacker != self:
|
||||
return
|
||||
_play_anim("1H_Melee_Attack_Chop", false)
|
||||
attack_lock_until = Time.get_ticks_msec() / 1000.0 + 0.5
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity != PlayerData:
|
||||
return
|
||||
_play_anim("Death_A", false)
|
||||
|
||||
func _on_player_respawned(_player: Node) -> void:
|
||||
_play_anim("Idle")
|
||||
@@ -1 +0,0 @@
|
||||
uid://cx6k5473yxno
|
||||
@@ -1,37 +0,0 @@
|
||||
extends Node
|
||||
|
||||
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
|
||||
|
||||
@onready var player: CharacterBody3D = get_parent()
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not player.is_on_floor():
|
||||
player.velocity.y -= gravity * delta
|
||||
|
||||
if Input.is_action_just_pressed("ui_accept") and player.is_on_floor():
|
||||
player.velocity.y = PlayerData.jump_velocity
|
||||
|
||||
var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
|
||||
var camera_pivot := player.get_node("CameraPivot") as Node3D
|
||||
var forward := -camera_pivot.global_transform.basis.z
|
||||
forward.y = 0
|
||||
forward = forward.normalized()
|
||||
var right := camera_pivot.global_transform.basis.x
|
||||
right.y = 0
|
||||
right = right.normalized()
|
||||
|
||||
var direction := (forward * -input_dir.y + right * input_dir.x).normalized()
|
||||
|
||||
if direction:
|
||||
player.velocity.x = direction.x * PlayerData.speed
|
||||
player.velocity.z = direction.z * PlayerData.speed
|
||||
var world_yaw: float = atan2(-direction.x, -direction.z)
|
||||
var local_yaw: float = world_yaw - player.rotation.y
|
||||
var mesh: Node3D = player.get_node_or_null("Mesh") as Node3D
|
||||
if mesh:
|
||||
mesh.rotation.y = lerp_angle(mesh.rotation.y, local_yaw, 10.0 * delta)
|
||||
else:
|
||||
player.velocity.x = move_toward(player.velocity.x, 0, PlayerData.speed)
|
||||
player.velocity.z = move_toward(player.velocity.z, 0, PlayerData.speed)
|
||||
|
||||
player.move_and_slide()
|
||||
@@ -1 +0,0 @@
|
||||
uid://fg87dh8fbc8
|
||||
@@ -1,45 +0,0 @@
|
||||
[gd_scene format=3 uid="uid://cdnkbt1f0db7e"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cx6k5473yxno" path="res://scenes/player/init.gd" id="1"]
|
||||
[ext_resource type="Script" uid="uid://cohjyjge1kqxb" path="res://scenes/player/camera.gd" id="2"]
|
||||
[ext_resource type="Script" uid="uid://fg87dh8fbc8" path="res://scenes/player/movement.gd" id="3"]
|
||||
[ext_resource type="Script" uid="uid://hh5yw7vcjdqr" path="res://scenes/player/ability.gd" id="4"]
|
||||
[ext_resource type="Script" uid="uid://b05nkuryipwny" path="res://scenes/player/targeting.gd" id="8"]
|
||||
[ext_resource type="Script" uid="uid://dhomrampxola4" path="res://scenes/player/role/role.gd" id="10"]
|
||||
[ext_resource type="Resource" uid="uid://btd0g0oiulssq" path="res://scenes/player/player_stats.tres" id="14"]
|
||||
[ext_resource type="PackedScene" path="res://assets/models/characters/Knight.glb" id="15"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
|
||||
radius = 0.3
|
||||
height = 1.8
|
||||
|
||||
[node name="Player" type="CharacterBody3D" unique_id=197716516]
|
||||
script = ExtResource("1")
|
||||
stats = ExtResource("14")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=311205642]
|
||||
shape = SubResource("CapsuleShape3D_1")
|
||||
|
||||
[node name="Mesh" type="Node3D" parent="." unique_id=1514179122]
|
||||
|
||||
[node name="Model" parent="Mesh" instance=ExtResource("15")]
|
||||
transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, -0.9, 0)
|
||||
|
||||
[node name="CameraPivot" type="Node3D" parent="." unique_id=1881685457]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
|
||||
script = ExtResource("2")
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="CameraPivot" unique_id=2062990383]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.966, 0.259, 0, -0.259, 0.966, 0, 2, 5)
|
||||
|
||||
[node name="Movement" type="Node" parent="." unique_id=811179177]
|
||||
script = ExtResource("3")
|
||||
|
||||
[node name="Ability" type="Node" parent="." unique_id=1184596245]
|
||||
script = ExtResource("4")
|
||||
|
||||
[node name="Targeting" type="Node" parent="." unique_id=1974574662]
|
||||
script = ExtResource("8")
|
||||
|
||||
[node name="Role" type="Node" parent="." unique_id=1637643687]
|
||||
script = ExtResource("10")
|
||||
@@ -1,10 +0,0 @@
|
||||
extends BaseStats
|
||||
class_name PlayerStats
|
||||
|
||||
@export var speed := 5.0
|
||||
@export var jump_velocity := 4.5
|
||||
@export var target_range := 20.0
|
||||
@export var combat_timeout := 3.0
|
||||
@export var respawn_time := 3.0
|
||||
@export var gcd_time := 0.5
|
||||
@export var aa_cooldown := 0.5
|
||||
@@ -1 +0,0 @@
|
||||
uid://ypyntbavbsto
|
||||
@@ -1,8 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="PlayerStats" format=3 uid="uid://btd0g0oiulssq"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ypyntbavbsto" path="res://scenes/player/player_stats.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
health_regen = 1.0
|
||||
max_shield = 50.0
|
||||
@@ -1,16 +0,0 @@
|
||||
extends Resource
|
||||
class_name Ability
|
||||
|
||||
enum Type { SINGLE, AOE, UTILITY, ULT, PASSIVE }
|
||||
|
||||
@export var ability_name: String = ""
|
||||
@export var type: Type = Type.SINGLE
|
||||
@export var damage: float = 0.0
|
||||
@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 = ""
|
||||
@export var is_heal: bool = false
|
||||
@export var passive_stat: String = "damage"
|
||||
@export var element: int = 0
|
||||
@@ -1 +0,0 @@
|
||||
uid://c03xbbf3yhfl3
|
||||
@@ -1,7 +0,0 @@
|
||||
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
|
||||
@@ -1 +0,0 @@
|
||||
uid://voedgs25cwrb
|
||||
@@ -1,13 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://bpx3l13iuynfv"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "AOE"
|
||||
type = 1
|
||||
damage = 20.0
|
||||
ability_range = 5.0
|
||||
cooldown = 3.0
|
||||
icon = "2"
|
||||
element = 1
|
||||
@@ -1,12 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://dadpl32yujwhe"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Damage Boost"
|
||||
type = 4
|
||||
damage = 50.0
|
||||
ability_range = 0.0
|
||||
uses_gcd = false
|
||||
icon = "P"
|
||||
@@ -1,12 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://dwvc8b3cmce8l"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Single"
|
||||
damage = 30.0
|
||||
ability_range = 20.0
|
||||
cooldown = 2.0
|
||||
icon = "1"
|
||||
element = 1
|
||||
@@ -1,14 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://s32wvlww2ls2"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Burst"
|
||||
type = 3
|
||||
damage = 10.0
|
||||
ability_range = 20.0
|
||||
cooldown = 15.0
|
||||
aoe_radius = 3.0
|
||||
icon = "4"
|
||||
element = 1
|
||||
@@ -1,12 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Shield Reset"
|
||||
type = 2
|
||||
ability_range = 0.0
|
||||
cooldown = 5.0
|
||||
uses_gcd = false
|
||||
icon = "3"
|
||||
@@ -1,14 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="AbilitySet" format=3 uid="uid://beodknb6i1pm4"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://voedgs25cwrb" path="res://scenes/player/role/ability_set.gd" id="1"]
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1_ability"]
|
||||
[ext_resource type="Resource" uid="uid://dwvc8b3cmce8l" path="res://scenes/player/role/damage/abilities/single.tres" id="2"]
|
||||
[ext_resource type="Resource" uid="uid://bpx3l13iuynfv" path="res://scenes/player/role/damage/abilities/aoe.tres" id="3"]
|
||||
[ext_resource type="Resource" path="res://scenes/player/role/damage/abilities/utility.tres" id="4"]
|
||||
[ext_resource type="Resource" uid="uid://s32wvlww2ls2" path="res://scenes/player/role/damage/abilities/ult.tres" id="5"]
|
||||
[ext_resource type="Resource" uid="uid://dadpl32yujwhe" path="res://scenes/player/role/damage/abilities/passive.tres" id="6"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
abilities = Array[ExtResource("1_ability")]([ExtResource("2"), ExtResource("3"), ExtResource("4"), ExtResource("5"), ExtResource("6")])
|
||||
aa_damage = 25.0
|
||||
@@ -1,13 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://m1kgk2uugnex"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Circle of Healing"
|
||||
type = 1
|
||||
damage = 10.0
|
||||
ability_range = 20.0
|
||||
cooldown = 3.0
|
||||
icon = "2"
|
||||
is_heal = true
|
||||
@@ -1,13 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Heal Aura"
|
||||
type = 4
|
||||
damage = 50.0
|
||||
ability_range = 0.0
|
||||
uses_gcd = false
|
||||
icon = "P"
|
||||
passive_stat = "heal"
|
||||
@@ -1,12 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://cqw1jy6kqvmnj"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Heal"
|
||||
damage = 15.0
|
||||
ability_range = 20.0
|
||||
cooldown = 2.0
|
||||
icon = "1"
|
||||
is_heal = true
|
||||
@@ -1,14 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://d04nu1leyki16"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Divine Light"
|
||||
type = 3
|
||||
damage = 25.0
|
||||
ability_range = 20.0
|
||||
cooldown = 15.0
|
||||
aoe_radius = 3.0
|
||||
icon = "4"
|
||||
is_heal = true
|
||||
@@ -1,12 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Shield Reset"
|
||||
type = 2
|
||||
ability_range = 0.0
|
||||
cooldown = 5.0
|
||||
uses_gcd = false
|
||||
icon = "3"
|
||||
@@ -1,16 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="AbilitySet" format=3 uid="uid://kcwuhnqy34mj"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://voedgs25cwrb" path="res://scenes/player/role/ability_set.gd" id="1"]
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1_ability"]
|
||||
[ext_resource type="Resource" path="res://scenes/player/role/healer/abilities/single.tres" id="2"]
|
||||
[ext_resource type="Resource" path="res://scenes/player/role/healer/abilities/aoe.tres" id="3"]
|
||||
[ext_resource type="Resource" path="res://scenes/player/role/healer/abilities/utility.tres" id="4"]
|
||||
[ext_resource type="Resource" path="res://scenes/player/role/healer/abilities/ult.tres" id="5"]
|
||||
[ext_resource type="Resource" path="res://scenes/player/role/healer/abilities/passive.tres" id="6"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
abilities = Array[ExtResource("1_ability")]([ExtResource("2"), ExtResource("3"), ExtResource("4"), ExtResource("5"), ExtResource("6")])
|
||||
aa_damage = 1.0
|
||||
aa_range = 20.0
|
||||
aa_is_heal = true
|
||||
@@ -1,14 +0,0 @@
|
||||
extends Node
|
||||
|
||||
@onready var player: CharacterBody3D = get_parent()
|
||||
|
||||
func _ready() -> void:
|
||||
EventBus.role_change_requested.emit(player, PlayerData.current_role)
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("class_tank"):
|
||||
EventBus.role_change_requested.emit(player, PlayerData.Role.TANK)
|
||||
elif event.is_action_pressed("class_damage"):
|
||||
EventBus.role_change_requested.emit(player, PlayerData.Role.DAMAGE)
|
||||
elif event.is_action_pressed("class_healer"):
|
||||
EventBus.role_change_requested.emit(player, PlayerData.Role.HEALER)
|
||||
@@ -1 +0,0 @@
|
||||
uid://dhomrampxola4
|
||||
@@ -1,12 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Tank AOE"
|
||||
type = 1
|
||||
damage = 10.0
|
||||
ability_range = 10.0
|
||||
cooldown = 3.0
|
||||
icon = "2"
|
||||
@@ -1,13 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Shield Aura"
|
||||
type = 4
|
||||
damage = 50.0
|
||||
ability_range = 0.0
|
||||
uses_gcd = false
|
||||
icon = "P"
|
||||
passive_stat = "shield"
|
||||
@@ -1,11 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Tank Strike"
|
||||
damage = 15.0
|
||||
ability_range = 3.0
|
||||
cooldown = 2.0
|
||||
icon = "1"
|
||||
@@ -1,11 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Fortress"
|
||||
type = 2
|
||||
damage = 300.0
|
||||
cooldown = 20.0
|
||||
icon = "4"
|
||||
@@ -1,12 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Shield Reset"
|
||||
type = 2
|
||||
ability_range = 0.0
|
||||
cooldown = 5.0
|
||||
uses_gcd = false
|
||||
icon = "3"
|
||||
@@ -1,15 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="AbilitySet" format=3 uid="uid://cgxtn7dfs40bh"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://voedgs25cwrb" path="res://scenes/player/role/ability_set.gd" id="1"]
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1_ability"]
|
||||
[ext_resource type="Resource" path="res://scenes/player/role/tank/abilities/single.tres" id="2"]
|
||||
[ext_resource type="Resource" path="res://scenes/player/role/tank/abilities/aoe.tres" id="3"]
|
||||
[ext_resource type="Resource" path="res://scenes/player/role/tank/abilities/utility.tres" id="4"]
|
||||
[ext_resource type="Resource" path="res://scenes/player/role/tank/abilities/ult.tres" id="5"]
|
||||
[ext_resource type="Resource" path="res://scenes/player/role/tank/abilities/passive.tres" id="6"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
abilities = Array[ExtResource("1_ability")]([ExtResource("2"), ExtResource("3"), ExtResource("4"), ExtResource("5"), ExtResource("6")])
|
||||
aa_damage = 5.0
|
||||
aa_range = 3.0
|
||||
@@ -1,60 +0,0 @@
|
||||
extends Node
|
||||
|
||||
const TARGET_RANGE := 100.0
|
||||
|
||||
var mouse_press_pos: Vector2 = Vector2.ZERO
|
||||
|
||||
@onready var player: CharacterBody3D = get_parent()
|
||||
@onready var camera: Camera3D = get_parent().get_node("CameraPivot/Camera3D")
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
if event.pressed:
|
||||
mouse_press_pos = event.position
|
||||
else:
|
||||
var drag: float = event.position.distance_to(mouse_press_pos)
|
||||
if drag < 5.0:
|
||||
_try_target_under_mouse(event.position)
|
||||
if event.is_action_pressed("target_next"):
|
||||
_cycle_target()
|
||||
|
||||
func _try_target_under_mouse(mouse_pos: Vector2) -> void:
|
||||
var from := camera.project_ray_origin(mouse_pos)
|
||||
var to := from + camera.project_ray_normal(mouse_pos) * TARGET_RANGE
|
||||
var space := player.get_world_3d().direct_space_state
|
||||
var query := PhysicsRayQueryParameters3D.create(from, to)
|
||||
query.collision_mask = 4
|
||||
query.collide_with_areas = true
|
||||
query.collide_with_bodies = false
|
||||
var result := space.intersect_ray(query)
|
||||
if result:
|
||||
var hit_target := result.collider.get_parent() as Node3D
|
||||
if hit_target and hit_target.is_in_group("tavern"):
|
||||
EventBus.target_requested.emit(player, null)
|
||||
return
|
||||
if hit_target and (hit_target.is_in_group("enemies") or hit_target.is_in_group("portals")):
|
||||
EventBus.target_requested.emit(player, hit_target)
|
||||
return
|
||||
EventBus.target_requested.emit(player, null)
|
||||
|
||||
func _cycle_target() -> void:
|
||||
var enemies := get_tree().get_nodes_in_group("enemies")
|
||||
var portals := get_tree().get_nodes_in_group("portals")
|
||||
var targets: Array = []
|
||||
for e in enemies:
|
||||
if is_instance_valid(e):
|
||||
targets.append(e)
|
||||
for p in portals:
|
||||
if is_instance_valid(p):
|
||||
targets.append(p)
|
||||
if targets.is_empty():
|
||||
EventBus.target_requested.emit(player, null)
|
||||
return
|
||||
targets.sort_custom(func(a, b): return player.global_position.distance_squared_to(a.global_position) < player.global_position.distance_squared_to(b.global_position))
|
||||
var current: Node3D = PlayerData.target
|
||||
if current == null or current not in targets:
|
||||
EventBus.target_requested.emit(player, targets[0])
|
||||
return
|
||||
var idx := targets.find(current)
|
||||
var next_idx := (idx + 1) % targets.size()
|
||||
EventBus.target_requested.emit(player, targets[next_idx])
|
||||
@@ -1 +0,0 @@
|
||||
uid://b05nkuryipwny
|
||||
@@ -1,36 +0,0 @@
|
||||
extends StaticBody3D
|
||||
|
||||
@export var target_scene: String = "res://scenes/dungeon/dungeon.tscn"
|
||||
@export var is_exit: bool = false
|
||||
|
||||
var active := false
|
||||
var dungeon_variant: int = 0
|
||||
|
||||
func _ready() -> void:
|
||||
if not is_exit:
|
||||
if PlayerData.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":
|
||||
PlayerData.save_cache()
|
||||
if is_exit:
|
||||
PlayerData.returning_from_dungeon = true
|
||||
else:
|
||||
PlayerData.portal_position = global_position
|
||||
GameState.last_dungeon_variant = dungeon_variant
|
||||
call_deferred("_change_scene")
|
||||
|
||||
func _change_scene() -> void:
|
||||
get_tree().change_scene_to_file(target_scene)
|
||||
@@ -1 +0,0 @@
|
||||
uid://ctci5mc3cd2ck
|
||||
@@ -1,36 +0,0 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/portal/gate.gd" id="1"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
|
||||
albedo_color = Color(0.1, 0.9, 0.3, 1)
|
||||
emission_enabled = true
|
||||
emission = Color(0.1, 0.9, 0.3, 1)
|
||||
emission_energy_multiplier = 0.5
|
||||
|
||||
[sub_resource type="CylinderMesh" id="CylinderMesh_1"]
|
||||
top_radius = 1.0
|
||||
bottom_radius = 1.0
|
||||
height = 2.0
|
||||
material = SubResource("StandardMaterial3D_1")
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_1"]
|
||||
radius = 3.0
|
||||
|
||||
[node name="Gate" type="StaticBody3D"]
|
||||
script = ExtResource("1")
|
||||
collision_layer = 0
|
||||
collision_mask = 0
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="."]
|
||||
mesh = SubResource("CylinderMesh_1")
|
||||
|
||||
[node name="GateArea" type="Area3D" parent="."]
|
||||
collision_layer = 0
|
||||
collision_mask = 1
|
||||
monitoring = true
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="GateArea"]
|
||||
shape = SubResource("SphereShape3D_1")
|
||||
|
||||
[connection signal="body_entered" from="GateArea" to="." method="_on_gate_area_body_entered"]
|
||||
@@ -1,37 +0,0 @@
|
||||
extends StaticBody3D
|
||||
|
||||
@export var stats: PortalStats
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("portals")
|
||||
if stats.variant == PortalStats.Kind.RED:
|
||||
add_to_group("red_portal")
|
||||
_apply_appearance()
|
||||
PortalData.register(self, stats)
|
||||
|
||||
func _exit_tree() -> void:
|
||||
PortalData.deregister(self)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
var mesh: Node3D = get_node_or_null("Mesh")
|
||||
if mesh:
|
||||
mesh.rotate_y(delta * 1.5)
|
||||
|
||||
func _apply_appearance() -> void:
|
||||
var mesh: MeshInstance3D = get_node_or_null("Mesh")
|
||||
if not mesh:
|
||||
return
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.emission_enabled = true
|
||||
mat.emission_energy_multiplier = 0.8
|
||||
if stats.variant == PortalStats.Kind.RED:
|
||||
mat.albedo_color = Color(0.9, 0.1, 0.1, 1)
|
||||
mat.emission = Color(1.0, 0.25, 0.25, 1)
|
||||
else:
|
||||
mat.albedo_color = Color(0.4, 0.2, 0.85, 1)
|
||||
mat.emission = Color(0.6, 0.35, 1.0, 1)
|
||||
mesh.material_override = mat
|
||||
|
||||
func _on_detection_area_body_entered(body: Node3D) -> void:
|
||||
if body is CharacterBody3D and body.name == "Player":
|
||||
EventBus.portal_entered.emit(self, body)
|
||||
@@ -1 +0,0 @@
|
||||
uid://dlexijybbqxop
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user