RapidGameFramework
Reusable Godot managers for data-driven small games
Loading...
Searching...
No Matches
room_event_director.gd
Go to the documentation of this file.
1extends RefCounted
2
3
7
8const EventManager = preload("res://scripts/systems/eventManager/event_manager.gd")
9
10var catalog = EventManager.new()
11var rng := RandomNumberGenerator.new()
12var event_chance := 0.10
13var quiet_gap_min := 2
14var quiet_gap_max := 5
15var trade_progress_multiplier := 3.0
16var next_eligible_stage := 1
17var last_seen:Dictionary = {}
18var errors:Array[String] = []
19
20
21func _init() -> void:
22 rng.randomize()
23
24
25func configure(settings:Dictionary) -> void:
26 event_chance = clampf(float(settings.get("event_chance", event_chance)), 0.0, 1.0)
27 quiet_gap_min = maxi(1, int(settings.get("quiet_gap_min", quiet_gap_min)))
28 quiet_gap_max = maxi(quiet_gap_min, int(settings.get("quiet_gap_max", quiet_gap_max)))
29 trade_progress_multiplier = maxf(1.0, float(settings.get("trade_progress_multiplier", trade_progress_multiplier)))
30
31
32func load_directory(path:String) -> bool:
33 catalog.load_events(path)
34 errors.clear()
35 var seen_ids := {}
36 for raw in catalog.all_events:
37 if not raw is Dictionary: continue
38 var event:Dictionary = raw
39 var id := str(event.get("id", ""))
40 if id == "":
41 errors.append("Event in %s has no id" % str(event.get("pack", "?")))
42 elif seen_ids.has(id):
43 errors.append("Duplicate room event id: %s" % id)
44 else:
45 seen_ids[id] = true
46 if float(event.get("weight", 0.0)) <= 0.0:
47 errors.append("Room event %s needs a positive weight" % id)
48 if float(event.get("duration", 0.0)) <= 0.0:
49 errors.append("Room event %s needs a positive duration" % id)
50 if int(event.get("minimum_objective", event.get("minimum_stage", 1))) < 1:
51 errors.append("Room event %s has an invalid minimum stage" % id)
52 if int(event.get("cooldown", 0)) < 0:
53 errors.append("Room event %s has a negative cooldown" % id)
54 if not event.get("allowed_themes", []) is Array:
55 errors.append("Room event %s has invalid allowed_themes" % id)
56 var visuals:Variant = event.get("visuals", [])
57 if not visuals is Array:
58 errors.append("Room event %s has invalid visuals" % id)
59 else:
60 for visual in visuals:
61 if not visual is Dictionary or str(visual.get("kind", "")) not in ["ring", "circle", "rect", "glyph", "sprite"]:
62 errors.append("Room event %s has an unknown visual primitive" % id)
63 if catalog.all_events.is_empty(): errors.append("No room events found in %s" % path)
64 if not errors.is_empty():
65 catalog.all_events.clear()
66 return false
67 return true
68
69
70func get_definitions() -> Array:
71 return catalog.all_events
72
73
74func get_by_id(event_id:String) -> Dictionary:
75 return catalog.get_event_by_id(event_id)
76
77
78func roll(stage:int, theme:int, context:Dictionary = {}) -> Dictionary:
79 if stage < next_eligible_stage or rng.randf() > event_chance: return {}
80 var candidates:Array = []
81 var total := 0.0
82 for raw in catalog.all_events:
83 var event:Dictionary = raw
84 var id := str(event.get("id", ""))
85 if stage < int(event.get("minimum_objective", event.get("minimum_stage", 1))): continue
86 if stage - int(last_seen.get(id, -100000)) < int(event.get("cooldown", 0)): continue
87 var allowed:Array = event.get("allowed_themes", [])
88 if not allowed.is_empty() and theme not in allowed: continue
89 var required_item := str(event.get("requires_item", ""))
90 if required_item != "" and int(context.get("inventory", {}).get(required_item, 0)) <= 0: continue
91 var inventory:Dictionary = context.get("inventory", {})
92 var missing_requirement := false
93 for required in event.get("requires_all", []):
94 if int(inventory.get(str(required), 0)) <= 0: missing_requirement = true
95 if missing_requirement: continue
96 var blocked_by_item := false
97 for excluded in event.get("excludes_items", []):
98 if int(inventory.get(str(excluded), 0)) > 0: blocked_by_item = true
99 if blocked_by_item: continue
100 var once_key := str(event.get("once_key", ""))
101 if once_key != "" and bool(context.get("trade_flags", {}).get(once_key, false)): continue
102 var effective_weight := float(event.get("weight", 1.0))
103 if str(event.get("trade_kind", "")) != "" and (required_item != "" or not event.get("requires_all", []).is_empty()):
104 effective_weight *= float(event.get("progress_multiplier", trade_progress_multiplier))
105 total += effective_weight
106 candidates.append({"event":event, "limit":total})
107 if candidates.is_empty(): return {}
108 var roll_value := rng.randf_range(0.0, total)
109 for candidate in candidates:
110 if roll_value <= float(candidate.limit):
111 var picked:Dictionary = candidate.event
112 last_seen[str(picked.get("id", ""))] = stage
113 next_eligible_stage = stage + rng.randi_range(quiet_gap_min, quiet_gap_max)
114 return picked
115 return {}
116
117
118func reset() -> void:
119 last_seen.clear()
120 next_eligible_stage = 1
121
122
123func get_state() -> Dictionary:
124 return {"last_seen":last_seen.duplicate(true), "next_eligible_stage":next_eligible_stage}
125
126
127func set_state(state:Dictionary) -> void:
128 last_seen = state.get("last_seen", {}).duplicate(true)
129 next_eligible_stage = maxi(1, int(state.get("next_eligible_stage", 1)))