8func generate(prototype:Dictionary, options := {}) -> Dictionary:
9 var rng = RandomNumberGenerator.new()
10 var seed = int(options.get(
"seed", Time.get_ticks_msec()))
12 var mode = str(options.get(
"mode",
"arena"))
14 return _generate_battle(prototype, options, rng, seed)
15 return _generate_arena(prototype, options, rng, seed)
18func _generate_arena(prototype:Dictionary, options:Dictionary, rng:RandomNumberGenerator, seed:int) -> Dictionary:
19 var world = prototype.get(
"world", {}).duplicate(true)
20 var grid_size = _cell_from_value(world.get(
"grid_size", [12, 12]))
21 var player_cell = _cell_from_value(prototype.get(
"player", {}).get(
"grid", [2, 2]))
22 var exit_cell = _cell_from_value(prototype.get(
"exit", {}).get(
"cell", [grid_size.x - 1, grid_size.y - 1]))
23 var blocked := {player_cell: true, exit_cell: true}
24 for direction
in [Vector2i.LEFT, Vector2i.RIGHT, Vector2i.UP, Vector2i.DOWN]:
25 var safe_cell = player_cell + direction
26 if safe_cell.x >= 0
and safe_cell.y >= 0
and safe_cell.x < grid_size.x
and safe_cell.y < grid_size.y:
27 blocked[safe_cell] = true
28 var arena_options = prototype.get(
"arena", {})
29 world[
"obstacles"] = _generate_obstacles(rng, grid_size, blocked, int(options.get(
"obstacles", arena_options.get(
"obstacles", 5))))
30 var obstacle_cells := {}
31 for obstacle
in world.get(
"obstacles", []):
32 for cell
in obstacle.get(
"cells", []):
33 obstacle_cells[_cell_from_value(cell)] = true
34 for cell
in obstacle_cells.keys():
36 world[
"material_nodes"] = _place_materials(rng, world.get(
"material_nodes", []), grid_size, blocked)
37 for node
in world.get(
"material_nodes", []):
38 blocked[_cell_from_value(node.get(
"cell", [0, 0]))] = true
39 world[
"spell_interactions"] = _place_interactions(rng, world.get(
"spell_interactions", []), grid_size, blocked)
40 for interaction
in world.get(
"spell_interactions", []):
41 blocked[_cell_from_value(interaction.get(
"cell", [0, 0]))] = true
42 var enemy_pool = _arena_enemy_pool(prototype.get(
"enemies", []), arena_options)
43 var enemy_min = max(0, int(arena_options.get(
"enemy_count_min", min(1, enemy_pool.size()))))
44 var enemy_max = max(enemy_min, int(arena_options.get(
"enemy_count_max", enemy_pool.size())))
45 _shuffle_values(enemy_pool, rng)
46 var enemy_count = min(enemy_pool.size(), rng.randi_range(enemy_min, enemy_max)
if enemy_max > 0
else 0)
47 var enemies = _place_enemies(rng, enemy_pool.slice(0, enemy_count), grid_size, blocked)
53 "player_grid": [player_cell.x, player_cell.y]
57func _generate_battle(prototype:Dictionary, options:Dictionary, rng:RandomNumberGenerator, seed:int) -> Dictionary:
58 var half_size = clamp(int(options.get(
"half_size", 3)), 3, 5)
59 var grid_size = Vector2i(half_size * 2, half_size)
60 var tile_size = float(prototype.get(
"world", {}).get(
"tile_size", 40.0))
61 var player_cell = Vector2i(max(0, int(half_size / 2)), max(0, int(half_size / 2)))
62 var exit_cell = Vector2i(0, max(0, half_size - 1))
63 var world = prototype.get(
"world", {}).duplicate(true)
64 world[
"grid_size"] = [grid_size.x, grid_size.y]
65 world[
"tile_size"] = tile_size
66 world[
"obstacles"] = []
67 world[
"material_nodes"] = []
68 world[
"spell_interactions"] = []
70 {
"id":
"player_side",
"name":
"Player Half",
"cells": [0, 0, half_size, half_size],
"color":
"#172834"},
71 {
"id":
"enemy_side",
"name":
"Enemy Half",
"cells": [half_size, 0, half_size, half_size],
"color":
"#301820"}
73 world[
"camera"] = {
"viewport_tiles": [grid_size.x, max(5, grid_size.y)]}
74 var blocked := {player_cell: true, exit_cell: true}
76 for y
in range(grid_size.y):
77 for x
in range(half_size, grid_size.x):
78 enemy_cells.append(Vector2i(x, y))
79 _shuffle_cells(enemy_cells, rng)
81 var enemy_pool = prototype.get(
"enemies", []).duplicate(true)
82 _shuffle_values(enemy_pool, rng)
83 var enemy_count = min(enemy_pool.size(), rng.randi_range(1, half_size))
84 for index
in range(enemy_count):
85 var enemy = enemy_pool[index]
86 if not enemy
is Dictionary
or enemy_cells.is_empty():
88 var cell = enemy_cells.pop_back()
92 var entry = enemy.duplicate(true)
93 entry[
"cell"] = [cell.x, cell.y]
97 "half_size": half_size,
101 "player_grid": [player_cell.x, player_cell.y],
103 "cell": [exit_cell.x, exit_cell.y],
104 "active_after_enemies_defeated": true,
110func _generate_obstacles(rng:RandomNumberGenerator, grid_size:Vector2i, blocked:Dictionary, count:int) -> Array:
113 while obstacles.size() < count
and attempts < count * 30:
115 var cell = Vector2i(rng.randi_range(1, max(1, grid_size.x - 2)), rng.randi_range(1, max(1, grid_size.y - 2)))
116 if blocked.has(cell):
120 "id":
"generated_obstacle_%d" % obstacles.size(),
121 "name":
"Field Obstacle",
122 "cells": [[cell.x, cell.y]]
127func _place_materials(rng:RandomNumberGenerator, nodes:Array, grid_size:Vector2i, blocked:Dictionary) -> Array:
130 if not node
is Dictionary:
132 var cell = _random_open_cell(rng, grid_size, blocked)
133 if cell == Vector2i(-1, -1):
136 var entry = node.duplicate(true)
137 entry[
"cell"] = [cell.x, cell.y]
142func _place_interactions(rng:RandomNumberGenerator, interactions:Array, grid_size:Vector2i, blocked:Dictionary) -> Array:
144 for interaction
in interactions:
145 if not interaction
is Dictionary:
147 var cell = _random_open_cell(rng, grid_size, blocked)
148 if cell == Vector2i(-1, -1):
151 var entry = interaction.duplicate(true)
152 entry[
"cell"] = [cell.x, cell.y]
157func _place_enemies(rng:RandomNumberGenerator, enemies:Array, grid_size:Vector2i, blocked:Dictionary) -> Array:
159 for enemy
in enemies:
160 if not enemy
is Dictionary:
162 var cell = _random_open_cell(rng, grid_size, blocked)
163 if cell == Vector2i(-1, -1):
166 var entry = enemy.duplicate(true)
167 entry[
"cell"] = [cell.x, cell.y]
172func _arena_enemy_pool(enemies:Array, arena_options:Dictionary) -> Array:
173 var allowed = arena_options.get(
"enemy_archetypes", [])
175 for enemy
in enemies:
176 if not enemy
is Dictionary:
178 if allowed
is Array
and not allowed.is_empty()
and not allowed.has(str(enemy.get(
"archetype",
""))):
180 pool.append(enemy.duplicate(true))
184func _random_open_cell(rng:RandomNumberGenerator, grid_size:Vector2i, blocked:Dictionary) -> Vector2i:
185 for _attempt
in range(200):
186 var cell = Vector2i(rng.randi_range(0, grid_size.x - 1), rng.randi_range(0, grid_size.y - 1))
187 if not blocked.has(cell):
189 return Vector2i(-1, -1)
192func _shuffle_cells(cells:Array, rng:RandomNumberGenerator) -> void:
193 for index
in range(cells.size() - 1, 0, -1):
194 var swap_index = rng.randi_range(0, index)
195 var temp = cells[index]
196 cells[index] = cells[swap_index]
197 cells[swap_index] = temp
200func _shuffle_values(values:Array, rng:RandomNumberGenerator) -> void:
201 for index
in range(values.size() - 1, 0, -1):
202 var swap_index = rng.randi_range(0, index)
203 var temp = values[index]
204 values[index] = values[swap_index]
205 values[swap_index] = temp
208func _cell_from_value(value) -> Vector2i:
209 if value
is Vector2i:
212 return Vector2i(int(value.x), int(value.y))
213 if value
is Array
and value.size() >= 2:
214 return Vector2i(int(value[0]), int(value[1]))