Trink-Animation, Enemy-Level-Skalierung, Droprates angepasst
- Drinking-Animation beim Trank-Benutzen (0.3s Blend, 0.7x Speed) - Bewegung bricht Trinken ab, Skills blockiert während Trinken - Enemy mob_level mit Skalierung: HP +30%, Schaden +20%, XP +40%, Gold +50% pro Level - Droprates gesenkt: Equipment 5%, HP-Trank 15%, Mana-Trank 10% - Spieler startet mit 3 Heiltränken auf Slot 3 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9ed18e034c
commit
3766be4017
8 changed files with 109 additions and 10 deletions
|
|
@ -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
|
- HP-Tränke: Nur wenn HP nicht voll, stellt HP wieder her
|
||||||
- Mana-Tränke: Nur wenn Mana nicht voll, stellt Ressource wieder her
|
- Mana-Tränke: Nur wenn Mana nicht voll, stellt Ressource wieder her
|
||||||
- **Stacking:** Gleichnamige Tränke werden automatisch gestackt (max 20)
|
- **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) |
|
| item | Resource | Das droppbare Item (Equipment oder Consumable) |
|
||||||
| drop_chance | float | Wahrscheinlichkeit (0.0 - 1.0) |
|
| 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/)
|
### 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%)
|
- **skeleton_loot.tres** — 5-15 Gold, Stahlschwert (10%), Holzschild (12%), Eisenhelm (15%)
|
||||||
|
|
||||||
### LootWindow (loot_window.gd)
|
### 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 |
|
| detection_range | 15.0 |
|
||||||
| patrol_radius | 8.0 |
|
| patrol_radius | 8.0 |
|
||||||
| xp_reward | 20 |
|
| 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)
|
### KI-Verhalten (State Machine)
|
||||||
| State | Beschreibung |
|
| State | Beschreibung |
|
||||||
|
|
|
||||||
BIN
assets/Animations/Drinking.fbx
Normal file
BIN
assets/Animations/Drinking.fbx
Normal file
Binary file not shown.
44
assets/Animations/Drinking.fbx.import
Normal file
44
assets/Animations/Drinking.fbx.import
Normal file
|
|
@ -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
|
||||||
17
enemy.gd
17
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 attack_speed: float = 2.0 # Sekunden zwischen Angriffen
|
||||||
@export var move_speed: float = 5.5
|
@export var move_speed: float = 5.5
|
||||||
@export var xp_reward: int = 20
|
@export var xp_reward: int = 20
|
||||||
|
@export var mob_level: int = 1
|
||||||
@export var detection_range: float = 15.0
|
@export var detection_range: float = 15.0
|
||||||
@export var loot_table: LootTable = null
|
@export var loot_table: LootTable = null
|
||||||
|
|
||||||
|
|
@ -81,11 +82,25 @@ var is_turning: bool = false
|
||||||
@onready var health_label: Label3D = $HealthDisplay/Label3D
|
@onready var health_label: Label3D = $HealthDisplay/Label3D
|
||||||
@onready var model: Node3D = $Model
|
@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
|
# READY
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
|
_apply_level_scaling()
|
||||||
current_hp = max_hp
|
current_hp = max_hp
|
||||||
_update_health_display()
|
_update_health_display()
|
||||||
health_label.visible = false
|
health_label.visible = false
|
||||||
|
|
@ -400,7 +415,7 @@ func _die():
|
||||||
|
|
||||||
# Loot generieren
|
# Loot generieren
|
||||||
if loot_table != null:
|
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)
|
enemy_dropped_loot.emit(loot, global_position)
|
||||||
|
|
||||||
# XP und Respawn-Signal (nur einmal!)
|
# XP und Respawn-Signal (nur einmal!)
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,10 @@ class_name LootTable
|
||||||
@export var possible_drops: Array[LootEntry] = []
|
@export var possible_drops: Array[LootEntry] = []
|
||||||
|
|
||||||
# Generiert Loot basierend auf Tabelle
|
# 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 = {
|
var result = {
|
||||||
"gold": randi_range(min_gold, max_gold),
|
"gold": int(randi_range(min_gold, max_gold) * gold_mult),
|
||||||
"items": []
|
"items": []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,25 +11,27 @@
|
||||||
[sub_resource type="Resource" id="entry_1"]
|
[sub_resource type="Resource" id="entry_1"]
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
item = ExtResource("3")
|
item = ExtResource("3")
|
||||||
drop_chance = 0.15
|
drop_chance = 0.05
|
||||||
|
|
||||||
[sub_resource type="Resource" id="entry_2"]
|
[sub_resource type="Resource" id="entry_2"]
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
item = ExtResource("4")
|
item = ExtResource("4")
|
||||||
|
drop_chance = 0.05
|
||||||
|
|
||||||
[sub_resource type="Resource" id="entry_3"]
|
[sub_resource type="Resource" id="entry_3"]
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
item = ExtResource("5")
|
item = ExtResource("5")
|
||||||
|
drop_chance = 0.05
|
||||||
|
|
||||||
[sub_resource type="Resource" id="entry_4"]
|
[sub_resource type="Resource" id="entry_4"]
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
item = ExtResource("6")
|
item = ExtResource("6")
|
||||||
drop_chance = 0.4
|
drop_chance = 0.15
|
||||||
|
|
||||||
[sub_resource type="Resource" id="entry_5"]
|
[sub_resource type="Resource" id="entry_5"]
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
item = ExtResource("7")
|
item = ExtResource("7")
|
||||||
drop_chance = 0.25
|
drop_chance = 0.1
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
|
|
|
||||||
23
player.gd
23
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_RUN_JUMP = "res://assets/Warrior+Animation/Running Jump.fbx"
|
||||||
const ANIM_ROLL = "res://assets/Warrior+Animation/Quick Roll To Run.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_TURN_180 = "res://assets/Warrior+Animation/Running Turn 180.fbx"
|
||||||
|
const ANIM_DRINKING = "res://assets/Animations/Drinking.fbx"
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
# CHARAKTER-STATS
|
# CHARAKTER-STATS
|
||||||
|
|
@ -148,6 +149,7 @@ var is_attacking: bool = false
|
||||||
var is_dead: bool = false
|
var is_dead: bool = false
|
||||||
var is_walking: bool = false
|
var is_walking: bool = false
|
||||||
var is_rolling: bool = false
|
var is_rolling: bool = false
|
||||||
|
var is_drinking: bool = false
|
||||||
var roll_direction: Vector3 = Vector3.ZERO
|
var roll_direction: Vector3 = Vector3.ZERO
|
||||||
var roll_cooldown: float = 0.0
|
var roll_cooldown: float = 0.0
|
||||||
const ROLL_COOLDOWN: float = 1.5
|
const ROLL_COOLDOWN: float = 1.5
|
||||||
|
|
@ -541,7 +543,7 @@ func _on_slot_clicked(slot_index: int):
|
||||||
_use_consumable_slot(slot_index, slot_content)
|
_use_consumable_slot(slot_index, slot_content)
|
||||||
|
|
||||||
func execute_skill(skill_id: String):
|
func execute_skill(skill_id: String):
|
||||||
if is_dead or is_casting:
|
if is_dead or is_casting or is_drinking:
|
||||||
return
|
return
|
||||||
var skill = _find_skill(skill_id)
|
var skill = _find_skill(skill_id)
|
||||||
if skill.is_empty():
|
if skill.is_empty():
|
||||||
|
|
@ -672,9 +674,12 @@ func use_consumable(item: Consumable) -> bool:
|
||||||
if consumable_cooldowns.get(item.item_name, 0.0) > 0:
|
if consumable_cooldowns.get(item.item_name, 0.0) > 0:
|
||||||
print(item.item_name + " im Cooldown!")
|
print(item.item_name + " im Cooldown!")
|
||||||
return false
|
return false
|
||||||
|
if is_drinking:
|
||||||
|
return false
|
||||||
var used = item.use(self)
|
var used = item.use(self)
|
||||||
if used:
|
if used:
|
||||||
consumable_cooldowns[item.item_name] = item.cooldown
|
consumable_cooldowns[item.item_name] = item.cooldown
|
||||||
|
_play_drinking_anim()
|
||||||
return used
|
return used
|
||||||
|
|
||||||
func _use_consumable_slot(slot_index: int, item: Consumable):
|
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_RUN_JUMP, "run_jump", false)
|
||||||
_load_anim_from_fbx(ANIM_ROLL, "roll", false)
|
_load_anim_from_fbx(ANIM_ROLL, "roll", false)
|
||||||
_load_anim_from_fbx(ANIM_TURN_180, "turn_180", false)
|
_load_anim_from_fbx(ANIM_TURN_180, "turn_180", false)
|
||||||
|
_load_anim_from_fbx(ANIM_DRINKING, "drinking", false)
|
||||||
|
|
||||||
_play_anim("idle")
|
_play_anim("idle")
|
||||||
|
|
||||||
|
|
@ -876,6 +882,15 @@ func _play_anim_once(name: String):
|
||||||
await anim_player.animation_finished
|
await anim_player.animation_finished
|
||||||
is_attacking = false
|
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():
|
func _do_roll():
|
||||||
# Bewegungsrichtung beim Roll-Start erfassen
|
# Bewegungsrichtung beim Roll-Start erfassen
|
||||||
|
|
@ -900,7 +915,7 @@ func _do_roll():
|
||||||
is_rolling = false
|
is_rolling = false
|
||||||
|
|
||||||
func _update_movement_animation(is_moving: bool, input_dir: Vector2):
|
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
|
return
|
||||||
var has_target = target != null and is_instance_valid(target)
|
var has_target = target != null and is_instance_valid(target)
|
||||||
if not is_on_floor():
|
if not is_on_floor():
|
||||||
|
|
@ -1027,9 +1042,11 @@ func _physics_process(delta):
|
||||||
# get_vector: x = links(-)/rechts(+), y = vor(-)/zurück(+)
|
# get_vector: x = links(-)/rechts(+), y = vor(-)/zurück(+)
|
||||||
var input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
|
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:
|
if is_casting and input_dir.length() > 0.1:
|
||||||
_cancel_cast()
|
_cancel_cast()
|
||||||
|
if is_drinking and input_dir.length() > 0.1:
|
||||||
|
is_drinking = false
|
||||||
|
|
||||||
var is_moving = input_dir.length() > 0.1
|
var is_moving = input_dir.length() > 0.1
|
||||||
|
|
||||||
|
|
|
||||||
5
world.gd
5
world.gd
|
|
@ -20,6 +20,7 @@ const RESPAWN_TIME = 5.0
|
||||||
const STARTER_SWORD = preload("res://equipment/iron_sword.tres")
|
const STARTER_SWORD = preload("res://equipment/iron_sword.tres")
|
||||||
const STARTER_STAFF = preload("res://equipment/wooden_staff.tres")
|
const STARTER_STAFF = preload("res://equipment/wooden_staff.tres")
|
||||||
const STARTER_CHEST = preload("res://equipment/leather_chest.tres")
|
const STARTER_CHEST = preload("res://equipment/leather_chest.tres")
|
||||||
|
const STARTER_POTION = preload("res://consumables/small_hp_potion.tres")
|
||||||
|
|
||||||
# Loot Tables
|
# Loot Tables
|
||||||
const GOBLIN_LOOT = preload("res://loot_tables/goblin_loot.tres")
|
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._calculate_stats()
|
||||||
player.current_hp = player.max_hp
|
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.current_resource = player.max_resource
|
||||||
player.hud.update_health(player.current_hp, player.max_hp)
|
player.hud.update_health(player.current_hp, player.max_hp)
|
||||||
player.hud.update_resource(player.current_resource, player.max_resource, player.get_resource_name())
|
player.hud.update_resource(player.current_resource, player.max_resource, player.get_resource_name())
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue