Add all files

This commit is contained in:
2025-04-18 22:30:19 +02:00
parent 44aec1a1fb
commit 4ec52c34d4
2477 changed files with 76624 additions and 0 deletions

View File

@@ -0,0 +1,624 @@
extends RefCounted # Using RefCounted instead of Node since this is a utility class
enum DUNGEON_ENTITY_TYPES {
ENEMY,
OBJECT,
TRAP
}
# Constants
const DOOR_MODIFIERS = [
{"name": "Locked Door", "type": "Locked"},
{"name": "Bomb Wall", "type": "Bombable"}
]
const FLOOR_TILES = [
7,8,9,10,11,12,
25,26,27,28,29,30,31,
44,45,46,47,48,49,50,
63,64,65,66,67,68,69
]
const WALL_VARIATIONS = [
0.45,
0.15,
0.15,
0.15,
0.1
]
const OBJECT_TYPES = [
{"name": "Barrel", "ti": [70], "ti2": [89], "openable": false, "liftable": true, "throwable": false, "hp": - 1, "pushable": true, "size": {"x": 1, "y": 1}},
{"name": "Pot", "ti": [13, 14, 15], "ti2": [51, 52, 53], "openable": false, "liftable": true, "throwable": true, "hp": 1, "pushable": true, "size": {"x": 1, "y": 1}},
{"name": "Chest", "ti": [108], "ti2": [127], "openable": true, "liftable": false, "throwable": false, "hp": - 1, "pushable": false, "size": {"x": 1, "y": 1}},
{"name": "Bench", "ti": [35, 36], "ti2": [16, 17], "openable": false, "liftable": false, "throwable": false, "hp": - 1, "pushable": false, "size": {"x": 2, "y": 1}}
]
const MONSTER_TYPES = [
{
"name": "Goblin",
},
{
"name": "Slime",
},
# ... other monster types similar to JS version
]
const TRAP_TYPES = [
{"name": "Spike Trap", "description": "Spikes shoot up from the floor when triggered."},
{"name": "Arrow Trap", "description": "Arrows fire from the walls when a player steps on a pressure plate."},
# ... other trap types
]
const ROOM_MODIFIERS = [
{"name": "Player Start", "description": "The players start here.", "type": "START", "negative_modifiers": {"min": 0, "max": 0}},
{"name": "Exit", "description": "Room contains an exit.", "type": "EXIT", "negative_modifiers": {"min": 0, "max": 0}},
# ... other room modifiers
]
# Main generation function
func generate_dungeon(map_size: Vector2, _num_rooms: int, min_room_size: int, max_room_size: int) -> Dictionary:
# Initialize grid
var grid = []
var randgrid = []
for x in range(map_size.x):
grid.append([])
randgrid.append([])
for y in range(map_size.y):
grid[x].append(0)
randgrid[x].append(0)
var all_rooms = []
var all_doors = []
# 1. Create first room at a random position
var first_w = rand_range_i(min_room_size, max_room_size)
var first_h = rand_range_i(min_room_size, max_room_size)
var first_room = {
"x": rand_range_i(4, map_size.x - first_w - 4), # Random position with buffer
"y": rand_range_i(4, map_size.y - first_h - 4),
"w": first_w,
"h": first_h,
"modifiers": []
}
set_floor(first_room, grid, map_size)
all_rooms.append(first_room)
var nrOfDoorErrors = 0
var nrOfRoomErrors = 0
# 2. Try to place rooms until we can't fit any more
var attempts = 1000 # Prevent infinite loops
while attempts > 0 and all_rooms.size() > 0:
# Pick a random existing room
var source_room = all_rooms[randi() % all_rooms.size()]
# Try to place a new room near it
var new_room = try_place_room_near(source_room, grid, map_size, min_room_size, max_room_size)
if new_room.w > 0: # Valid room created
set_floor(new_room, grid, map_size)
all_rooms.append(new_room)
attempts -= 1
if attempts <= 0:
print("Dungeon generator:Reached maximum attempts")
nrOfRoomErrors += 1
break
# 3. Connect rooms with corridors/doors
if all_rooms.size() > 1:
var connected_rooms = {}
for room in all_rooms:
connected_rooms[room] = []
# First pass: try to connect each room to its closest neighbors
for room in all_rooms:
var closest_rooms = find_closest_rooms(room, all_rooms)
#print("Connecting room at ", room.x, ",", room.y)
var connection_attempts = 0
var max_connection_attempts = 3 # Try to connect to multiple neighbors
for target_room in closest_rooms:
if connection_attempts >= max_connection_attempts:
break
if not rooms_are_connected(room, target_room, all_doors):
var door = create_corridor_between_rooms(room, target_room, grid)
if door.size() > 0:
#print("Created direct connection between rooms")
set_door(door, grid)
all_doors.append(door)
connected_rooms[room].append(target_room)
connected_rooms[target_room].append(room)
connection_attempts += 1
# Second pass: ensure all rooms are connected
var attempts2 = 100
while attempts2 > 0:
var reachable = find_reachable_rooms(all_rooms[0], all_rooms, all_doors)
#print("Reachable rooms: ", reachable.size(), "/", all_rooms.size())
if reachable.size() == all_rooms.size():
#print("All rooms connected!")
break
# Find an unreachable room and try to connect it
for room in all_rooms:
if not reachable.has(room):
print("Found unreachable room at ", room.x, ",", room.y)
var connected = false
# Try to connect to each reachable room until success
for target_room in reachable:
var door = create_corridor_between_rooms(room, target_room, grid)
if door.size() > 0:
print("Connected unreachable room")
set_door(door, grid)
all_doors.append(door)
connected = true
break
if not connected:
print("Failed to connect room directly, trying intermediate room")
# Try creating intermediate room with multiple positions
for offset_x in [-2, 0, 2]:
for offset_y in [-2, 0, 2]:
var mid_room = create_intermediate_room(room, reachable[0], offset_x, offset_y)
if is_valid_room_position(mid_room, grid, map_size):
set_floor(mid_room, grid, map_size)
all_rooms.append(mid_room)
var door1 = create_corridor_between_rooms(room, mid_room, grid)
var door2 = create_corridor_between_rooms(mid_room, reachable[0], grid)
if door1.size() > 0 and door2.size() > 0:
set_door(door1, grid)
set_door(door2, grid)
all_doors.append(door1)
all_doors.append(door2)
connected = true
break
if connected:
break
if connected:
break
attempts2 -= 1
if attempts2 <= 0:
print("Failed to connect all rooms after maximum attempts")
nrOfDoorErrors += 1
break
for x in range(map_size.x):
for y in range(map_size.y):
if grid[x][y] == 0: # wall
var rand = randf()
var sum:float = 0.0
for i in WALL_VARIATIONS.size():
sum += WALL_VARIATIONS[i];
if rand <= sum:
randgrid[x][y] = i
break
elif grid[x][y] == 1: # floor
if randf() < 0.6:
randgrid[x][y] = 0
else:
randgrid[x][y] = randi_range(1,FLOOR_TILES.size()-1)
elif grid[x][y] == 2: # door
randgrid[x][y] = 0 # we dont care about these... only have 1 variant
var startRoomIndex = randi_range(0,all_rooms.size()-1)
all_rooms[startRoomIndex].modifiers.push_back(ROOM_MODIFIERS[0])
var farthestRoom = null
var maxDistance = 0
var exitRoomIndex = -1
var roomIndex = 0
for r in all_rooms:
var distance = abs(r.x - all_rooms[startRoomIndex].x) + abs(r.y - all_rooms[startRoomIndex].y)
if (distance > maxDistance):
maxDistance = distance
farthestRoom = r
exitRoomIndex = roomIndex
roomIndex+=1
pass
farthestRoom.modifiers.push_back(ROOM_MODIFIERS[1])
var entities = []
var TILE_SIZE = 16
roomIndex = 0
#populate rooms and decide modifiers for rooms
for r in all_rooms:
if roomIndex != startRoomIndex and roomIndex != exitRoomIndex:
var validRoomLocations = []
var min_x = (r.x + 1)
var max_x = ((r.x + r.w) - 1)
var min_y = (r.y + 1)
var max_y = ((r.y + r.h) - 1)
for rw in range(min_x,max_x):
for rh in range(min_y, max_y):
validRoomLocations.push_back(Vector2(rw*TILE_SIZE - 8, rh*TILE_SIZE - 8)) # we assume entities are 16x16 are centered
# bigger rooms can have a larger content number!
var randNrOfEntities = randi_range(0, 4)
for entI in randNrOfEntities:
var enttype:DUNGEON_ENTITY_TYPES = randi_range(0, DUNGEON_ENTITY_TYPES.size()-1) as DUNGEON_ENTITY_TYPES
var entStats = {}
# hand code to only be enemies atm
if enttype == DUNGEON_ENTITY_TYPES.TRAP:
enttype = DUNGEON_ENTITY_TYPES.OBJECT
#enttype = DUNGEON_ENTITY_TYPES.OBJECT ## only objects now...
var subtype = "goblin"
if enttype == DUNGEON_ENTITY_TYPES.ENEMY:
var randType = randi_range(0, 1)
var cStats = CharacterStats.new()
if randType == 1:
cStats.hp = 2
subtype = "slime"
else:
cStats.hp = 3
cStats.skin = "res://assets/gfx/Puny-Characters/Layer 0 - Skins/Orc1.png"
cStats.skin = "res://assets/gfx/Puny-Characters/Layer 0 - Skins/Orc2.png"
var hair = 0
if randf() > 0.6:
hair = randi_range(1,13)
cStats.setHair(hair, randi_range(0,8))
var facialhair = 0
if randf() > 0.75: # very uncommon for facial hair on goblins
facialhair = randi_range(1,3)
cStats.setFacialHair(facialhair, randi_range(0, 4))
#cStats.add_on = "res://assets/gfx/Puny-Characters/Layer 7 - Add-ons/Orc Add-ons/GoblinEars1.png"
cStats.add_on = "res://assets/gfx/Puny-Characters/Layer 7 - Add-ons/Orc Add-ons/GoblinEars2.png"
#cStats.add_on = "res://assets/gfx/Puny-Characters/Layer 7 - Add-ons/Orc Add-ons/OrcJaw1.png"
#cStats.add_on = "res://assets/gfx/Puny-Characters/Layer 7 - Add-ons/Orc Add-ons/OrcJaw2.png"
# randomize if the goblin will have a weapon like dagger or sword
# randomize if the goblin will have bow and arrows also
# randomize if the goblin will have an armour and helmet etc.
entStats = cStats.save()
elif enttype == DUNGEON_ENTITY_TYPES.OBJECT:
subtype = "pot"
else:
subtype = "spike"
var posI = randi_range(0, validRoomLocations.size()-1)
var entity = {
"type": enttype,
"subtype": subtype,
"stats": entStats,
"position": {
"x": validRoomLocations[posI].x,
"y": validRoomLocations[posI].y
}
}
entities.push_back(entity)
validRoomLocations.remove_at(posI) # this is now occupied... don't allow anything else spawn on it.
# fill up modifiers per room
if ROOM_MODIFIERS.size() > 2:
r.modifiers.push_back(ROOM_MODIFIERS[randi_range(2, ROOM_MODIFIERS.size()-2)])
pass
roomIndex += 1
return {
"rooms": all_rooms,
"entities": entities,
"doors": all_doors,
"grid": grid,
"randgrid": randgrid, # grid containing actual tile index-ish(ish)
"mapSize": map_size,
"nrOfDoorErrors": nrOfDoorErrors,
"nrOfRoomErrors": nrOfRoomErrors
}
# Helper functions
func create_random_room(map_size: Vector2, min_size: int, max_size: int) -> Dictionary:
var x = randi() % (int(map_size.x) - max_size - 2) + 1
var y = randi() % (int(map_size.y) - max_size - 2) + 1
var w = rand_range_i(min_size, max_size)
var h = rand_range_i(min_size, max_size)
return {"x": x, "y": y, "w": w, "h": h, "modifiers": []}
func rand_range_i(min_val: int, max_val: int) -> int:
return min_val + (randi() % (max_val - min_val + 1))
func set_floor(room: Dictionary, grid: Array, map_size: Vector2) -> void:
for x in range(room.x, room.x + room.w):
for y in range(room.y, room.y + room.h):
if x >= 0 and x < map_size.x and y >= 0 and y < map_size.y:
grid[x][y] = 1 # Set as floor tile
# ... Additional helper functions and implementation details would follow ...
# ... previous code ...
func try_place_room_near(source_room: Dictionary, grid: Array, map_size: Vector2,
min_room_size: int, max_room_size: int) -> Dictionary:
var attempts = 20
while attempts > 0:
var w = rand_range_i(min_room_size, max_room_size)
var h = rand_range_i(min_room_size, max_room_size)
# Try all four sides of the source room
var sides = ["N", "S", "E", "W"]
sides.shuffle()
for side in sides:
var x = source_room.x
var y = source_room.y
match side:
"N":
x = source_room.x + (randi() % max(1, source_room.w - w))
y = source_room.y - h - 4 # 4 tiles away
"S":
x = source_room.x + (randi() % max(1, source_room.w - w))
y = source_room.y + source_room.h + 4
"W":
x = source_room.x - w - 4
y = source_room.y + (randi() % max(1, source_room.h - h))
"E":
x = source_room.x + source_room.w + 4
y = source_room.y + (randi() % max(1, source_room.h - h))
if is_valid_room_position({"x": x, "y": y, "w": w, "h": h}, grid, map_size):
return {"x": x, "y": y, "w": w, "h": h, "modifiers": []}
attempts -= 1
return {"x": 0, "y": 0, "w": 0, "h": 0, "modifiers": []}
func find_closest_rooms(room: Dictionary, all_rooms: Array) -> Array:
if all_rooms.size() <= 1:
return []
var distances = []
for other in all_rooms:
if other == room:
continue
var dist = abs(room.x - other.x) + abs(room.y - other.y)
distances.append({"room": other, "distance": dist})
# Sort by distance
if distances.size() > 0:
distances.sort_custom(func(a, b): return a.distance < b.distance)
# Return the rooms only, in order of distance
return distances.map(func(item): return item.room)
return []
func rooms_are_connected(room1: Dictionary, room2: Dictionary, doors: Array) -> bool:
for door in doors:
if (door.room1 == room1 and door.room2 == room2) or \
(door.room1 == room2 and door.room2 == room1):
return true
return false
func create_corridor_between_rooms(room1: Dictionary, room2: Dictionary, _grid: Array) -> Dictionary:
# Determine if rooms are more horizontal or vertical from each other
var dx = abs(room2.x - room1.x)
var dy = abs(room2.y - room1.y)
# Check if rooms are too far apart (more than 8 tiles)
if dx > 8 and dy > 8:
return {}
if dx > dy:
# Horizontal corridor
var leftRoom = room1 if room1.x < room2.x else room2
var rightRoom = room2 if room1.x < room2.x else room1
# Check if rooms are horizontally adjacent (gap should be reasonable)
if rightRoom.x - (leftRoom.x + leftRoom.w) > 8:
return {}
# Door must start at the right edge of left room plus 1 tile gap
var door_x = leftRoom.x + leftRoom.w
# Door y must be within both rooms' height ranges, accounting for walls
var min_y = max(leftRoom.y + 1, rightRoom.y + 1) # +1 to account for walls
var max_y = min(leftRoom.y + leftRoom.h - 2, rightRoom.y + rightRoom.h - 2) # -2 to ensure both tiles fit
# Make sure we have a valid range
if max_y < min_y:
return {}
# Pick a valid y position within the range
var door_y = min_y + (randi() % max(1, max_y - min_y + 1))
# Calculate actual width needed (distance between rooms)
var door_width = rightRoom.x - (leftRoom.x + leftRoom.w + 1)
# Use the larger of minimum width (4) or actual distance
door_width = max(4, door_width + 1)
# Create door with calculated width
var door = {
"x": door_x,
"y": door_y,
"w": door_width, # Use calculated width
"h": 2, # Fixed height for horizontal doors
"dir": "E" if leftRoom == room1 else "W",
"room1": room1,
"room2": room2
}
return door
else:
# Vertical corridor
var topRoom = room1 if room1.y < room2.y else room2
var bottomRoom = room2 if room1.y < room2.y else room1
# Check if rooms are vertically adjacent (gap should be reasonable)
if bottomRoom.y - (topRoom.y + topRoom.h) > 8:
return {}
# Door must start at the bottom edge of top room plus 1 tile gap
var door_y = topRoom.y + topRoom.h
# Door x must be within both rooms' width ranges, accounting for walls
var min_x = max(topRoom.x + 1, bottomRoom.x + 1) # +1 to account for walls
var max_x = min(topRoom.x + topRoom.w - 2, bottomRoom.x + bottomRoom.w - 2) # -2 to ensure both tiles fit
# Make sure we have a valid range
if max_x < min_x:
return {}
# Pick a valid x position within the range
var door_x = min_x + (randi() % max(1, max_x - min_x + 1))
# Calculate actual height needed (distance between rooms)
var door_height = bottomRoom.y - (topRoom.y + topRoom.h + 1)
# Use the larger of minimum height (4) or actual distance
door_height = max(4, door_height + 1)
# Create door with calculated height
var door = {
"x": door_x,
"y": door_y,
"w": 2, # Fixed width for vertical doors
"h": door_height, # Use calculated height
"dir": "S" if topRoom == room1 else "N",
"room1": room1,
"room2": room2
}
return door
func add_room_modifiers(rooms: Array) -> void:
# Add start room modifier to first room
rooms[0].modifiers.append(ROOM_MODIFIERS[0]) # START modifier
# Add exit to last room
rooms[-1].modifiers.append(ROOM_MODIFIERS[1]) # EXIT modifier
# Add random modifiers to other rooms
for i in range(1, rooms.size() - 1):
if randf() < 0.3: # 30% chance for a modifier
var available_modifiers = ROOM_MODIFIERS.slice(2, ROOM_MODIFIERS.size())
# Only add modifier if there are available ones
if available_modifiers.size() > 0:
rooms[i].modifiers.append(available_modifiers[randi() % available_modifiers.size()])
func generate_all_room_objects(rooms: Array, doors: Array) -> Array:
var room_objects = []
# Generate objects for each room
for room in rooms:
var objects = generate_room_objects(room)
room_objects.append_array(objects)
# Add door modifiers
for door in doors:
if randf() < 0.2: # 20% chance for door modifier
var modifier = DOOR_MODIFIERS[randi() % DOOR_MODIFIERS.size()]
room_objects.append({
"type": "Door",
"x": door.x,
"y": door.y,
"modifier": modifier
})
return room_objects
func generate_room_objects(room: Dictionary) -> Array:
var objects = []
var max_objects = int((room.w * room.h) / 16) # Roughly one object per 16 tiles
for _i in range(max_objects):
if randf() < 0.7: # 70% chance to place each potential object
var obj_type = OBJECT_TYPES[randi() % OBJECT_TYPES.size()]
var x = rand_range_i(room.x + 1, room.x + room.w - obj_type.size.x - 1)
var y = rand_range_i(room.y + 1, room.y + room.h - obj_type.size.y - 1)
# Check if position is free
var can_place = true
for obj in objects:
if abs(obj.x - x) < 2 and abs(obj.y - y) < 2:
can_place = false
break
if can_place:
objects.append({
"type": obj_type.name,
"x": x,
"y": y,
"properties": obj_type
})
return objects
func set_door(door: Dictionary, grid: Array) -> void:
match door.dir:
"N", "S":
if door.h > 4:
print("ns - larger door", door.h)
# Set 2x4 corridor
for dx in range(2):
for dy in range(door.h):
#if grid[door.x + dx][door.y + dy] != 1: # Don't overwrite room
grid[door.x + dx][door.y + dy] = 2
"E", "W":
if door.w > 4:
print("ew - larger door", door.w)
# Set 4x2 corridor
for dx in range(door.w):
for dy in range(2):
#if grid[door.x + dx][door.y + dy] != 1: # Don't overwrite room
grid[door.x + dx][door.y + dy] = 2
func is_valid_room_position(room: Dictionary, grid: Array, map_size: Vector2) -> bool:
# Check if room is within map bounds with buffer
if room.x < 4 or room.y < 4 or room.x + room.w >= map_size.x - 4 or room.y + room.h >= map_size.y - 4:
#print("Room outside map bounds")
return false
# Check if room overlaps with existing rooms or corridors
# Check the actual room area plus 4-tile buffer for spacing
for x in range(room.x - 4, room.x + room.w + 4):
for y in range(room.y - 4, room.y + room.h + 4):
if x >= 0 and x < map_size.x and y >= 0 and y < map_size.y:
if grid[x][y] != 0: # If tile is not empty
#print("Room overlaps at ", Vector2(x, y))
return false
return true
# Add this helper function to check room connectivity
func find_reachable_rooms(start_room: Dictionary, _all_rooms: Array, all_doors: Array) -> Array:
var reachable = [start_room]
var queue = [start_room]
while queue.size() > 0:
var current = queue.pop_front()
for door in all_doors:
var next_room = null
if door.room1 == current:
next_room = door.room2
elif door.room2 == current:
next_room = door.room1
if next_room != null and not reachable.has(next_room):
reachable.append(next_room)
queue.append(next_room)
return reachable
func create_intermediate_room(room1: Dictionary, room2: Dictionary, offset_x: int, offset_y: int) -> Dictionary:
return {
"x": room1.x + (room2.x - room1.x) / 2 + offset_x,
"y": room1.y + (room2.y - room1.y) / 2 + offset_y,
"w": 6,
"h": 6,
"modifiers": []
}