diff --git a/PROJEKTDOKU.md b/PROJEKTDOKU.md index 408221f..8fed09c 100644 --- a/PROJEKTDOKU.md +++ b/PROJEKTDOKU.md @@ -232,6 +232,9 @@ Resource-Klasse für verbrauchbare Items (Tränke, Essen, etc.). - HP-Tränke: Nur wenn HP nicht voll, stellt HP wieder her - Mana-Tränke: Nur wenn Mana nicht voll, stellt Ressource wieder her - **Stacking:** Gleichnamige Tränke werden automatisch gestackt (max 20) +- **Trink-Animation:** Beim Benutzen wird die Drinking-Animation abgespielt (0.3s Blend, 0.7x Speed) +- Während des Trinkens: keine Skills, kein weiterer Trank, Bewegung bricht ab +- **Starter-Tränke:** Spieler startet mit 3 kleinen Heiltränken auf Slot 3 --- @@ -284,8 +287,12 @@ Ein einzelner Drop-Eintrag. | item | Resource | Das droppbare Item (Equipment oder Consumable) | | drop_chance | float | Wahrscheinlichkeit (0.0 - 1.0) | +### Gold-Skalierung nach Enemy-Level +- `generate_loot(enemy_level)` berechnet Gold: `base_gold * (1.0 + (level-1) * 0.5)` +- Level 1: x1.0, Level 3: x2.0, Level 5: x3.0 + ### Vorhandene LootTables (loot_tables/) -- **goblin_loot.tres** — 2-8 Gold, Eisenschwert (15%), Lederrüstung (10%), Eisenhelm (10%), Heiltrank (40%), Manatrank (25%) +- **goblin_loot.tres** — 2-8 Gold, Eisenschwert (5%), Lederrüstung (5%), Eisenhelm (5%), Heiltrank (15%), Manatrank (10%) - **skeleton_loot.tres** — 5-15 Gold, Stahlschwert (10%), Holzschild (12%), Eisenhelm (15%) ### LootWindow (loot_window.gd) @@ -375,6 +382,14 @@ Beispiel: Waffe mit 1.5s + 50% Haste → `1.5 / 1.5 = 1.0s` | detection_range | 15.0 | | patrol_radius | 8.0 | | xp_reward | 20 | +| mob_level | 1 | + +### Level-Skalierung +Stats werden in `_ready()` basierend auf `mob_level` skaliert (Basiswerte = Level 1): +- **HP:** +30% pro Level (`max_hp * (1 + (level-1) * 0.3)`) +- **Schaden:** +20% pro Level (`damage * (1 + (level-1) * 0.2)`) +- **XP:** +40% pro Level (`xp * (1 + (level-1) * 0.4)`) +- **Gold:** +50% pro Level (in `generate_loot`) ### KI-Verhalten (State Machine) | State | Beschreibung | diff --git a/assets/Animations/Drinking.fbx b/assets/Animations/Drinking.fbx new file mode 100644 index 0000000..977e185 Binary files /dev/null and b/assets/Animations/Drinking.fbx differ diff --git a/assets/Animations/Drinking.fbx.import b/assets/Animations/Drinking.fbx.import new file mode 100644 index 0000000..9a32667 --- /dev/null +++ b/assets/Animations/Drinking.fbx.import @@ -0,0 +1,44 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://bybwuqupuicct" +path="res://.godot/imported/Drinking.fbx-e77657836eed0f61a96552dfc25075c4.scn" + +[deps] + +source_file="res://assets/Animations/Drinking.fbx" +dest_files=["res://.godot/imported/Drinking.fbx-e77657836eed0f61a96552dfc25075c4.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +nodes/use_name_suffixes=true +nodes/use_node_type_suffixes=true +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=true +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +materials/extract=0 +materials/extract_format=0 +materials/extract_path="" +_subresources={} +fbx/importer=0 +fbx/allow_geometry_helper_nodes=false +fbx/embedded_image_handling=1 +fbx/naming_version=2 diff --git a/enemy.gd b/enemy.gd index 6a8239b..2992524 100644 --- a/enemy.gd +++ b/enemy.gd @@ -32,6 +32,7 @@ signal enemy_dropped_loot(loot: Dictionary, world_pos: Vector3) @export var attack_speed: float = 2.0 # Sekunden zwischen Angriffen @export var move_speed: float = 5.5 @export var xp_reward: int = 20 +@export var mob_level: int = 1 @export var detection_range: float = 15.0 @export var loot_table: LootTable = null @@ -81,11 +82,25 @@ var is_turning: bool = false @onready var health_label: Label3D = $HealthDisplay/Label3D @onready var model: Node3D = $Model +# ═══════════════════════════════════════════════════════════════ +# LEVEL-SKALIERUNG +# ═══════════════════════════════════════════════════════════════ + +func _apply_level_scaling(): + if mob_level <= 1: + return + var lvl = mob_level - 1 + max_hp = int(max_hp * (1.0 + lvl * 0.3)) # +30% HP pro Level + min_damage = int(min_damage * (1.0 + lvl * 0.2)) # +20% Schaden pro Level + max_damage = int(max_damage * (1.0 + lvl * 0.2)) + xp_reward = int(xp_reward * (1.0 + lvl * 0.4)) # +40% XP pro Level + # ═══════════════════════════════════════════════════════════════ # READY # ═══════════════════════════════════════════════════════════════ func _ready(): + _apply_level_scaling() current_hp = max_hp _update_health_display() health_label.visible = false @@ -400,7 +415,7 @@ func _die(): # Loot generieren if loot_table != null: - var loot = loot_table.generate_loot() + var loot = loot_table.generate_loot(mob_level) enemy_dropped_loot.emit(loot, global_position) # XP und Respawn-Signal (nur einmal!) diff --git a/loot_table.gd b/loot_table.gd index 44e6e7e..31e11ad 100644 --- a/loot_table.gd +++ b/loot_table.gd @@ -11,9 +11,10 @@ class_name LootTable @export var possible_drops: Array[LootEntry] = [] # Generiert Loot basierend auf Tabelle -func generate_loot() -> Dictionary: +func generate_loot(enemy_level: int = 1) -> Dictionary: + var gold_mult = 1.0 + (enemy_level - 1) * 0.5 var result = { - "gold": randi_range(min_gold, max_gold), + "gold": int(randi_range(min_gold, max_gold) * gold_mult), "items": [] } diff --git a/loot_tables/goblin_loot.tres b/loot_tables/goblin_loot.tres index 8278921..5ce2e7d 100644 --- a/loot_tables/goblin_loot.tres +++ b/loot_tables/goblin_loot.tres @@ -11,25 +11,27 @@ [sub_resource type="Resource" id="entry_1"] script = ExtResource("2") item = ExtResource("3") -drop_chance = 0.15 +drop_chance = 0.05 [sub_resource type="Resource" id="entry_2"] script = ExtResource("2") item = ExtResource("4") +drop_chance = 0.05 [sub_resource type="Resource" id="entry_3"] script = ExtResource("2") item = ExtResource("5") +drop_chance = 0.05 [sub_resource type="Resource" id="entry_4"] script = ExtResource("2") item = ExtResource("6") -drop_chance = 0.4 +drop_chance = 0.15 [sub_resource type="Resource" id="entry_5"] script = ExtResource("2") item = ExtResource("7") -drop_chance = 0.25 +drop_chance = 0.1 [resource] script = ExtResource("1") diff --git a/player.gd b/player.gd index 2325a4e..400bcc6 100644 --- a/player.gd +++ b/player.gd @@ -56,6 +56,7 @@ const ANIM_WALK_JUMP = "res://assets/Warrior+Animation/Walking Jump.fbx" const ANIM_RUN_JUMP = "res://assets/Warrior+Animation/Running Jump.fbx" const ANIM_ROLL = "res://assets/Warrior+Animation/Quick Roll To Run.fbx" const ANIM_TURN_180 = "res://assets/Warrior+Animation/Running Turn 180.fbx" +const ANIM_DRINKING = "res://assets/Animations/Drinking.fbx" # ═══════════════════════════════════════════════════════════════ # CHARAKTER-STATS @@ -148,6 +149,7 @@ var is_attacking: bool = false var is_dead: bool = false var is_walking: bool = false var is_rolling: bool = false +var is_drinking: bool = false var roll_direction: Vector3 = Vector3.ZERO var roll_cooldown: float = 0.0 const ROLL_COOLDOWN: float = 1.5 @@ -541,7 +543,7 @@ func _on_slot_clicked(slot_index: int): _use_consumable_slot(slot_index, slot_content) func execute_skill(skill_id: String): - if is_dead or is_casting: + if is_dead or is_casting or is_drinking: return var skill = _find_skill(skill_id) if skill.is_empty(): @@ -672,9 +674,12 @@ func use_consumable(item: Consumable) -> bool: if consumable_cooldowns.get(item.item_name, 0.0) > 0: print(item.item_name + " im Cooldown!") return false + if is_drinking: + return false var used = item.use(self) if used: consumable_cooldowns[item.item_name] = item.cooldown + _play_drinking_anim() return used func _use_consumable_slot(slot_index: int, item: Consumable): @@ -755,6 +760,7 @@ func _setup_animations(): _load_anim_from_fbx(ANIM_RUN_JUMP, "run_jump", false) _load_anim_from_fbx(ANIM_ROLL, "roll", false) _load_anim_from_fbx(ANIM_TURN_180, "turn_180", false) + _load_anim_from_fbx(ANIM_DRINKING, "drinking", false) _play_anim("idle") @@ -876,6 +882,15 @@ func _play_anim_once(name: String): await anim_player.animation_finished is_attacking = false +func _play_drinking_anim(): + if anim_player == null or not anim_player.has_animation("drinking"): + return + is_drinking = true + anim_player.speed_scale = 1.0 + anim_player.play("drinking", 0.3, 0.7, false) + await anim_player.animation_finished + is_drinking = false + func _do_roll(): # Bewegungsrichtung beim Roll-Start erfassen @@ -900,7 +915,7 @@ func _do_roll(): is_rolling = false func _update_movement_animation(is_moving: bool, input_dir: Vector2): - if is_attacking or is_dead or is_rolling: + if is_attacking or is_dead or is_rolling or is_drinking: return var has_target = target != null and is_instance_valid(target) if not is_on_floor(): @@ -1027,9 +1042,11 @@ func _physics_process(delta): # get_vector: x = links(-)/rechts(+), y = vor(-)/zurück(+) var input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_back") - # Cast unterbrechen bei Bewegung + # Cast/Trinken unterbrechen bei Bewegung if is_casting and input_dir.length() > 0.1: _cancel_cast() + if is_drinking and input_dir.length() > 0.1: + is_drinking = false var is_moving = input_dir.length() > 0.1 diff --git a/world.gd b/world.gd index 17369b4..25af806 100644 --- a/world.gd +++ b/world.gd @@ -20,6 +20,7 @@ const RESPAWN_TIME = 5.0 const STARTER_SWORD = preload("res://equipment/iron_sword.tres") const STARTER_STAFF = preload("res://equipment/wooden_staff.tres") const STARTER_CHEST = preload("res://equipment/leather_chest.tres") +const STARTER_POTION = preload("res://consumables/small_hp_potion.tres") # Loot Tables const GOBLIN_LOOT = preload("res://loot_tables/goblin_loot.tres") @@ -105,6 +106,10 @@ func _on_class_selected(character_class: CharacterClass): player._calculate_stats() player.current_hp = player.max_hp + var potion = STARTER_POTION.duplicate() + potion.stack_size = 3 + player.inventory.add_item(potion) + player.assign_to_action_bar(2, potion) player.current_resource = player.max_resource player.hud.update_health(player.current_hp, player.max_hp) player.hud.update_resource(player.current_resource, player.max_resource, player.get_resource_name())