CODE
### Values that may need to be modified
# For a Zelda-style dungeon, these are the locations (x,y) on each cell
# to where the player should be teleported when entering from the:
# Bottom, Left, Top, Right, Upstairs, Downstairs
RDZ_POS_XY = [[8, 11], [1, 7], [8, 1], [15, 7], [8, 7], [8, 7]]
# These are the 16 Map IDs to use to refer to each of the 16 possible
# cells to display (0...15) Each map should have events on it (which
# should be controlled by switches) for stairs up and down
RDZ_MAP_ID = [18, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
# Describes which three variables are used to store the "entrance position"
# (position on the world map or other map where the player is just before
# entering the random dungeon) Before entering the dungeon, the in-game
# event should store MapID, PlayerX, PlayerY to these three variables
RDZ_ENTRANCE_POS = [1, 2, 3]
# Describes which three variables are used to store the "next position"
# where the player will teleport to. The events already setup on the
# example map cells do this already, but these three variables must be
# specifically reserved for this purpose. They are MapID, X, Y, like above.
RDZ_NEXT_POS = [4, 5, 6]
# The script reports back (in variables, which can be accessed by events)
# the direction of the last move, and the current X,Y,Z location of the
# player within the random dungeon
# Special value is a value 0...65535 assigned to the cell randomly at
# creation time (useful for placing random events only to appear occasionally)
RDZ_LAST_MOVE = 7
RDZ_CURRENT_Z = 8
RDZ_CURRENT_Y = 9
RDZ_CURRENT_X = 10
RDZ_SPECIAL_VALUE = 11
# Designate two switches (which should be used by the stairs events
# mentioned above) that are used by the script to report back which set(s)
# of stairs should be enabled on each cell
RDZ_STAIR_SWITCH = [1, 2]
# Designate two switches (which can be optionally used by any special
# events) that are used by the script to report back whether the current
# cell is the "far point" of the dungeon (further distance from entrance of
# any cell), and whether or not it has a special designation
RDZ_FAR_POINT = 3
# For a flat-floor style random dungeon, these three variables indicate
# the MapID, X, Y to which the event should transfer the player after
# calling the appropriate scripts to create the random dungeon
RDF_NEXT_POS = [4, 5, 6]
# The event ID of the "exit dungeon" event on a flat-map style
# dungeon. This is needed so that the script can place the event
# appropriately after assembling the map
RDF_EXIT_EVENT_ID = 4
RDF_STAIRS_UP_EVENT_ID = [1, 6, 7]
RDF_STAIRS_DOWN_EVENT_ID = [2, 8, 9]
RDF_SPECIAL_EVENT_ID = 3
# The default MAP_ID to be used as the source of tiling when
# assembling a floor-style random dungeon
RDF_DEFAULT_TILE_MAP_ID = 2
# (x,y) location (relative to upper left of a cell of tiles)
# where the stairs and any chests/random-event should be placed
RDF_STAIRS_LOCATION = [3, 2]
RDF_CHEST_LOCATION = [1, 2]
# A fraction representing the proportion of upper layer (Tileset B...E)
# tiles to copy; higher number = less copied (1/x)
RDF_EMBELLISHMENT_COPY = 7
### End of modification Section
# The following are overrides/modifications to the built-in default
# classes, to allow us to do some of what is needed with setting up
# maps to hold a randomly generated dungeon.
class Spriteset_Map
def set_map_data(data)
@tilemap.map_data = data
end
end
class Scene_Map
def set_map_data(data)
@spriteset.set_map_data(data)
end
end
class Game_Event
def set_map_id(map_id)
@map_id = map_id if @event.name.include?("$rand")
end
end
class Game_Interpreter
alias tkirch_rd_command_123 command_123
def command_123
if @original_event_id > 0
key = [$game_map.random_map_id, @original_event_id, @params[0]]
$game_self_switches[key] = (@params[1] == 0)
end
$game_map.need_refresh = true
return true
end
alias tkirch_rd_command_111 command_111
def command_111
result = false
case @params[0]
when 0 # Switch
result = ($game_switches[@params[1]] == (@params[2] == 0))
when 1 # Variable
value1 = $game_variables[@params[1]]
if @params[2] == 0
value2 = @params[3]
else
value2 = $game_variables[@params[3]]
end
case @params[4]
when 0 # value1 is equal to value2
result = (value1 == value2)
when 1 # value1 is greater than or equal to value2
result = (value1 >= value2)
when 2 # value1 is less than or equal to value2
result = (value1 <= value2)
when 3 # value1 is greater than value2
result = (value1 > value2)
when 4 # value1 is less than value2
result = (value1 < value2)
when 5 # value1 is not equal to value2
result = (value1 != value2)
end
when 2 # Self switch
if @original_event_id > 0
key = [$game_map.random_map_id, @original_event_id, @params[1]]
if @params[2] == 0
result = ($game_self_switches[key] == true)
else
result = ($game_self_switches[key] != true)
end
end
when 3 # Timer
if $game_system.timer_working
sec = $game_system.timer / Graphics.frame_rate
if @params[2] == 0
result = (sec >= @params[1])
else
result = (sec <= @params[1])
end
end
when 4 # Actor
actor = $game_actors[@params[1]]
if actor != nil
case @params[2]
when 0 # in party
result = ($game_party.members.include?(actor))
when 1 # name
result = (actor.name == @params[3])
when 2 # skill
result = (actor.skill_learn?($data_skills[@params[3]]))
when 3 # weapon
result = (actor.weapons.include?($data_weapons[@params[3]]))
when 4 # armor
result = (actor.armors.include?($data_armors[@params[3]]))
when 5 # state
result = (actor.state?(@params[3]))
end
end
when 5 # Enemy
enemy = $game_troop.members[@params[1]]
if enemy != nil
case @params[2]
when 0 # appear
result = (enemy.exist?)
when 1 # state
result = (enemy.state?(@params[3]))
end
end
when 6 # Character
character = get_character(@params[1])
if character != nil
result = (character.direction == @params[2])
end
when 7 # Gold
if @params[2] == 0
result = ($game_party.gold >= @params[1])
else
result = ($game_party.gold <= @params[1])
end
when 8 # Item
result = $game_party.has_item?($data_items[@params[1]])
when 9 # Weapon
result = $game_party.has_item?($data_weapons[@params[1]], @params[2])
when 10 # Armor
result = $game_party.has_item?($data_armors[@params[1]], @params[2])
when 11 # Button
result = Input.press?(@params[1])
when 12 # Script
result = eval(@params[1])
when 13 # Vehicle
result = ($game_player.vehicle_type == @params[1])
end
@branch[@indent] = result # Store determination results in hash
if @branch[@indent] == true
@branch.delete(@indent)
return true
end
return command_skip
end
end
class Scene_File
alias tkirch_rd_write_save_data write_save_data
def write_save_data(file)
tkirch_rd_write_save_data(file)
if $game_random_dungeon != nil
Marshal.dump($game_random_dungeon, file)
end
end
alias tkirch_rd_read_save_data read_save_data
def read_save_data(file)
tkirch_rd_read_save_data(file)
begin
$game_random_dungeon = Marshal.load(file)
rescue
end
end
end
class Game_Map
def set_random_id(id)
@random_map_id = id
end
def random_map_id
if @random_map_id == nil
@random_map_id = @map_id
end
return @random_map_id
end
alias tkirch_rd_setup setup
def setup(map_id)
@random_map_id = map_id
tkirch_rd_setup(map_id)
end
def set_map_data(width, height, data)
@map.width = width
@map.height = height
@map.data = data
end
def setup_rdz_events(random_map_id)
for i in @events.keys
@events[i].set_map_id(random_map_id)
end
end
def setup_rd_events(key_map_id, escape, floor, tile_width, tile_height)
stairsUp = RDF_STAIRS_UP_EVENT_ID.clone
stairsDown = RDF_STAIRS_DOWN_EVENT_ID.clone
@events = {}
@events[RDF_EXIT_EVENT_ID] = Game_Event.new(key_map_id, @map.events[RDF_EXIT_EVENT_ID])
@events[RDF_EXIT_EVENT_ID].moveto(escape[0], escape[1])
empty_positions = {}
for yy in 0...floor.size
for xx in 0...floor[yy].size
if floor[yy][xx] & 16 == 16
event_id = stairsUp.delete_at(0)
@events[event_id] = Game_Event.new(key_map_id, @map.events[event_id])
@events[event_id].moveto(xx * tile_width + RDF_STAIRS_LOCATION[0], yy * tile_height + RDF_STAIRS_LOCATION[1])
elsif floor[yy][xx] & 32 == 32
event_id = stairsDown.delete_at(0)
@events[event_id] = Game_Event.new(key_map_id, @map.events[event_id])
@events[event_id].moveto(xx * tile_width + RDF_STAIRS_LOCATION[0], yy * tile_height + RDF_STAIRS_LOCATION[1])
elsif floor[yy][xx] & 64 == 64
@events[RDF_SPECIAL_EVENT_ID] = Game_Event.new(key_map_id, @map.events[RDF_SPECIAL_EVENT_ID])
@events[RDF_SPECIAL_EVENT_ID].moveto(xx * tile_width + RDF_CHEST_LOCATION[0], yy * tile_height + RDF_CHEST_LOCATION[1])
@events[RDF_SPECIAL_EVENT_ID].refresh
elsif floor[yy][xx] & 15 > 0
empty_positions[floor[yy][xx] / 256] = [xx, yy]
end
end
end
for event in @map.events.values
if event.name.include?("$rand") and empty_positions.size > 0
data_key = empty_positions.keys.sort[0]
location = empty_positions[data_key]
xx = location[0]
yy = location[1]
@events[event.id] = Game_Event.new(key_map_id, event)
@events[event.id].moveto(xx * tile_width + RDF_CHEST_LOCATION[0], yy * tile_height + RDF_CHEST_LOCATION[1])
@events[event.id].refresh
empty_positions.delete(data_key)
end
end
end
def generateFromVariables
$game_random_dungeon = RDF.new($game_variables[14], $game_variables[14], $game_variables[15], $game_variables[13], 2)
end
end
class Scene_Title
alias tkirch_rd_create_game_objects create_game_objects
def create_game_objects
tkirch_rd_create_game_objects
$game_random_dungeon = nil
end
end
# The base class for creating a random dungeon layout
# Basically, it's a 3-dimensional array [z][y][x] with a single
# integer describing each "cell" of the dungeon
# In a Zelda-style dungeon, each cell is a screen
# In a flat-style dungeon, each cell is a rectangular area of tiles
# in the larger created map (in the example created, each cell is 5x5 tiles
# but they can be basically any size)
#
# The random number is 24 bits in length. The lower 8 bits describe the
# nature of the cell (see in code below), the upper 16 bits are a random
# value that can be used to determine random, but fixed display properties
# (intended for things like cosmetic decorations, etc, not yet implemented)
class Random_Dungeon_Base
attr_accessor :width
attr_accessor :height
attr_accessor :depth
attr_accessor :cells
# These constants describe the bitmask used by the lower 8 bits of the
# cell description value; they indicate if there's an opening to the bottom,
# left, top, right; if there's a stairway up or down, and a "special" value,
# indicating that the cell is the furthest one from the entrance to the
# dungeon
BT = 1
LT = 2
TP = 4
RT = 8
UP = 16
DW = 32
SP = 64
FN = 128
# Initializer, just sets up the array, etc
def initialize(width, height, depth)
@width = width
@height = height
@depth = depth
@cells = Array.new(@depth) { Array.new(@height) { Array.new(@width, 0) } }
end
# Make the random dungeon
# seed is the value to determine "which" random dungeon to generate
# cutProb = probability that a cell will have an exit in any given direction
# joinProb = probability that a cell will have an exit that reconnects to an
# already-explored portion of the dungeon
# sProb = probability of a cell having an up or down staircase
# sJoinProb = probability a cell has a staircase that reconnects to an
# already-explored portion of the dungeon (e.g. redundant staircase)
# maxS = max number of staircases descending from any single floor
# Leaving out an in-depth discussion of the internals of how this method
# works, it will suffice to say that it basically executes a breath-first
# search, starting from the entrance, and continuing until no new paths
# are (randomly selected to be) created
def make(seed = 0, cutProb = 0.5, joinProb = 0.2, sProb = 0.15, sJoinProb = 0.05, maxS = 3)
oldseed = srand(seed)
queue = []
startZ = 0
startY = @height - 1
startX = @width / 2
@cells[startZ][startY][startX] = BT
queue << [startX, startY, startZ, 0]
queue << [startX, startY, startZ, 0]
queue << [startX, startY, startZ, 0]
farP = [startX, startY, startZ]
farS = 1
stCount = Array.new(@depth, 0)
while queue.length > 0
curPos = queue.delete_at(0)
curX = curPos[0]
curY = curPos[1]
curZ = curPos[2]
curS = curPos[3]
if curS > farS
farP = [curX, curY, curZ]
farS = curS
end
if curY < @height - 1
modify = false
if @cells[curZ][curY + 1][curX] > 0
modify = true if rand() < joinProb
else
if rand() < cutProb
modify = true
queue << [curX, curY + 1, curZ, curS + 1]
end
end
if modify
@cells[curZ][curY][curX] |= BT
@cells[curZ][curY + 1][curX] |= TP
end
end
if curX > 0
modify = false
if @cells[curZ][curY][curX - 1] > 0
modify = true if rand() < joinProb
else
if rand() < cutProb
modify = true
queue << [curX - 1, curY, curZ, curS + 1]
end
end
if modify
@cells[curZ][curY][curX] |= LT
@cells[curZ][curY][curX - 1] |= RT
end
end
if curY > 0
modify = false
if @cells[curZ][curY - 1][curX] > 0
modify = true if rand() < joinProb
else
if rand() < cutProb
modify = true
queue << [curX, curY - 1, curZ, curS + 1]
end
end
if modify
@cells[curZ][curY][curX] |= TP
@cells[curZ][curY - 1][curX] |= BT
end
end
if curX < @width - 1
modify = false
if @cells[curZ][curY][curX + 1] > 0
modify = true if rand() < joinProb
else
if rand() < cutProb
modify = true
queue << [curX + 1, curY, curZ, curS + 1]
end
end
if modify
@cells[curZ][curY][curX] |= RT
@cells[curZ][curY][curX + 1] |= LT
end
end
if curZ > 0 && stCount[curZ - 1] < maxS && (@cells[curZ][curY][curX] & DW == 0)
modify = false
if @cells[curZ - 1][curY][curX] > 0
modify = true if rand() < sJoinProb
else
if rand() < sProb
modify = true
queue << [curX, curY, curZ - 1, curS + 1]
end
end
if modify
@cells[curZ][curY][curX] |= UP
@cells[curZ - 1][curY][curX] |= DW
stCount[curZ - 1] = stCount[curZ - 1] + 1
end
end
if curZ < @depth - 1 && stCount[curZ] < maxS && (@cells[curZ][curY][curX] & UP == 0)
modify = false
if @cells[curZ + 1][curY][curX] > 0
modify = true if rand() < sJoinProb
else
if rand() < sProb
modify = true
queue << [curX, curY, curZ + 1, curS + 1]
end
end
if modify
@cells[curZ][curY][curX] |= DW
@cells[curZ + 1][curY][curX] |= UP
stCount[curZ] = stCount[curZ] + 1
end
end
end
@cells[farP[2]][farP[1]][farP[0]] |= SP
@cells.each { |level| level.each { |row| row.each_index { |i| row[i] |= (256 * rand(65536)) } } }
srand(oldseed)
end
end
# This is the instance of the class that creates a Zelda-style random dungeon
# Initialize with RDZ.new(height, width, depth, seed)
class RDZ < Random_Dungeon_Base
attr_accessor :curX
attr_accessor :curY
attr_accessor :curZ
def initialize(height, width, depth, seed)
super(height, width, depth)
make(seed)
@curX = @width / 2
@curY = @height - 1
@curZ = 0
@lastMove = 0
setVars
end
# The next six methods are already called by the example-tiles
# The "tell" the random dungeon that we want to go in a certain
# direction, which sets up the variables that tell the game
# where to next move
def goRight
@curX += 1
@lastMove = 1
setVars
end
def goLeft
@curX -= 1
@lastMove = 3
setVars
end
def goTop
@curY -= 1
@lastMove = 0
setVars
end
def goBottom
@curY += 1
@lastMove = 2
setVars
end
def goUp
@curZ -= 1
@lastMove = 4
setVars
end
def goDown
@curZ += 1
@lastMove = 5
setVars
end
# Set some game variables based upon the "state" of (our position in) the random dungeon
def setVars
if curY == @height
$game_variables[RDZ_NEXT_POS[0]] = $game_variables[RDZ_ENTRANCE_POS[0]]
$game_variables[RDZ_NEXT_POS[1]] = $game_variables[RDZ_ENTRANCE_POS[1]]
$game_variables[RDZ_NEXT_POS[2]] = $game_variables[RDZ_ENTRANCE_POS[2]]
return
end
cell = @cells[curZ][curY][curX]
$game_variables[RDZ_NEXT_POS[0]] = RDZ_MAP_ID[cell & 15]
$game_variables[RDZ_NEXT_POS[1]] = RDZ_POS_XY[@lastMove][0]
$game_variables[RDZ_NEXT_POS[2]] = RDZ_POS_XY[@lastMove][1]
$game_switches[RDZ_STAIR_SWITCH[0]] = false
$game_switches[RDZ_STAIR_SWITCH[1]] = false
$game_switches[RDZ_STAIR_SWITCH[0]] = true if (cell & UP) == UP
$game_switches[RDZ_STAIR_SWITCH[1]] = true if (cell & DW) == DW
$game_switches[RDZ_FAR_POINT] = true if (cell & SP) == SP
$game_variables[RDZ_LAST_MOVE] = @lastMove
$game_variables[RDZ_CURRENT_Z] = @curZ
$game_variables[RDZ_CURRENT_Y] = @curY
$game_variables[RDZ_CURRENT_X] = @curX
$game_variables[RDZ_SPECIAL_VALUE] = cell / 256
end
def setRandomMapId
rmap_id = [@width, @height, @depth, @seed, @curX, @curY, @curZ]
$game_map.set_random_id(rmap_id)
$game_map.setup_rdz_events(rmap_id)
$game_map.refresh
end
end
class RDF < Random_Dungeon_Base
def initialize(height, width, depth, seed, tileMapId = 0)
super(height, width, depth)
make(seed)
@seed = seed
@tile_map_id = tileMapId
@tile_map_id = RDF_DEFAULT_TILE_MAP_ID if @tile_map_id == 0
@tiling_map = load_data(sprintf("Data/Map%03d.rvdata", @tile_map_id))
@curZ = 0
@tile_width = @tiling_map.width / 4 - 1
@tile_height = @tiling_map.height / 4 - 1
@map_width = @tile_width * @width
@map_height = @tile_height * @height
@map_data = Array.new(@depth)
@map_data.each_index { |z| fillLayer(z) }
$game_variables[RDF_NEXT_POS[0]] = @tile_map_id
$game_variables[RDF_NEXT_POS[1]] = (@width / 2) * @tile_width + (@tile_width / 2)
$game_variables[RDF_NEXT_POS[2]] = @map_height - 2
end
def saveCurrentPosition
$game_variables[RDF_NEXT_POS[0]] = @tile_map_id
$game_variables[RDF_NEXT_POS[1]] = $game_player.x
$game_variables[RDF_NEXT_POS[2]] = $game_player.y
$game_map.setup(@tile_map_id)
end
def goDownStairs
@curZ += 1
showCurrentLayer
end
def goUpStairs
@curZ -= 1
showCurrentLayer
end
def showCurrentLayer
key = [@width, @height, @depth, @seed, @curZ]
escape = [(@width / 2) * @tile_width + (@tile_width / 2), @map_height - 1]
$game_map.set_map_data(@map_width, @map_height, @map_data[@curZ])
$game_map.set_random_id(key)
$game_map.setup_rd_events(key, escape, @cells[@curZ], @tile_width, @tile_height)
$scene.set_map_data(@map_data[@curZ])
end
def fillLayer(z)
@map_data[z] = Table.new(@map_width, @map_height, 3)
@cells[z].each_index { |y| fillRow(z, y) }
end
def fillRow(z, y)
@cells[z][y].each_index { |x| copyTile(x, y, z, @cells[z][y][x] & 15, @cells[z][y][x] / 256) }
end
def copyTile(x, y, z, tileNum, rval)
tilex = (tileNum % 4) * (@tile_width + 1)
tiley = (tileNum / 4) * (@tile_height + 1)
mapx = x * @tile_width
mapy = y * @tile_height
a = 16807
r = rval % 65536
c = 13
m = 2147483647
for xx in 0...@tile_width
for yy in 0...@tile_height
@map_data[z][mapx + xx, mapy + yy, 0] = @tiling_map.data[tilex + xx, tiley + yy, 0]
#@map_data[z][mapx + xx, mapy + yy, 1] = @tiling_map.data[tilex + xx, tiley + yy, 1]
@map_data[z][mapx + xx, mapy + yy, 2] = @tiling_map.data[tilex + xx, tiley + yy, 2] if r % RDF_EMBELLISHMENT_COPY == 0
r = (a * r + c) % m
end
end
end
end