Just created an account for this purpose:
I had to use RPG Maker VX for a college course, and I modified this script heavily into something for myself. I probably won't stick with the program, but the script is complete enough that I thought I'd pass it on to you guys.
Enjoy.
#!/usr/bin/ruby
#===============================================================================
# Roguelike Random Dungeon Generator
#-------------------------------------------------------------------------------
# Version: 2.1
# Forked By: Taylor C. Richberger
# Original Author: cozziekuns (rmrk)
# Last Date Updated: 02 November, 2011
#===============================================================================
# Description:
#-------------------------------------------------------------------------------
# A random dungeon generator akin to that of Roguelikes
#===============================================================================
# Use:
#-------------------------------------------------------------------------------
# -Top left tile on the map (tile 0, 0) is the floor tile
# -Next tile to the right (tile 1, 0) is the wall tile (be careful and only
# use a useful wall tile. This rule is more relaxed for a floating-style
# dungeon). For most wall tiles (using W or O as the type), you want to
# use the tile that shows the TOP of the wall.
# Experiment to get a desirable result.
# -Events are randomly placed in map, and then player is placed on top of
# whatever event is titled ENTRANCE.
# -NO EVENT ON A RANDOMLY GENERATED DUNGEON MAY MOVE THE PLAYER TO IT'S OWN
# MAP. THIS WILL CAUSE PROBLEMS.
# -Map seed information is saved based on the save slot.
# Saved as "Save[filenumber].seeds". Because of this, any map that you
# want to be saved NEEDS a unique
# -Give the map some space. If it is just big enough, the generator will
# take a long time, and if it is too small, it will enter an infinite loop
# without crashing. It is reccommended to use a map at least 100x100 in
# size.
# -Player will automatically be transferred to targeted event from another
# map's event. This is useful for multi-level random dungeons.
# -Map name influences dungeon.
# As long as map name starts with DUNGEON, the generator expects to see
# the format:
# DUNGEON-[roomminsize]-[roommaxsize]-[features]-[type]-[s level]-[s passes]-[name]
# roomminsize: Minimum room size, varies.
# roommaxsize: Maximum room size, varies.
# features: Approximate hallway count.
# type: Determines the type of dungeon.
# F is floating
# W is full walls
# O is walls with an empty outline
# S is for short or level walls (like water or tables).
# s level: Levels of smoothing, from 0 to 10. This is not glitch-
# free, but generally behaves well as long as you don't
# go crazy with it. You may specify this value as 0 to
# disable smoothing.
# s passes: passes for smoothing, may be any integer. Like the
# previous value, this is not entirely glitch free.
# Experiment with different values of smoothing levels and
# passes to get different results. You may specify this
# value as 0 to disable smoothing.
# name: A unique name. May not contain hyphens. This is used for
# generating the same map every time, as a sort of pseudo
# map-save system. Name this variable "RAND" (without the quotes)
# in order to have it randomly generate every time. This variable
# MUST BE PRESENT, or the game will crash
# ex: DUNGEON-03-05-5-S-4-1-Town, DUNGEON-03-05-5-W-0-0-Crypt, or DUNGEON-05-08-20-F-2-3-RAND
#
# Without beginning with DUNGEON, the map is loaded as default
#===============================================================================
# Latest updates:
#-------------------------------------------------------------------------------
# - Small bug fix. Exits were still being randomized.
#===============================================================================
# Future goals:
#-------------------------------------------------------------------------------
# - Make sure event named EXIT is placed far from player
# - Possibly allow for true handling of an enemy named BOSS
#===============================================================================
# adds an event reader to Game_Event. This is needed for the Dungeon Generator
class Game_Event
attr_reader :event
end
# adds a map reader to Game_Map.
class Game_Map
attr_reader :map
def name
map_infos = load_data("Data/MapInfos.rvdata")
name = map_infos[@map_id].name
name.gsub!(/\\N\[([0-9]+)\]/i) { $game_actors[$1.to_i].name }
return name
end
end
# creates seed array for new game, and loads seed file for old game
class Scene_Title
def command_new_game
confirm_player_location
Sound.play_decision
$seeds = Array.new(Array.new())
$game_party.setup_starting_members # Initial party
$game_map.setup($data_system.start_map_id) # Initial map position
$game_player.moveto($data_system.start_x, $data_system.start_y)
$game_player.refresh
$scene = Scene_Map.new
RPG::BGM.fade(1500)
close_command_window
Graphics.fadeout(60)
Graphics.wait(40)
Graphics.frame_count = 0
RPG::BGM.stop
$game_map.autoplay
end
end
class Scene_File
def do_save
file = File.open(@savefile_windows[@index].filename, "wb")
write_save_data(file)
outSeeds = File.open(File.basename(file.path(), File.extname(file.path())) + ".seeds", "w")
file.close()
if (!$seeds.empty?)
for i in $seeds
outSeeds.puts(i[0] + " " + i[1].to_s())
end
end
outSeeds.close()
return_scene
end
def do_load
file = File.open(@savefile_windows[@index].filename, "rb")
read_save_data(file)
$seeds = Array.new()
inSeeds = File.open(File.basename(file.path(), File.extname(file.path())) + ".seeds", "r")
file.close()
inSeeds.each_line do |line|
$seeds << [line.split(" ")[0], line.split(" ")[1]]
end
inSeeds.close()
$scene = Scene_Map.new
RPG::BGM.fade(1500)
Graphics.fadeout(60)
Graphics.wait(40)
@last_bgm.play
@last_bgs.play
end
end
module RDG
def self.map_settings(room_min, room_max, feat, type, smooth, passes, name)
room_min_width = room_min
room_max_width = room_max
room_min_height = room_min
room_max_height = room_max
features = feat
short = (type == "S")
black_outline = (type == "O")
floating = (type == "F")
smoothLevels = smooth
smoothPasses = passes
mapName = name.to_s()
return [room_min_width, room_max_width, room_min_height, room_max_height, features, black_outline, floating, short, smoothLevels, smoothPasses, mapName].compact
end
end
#==============================================================================
# ** Game_Map
#==============================================================================
class Game_Map
alias old_setup setup
def setup(*args)
srand(Time.now().to_i())
old_setup(*args)
if ($game_map.name().split("-")[0] == "DUNGEON")
create_dungeon
end
end
def create_dungeon
set_variables()
process_seed()
fill_wall_tiles()
dig_random_room()
for i in 0...@features
count = 0
loop do
count += 1
choose_next_wall()
make_new_feature()
break if @can_make or count == 5
end
end
refine_tilemap()
# set_player_and_events() is commented out because it is called upon update() anyway,
#+and this behavior caused problems with the entrance and exit events staying put.
#set_player_and_events()
end
def set_variables
settings = RDG.map_settings($game_map.name().split("-")[1].to_i(), $game_map.name().split("-")[2].to_i(), $game_map.name().split("-")[3].to_i(), $game_map.name().split("-")[4], $game_map.name().split("-")[5].to_i(), $game_map.name().split("-")[6].to_i(), $game_map.name().split("-")[7])
@black_outline = settings[5]
@floating = settings[6]
@short = settings[7]
@rmiw = settings[0]
@rmaw = settings[1]
@rmih = settings[2]
@rmah = settings[3]
@features = settings[4]
@walls = []
@floor = []
@next_wall = []
@smoothLevel = settings[8]
@smoothPasses = settings[9]
@mapName = settings[10]
if (!@floating)
@wall_tile = @map.data[1, 0, 0] - (@map.data[1, 0, 0] % 48) - 16
@wall2_tile = @wall_tile + 384
else
@wall_tile = @map.data[1, 0, 0]
end
@floor_tile = @map.data[0, 0, 0]
@next_feature = 0
@can_make = false
@player_transfered = false
end
def process_seed()
# If the name is not RAND, find or save seed
if (@mapName != "RAND")
found = false
if (!$seeds.empty?)
for i in $seeds
# Set seed for map if found
if (i[0] == @mapName)
found = true
srand(i[1].to_i())
end
end
end
# Save seed for map if not found
if (!found)
newSeed = Time.now().to_i()
$seeds << [@mapName, newSeed]
srand(newSeed)
for i in $seeds
# Set seed for map if found
if (i[0] == @mapName)
found = true
srand(i[1].to_i())
end
end
end
end
end
def set_player_and_events
# Sets the placement event for transferring between maps
placementEvent = "NULL"
for event in $game_map.events.values
if (($game_player.x == event.x) and ($game_player.y == event.y))
placementEvent = event.event.name
end
end
# Places player, entrance, and exit only, so that they are the same each time
for event in $game_map.events.values
if ((event.event.name == "ENTRANCE") or (event.event.name == "EXIT") or (event.event.name == "CONST"))
loop do
ax = @floor[rand(@floor.size)][0]
ay = @floor[rand(@floor.size)][1]
if passable_in_area?(ax - 1, ay - 1, event)
event.moveto(ax, ay)
break
end
end
end
end
# Places all other enemies randomly
srand(Time.now().to_i())
for event in $game_map.events.values
if ((event.event.name == "ENTRANCE") or (event.event.name == "CONST") or (event.event.name == "EXIT"))
next
else
loop do
ax = @floor[rand(@floor.size)][0]
ay = @floor[rand(@floor.size)][1]
if passable_in_area?(ax - 1, ay - 1, event)
event.moveto(ax, ay)
break
end
end
end
end
# Places player on event titled "ENTRANCE"
for event in $game_map.events.values
if (placementEvent == "NULL")
if (event.event.name == "ENTRANCE")
$game_player.moveto(event.x, event.y)
end
elsif (event.event.name == placementEvent)
$game_player.moveto(event.x, event.y)
end
end
end
alias old_update update
def update
old_update
if ($game_map.name[0..6] == "DUNGEON")
unless @player_transfered
@player_transfered = true
set_player_and_events
end
end
end
def passable_in_area?(x, y, char, width = 3, height = 3)
for i in x...x + width
for j in y...y + height
return false if not char.passable?(i, j)
end
end
return true
end
def fill_wall_tiles
for i in 0...width
for j in 0...height
if (@floating)
@map.data[i, j, 0] = 0
else
@map.data[i, j, 0] = @wall_tile
end
end
end
end
def dig_random_room
x = [rand(width) + 1, width - @rmaw - 1].min
y = [rand(height) + 1, height - @rmah - 1].min
dig_room(x, y)
end
def dig_room(x, y, direction = 2)
width = rand(@rmiw) + @rmaw - @rmiw + 1
height = rand(@rmih) + @rmah - @rmih + 1
for i in 0...width
for j in 0...height
case direction
when 0
new_x = x + j
new_y = y + i
when 1
new_x = x - i
new_y = y + j
when 2
new_x = x + i
new_y = y + j
when 3
new_x = x + j
new_y = y - i
end
@map.data[new_x, new_y, 0] = @floor_tile
@floor.push([new_x, new_y])
@walls.push([new_x - 1, new_y, 1]) if new_x == x
@walls.push([new_x + 1, new_y, 2]) if new_x == x + width - 1
@walls.push([new_x, new_y - 1, 3]) if new_y == y
@walls.push([new_x, new_y + 1, 0]) if new_y == y + height - 1
end
end
@next_feature = 1
end
def dig_corridor(x, y, direction)
j = 0
width = rand(@rmiw) + @rmaw - @rmiw + 1
height = 0
for i in 0...width
case direction
when 0
new_x = x + j
new_y = y + i
when 1
new_x = x - i
new_y = y + j
when 2
new_x = x + i
new_y = y + j
when 3
new_x = x + j
new_y = y - i
end
@map.data[new_x, new_y, 0] = @floor_tile
@floor.push([new_x, new_y])
@walls.push([new_x - 1, new_y, 1]) if new_x == x
@walls.push([new_x + 1, new_y, 2]) if new_x == x + width - 1
@walls.push([new_x, new_y - 1, 3]) if new_y == y
@walls.push([new_x, new_y + 1, 0]) if new_y == y + height - 1
end
case direction
when 0
@next_wall = [x + height, y + width, 0]
when 1
@next_wall = [x - width, y + height, 1]
when 2
@next_wall = [x + width, y + height, 2]
when 3
@next_wall = [x + height, y - width, 3]
end
@next_feature = 0
end
def choose_next_wall
@next_wall = @walls[rand(@walls.size)]
end
def make_new_feature
direction = rand(4)
@can_make = scan_area(@next_wall[0], @next_wall[1], @next_wall[2])
return if not @can_make
@walls.delete(@next_wall)
dig_corridor(@next_wall[0], @next_wall[1], @next_wall[2])
@can_make = scan_area(@next_wall[0], @next_wall[1], @next_wall[2])
dig_room(@next_wall[0], @next_wall[1], @next_wall[2]) if @can_make
@next_feature = 1
end
def scan_area(x, y, direction)
case @next_feature
when 0
for i in 0..@rmaw + 1
for j in 0..@rmah + 1
case direction
when 0
return false if passable?(x + j, y + i) or !valid?(x + j, y + i)
when 1
return false if passable?(x - i, y + j) or !valid?(x - i, y + j)
when 2
return false if passable?(x + i, y + j) or !valid?(x + i, y + j)
when 3
return false if passable?(x + j, y - i) or !valid?(x + j, y - i)
end
end
end
when 1
for i in 0..@rmaw + 1
for j in -2..2
case direction
when 0
return false if passable?(x + j, y + i) or !valid?(x + j, y + i)
when 1
return false if passable?(x - i, y + j) or !valid?(x - i, y + j)
when 2
return false if passable?(x + i, y + j) or !valid?(x + i, y + j)
when 3
return false if passable?(x + j, y - i) or !valid?(x + j, y - i)
end
end
end
end
return true
end
def refine_tilemap
for i in 1..@smoothPasses
smoothingArray = Array.new()
for point in @floor
if (rand(10) < @smoothLevel)
if (!passable?(point[0] + 1, point[1]))
smoothingArray.push([point[0] + 1, point[1]])
end
end
if (rand(10) < @smoothLevel)
if (!passable?(point[0] - 1, point[1]))
smoothingArray.push([point[0] - 1, point[1]])
end
end
if (rand(10) < @smoothLevel)
if (!passable?(point[0], point[1] + 1))
smoothingArray.push([point[0], point[1] + 1])
end
end
if (rand(10) < @smoothLevel)
if (!passable?(point[0], point[1] - 1))
smoothingArray.push([point[0], point[1] - 1])
end
end
end
smoothingArray.uniq!()
for i in smoothingArray
@map.data[i[0], i[1]] = @floor_tile
@floor.push([i[0], i[1]])
@walls.delete([i[0], i[1]])
end
if ((not @floating) and (not @short))
deleteArray = Array.new()
for i in @floor
if (!passable?(i[0], i[1] + 1) and passable?(i[0], i[1] + 2))
deleteArray.push([i[0], i[1] + 1])
end
end
deleteArray.uniq!()
for i in deleteArray
@walls.delete(i)
@floor.push(i)
@map.data[i[0], i[1]] = @floor_tile
end
end
end
if (@floating)
for point in @floor
next if passable?(point[0], point[1] + 1)
@map.data[point[0], point[1] + 1, 0] = @wall_tile
end
else
if (!@short)
for point in @floor
next if passable?(point[0], point[1] - 1)
@map.data[point[0], point[1] - 1, 0] = @wall2_tile
end
end
for i in 0...width
for j in 0...height
next if @map.data[i, j, 0] == @floor_tile
type = @map.data[i, j, 0] == @wall2_tile ? 1 : 0
@map.data[i, j, 0] = rdg_tilemap_id(i, j, type)
end
end
return if not @black_outline
for i in 0...width
for j in 0...height
next if @floor.include?([i, j])
if ((@map.data[i, j, 0] - 2048) % 48 == 0)
@map.data[i, j, 0] = 0
end
end
end
end
end
def rdg_tilemap_id(x, y, type)
data = @map.data
base = @map.data[x, y, 0]
return base if x == 0 or x == width - 1 or y == 0 or y == width - 1
case type
when 0
count = 0
count += 1 if ((data[x - 1, y, 0] - 2048) / 48 != (base - 2048) / 48)
count += 2 if ((data[x, y - 1, 0] - 2048) / 48 != (base - 2048) / 48)
count += 4 if ((data[x + 1, y, 0] - 2048) / 48 != (base - 2048) / 48)
count += 8 if ((data[x, y + 1, 0] - 2048) / 48 != (base - 2048) / 48)
id = case count
when 0
check_corners(x, y, base)
when 1
16 + ((check_corners(x, y, base, [2, 4])) / 2)
when 2
20 + ((check_corners(x, y, base, [4, 8])) / 4)
when 3
34 + ((check_corners(x, y, base, [4])) / 4)
when 4
24 + ([0,8,1,9].index(check_corners(x, y, base, [1, 8])))
when 5
32
when 6
36 + ((check_corners(x, y, base, [8])) / 8)
when 7
42
when 8
28 + check_corners(x, y, base, [1, 2])
when 9
40 + ((check_corners(x, y, base, [2])) / 2)
when 10
33
when 11
43
when 12
38 + check_corners(x, y, base, [1])
else
31 + count
end
when 1
count = 0
count += 1 if passable?(x - 1, y)
count += 4 if passable?(x + 1, y)
id = count + 10
end
return base + id
end
def check_corners(x, y, base, corners = [1, 2, 4, 8])
count = 0
data = @map.data
count += 1 if (corners.include?(1) and (data[x - 1, y - 1, 0] - 2048) / 48 != (base - 2048) / 48)
count += 2 if (corners.include?(2) and (data[x + 1, y - 1, 0] - 2048) / 48 != (base - 2048) / 48)
count += 4 if (corners.include?(4) and (data[x + 1, y + 1, 0] - 2048) / 48 != (base - 2048) / 48)
count += 8 if (corners.include?(8) and (data[x - 1, y + 1, 0] - 2048) / 48 != (base - 2048) / 48)
return count
end
end
One last thing. The generator works in a very specific manner (based mostly on the events in the map, the top two tiles, and the map name). Make sure you read the entire header before you attempt to use this script.
Also, I don't know how licensing issues work with RPGVX scripts, so just in case, I relinquish any ownership I might have of all of my modifications of this RDG script to cozziekuns. Feel free to use as much of it as you want without requiring my permission. I think this is only fair as it is a modification of your script in the first place.
Latest Edit: Updated the script. It was still randomizing the placement of the EXIT event.