Commit 745ee6bc authored by tgw3ff's avatar tgw3ff
Browse files

Implemented Biome Placement, Hole Placement, and Player Spawn Location CHoosing

Biomes that are now implemented are the boulder and mushroom biomes. Lake biome
will be implemented if time allows after the player character has a way to
traverse water tiles. Brick biomes, or brick paths will be implemented if time
allows.

There is a tree that spans across each cave room in the map. For each line
that branches off a room, a hole is spawned in that room and linked to a
hole in the room on the opposite end of the line.

There is also one hole placed in a random cave room that is not linked to any
other holes. This hole will serve as the end of the level.

This branch is ready to have the player character branch merged into it as
that branch contains the player teleportation feature needed for additional
development.
parent 340be132
56360
C:\Users\Username\Desktop\Godot_v3.2.3-stable_mono_win64\Godot_v3.2.3-stable_mono_win64.exe
......@@ -16,7 +16,7 @@ _global_script_class_icons={
[application]
config/name="Cs 4096 - 97 Top Down Game"
run/main_scene="res://test.tscn"
run/main_scene="res://Main.tscn"
config/icon="res://icon.png"
[input]
......
# Integer identifiers for the Vector2 and array types according to typeof() results
const TYPE_VECTOR2 = 5
const TYPE_ARRAY = 19
# Declare member variables here
# Enumerator that stores which integers represent which type of tile
# The pairings also equate to the corresponding tile group's index in the
# atlas tileset
enum {
HOLE = -2,
EMPTY = -1,
WALL,
BRICK,
WATER,
PLANT_T,
ROCK_T,
WATER_T
}
var x
var y
# Stores what type of tile is in this cell. See the above enum for more information
var content
# Stores whether or not this cell is an elligible hole spawning location
var elligible_hole = true
# Stores whether or not this cell is an elligible enemy spawning location
var elligible_enemy = true
# Stores the other hole or coordinates to which this cell's hole leads to, if
# it hosts a hole
var destination = null
# Stores the cell's distance from the nearest wall cell (default is the maximum
# signed int value)
var distance_wall = 0
# Stores whether or not this cell is the player's spawn
var player_spawn = false
func _init(coord, value):
# Account for Vector2 or array input for for x and y
if typeof(coord) == TYPE_ARRAY:
x = coord[0]
y = coord[1]
elif typeof(coord) == TYPE_VECTOR2:
x = coord.x
y = coord.y
content = value
if value == WALL:
distance_wall = 0
func spawn_hole(pair):
set_content(HOLE)
func set_content(value):
# Change content
content = value
# Change elligible_hole and elligible_enemy if needed
if content != EMPTY:
elligible_hole = false
elligible_enemy = false
const CELL = preload("res://src/Cell.gd")
const ROOM = preload("res://src/Room.gd")
# Stores the empty coordinates on the map and which "room" they are each a part of
var rooms = []
var centers = []
var minimum_tree = []
# Declare member variables here
# Enumerator that stores which integers represent which type of tile
# The pairings also equate to the corresponding tile group's index in the
......@@ -76,8 +80,6 @@ const FILLED = [ [WALL ,WALL ,WALL ],
# Stores a 2D array that represents the level map
# When referencing an index in the grid, use [y][x] format
var grid = []
# Stores the empty coordinates on the map and which "room" they are each a part of
var rooms = {}
func _init():
......@@ -91,16 +93,16 @@ func initialize_grid(fill_percent, width, height):
for h in height:
grid.append([])
for w in width:
var value = EMPTY
var value = CELL.EMPTY
if (w == 0 || w == width - 1 || h == 0 || h == height - 1):
value = WALL
value = CELL.WALL
else :
value = randi()%101
if value <= fill_percent:
value = WALL
value = CELL.WALL
elif value > fill_percent:
value = EMPTY
grid[h].append(value)
value = CELL.EMPTY
grid[h].append(CELL.new([w, h], value))
# Returns the number of filled cells in the grid that surround the given coordinate
......@@ -110,7 +112,7 @@ func get_surrounding_wall_count(grid_x, grid_y, width, height):
for y in range(grid_y - 1, grid_y + 2):
if (x >= 0 && x < width && y >= 0 && y < height):
if (x != grid_x || y != grid_y):
if grid[y][x] == WALL:
if grid[y][x].content == CELL.WALL:
wall_count += 1
else:
wall_count += 1
......@@ -124,12 +126,13 @@ func smooth_grid(width, height):
for y in range(0, height - 1):
var neighborWallTiles = get_surrounding_wall_count(x, y, width, height)
if neighborWallTiles > 4:
grid[y][x] = WALL
grid[y][x].set_content(CELL.WALL)
elif neighborWallTiles < 4:
grid[y][x] = EMPTY
grid[y][x].set_content(CELL.EMPTY)
# Return a matrix of the cell with the given coords and its surrounding 8 cells
# Return a matrix of the content of the cell at the given coords and its
# surrounding 8 cells' contents
func get_surrounding(grid_x, grid_y):
var surrounding = [ [],
[],
......@@ -137,7 +140,7 @@ func get_surrounding(grid_x, grid_y):
for y in range(grid_y - 1, grid_y + 2):
for x in range(grid_x - 1, grid_x + 2):
surrounding[y - grid_y + 1].append(grid[y][x])
surrounding[y - grid_y + 1].append(grid[y][x].content)
return surrounding
......@@ -165,7 +168,7 @@ func rotate_matrix(matrix=[[]], n=1):
for w in p_width:
matrix_p.append([])
for h in p_height:
matrix_p[w].append(EMPTY)
matrix_p[w].append(CELL.EMPTY)
# Fill new matrix with original rotated...
match n % 4:
......@@ -194,7 +197,7 @@ func mirror_matrix(matrix=[[]], v=false):
for h in m_height:
matrix_p.append([])
for w in m_width:
matrix_p[h].append(EMPTY)
matrix_p[h].append(CELL.EMPTY)
for x in range(0, m_width):
for y in range(0, m_height):
......@@ -205,12 +208,15 @@ func mirror_matrix(matrix=[[]], v=false):
return matrix_p
# Replaces a 3x3 submatrix with a center of grid_x, grid_y with the given replacement
################################################################################
# Fix to accept any size replacement grid
################################################################################
# Replaces a 3x3 submatrix with a center of grid_x, grid_y with the given
# replacement
func replace_3x3_grid(grid_x, grid_y, replacement):
for r_y in range(0, 3):
for r_x in range(0, 3):
grid[grid_y + r_y - 1][grid_x + r_x - 1] = replacement[r_y][r_x]
grid[grid_y + r_y - 1][grid_x + r_x - 1].set_content(replacement[r_y][r_x])
# Replace invalid submatrices in the grid/map
......@@ -258,79 +264,70 @@ func replace_invalids(width, height):
# Returns whether or not all of the coordinates in the rooms dictionary have been assigned a room
func assign_rooms_completed():
if null in rooms.values():
return false
else:
return true
var all_assigned = true
for row in grid:
for cell in row:
if cell.content != CELL.WALL:
var cell_in_rooms = false
for room in rooms:
if Vector2(cell.x, cell.y) in room.cells.keys():
cell_in_rooms = true
if all_assigned == true && cell_in_rooms == false:
all_assigned = false
return all_assigned
# Iterates through all rooms to see if the given coord is in any of them
func is_coord_in_rooms(coord):
var in_rooms = false
var coord_vector = Vector2()
for room in rooms:
if typeof(coord) == TYPE_ARRAY:
coord_vector.x = coord[0]
coord_vector.y = coord[1]
elif typeof(coord) == TYPE_VECTOR2:
coord_vector = coord
if coord_vector in room.cells.keys():
in_rooms = true
return in_rooms
# Assigns rooms to each of the coordinate keys in the rooms dictionary
func assign_rooms(width, height):
var complete = false
var unexplored = []
var queue = []
var curr_room = 0
var curr_room_id = 0
var curr_room = []
# Add each coordinate that does not house a wall as a key to the rooms dictionary
# Add each coordinate that does not house a wall to the unexplored array
for y in range(0, height):
for x in range(0, width):
if grid[y][x] != WALL:
rooms[Vector2(x, y)] = null
if grid[y][x].content != CELL.WALL:
unexplored.append(Vector2(x, y))
# While there are still empty coords that have not been assigned rooms...
while !complete:
# Assign rooms to every empty coord
for key in rooms:
complete = true
if rooms[key] == null:
complete = false
queue.append(key)
for coord in unexplored:
if !is_coord_in_rooms(coord):
queue.append(coord)
while !queue.empty():
var curr_queue_coord = queue.pop_front()
curr_room.append(grid[curr_queue_coord.y][curr_queue_coord.x])
# While the queue isn't empty...
while !queue.empty():
# Assign a room to the first coord in the queue
var curr_key = queue.pop_front()
rooms[curr_key] = curr_room
# Insert the current coordinate's 4 neighbors into the queue
# (if they are not already in the queue, are empty, and
# have not already been assigned a room)
for x_mod in range (-1, 2):
for y_mod in range(-1, 2):
for x_mod in range(-1, 2):
if abs(y_mod) != abs(x_mod):
var neighbor = Vector2(curr_key.x + x_mod, curr_key.y + y_mod)
if !queue.has(neighbor) && rooms.has(neighbor) && rooms[neighbor] == null:
queue.append(neighbor)
# All coordinates of the current room have been found, so look
# for coords that belong to the next room
curr_room += 1
# Returns an array of the centers of each room in Vector2 format
func get_centers():
var values = []
var centers = []
# Get all of the unique values, or rooms, in the rooms dictionary
for key in rooms:
if !(rooms[key] in values):
values.append(rooms[key])
while !values.empty():
var curr_value = values.pop_front()
var curr_value_count = 0
var x_avg = 0
var y_avg = 0
for key in rooms:
if rooms[key] == curr_value:
curr_value_count += 1
x_avg += key.x
y_avg += key.y
centers.append([Vector2(x_avg / curr_value_count, y_avg / curr_value_count), curr_value_count])
return centers
var neighbor_coord = Vector2(curr_queue_coord.x + x_mod, curr_queue_coord.y + y_mod)
if !(x_mod == 0 && y_mod == 0) && unexplored.has(neighbor_coord) && !queue.has(neighbor_coord) && !curr_room.has(grid[neighbor_coord.y][neighbor_coord.x]):
queue.append(neighbor_coord)
# All coordinates of the current room have been found, so insert cells
# at the found coords into a new room and then look for coords that
# belong to the next room
rooms.append(ROOM.new(curr_room_id, curr_room))
curr_room.clear()
curr_room_id += 1
centers.append(rooms.back().center)
# A modified Kruskal's algorithm to find close to the minimum spanning tree
......@@ -341,13 +338,13 @@ func get_minimum_spanning_tree():
var m_s_tree = []
var visited = []
for p1 in centers:
for p2 in centers:
if p1 != p2:
var distance = p1[0].distance_to(p2[0])
for r1 in rooms:
for r2 in rooms:
if r1.center != r2.center:
var distance = r1.center.distance_to(r2.center)
if tree.empty():
tree.append([p1[0], p2[0], distance])
tree.append([r1, r2, distance])
else:
var index = -1
var duplicate = false
......@@ -355,39 +352,86 @@ func get_minimum_spanning_tree():
if index == -1 && distance < tree[i][2]:
index = i
if tree[i][0] == p2[0] && tree[i][1] == p1[0]:
if tree[i][0].center == r2.center && tree[i][1].center == r1.center:
duplicate = true
if !duplicate:
if index == -1:
tree.push_back([p1[0], p2[0], distance])
tree.push_back([r1, r2, distance])
else:
tree.insert(index, [p1[0], p2[0], distance])
tree.insert(index, [r1, r2, distance])
while visited.size() != centers.size():
while visited.size() != rooms.size():
if m_s_tree.empty():
var biggest_room
var curr_max = 0
for center in centers:
if center[1] > curr_max:
curr_max = center[1]
biggest_room = center[0]
for room in rooms:
if room.cells.size() > curr_max:
curr_max = room.cells.size()
biggest_room = room.id
visited.append(biggest_room)
for line in tree:
if visited.has(line[0]) != visited.has(line[1]):
if !visited.has(line[0]):
visited.append(line[0])
if visited.has(line[0].id) != visited.has(line[1].id):
if !visited.has(line[0].id):
visited.append(line[0].center)
else:
visited.append(line[1])
visited.append(line[1].center)
m_s_tree.append(line)
m_s_tree.append([line[0].center, line[1].center, line[2]])
return m_s_tree
# Spawn holes
func spawn_holes():
for line in minimum_tree:
var center1 = line[0]
var center2 = line[1]
var room1
var room2
for room in rooms:
if room.center == center1:
room1 = room
elif room.center == center2:
room2 = room
var hole1 = room1.get_random_hole_spawn()
var hole2 = room2.get_random_hole_spawn()
room1.spawn_hole(hole1, hole2)
room2.spawn_hole(hole2, hole1)
# Spawn the end of the level hole
var room
var room_found = false
while !room_found:
randomize()
room = rooms[randi() % rooms.size()]
if room.count_elligible_hole > 0:
room_found = true
room.spawn_hole(room.get_random_hole_spawn(), null)
# Spawn player
func spawn_player():
randomize()
var room = rooms[randi() % rooms.size()]
var possible_spawns = []
var possible_replacements = [CELL.EMPTY, CELL.PLANT_T, CELL.ROCK_T, CELL.BRICK]
for cell in room.cells.keys():
if room.cells[cell].content in possible_replacements:
possible_spawns.append(cell)
randomize()
var spawn_coord = possible_spawns[randi() % possible_spawns.size()]
if room.cells[spawn_coord].content != CELL.BRICK:
room.cells[spawn_coord].content = CELL.EMPTY
room.cells[spawn_coord].player_spawn = true
# Generates a new map
func generate_map(fill_percent, width, height):
rooms.clear()
......@@ -400,11 +444,8 @@ func generate_map(fill_percent, width, height):
assign_rooms(width, height)
centers = get_centers()
minimum_tree = get_minimum_spanning_tree()
# Returns the grid member variable
func get_grid():
return grid
spawn_holes()
spawn_player()
const CELL = preload("res://src/Cell.gd")
const BOULDER_RANGE = 2
const BOULDER_FILL_PERCENT = 0.25
const MUSHROOM_CLUSTER_RADIUS = 4
const MUSHROOM_CLUSTER_FILL_PERCENT = 0.15
const MUSHROOM_ROOM_FILL_PERCENT = 0.15
# Stores the id of the room
var id
# Stores a dictionary with the keys being Vector2's that represent coordinates
# and values that are the cells located at those coordinates
var cells = {}
# Stores an average of all of the coordinates of all of the room's cells
var center
# Stores an updated count of the room's cells that are valid hole spawns
var count_elligible_hole = 0
# Calulates the average of all of the coordinates of the room's cells and sets
# the result as the room's center
func find_center():
var count_cells = 0
var x_avg = 0
var y_avg = 0
for key in cells:
count_cells += 1
x_avg += key.x
y_avg += key.y
center = Vector2(x_avg / count_cells, y_avg / count_cells)
# Returns the Vector2 coords of the neighboring cells of the given coord in the
# given radius including itself
func get_surrounding_coords(coord, radius):
var center_neighbors = Vector2()
var neighbors = []
# Account for Vector2 or array coords
if typeof(coord) == TYPE_ARRAY:
center_neighbors.x = coord[0]
center_neighbors.y = coord[1]
elif typeof(coord) == TYPE_VECTOR2:
center_neighbors = coord
# Compile a list of the coords surrounding the given coord in a 3x3 area
for y_mod in range(-radius, radius + 1):
for x_mod in range(-radius, radius + 1):
var neighbor = Vector2(coord.x + x_mod, coord.y + y_mod)
if neighbor in cells.keys():
neighbors.append(Vector2(coord.x + x_mod, coord.y + y_mod))
return neighbors
# Return the given cell's distance from the nearest cell with the given content
# value
func find_distance_individual(coord, value):
var distance_current = -1
var distance_found = false
while !distance_found:
var surrounding_contents = []
distance_current += 1
for x_mod in range (-distance_current, distance_current + 1):
for y_mod in range (-distance_current, distance_current + 1):
if cells.keys().has(Vector2(coord.x + x_mod, coord.y + y_mod)):
surrounding_contents.append(cells[Vector2(coord.x + x_mod, coord.y + y_mod)].content)
elif value == CELL.WALL:
distance_found = true
if value in surrounding_contents:
distance_found = true
surrounding_contents = []
return distance_current
# For each cell, find how far it is away from the nearest wall tile
func find_distances_wall():
for cell in cells:
cells[cell].distance_wall = find_distance_individual(cell, CELL.WALL)
# Spawns boulders randomly within a certain distance from wall tiles until there
# are no more valid spawn locations or until the amount of boulder tiles in
# relation to the room's total cell count reaches or excedes the fill_percent
func spawn_boulders(max_distance, fill_percent, fail_rate = 0.00):
var possible_spawns = cells.keys()
var boulder_count = 0.0
while (boulder_count / cells.size()) <= fill_percent && !possible_spawns.empty():
randomize()
var curr_index = randi() % possible_spawns.size()
var curr_spawn = possible_spawns[curr_index]
possible_spawns.remove(curr_index)
if cells[curr_spawn].content == CELL.EMPTY && find_distance_individual(curr_spawn, CELL.WALL) <= max_distance:
randomize()
if ((randi() % 100) / 100.0) >= fail_rate:
cells[curr_spawn].content = CELL.ROCK_T
boulder_count += 1
# Spawns mushrooms randomly in clusters until a percentage of the room is filled
# with mushrooms
func spawn_mushrooms(max_radius, cluster_fill_percent, room_fill_percent, fail_rate = 0.00):
var possible_spawns = cells.keys()
var mushroom_room_count = 0.0
while (mushroom_room_count / cells.size()) <= room_fill_percent && !possible_spawns.empty():
randomize()
var curr_index = randi() % possible_spawns.size()
var curr_cluster_center = possible_spawns[curr_index]
possible_spawns.remove(curr_index)
randomize()
var possible_cluster_spawns = get_surrounding_coords(curr_cluster_center, (randi() % max_radius))
var cluster_size = possible_cluster_spawns.size()
var mushroom_cluster_count = 0.0
while (mushroom_cluster_count / cluster_size) <= cluster_fill_percent && (mushroom_room_count / cells.size()) <= room_fill_percent && !possible_cluster_spawns.empty():
randomize()
var curr_cluster_index = randi() % possible_cluster_spawns.size()
var curr_cluster_spawn = possible_cluster_spawns[curr_cluster_index]
possible_cluster_spawns.remove(curr_cluster_index)
if cells[curr_cluster_spawn].content == CELL.EMPTY:
randomize()
if ((randi() % 100) / 100.0) >= fail_rate:
cells[curr_cluster_spawn].content = CELL.PLANT_T
mushroom_room_count += 1
mushroom_cluster_count += 1
func _init(i, room_cells):
id = i
for cell in room_cells:
cells[Vector2(cell.x, cell.y)] = cell
# If the cell elligible for hole placement then increase the rooms count of such cells
if cell.elligible_hole:
count_elligible_hole += 1