RapidGameFramework
Reusable Godot managers for data-driven small games
Loading...
Searching...
No Matches
research_lab_manager.gd
Go to the documentation of this file.
1extends RefCounted
2
3
6
7
8
10func attempt_inventory_reaction(alchemy_manager, inventory:Dictionary, inputs:Array, catalysts:Array = [], rng:RandomNumberGenerator = null) -> Dictionary:
11 var normalized_inputs = _normalized_inputs(inputs)
12 if normalized_inputs.size() != 2:
13 return {
14 "success": false,
15 "valid": false,
16 "message": "Choose two materials before starting research.",
17 "inputs": normalized_inputs
18 }
19 if not _has_distinct_inputs(normalized_inputs):
20 return {
21 "success": false,
22 "valid": false,
23 "message": "Choose two different materials for research.",
24 "inputs": normalized_inputs
25 }
26 if not _has_required_inventory(inventory, normalized_inputs):
27 return {
28 "success": false,
29 "valid": false,
30 "message": "Not enough materials for that research attempt.",
31 "inputs": normalized_inputs
32 }
33 var reaction = alchemy_manager.attempt_reaction(normalized_inputs, catalysts)
34 if bool(reaction.get("success", false)):
35 var result_id = str(reaction.get("result", ""))
36 var gain := {}
37 if result_id != "":
38 gain[result_id] = 1
39 return {
40 "success": true,
41 "valid": true,
42 "inputs": normalized_inputs,
43 "consume": _counts_for(normalized_inputs),
44 "gain": gain,
45 "reaction": reaction,
46 "message": "Research succeeded."
47 }
48 if rng == null:
49 rng = RandomNumberGenerator.new()
50 rng.randomize()
51 var destroyed_index = rng.randi_range(0, normalized_inputs.size() - 1)
52 var destroyed = str(normalized_inputs[destroyed_index])
53 var returned = str(normalized_inputs[1 - destroyed_index])
54 reaction["destroyed"] = destroyed
55 reaction["returned"] = returned
56 return {
57 "success": false,
58 "valid": true,
59 "inputs": normalized_inputs,
60 "consume": {destroyed: 1},
61 "gain": {},
62 "destroyed": destroyed,
63 "returned": returned,
64 "reaction": reaction,
65 "message": "Research failed."
66 }
67
68
69
70func preview_inventory_reaction(alchemy_manager, inventory:Dictionary, inputs:Array, known_result_ids:Array = [], catalysts:Array = []) -> Dictionary:
71 var normalized_inputs = _normalized_inputs(inputs)
72 if normalized_inputs.size() != 2:
73 return {
74 "valid": false,
75 "known": false,
76 "result": "",
77 "label": "Unknown",
78 "message": "Choose two materials."
79 }
80 if not _has_distinct_inputs(normalized_inputs):
81 return {
82 "valid": false,
83 "known": false,
84 "inputs": normalized_inputs,
85 "result": "",
86 "label": "Unknown",
87 "message": "Choose two different materials."
88 }
89 if not _has_required_inventory(inventory, normalized_inputs):
90 return {
91 "valid": false,
92 "known": false,
93 "inputs": normalized_inputs,
94 "result": "",
95 "label": "Unknown",
96 "message": "Not enough materials."
97 }
98 var reaction = alchemy_manager.attempt_reaction(normalized_inputs, catalysts)
99 var result_id = str(reaction.get("result", ""))
100 var success = bool(reaction.get("success", false)) and result_id != ""
101 var known = success and known_result_ids.has(result_id)
102 return {
103 "valid": true,
104 "success": success,
105 "known": known,
106 "inputs": normalized_inputs,
107 "result": result_id if known else "",
108 "hidden_result": result_id,
109 "label": result_id.capitalize() if known else "Unknown",
110 "reaction": reaction,
111 "message": "Known recipe." if known else "Unknown recipe."
112 }
113
114
115
116func craft_known_reaction(alchemy_manager, inventory:Dictionary, inputs:Array, known_result_ids:Array, catalysts:Array = []) -> Dictionary:
117 var preview = preview_inventory_reaction(alchemy_manager, inventory, inputs, known_result_ids, catalysts)
118 if not bool(preview.get("known", false)):
119 preview["valid"] = false
120 preview["message"] = "Research this recipe before crafting it."
121 return preview
122 var result_id = str(preview.get("result", ""))
123 return {
124 "success": true,
125 "valid": true,
126 "known": true,
127 "inputs": preview.get("inputs", []).duplicate(true),
128 "consume": _counts_for(preview.get("inputs", [])),
129 "gain": {result_id: 1},
130 "reaction": preview.get("reaction", {}),
131 "message": "Crafted known recipe."
132 }
133
134
135
136func apply_inventory_delta(inventory:Dictionary, result:Dictionary) -> Dictionary:
137 var next_inventory = inventory.duplicate(true)
138 for id in result.get("consume", {}).keys():
139 var remaining = int(next_inventory.get(id, 0)) - int(result.get("consume", {}).get(id, 0))
140 if remaining > 0:
141 next_inventory[id] = remaining
142 else:
143 next_inventory.erase(id)
144 for id in result.get("gain", {}).keys():
145 next_inventory[id] = int(next_inventory.get(id, 0)) + int(result.get("gain", {}).get(id, 0))
146 return next_inventory
147
148
149func get_available_inputs(inventory:Dictionary) -> Array:
150 var available := []
151 for id in inventory.keys():
152 if int(inventory.get(id, 0)) > 0:
153 available.append(str(id))
154 available.sort()
155 return available
156
157
158func _normalized_inputs(inputs:Array) -> Array:
159 var normalized := []
160 for input in inputs:
161 var id = str(input).strip_edges().to_lower()
162 if id != "":
163 normalized.append(id)
164 normalized.sort()
165 return normalized
166
167
168func _has_distinct_inputs(inputs:Array) -> bool:
169 if inputs.size() < 2:
170 return false
171 return str(inputs[0]) != str(inputs[1])
172
173
174func _has_required_inventory(inventory:Dictionary, inputs:Array) -> bool:
175 var required = _counts_for(inputs)
176 for id in required.keys():
177 if int(inventory.get(id, 0)) < int(required[id]):
178 return false
179 return true
180
181
182func _counts_for(inputs:Array) -> Dictionary:
183 var counts := {}
184 for input in inputs:
185 var id = str(input)
186 counts[id] = int(counts.get(id, 0)) + 1
187 return counts