RapidGameFramework
Reusable Godot managers for data-driven small games
Loading...
Searching...
No Matches
battle_feedback_manager.gd
Go to the documentation of this file.
1extends RefCounted
2
3
7
8const DEFAULT_DAMAGE_COLOR := "#f05f64"
9const DEFAULT_HEAL_COLOR := "#55d17a"
10
11var feedback_by_side := {}
12
13
14func clear() -> void:
15 feedback_by_side.clear()
16
17
18func record_event(event:Dictionary, now_ms:int, options:Dictionary = {}) -> Dictionary:
19 var event_type = str(event.get("type", "damage"))
20 if event_type == "heal":
21 return record_heal(str(event.get("target", event.get("side", ""))), int(event.get("amount", 0)), now_ms, options)
22 if event_type == "damage":
23 return record_damage(str(event.get("defender", event.get("target", event.get("side", "")))), int(event.get("damage", event.get("amount", 0))), now_ms, options)
24 return {}
25
26
27func record_damage(side:String, amount:int, now_ms:int, options:Dictionary = {}) -> Dictionary:
28 if side == "" or amount <= 0:
29 return {}
30 return _push_feedback(side, {
31 "kind": "damage",
32 "amount": -abs(amount),
33 "text": "-%d" % abs(amount),
34 "color": str(options.get("damage_color", DEFAULT_DAMAGE_COLOR)),
35 "created_ms": now_ms,
36 "duration_ms": int(options.get("duration_ms", 900)),
37 "hurt_until_ms": now_ms + int(options.get("hurt_ms", 260))
38 })
39
40
41func record_heal(side:String, amount:int, now_ms:int, options:Dictionary = {}) -> Dictionary:
42 if side == "" or amount <= 0:
43 return {}
44 return _push_feedback(side, {
45 "kind": "heal",
46 "amount": abs(amount),
47 "text": "+%d" % abs(amount),
48 "color": str(options.get("heal_color", DEFAULT_HEAL_COLOR)),
49 "created_ms": now_ms,
50 "duration_ms": int(options.get("duration_ms", 900)),
51 "hurt_until_ms": now_ms
52 })
53
54
55func step(now_ms:int) -> void:
56 for side in feedback_by_side.keys():
57 var active_entries := []
58 for entry in feedback_by_side.get(side, []):
59 if now_ms - int(entry.get("created_ms", 0)) <= int(entry.get("duration_ms", 0)):
60 active_entries.append(entry)
61 if active_entries.is_empty():
62 feedback_by_side.erase(side)
63 else:
64 feedback_by_side[side] = active_entries
65
66
67func get_side_feedback(side:String, now_ms:int) -> Dictionary:
68 step(now_ms)
69 var entries:Array = feedback_by_side.get(side, [])
70 if entries.is_empty():
71 return {}
72 return _with_timing(entries[entries.size() - 1], now_ms)
73
74
75func is_side_hurting(side:String, now_ms:int) -> bool:
76 step(now_ms)
77 for entry in feedback_by_side.get(side, []):
78 if now_ms <= int(entry.get("hurt_until_ms", 0)):
79 return true
80 return false
81
82
83func has_active_feedback(now_ms:int) -> bool:
84 step(now_ms)
85 return not feedback_by_side.is_empty()
86
87
88func get_state(now_ms:int = -1) -> Dictionary:
89 if now_ms >= 0:
90 step(now_ms)
91 return feedback_by_side.duplicate(true)
92
93
94func apply_state(state:Dictionary) -> void:
95 feedback_by_side = state.duplicate(true)
96
97
98func _push_feedback(side:String, entry:Dictionary) -> Dictionary:
99 var entries:Array = feedback_by_side.get(side, [])
100 entries.append(entry)
101 feedback_by_side[side] = entries
102 return entry.duplicate(true)
103
104
105func _with_timing(entry:Dictionary, now_ms:int) -> Dictionary:
106 var next:Dictionary = entry.duplicate(true)
107 var duration_ms:int = max(1, int(next.get("duration_ms", 1)))
108 var age_ms:int = max(0, now_ms - int(next.get("created_ms", now_ms)))
109 var progress:float = clamp(float(age_ms) / float(duration_ms), 0.0, 1.0)
110 next["age_ms"] = age_ms
111 next["progress"] = progress
112 next["alpha"] = clamp(1.0 - progress, 0.0, 1.0)
113 next["rise"] = -18.0 * progress
114 next["scale"] = 1.0 + (0.18 * (1.0 - progress))
115 return next