Zwiebelchen
Hosted Project GR
- Joined
- Sep 17, 2009
- Messages
- 7,234



After almost two days of non-stop working on this, I finally finished my masterpiece.
This baby allows generating complete dungeons and cave designs directly by RNG.
For dungeons, it applies a pattern of rooms and connects them with corridors; caves on the other hand are generated by using cellular automata, to create a natural looking flow.
Check the sketch below for an example of how such a generated pattern will look like.

And to see how this could look ingame, of course check out the demomap for examples of what is possible with this baby.
I will add a tutorial tomorrow, for those interested.
JASS:
library DungeonStamp uses Ascii
globals
//=========================================
//configurables
private constant integer TERRAIN_DEFAULT_TILE = 'Ddrt' //defines the default ground tile for the dungeon
private constant integer TERRAIN_DEFAULT_VARIANCE = 17 //defines the variation used for the default tile (use -1 for random)
private constant integer STAMP_SIZE = 5 //defines the edge size of each stamp; must be an odd number
private constant integer STAMP_SIZE_SQUARED = 25 //please calculate STAMP_SIZE*STAMP_SIZE and enter the value here
//limit:
// -> n * STAMP_SIZE_SQUARED < 8192
// -> for n = number of registered stamps
// -> print registry methods only supported for stamp size of 5
// -> for other sizes use rect registry method instead
private constant integer MAX_DESTRUCTABLES_PER_STAMP = 25 //defines the maximum number of destructables per stamp
//limit:
// -> n * MAX_DESTRUCTABLES_PER_STAMP < 8192
// -> for n = number of registered stamps
// -> print registry methods only supported for MAX_DESTRUCTABLES_PER_STAMP = 25
// -> for other values use rect registry method instead
constant string DUNGEON_PRINT_DIRECTORY = ".\\save\\" //This is the directory where all generated text files from the print commands will be saved.
//=========================================
//=========================================
constant integer STAMP_SIZE_REAL = STAMP_SIZE*128
constant integer ORIENTATION_0 = 0
constant integer ORIENTATION_90 = 200
constant integer ORIENTATION_180 = 400
constant integer ORIENTATION_270 = 600
private constant hashtable HASH = InitHashtable()
private constant integer CAVE_OFFSET = 20
private constant integer STAIR_UP_OFFSET = 40
private constant integer STAIR_DOWN_OFFSET = 80
/*
These are the stamp shapes for the dungeon type
*/
constant integer SHAPE_BLACK = 0
constant integer SHAPE_HALL = 1 // Default Orientation:
constant integer SHAPE_WALL_L = 2 // I = 0° (left wall)
constant integer SHAPE_WALL_L_B = 3 // I_ = 0° (left wall and bottom wall)
constant integer SHAPE_WALL_L_B_R = 4 // I_I = 0° (left wall, bottom wall and right wall (dead end))
constant integer SHAPE_WALL_BL = 5 // . = 0° (wall in bottom left corner)
constant integer SHAPE_WALL_BL_TL = 6 // : = 0° (wall in bottom left and top left corner)
constant integer SHAPE_WALL_BL_TR = 7 // . ' = 0° (wall in bottom left and top right corner)
constant integer SHAPE_WALL_BL_TL_BR = 8 // : . = 0° (wall in bottom left, top left and bottom right corner)
constant integer SHAPE_WALL_BL_TL_BR_TP = 9 // : : = 0° (wall in all corners, plus-shaped corridor)
constant integer SHAPE_WALL_L_BR = 10 // I . = 0° (left wall and bottom right corner)
constant integer SHAPE_WALL_L_TR = 11 // I ' = 0° (left wall and top right corner)
constant integer SHAPE_WALL_L_R = 12 // I I = 0° (left wall and right wall)
constant integer SHAPE_WALL_L_B_TR = 13 // I_' = 0° (left wall, bottom wall, top-right corner)
constant integer SHAPE_WALL_L_BR_TR = 14 // I : = 0° (left wall, bottom-right corner and top-right corner)
constant integer SHAPE_STAIRS_UP_WALL = SHAPE_WALL_L + STAIR_UP_OFFSET
// I> = 0° (Stairs in western wall, poiting east)
constant integer SHAPE_STAIRS_UP_HALL = SHAPE_HALL + STAIR_UP_OFFSET
// > = 0° (Stairs inside room, pointing east)
constant integer SHAPE_STAIRS_UP_END = SHAPE_WALL_L_B_R + STAIR_UP_OFFSET
// I^I = 0° (Stairs at dead end, pointing north)
constant integer SHAPE_STAIRS_DOWN_WALL = SHAPE_WALL_L + STAIR_DOWN_OFFSET
// I> = 0° (Stairs in western wall, poiting east)
constant integer SHAPE_STAIRS_DOWN_HALL = SHAPE_HALL + STAIR_DOWN_OFFSET
// > = 0° (Stairs inside room, pointing east)
constant integer SHAPE_STAIRS_DOWN_END = SHAPE_WALL_L_B_R + STAIR_DOWN_OFFSET
// I^I = 0° (Stairs at dead end, pointing north)
/*
These are the stamp shapes for the cavern type
*/
constant integer SHAPE_CAVE_HALL = 1+CAVE_OFFSET // Default Orientation:
constant integer SHAPE_CAVE_WALL_L = 2+CAVE_OFFSET // I = 0° (left wall)
constant integer SHAPE_CAVE_WALL_L_B = 3+CAVE_OFFSET // I_ = 0° (left wall and bottom wall)
constant integer SHAPE_CAVE_WALL_L_B_R = 4+CAVE_OFFSET // I_I = 0° (left wall, bottom wall and right wall (dead end))
constant integer SHAPE_CAVE_WALL_BL = 5+CAVE_OFFSET // . = 0° (wall in bottom left corner)
constant integer SHAPE_CAVE_WALL_BL_TL = 6+CAVE_OFFSET // : = 0° (wall in bottom left and top left corner)
constant integer SHAPE_CAVE_WALL_BL_TR = 7+CAVE_OFFSET // . ' = 0° (wall in bottom left and top right corner)
constant integer SHAPE_CAVE_WALL_BL_TL_BR = 8+CAVE_OFFSET // : . = 0° (wall in bottom left, top left and bottom right corner)
constant integer SHAPE_CAVE_WALL_BL_TL_BR_TP = 9+CAVE_OFFSET // : : = 0° (wall in all corners, plus-shaped corridor)
constant integer SHAPE_CAVE_WALL_L_BR = 10+CAVE_OFFSET // I . = 0° (left wall and bottom right corner)
constant integer SHAPE_CAVE_WALL_L_TR = 11+CAVE_OFFSET // I ' = 0° (left wall and top right corner)
constant integer SHAPE_CAVE_WALL_L_R = 12+CAVE_OFFSET // I I = 0° (left wall and right wall)
constant integer SHAPE_CAVE_WALL_L_B_TR = 13+CAVE_OFFSET // I_' = 0° (left wall, bottom wall, top-right corner)
constant integer SHAPE_CAVE_WALL_L_BR_TR = 14+CAVE_OFFSET // I : = 0° (left wall, bottom-right corner and top-right corner)
constant integer SHAPE_CAVE_STAIRS_UP_WALL = SHAPE_WALL_L + STAIR_UP_OFFSET + CAVE_OFFSET
// I> = 0° (Stairs in western wall, poiting east)
constant integer SHAPE_CAVE_STAIRS_UP_HALL = SHAPE_HALL + STAIR_UP_OFFSET + CAVE_OFFSET
// > = 0° (Stairs inside room, pointing east)
constant integer SHAPE_CAVE_STAIRS_UP_END = SHAPE_WALL_L_B_R + STAIR_UP_OFFSET + CAVE_OFFSET
// I^I = 0° (Stairs at dead end, pointing north)
constant integer SHAPE_CAVE_STAIRS_DOWN_WALL = SHAPE_WALL_L + STAIR_DOWN_OFFSET + CAVE_OFFSET
// I> = 0° (Stairs in western wall, poiting east)
constant integer SHAPE_CAVE_STAIRS_DOWN_HALL = SHAPE_HALL + STAIR_DOWN_OFFSET + CAVE_OFFSET
// > = 0° (Stairs inside room, pointing east)
constant integer SHAPE_CAVE_STAIRS_DOWN_END = SHAPE_WALL_L_B_R + STAIR_DOWN_OFFSET + CAVE_OFFSET
// I^I = 0° (Stairs at dead end, pointing north)
endglobals
struct stamp
private integer shape
private integer number
private integer tile
private real rare
integer data
real value
integer array destraw[MAX_DESTRUCTABLES_PER_STAMP]
real array destx[MAX_DESTRUCTABLES_PER_STAMP]
real array desty[MAX_DESTRUCTABLES_PER_STAMP]
real array destrot[MAX_DESTRUCTABLES_PER_STAMP]
integer array terrain[STAMP_SIZE_SQUARED]
integer array variation[STAMP_SIZE_SQUARED]
private static thistype temp
private static integer tempindex = 0
private static real tempx = 0
private static real tempy = 0
static method create takes integer shp, integer tileset, real rarity, integer customdata, real customvalue returns thistype
local thistype this = thistype.allocate()
set this.shape = shp
set this.tile = tileset
set this.rare = rarity
set this.data = customdata
set this.value = customvalue
set this.number = LoadInteger(HASH, this.shape, 0) + 1
call SaveInteger(HASH, this.shape, 0, this.number)
call SaveInteger(HASH, this.shape, this.number, this)
return this
endmethod
static method setDestructableData takes integer raw, real scale, real z, boolean alive returns nothing
call SaveReal(HASH, raw, 0, scale)
call SaveReal(HASH, raw, 1, z)
call SaveBoolean(HASH, raw, 2, alive)
endmethod
static method getDestructableScale takes integer raw returns real
if HaveSavedReal(HASH, raw, 0) then
return LoadReal(HASH, raw, 0)
endif
return 1.
endmethod
static method getDestructableZ takes integer raw returns real
if HaveSavedReal(HASH, raw, 1) then
return LoadReal(HASH, raw, 1)
endif
return 0.
endmethod
static method getDestructableAlive takes integer raw returns boolean
if HaveSavedBoolean(HASH, raw, 2) then
return LoadBoolean(HASH, raw, 2)
endif
return true
endmethod
method canHaveStairs takes nothing returns boolean
local integer shp = ModuloInteger(this.shape, CAVE_OFFSET)
return (shp == SHAPE_WALL_L or shp == SHAPE_WALL_L_B_R or shp == SHAPE_HALL)
endmethod
private static method removeSub takes nothing returns nothing
call RemoveDestructable(GetEnumDestructable())
endmethod
static method remove takes rect area, integer gridX, integer gridY returns nothing
local integer i = 0
local integer j
local real x = GetRectMinX(area)+gridX*STAMP_SIZE_REAL
local real y = GetRectMinY(area)+gridY*STAMP_SIZE_REAL
local rect r = Rect(x-16, y-16, x+16+STAMP_SIZE_REAL, y+16+STAMP_SIZE_REAL)
loop
exitwhen i >= STAMP_SIZE
set j = 0
loop
exitwhen j >= STAMP_SIZE
set x = GetRectMinX(area)+gridX*STAMP_SIZE_REAL+i*128
set y = GetRectMinY(area)+gridY*STAMP_SIZE_REAL+j*128
if x <= GetRectMaxX(area) and y <= GetRectMaxY(area) then
call SetTerrainType(x, y, TERRAIN_DEFAULT_TILE, TERRAIN_DEFAULT_VARIANCE, 1, 1)
endif
set j = j + 1
endloop
set i = i + 1
endloop
if GetRectMaxX(r) <= GetRectMaxX(area) and GetRectMaxY(r) <= GetRectMaxY(area) then
call EnumDestructablesInRect(r, null, function thistype.removeSub)
else
call SetRect(r, GetRectMinX(r), GetRectMinY(r), GetRectMaxX(area), GetRectMaxY(area))
call EnumDestructablesInRect(r, null, function thistype.removeSub)
endif
call RemoveRect(r)
set r = null
endmethod
method place takes rect area, integer gridX, integer gridY, integer orientation returns nothing
local integer i = 0
local integer j
local location loc = Location(0,0)
local real x
local real y
local integer u
local integer v
call thistype.remove(area, gridX, gridY) //remove existing stamps
//paint ground tiles
if GetRectMinX(area)+gridX*STAMP_SIZE_REAL <= GetRectMaxX(area) and GetRectMinY(area)+gridY*STAMP_SIZE_REAL <= GetRectMaxY(area) then
loop
exitwhen i >= STAMP_SIZE
set j = 0
loop
exitwhen j >= STAMP_SIZE
set x = GetRectMinX(area)+gridX*STAMP_SIZE_REAL+i*128
set y = GetRectMinY(area)+gridY*STAMP_SIZE_REAL+j*128
if orientation == ORIENTATION_0 then
set u = i
set v = j
elseif orientation == ORIENTATION_90 then
set u = j
set v = STAMP_SIZE-1-i
elseif orientation == ORIENTATION_180 then
set u = STAMP_SIZE-1-i
set v = STAMP_SIZE-1-j
else
set u = STAMP_SIZE-1-j
set v = i
endif
call SetTerrainType(x, y, this.terrain[u*STAMP_SIZE+v], this.variation[u*STAMP_SIZE+v], 1, 1)
if i == (STAMP_SIZE-1) then
if this.terrain[u*STAMP_SIZE+v] != TERRAIN_DEFAULT_TILE and GetTerrainType(x+128, y) == TERRAIN_DEFAULT_TILE then
call SetTerrainType(x+128, y, this.terrain[u*STAMP_SIZE+v], this.variation[u*STAMP_SIZE+v], 1, 1)
endif
endif
if j == (STAMP_SIZE-1) then
if this.terrain[u*STAMP_SIZE+v] != TERRAIN_DEFAULT_TILE and GetTerrainType(x, y+128) == TERRAIN_DEFAULT_TILE then
call SetTerrainType(x, y+128, this.terrain[u*STAMP_SIZE+v], this.variation[u*STAMP_SIZE+v], 1, 1)
endif
endif
if u == (STAMP_SIZE-1) and j == (STAMP_SIZE-1) then
if this.terrain[u*STAMP_SIZE+v] != TERRAIN_DEFAULT_TILE and GetTerrainType(x+128, y+128) == TERRAIN_DEFAULT_TILE then
call SetTerrainType(x+128, y+128, this.terrain[u*STAMP_SIZE+v], this.variation[u*STAMP_SIZE+v], 1, 1)
endif
endif
set j = j + 1
endloop
set i = i + 1
endloop
//place destructables
set i = 0
loop
exitwhen i >= MAX_DESTRUCTABLES_PER_STAMP
if this.destraw[i] != 0 then
if orientation == ORIENTATION_0 then
call MoveLocation(loc, GetRectMinX(area)+gridX*STAMP_SIZE_REAL+STAMP_SIZE_REAL/2+this.destx[i], GetRectMinY(area)+gridY*STAMP_SIZE_REAL+STAMP_SIZE_REAL/2+this.desty[i])
if stamp.getDestructableAlive(this.destraw[i]) then
call CreateDestructableZ(this.destraw[i], GetLocationX(loc), GetLocationY(loc), GetLocationZ(loc)+stamp.getDestructableZ(this.destraw[i]), this.destrot[i], stamp.getDestructableScale(this.destraw[i]), 0)
else
call CreateDeadDestructableZ(this.destraw[i], GetLocationX(loc), GetLocationY(loc), GetLocationZ(loc)+stamp.getDestructableZ(this.destraw[i]), this.destrot[i], stamp.getDestructableScale(this.destraw[i]), 0)
endif
elseif orientation == ORIENTATION_90 then
call MoveLocation(loc, GetRectMinX(area)+gridX*STAMP_SIZE_REAL+STAMP_SIZE_REAL/2-this.desty[i], GetRectMinY(area)+gridY*STAMP_SIZE_REAL+STAMP_SIZE_REAL/2+this.destx[i])
if stamp.getDestructableAlive(this.destraw[i]) then
call CreateDestructableZ(this.destraw[i], GetLocationX(loc), GetLocationY(loc), GetLocationZ(loc)+stamp.getDestructableZ(this.destraw[i]), this.destrot[i]+90, stamp.getDestructableScale(this.destraw[i]), 0)
else
call CreateDeadDestructableZ(this.destraw[i], GetLocationX(loc), GetLocationY(loc), GetLocationZ(loc)+stamp.getDestructableZ(this.destraw[i]), this.destrot[i]+90, stamp.getDestructableScale(this.destraw[i]), 0)
endif
elseif orientation == ORIENTATION_180 then
call MoveLocation(loc, GetRectMinX(area)+gridX*STAMP_SIZE_REAL+STAMP_SIZE_REAL/2-this.destx[i], GetRectMinY(area)+gridY*STAMP_SIZE_REAL+STAMP_SIZE_REAL/2-this.desty[i])
if stamp.getDestructableAlive(this.destraw[i]) then
call CreateDestructableZ(this.destraw[i], GetLocationX(loc), GetLocationY(loc), GetLocationZ(loc)+stamp.getDestructableZ(this.destraw[i]), this.destrot[i]+180, stamp.getDestructableScale(this.destraw[i]), 0)
else
call CreateDeadDestructableZ(this.destraw[i], GetLocationX(loc), GetLocationY(loc), GetLocationZ(loc)+stamp.getDestructableZ(this.destraw[i]), this.destrot[i]+180, stamp.getDestructableScale(this.destraw[i]), 0)
endif
else
call MoveLocation(loc, GetRectMinX(area)+gridX*STAMP_SIZE_REAL+STAMP_SIZE_REAL/2+this.desty[i], GetRectMinY(area)+gridY*STAMP_SIZE_REAL+STAMP_SIZE_REAL/2-this.destx[i])
if stamp.getDestructableAlive(this.destraw[i]) then
call CreateDestructableZ(this.destraw[i], GetLocationX(loc), GetLocationY(loc), GetLocationZ(loc)+stamp.getDestructableZ(this.destraw[i]), this.destrot[i]+270, stamp.getDestructableScale(this.destraw[i]), 0)
else
call CreateDeadDestructableZ(this.destraw[i], GetLocationX(loc), GetLocationY(loc), GetLocationZ(loc)+stamp.getDestructableZ(this.destraw[i]), this.destrot[i]+270, stamp.getDestructableScale(this.destraw[i]), 0)
endif
endif
endif
set i = i + 1
endloop
endif
call RemoveLocation(loc)
set loc = null
endmethod
private static method getDest takes nothing returns boolean
local destructable d = GetFilterDestructable()
if thistype.tempindex < MAX_DESTRUCTABLES_PER_STAMP then
set thistype.temp.destraw[thistype.tempindex] = GetDestructableTypeId(d)
set thistype.temp.destx[thistype.tempindex] = GetDestructableX(d)-thistype.tempx
set thistype.temp.desty[thistype.tempindex] = GetDestructableY(d)-thistype.tempy
if GetDestructableLife(d) == GetDestructableMaxLife(d) then
set thistype.temp.destrot[thistype.tempindex] = 0
else
set thistype.temp.destrot[thistype.tempindex] = ModuloReal(GetDestructableLife(d)/GetDestructableMaxLife(d)*1000, 360)
endif
set thistype.tempindex = thistype.tempindex + 1
else
call BJDebugMsg("ERROR: Stamp contains too many destructables! Increase MAX_DESTRUCTABLES_PER_STAMP or reduce number of destructables!")
endif
set d = null
return false
endmethod
static method register takes rect which, integer shp, integer tileset, real rarity, integer customdata, real customvalue returns thistype
local thistype this = thistype.create(shp, tileset, rarity, customdata, customvalue)
local integer i = 0
local integer j
set thistype.temp = this
set thistype.tempindex = 0
set thistype.tempx = GetRectMinX(which)+STAMP_SIZE_REAL/2
set thistype.tempy = GetRectMinY(which)+STAMP_SIZE_REAL/2
call EnumDestructablesInRect(which, Condition(function thistype.getDest), null)
loop
exitwhen i >= STAMP_SIZE
set j = 0
loop
exitwhen j >= STAMP_SIZE
set this.terrain[i*STAMP_SIZE+j] = GetTerrainType(GetRectMinX(which)+i*128, GetRectMinY(which)+j*128)
set this.variation[i*STAMP_SIZE+j] = GetTerrainVariance(GetRectMinX(which)+i*128, GetRectMinY(which)+j*128)
set j = j + 1
endloop
set i = i + 1
endloop
return this
endmethod
static method placeByLogic takes rect area, integer x, integer y, boolean isCave, boolean isStairs, boolean isUp, integer fromTileset, boolean sw, boolean w, boolean nw, boolean s, boolean c, boolean n, boolean se, boolean e, boolean ne returns thistype
local integer wc = 0
local integer cc = 0
local thistype this
local thistype array valid
local integer validcount = 0
local integer i = 0
local integer shp = -1
local integer max = 0
local integer orient = 0
local integer stairs = 0
if isStairs then
if isUp then
set stairs = STAIR_UP_OFFSET
else
set stairs = STAIR_DOWN_OFFSET
endif
endif
if sw and not (s or w) then
set cc = cc + 1
endif
if nw and not (n or w) then
set cc = cc + 1
endif
if se and not (s or e) then
set cc = cc + 1
endif
if ne and not (n or e) then
set cc = cc + 1
endif
if w then
set wc = wc + 1
endif
if s then
set wc = wc + 1
endif
if n then
set wc = wc + 1
endif
if e then
set wc = wc + 1
endif
if c then
set shp = SHAPE_BLACK + ORIENTATION_0
else
if wc == 0 then
if cc == 0 then
set shp = SHAPE_HALL + GetRandomInt(0,3)*ORIENTATION_90 + stairs
elseif cc == 1 then
if sw then
set shp = SHAPE_WALL_BL + ORIENTATION_0
elseif se then
set shp = SHAPE_WALL_BL + ORIENTATION_90
elseif ne then
set shp = SHAPE_WALL_BL + ORIENTATION_180
elseif nw then
set shp = SHAPE_WALL_BL + ORIENTATION_270
endif
elseif cc == 2 then
if sw and nw then
set shp = SHAPE_WALL_BL_TL + ORIENTATION_0
elseif sw and se then
set shp = SHAPE_WALL_BL_TL + ORIENTATION_90
elseif se and ne then
set shp = SHAPE_WALL_BL_TL + ORIENTATION_180
elseif ne and nw then
set shp = SHAPE_WALL_BL_TL + ORIENTATION_270
elseif sw and ne then
set shp = SHAPE_WALL_BL_TR + (GetRandomInt(0,1)*2)*ORIENTATION_90
elseif se and nw then
set shp = SHAPE_WALL_BL_TR + (GetRandomInt(0,1)*2+1)*ORIENTATION_90
endif
elseif cc == 3 then
if not ne then
set shp = SHAPE_WALL_BL_TL_BR + ORIENTATION_0
elseif not nw then
set shp = SHAPE_WALL_BL_TL_BR + ORIENTATION_90
elseif not sw then
set shp = SHAPE_WALL_BL_TL_BR + ORIENTATION_180
elseif not se then
set shp = SHAPE_WALL_BL_TL_BR + ORIENTATION_270
endif
elseif cc == 4 then
set shp = SHAPE_WALL_BL_TL_BR_TP + GetRandomInt(0,3)*ORIENTATION_90
endif
elseif wc == 1 then
if cc == 0 then
if w then
set shp = SHAPE_WALL_L + ORIENTATION_0 + stairs
elseif s then
set shp = SHAPE_WALL_L + ORIENTATION_90 + stairs
elseif e then
set shp = SHAPE_WALL_L + ORIENTATION_180 + stairs
elseif n then
set shp = SHAPE_WALL_L + ORIENTATION_270 + stairs
endif
elseif cc == 1 then
if w then
if se then
set shp = SHAPE_WALL_L_BR + ORIENTATION_0
elseif ne then
set shp = SHAPE_WALL_L_TR + ORIENTATION_0
endif
elseif s then
if ne then
set shp = SHAPE_WALL_L_BR + ORIENTATION_90
elseif nw then
set shp = SHAPE_WALL_L_TR + ORIENTATION_90
endif
elseif e then
if nw then
set shp = SHAPE_WALL_L_BR + ORIENTATION_180
elseif sw then
set shp = SHAPE_WALL_L_TR + ORIENTATION_180
endif
elseif n then
if sw then
set shp = SHAPE_WALL_L_BR + ORIENTATION_270
elseif se then
set shp = SHAPE_WALL_L_TR + ORIENTATION_270
endif
endif
elseif cc == 2 then
if w then
set shp = SHAPE_WALL_L_BR_TR + ORIENTATION_0
elseif s then
set shp = SHAPE_WALL_L_BR_TR + ORIENTATION_90
elseif e then
set shp = SHAPE_WALL_L_BR_TR + ORIENTATION_180
elseif n then
set shp = SHAPE_WALL_L_BR_TR + ORIENTATION_270
endif
endif
elseif wc == 2 then
if cc == 0 then
if w and s then
set shp = SHAPE_WALL_L_B + ORIENTATION_0
elseif s and e then
set shp = SHAPE_WALL_L_B + ORIENTATION_90
elseif e and n then
set shp = SHAPE_WALL_L_B + ORIENTATION_180
elseif n and w then
set shp = SHAPE_WALL_L_B + ORIENTATION_270
elseif w and e then
set shp = SHAPE_WALL_L_R + (GetRandomInt(0,1)*2)*ORIENTATION_90
elseif n and s then
set shp = SHAPE_WALL_L_R + (GetRandomInt(0,1)*2+1)*ORIENTATION_90
endif
elseif cc == 1 then
if w and s then
set shp = SHAPE_WALL_L_B_TR + ORIENTATION_0
elseif s and e then
set shp = SHAPE_WALL_L_B_TR + ORIENTATION_90
elseif e and n then
set shp = SHAPE_WALL_L_B_TR + ORIENTATION_180
elseif n and w then
set shp = SHAPE_WALL_L_B_TR + ORIENTATION_270
endif
endif
elseif wc == 3 then
if not n then
set shp = SHAPE_WALL_L_B_R + ORIENTATION_0 + stairs
elseif not w then
set shp = SHAPE_WALL_L_B_R + ORIENTATION_90 + stairs
elseif not s then
set shp = SHAPE_WALL_L_B_R + ORIENTATION_180 + stairs
elseif not e then
set shp = SHAPE_WALL_L_B_R + ORIENTATION_270 + stairs
endif
elseif wc == 4 then
set shp = SHAPE_BLACK + ORIENTATION_0
endif
endif
if shp == -1 then
call BJDebugMsg("ERROR: no shape found by wall logic!")
return 0
else
if isCave then
set shp = shp + CAVE_OFFSET
endif
set max = shp
set shp = ModuloInteger(shp,ORIENTATION_90)
set orient = max-shp
endif
set max = LoadInteger(HASH, shp, 0)
set i = 1
loop
exitwhen i > max
set this = LoadInteger(HASH, shp, i)
if this.tile == fromTileset then
if validcount <= 0 then
set valid[validcount] = this
set validcount = validcount+1
elseif GetRandomReal(0,1) <= this.rare then
set valid[validcount] = this
set validcount = validcount+1
endif
endif
set i = i + 1
endloop
if validcount <= 0 then
if HaveSavedInteger(HASH, shp, 1) then
set valid[0] = LoadInteger(HASH, shp, 1)
set validcount = 1
else
call BJDebugMsg("ERROR: No registered stamp found. Please register stamps for this tileset before calling the build algorithm!")
return 0
endif
endif
set this = valid[GetRandomInt(0,validcount-1)]
call this.place(area,x,y,orient)
return this
endmethod
method print takes nothing returns nothing
local integer i = 0
local string terraindump = ""
local string variationdump = ""
local string destrawdump = ""
local string xdump = ""
local string ydump = ""
local string facedump = ""
if MAX_DESTRUCTABLES_PER_STAMP == 25 and STAMP_SIZE_SQUARED == 25 then
loop
exitwhen i >= 25
if i > 0 then
set terraindump = terraindump + ",'" + A2S(this.terrain[i]) + "'"
set variationdump = variationdump + "," + I2S(this.variation[i])
if this.destraw[i] != 0 then
set destrawdump = destrawdump + ",'" + A2S(this.destraw[i]) + "'"
else
set destrawdump = destrawdump + ",0"
endif
set xdump = xdump + "," + I2S(R2I(this.destx[i]))
set ydump = ydump + "," + I2S(R2I(this.desty[i]))
set facedump = facedump + "," + I2S(R2I(this.destrot[i]))
else
set terraindump = "'" + A2S(this.terrain[i]) + "'"
set variationdump = I2S(this.variation[i])
if this.destraw[i] != 0 then
set destrawdump = "'" + A2S(this.destraw[i]) + "'"
else
set destrawdump = "0"
endif
set xdump = I2S(R2I(this.destx[i]))
set ydump = I2S(R2I(this.desty[i]))
set facedump = I2S(R2I(this.destrot[i]))
endif
set i = i + 1
endloop
call PreloadGenClear()
call PreloadGenStart()
call Preload("\")\n\n call data.registerTerrain("+terraindump+") \n\n//")
call Preload("\")\n\n call data.registerVariation("+variationdump+") \n\n//")
call Preload("\")\n\n call data.registerDestructable("+destrawdump+") \n\n//")
call Preload("\")\n\n call data.registerDestX("+xdump+") \n\n//")
call Preload("\")\n\n call data.registerDestY("+ydump+") \n\n//")
call Preload("\")\n\n call data.registerDestFace("+facedump+") \n\n//")
call Preload("\")\n\n \n\n//")
call PreloadGenEnd(DUNGEON_PRINT_DIRECTORY+"stamp(shape"+I2S(this.shape)+").txt")
else
call BJDebugMsg("ERROR: Text-based stamp registry only supported for default values of stamp size (5x5) and destructables per stamp (25)!")
endif
endmethod
//==========================================================================
//The following methods are wrappers designed for use with the .print method
//==========================================================================
method registerTerrain takes integer t00, integer t01, integer t02, integer t03, integer t04, integer t10, integer t11, integer t12, integer t13, integer t14, integer t20, integer t21, integer t22, integer t23, integer t24, integer t30, integer t31, integer t32, integer t33, integer t34, integer t40, integer t41, integer t42, integer t43, integer t44 returns nothing
if STAMP_SIZE_SQUARED == 25 then
set this.terrain[0] = t00
set this.terrain[1] = t01
set this.terrain[2] = t02
set this.terrain[3] = t03
set this.terrain[4] = t04
set this.terrain[5] = t10
set this.terrain[6] = t11
set this.terrain[7] = t12
set this.terrain[8] = t13
set this.terrain[9] = t14
set this.terrain[10] = t20
set this.terrain[11] = t21
set this.terrain[12] = t22
set this.terrain[13] = t23
set this.terrain[14] = t24
set this.terrain[15] = t30
set this.terrain[16] = t31
set this.terrain[17] = t32
set this.terrain[18] = t33
set this.terrain[19] = t34
set this.terrain[20] = t40
set this.terrain[21] = t41
set this.terrain[22] = t42
set this.terrain[23] = t43
set this.terrain[24] = t44
endif
endmethod
method registerVariation takes integer t00, integer t01, integer t02, integer t03, integer t04, integer t10, integer t11, integer t12, integer t13, integer t14, integer t20, integer t21, integer t22, integer t23, integer t24, integer t30, integer t31, integer t32, integer t33, integer t34, integer t40, integer t41, integer t42, integer t43, integer t44 returns nothing
if STAMP_SIZE_SQUARED == 25 then
set this.variation[0] = t00
set this.variation[1] = t01
set this.variation[2] = t02
set this.variation[3] = t03
set this.variation[4] = t04
set this.variation[5] = t10
set this.variation[6] = t11
set this.variation[7] = t12
set this.variation[8] = t13
set this.variation[9] = t14
set this.variation[10] = t20
set this.variation[11] = t21
set this.variation[12] = t22
set this.variation[13] = t23
set this.variation[14] = t24
set this.variation[15] = t30
set this.variation[16] = t31
set this.variation[17] = t32
set this.variation[18] = t33
set this.variation[19] = t34
set this.variation[20] = t40
set this.variation[21] = t41
set this.variation[22] = t42
set this.variation[23] = t43
set this.variation[24] = t44
endif
endmethod
method registerDestructable takes integer t00, integer t01, integer t02, integer t03, integer t04, integer t10, integer t11, integer t12, integer t13, integer t14, integer t20, integer t21, integer t22, integer t23, integer t24, integer t30, integer t31, integer t32, integer t33, integer t34, integer t40, integer t41, integer t42, integer t43, integer t44 returns nothing
if MAX_DESTRUCTABLES_PER_STAMP == 25 then
set this.destraw[0] = t00
set this.destraw[1] = t01
set this.destraw[2] = t02
set this.destraw[3] = t03
set this.destraw[4] = t04
set this.destraw[5] = t10
set this.destraw[6] = t11
set this.destraw[7] = t12
set this.destraw[8] = t13
set this.destraw[9] = t14
set this.destraw[10] = t20
set this.destraw[11] = t21
set this.destraw[12] = t22
set this.destraw[13] = t23
set this.destraw[14] = t24
set this.destraw[15] = t30
set this.destraw[16] = t31
set this.destraw[17] = t32
set this.destraw[18] = t33
set this.destraw[19] = t34
set this.destraw[20] = t40
set this.destraw[21] = t41
set this.destraw[22] = t42
set this.destraw[23] = t43
set this.destraw[24] = t44
endif
endmethod
method registerDestX takes real t00, real t01, real t02, real t03, real t04, real t10, real t11, real t12, real t13, real t14, real t20, real t21, real t22, real t23, real t24, real t30, real t31, real t32, real t33, real t34, real t40, real t41, real t42, real t43, real t44 returns nothing
if MAX_DESTRUCTABLES_PER_STAMP == 25 then
set this.destx[0] = t00
set this.destx[1] = t01
set this.destx[2] = t02
set this.destx[3] = t03
set this.destx[4] = t04
set this.destx[5] = t10
set this.destx[6] = t11
set this.destx[7] = t12
set this.destx[8] = t13
set this.destx[9] = t14
set this.destx[10] = t20
set this.destx[11] = t21
set this.destx[12] = t22
set this.destx[13] = t23
set this.destx[14] = t24
set this.destx[15] = t30
set this.destx[16] = t31
set this.destx[17] = t32
set this.destx[18] = t33
set this.destx[19] = t34
set this.destx[20] = t40
set this.destx[21] = t41
set this.destx[22] = t42
set this.destx[23] = t43
set this.destx[24] = t44
endif
endmethod
method registerDestY takes real t00, real t01, real t02, real t03, real t04, real t10, real t11, real t12, real t13, real t14, real t20, real t21, real t22, real t23, real t24, real t30, real t31, real t32, real t33, real t34, real t40, real t41, real t42, real t43, real t44 returns nothing
if MAX_DESTRUCTABLES_PER_STAMP == 25 then
set this.desty[0] = t00
set this.desty[1] = t01
set this.desty[2] = t02
set this.desty[3] = t03
set this.desty[4] = t04
set this.desty[5] = t10
set this.desty[6] = t11
set this.desty[7] = t12
set this.desty[8] = t13
set this.desty[9] = t14
set this.desty[10] = t20
set this.desty[11] = t21
set this.desty[12] = t22
set this.desty[13] = t23
set this.desty[14] = t24
set this.desty[15] = t30
set this.desty[16] = t31
set this.desty[17] = t32
set this.desty[18] = t33
set this.desty[19] = t34
set this.desty[20] = t40
set this.desty[21] = t41
set this.desty[22] = t42
set this.desty[23] = t43
set this.desty[24] = t44
endif
endmethod
method registerDestFace takes real t00, real t01, real t02, real t03, real t04, real t10, real t11, real t12, real t13, real t14, real t20, real t21, real t22, real t23, real t24, real t30, real t31, real t32, real t33, real t34, real t40, real t41, real t42, real t43, real t44 returns nothing
if MAX_DESTRUCTABLES_PER_STAMP == 25 then
set this.destrot[0] = t00
set this.destrot[1] = t01
set this.destrot[2] = t02
set this.destrot[3] = t03
set this.destrot[4] = t04
set this.destrot[5] = t10
set this.destrot[6] = t11
set this.destrot[7] = t12
set this.destrot[8] = t13
set this.destrot[9] = t14
set this.destrot[10] = t20
set this.destrot[11] = t21
set this.destrot[12] = t22
set this.destrot[13] = t23
set this.destrot[14] = t24
set this.destrot[15] = t30
set this.destrot[16] = t31
set this.destrot[17] = t32
set this.destrot[18] = t33
set this.destrot[19] = t34
set this.destrot[20] = t40
set this.destrot[21] = t41
set this.destrot[22] = t42
set this.destrot[23] = t43
set this.destrot[24] = t44
endif
endmethod
endstruct
endlibrary
JASS:
library DungeonGenerator uses DungeonStamp
globals
private constant integer DUNGEON_SIZE_X = 64 //grid dimensions of the dungeon integer field
private constant integer DUNGEON_SIZE_Y = 64
//Ingame coordinates: DUNGEON_SIZE_X * TILE_SIZE * 128
private constant real DUNGEON_VERIFICATION_THRESHOLD = 0.1 //fraction of a generated dungeon map that must be verified as reachable before the verification algorithm stops
private constant integer MAX_VERIFY_ATTEMPTS = 10 //maximum number of verification attempts to reach the specified threshold
//-> when failing, the generated map is considered unusable and should be discarded by the user
//====================
//end of configurables
constant integer DUNGEON_EMPTY = 0
constant integer DUNGEON_ROOM = 1
constant integer DUNGEON_CAVE = 2
constant integer DUNGEON_CORRIDOR = 3
endglobals
struct Room
thistype llPrev
thistype llNext
integer minx
integer maxx
integer miny
integer maxy
integer centerx
integer centery
method getNext takes nothing returns thistype
return this.llNext
endmethod
static method create takes integer x1, integer y1, integer x2, integer y2 returns thistype
local thistype this = thistype.allocate()
set this.llNext = 0
set this.llPrev = 0
set this.minx = x1
set this.maxx = x2
set this.miny = y1
set this.maxy = y2
set this.centerx = (x1+x2)/2
set this.centery = (y1+y2)/2
return this
endmethod
method onDestroy takes nothing returns nothing
set this.llPrev = 0
set this.llNext = 0
endmethod
endstruct
struct RoomList
private Room llHead
static method create takes nothing returns thistype
local thistype this = thistype.allocate()
set this.llHead = 0
return this
endmethod
private method llAdd takes Room b returns nothing
set b.llNext = this.llHead
if this.llHead != 0 then
set this.llHead.llPrev = b
endif
set this.llHead = b
set b.llPrev = 0
endmethod
private method llRemove takes Room b returns nothing
if b.llPrev != 0 then
set b.llPrev.llNext = b.llNext
else
set this.llHead = b.llNext
endif
if b.llNext != 0 then
set b.llNext.llPrev = b.llPrev
endif
endmethod
method getFirst takes nothing returns Room
return this.llHead
endmethod
method add takes Room b returns nothing
call this.llAdd(b)
endmethod
method remove takes Room b returns nothing
call this.llRemove(b)
endmethod
method iterator takes nothing returns RoomListIterator
return RoomListIterator.create(this)
endmethod
method onDestroy takes nothing returns nothing
local RoomListIterator iter = this.iterator()
local Room nxt
loop
exitwhen iter.hasNoNext()
set nxt = iter.next()
call this.llRemove(nxt)
call nxt.destroy()
endloop
call iter.destroy()
endmethod
endstruct
struct RoomListIterator
private RoomList roomList
private Room nextRoom
static method create takes RoomList roomList returns thistype
local thistype this = thistype.allocate()
set this.roomList = roomList
set this.nextRoom = roomList.getFirst()
return this
endmethod
method hasNext takes nothing returns boolean
return this.nextRoom != 0
endmethod
method hasNoNext takes nothing returns boolean
return this.hasNext() == false
endmethod
method next takes nothing returns Room
local Room tmp = this.nextRoom
set this.nextRoom = this.nextRoom.getNext()
return tmp
endmethod
endstruct
globals
private constant integer MAX_VERIFY_PER_ITERATION = 64
private integer array map[DUNGEON_SIZE_X][DUNGEON_SIZE_Y]
private boolean array verifymap[DUNGEON_SIZE_X][DUNGEON_SIZE_Y]
private stamp array stamps[DUNGEON_SIZE_X][DUNGEON_SIZE_Y]
private integer stacksize = 0
private integer array stackx
private integer array stacky
private boolean usable = false
private integer verified
private integer veriAttempts = 0
private trigger veriTrig = null
private trigger printTrig = null
private integer printCount = 0
private trigger clearTrig = null
private integer clearCount = 0
private trigger veriClearTrig = null
private integer veriClearCount = 0
private trigger clearUnverifiedTrig = null
private integer clearUnverifiedCount = 0
private rect buildArea = null
private timer buildTimer = null
private trigger buildTrig = null
private integer buildx = 0
private integer buildy = 0
private integer buildTileset
private integer buildStairsConsidered
private integer buildStairsUpPickX
private integer buildStairsUpPickY
private integer buildStairsDownPickX
private integer buildStairsDownPickY
private trigger removeTrig = null
private rect enumArea = null
private trigger enumTrig = null
private trigger enumCallTrig = null
private integer enumx = 0
private integer enumy = 0
private real lastUpX = 0
private real lastUpY = 0
private real lastDownX = 0
private real lastDownY = 0
private stamp curstamp = 0
endglobals
//For use with .enumerateMap()
function GetEnumStamp takes nothing returns stamp
return curstamp
endfunction
function GetEnumStampData takes nothing returns integer
return curstamp.data
endfunction
function GetEnumStampValue takes nothing returns real
return curstamp.value
endfunction
function GetEnumStampCenterX takes nothing returns real
return GetRectMinX(enumArea)+enumx*STAMP_SIZE_REAL+STAMP_SIZE_REAL/2
endfunction
function GetEnumStampCenterY takes nothing returns real
return GetRectMinY(enumArea)+enumy*STAMP_SIZE_REAL+STAMP_SIZE_REAL/2
endfunction
function GetLastCreatedStairs takes boolean up returns location
if up then
return Location(lastUpX, lastUpY)
else
return Location(lastDownX, lastDownY)
endif
endfunction
struct Dungeon extends array
static method isVerified takes integer x, integer y returns boolean
return verifymap[x][y]
endmethod
static method getTile takes integer x, integer y returns integer
return map[x][y]
endmethod
static method isWall takes integer x, integer y returns boolean
if x >= 0 and x < DUNGEON_SIZE_X and y >= 0 and y < DUNGEON_SIZE_Y then
return map[x][y] == DUNGEON_EMPTY
endif
return true
endmethod
static method getAdjacentWalls takes integer x, integer y, integer radius returns integer
local integer count = 0
local integer i = -radius
local integer j
loop
exitwhen i > radius
set j = -radius
loop
exitwhen j > radius
if not (i == x and j == y) then
if map[x+i][y+j] <= DUNGEON_EMPTY then
set count = count + 1
endif
endif
set j = j + 1
endloop
set i = i + 1
endloop
return count
endmethod
private static method enumSub takes nothing returns nothing
local integer j = 0
local integer i = enumx
loop
exitwhen j >= DUNGEON_SIZE_Y
set curstamp = stamps[enumx][j]
set enumy = j
call TriggerExecute(enumCallTrig)
set j = j + 1
endloop
set enumx = enumx+1
if enumx < DUNGEON_SIZE_X then
call TriggerExecute(enumTrig)
endif
endmethod
static method enumerateMap takes rect area, code actionFunc returns nothing
if enumTrig == null then
set enumTrig = CreateTrigger()
call TriggerAddAction(enumTrig, function thistype.enumSub)
endif
if enumCallTrig != null then
call TriggerClearActions(enumCallTrig)
call DestroyTrigger(enumCallTrig)
endif
set enumCallTrig = CreateTrigger()
call TriggerAddAction(enumCallTrig, actionFunc)
set enumArea = area
set enumx = 0
set enumy = 0
call TriggerExecute(enumTrig)
endmethod
private static method removeSub takes nothing returns nothing
local integer j = 0
local integer i = buildx
loop
exitwhen j >= DUNGEON_SIZE_Y
call stamp.remove(buildArea,i,j)
set stamps[i][j] = 0
set j = j + 1
endloop
set buildx = buildx+1
if buildx < DUNGEON_SIZE_X then
call TriggerExecute(removeTrig)
endif
endmethod
static method removeAll takes rect area returns nothing
if removeTrig == null then
set removeTrig = CreateTrigger()
call TriggerAddAction(removeTrig, function thistype.removeSub)
endif
set buildArea = area
set buildx = 0
call TriggerExecute(removeTrig)
endmethod
private static method buildSub takes nothing returns nothing
local integer x = buildx
local integer y = buildy
local boolean cave = false
local stamp s = 0
local integer rnd
if map[x][y] == DUNGEON_CAVE then
set cave = true
endif
if map[x][y] >= DUNGEON_ROOM then
set s = stamp.placeByLogic(buildArea, x, y, cave, false, false, buildTileset, thistype.isWall(x-1,y-1), thistype.isWall(x-1,y), thistype.isWall(x-1,y+1), thistype.isWall(x,y-1), thistype.isWall(x,y), thistype.isWall(x,y+1), thistype.isWall(x+1,y-1), thistype.isWall(x+1,y), thistype.isWall(x+1,y+1))
set stamps[x][y] = s
if s == 0 then
elseif s.canHaveStairs() then
//reservoir sampling
set buildStairsConsidered = buildStairsConsidered+1
set rnd = GetRandomInt(1,buildStairsConsidered)
if rnd == 1 then
set buildStairsUpPickX = x
set buildStairsUpPickY = y
elseif rnd == 2 then
set buildStairsDownPickX = x
set buildStairsDownPickY = y
endif
endif
elseif map[x][y] == DUNGEON_EMPTY then
set s = stamp.placeByLogic(buildArea, x, y, cave, false, false, buildTileset, thistype.isWall(x-1,y-1), thistype.isWall(x-1,y), thistype.isWall(x-1,y+1), thistype.isWall(x,y-1), thistype.isWall(x,y), thistype.isWall(x,y+1), thistype.isWall(x+1,y-1), thistype.isWall(x+1,y), thistype.isWall(x+1,y+1))
set stamps[x][y] = s
endif
set buildy = buildy+1
if buildy >= DUNGEON_SIZE_Y then
set buildy = 0
set buildx = buildx+1
endif
if buildx < DUNGEON_SIZE_X and buildy < DUNGEON_SIZE_Y then
call TimerStart(buildTimer, 0, false, function thistype.buildSub)
else
//place stairs
set x = buildStairsUpPickX
set y = buildStairsUpPickY
if map[x][y] == DUNGEON_CAVE then
set cave = true
endif
set s = stamp.placeByLogic(buildArea, x, y, cave, true, true, buildTileset, thistype.isWall(x-1,y-1), thistype.isWall(x-1,y), thistype.isWall(x-1,y+1), thistype.isWall(x,y-1), thistype.isWall(x,y), thistype.isWall(x,y+1), thistype.isWall(x+1,y-1), thistype.isWall(x+1,y), thistype.isWall(x+1,y+1))
set lastUpX = GetRectMinX(buildArea)+x*STAMP_SIZE_REAL+STAMP_SIZE_REAL/2
set lastUpY = GetRectMinY(buildArea)+y*STAMP_SIZE_REAL+STAMP_SIZE_REAL/2
set x = buildStairsDownPickX
set y = buildStairsDownPickY
if map[x][y] == DUNGEON_CAVE then
set cave = true
endif
set s = stamp.placeByLogic(buildArea, x, y, cave, true, false, buildTileset, thistype.isWall(x-1,y-1), thistype.isWall(x-1,y), thistype.isWall(x-1,y+1), thistype.isWall(x,y-1), thistype.isWall(x,y), thistype.isWall(x,y+1), thistype.isWall(x+1,y-1), thistype.isWall(x+1,y), thistype.isWall(x+1,y+1))
set lastDownX = GetRectMinX(buildArea)+x*STAMP_SIZE_REAL+STAMP_SIZE_REAL/2
set lastDownY = GetRectMinY(buildArea)+y*STAMP_SIZE_REAL+STAMP_SIZE_REAL/2
call TriggerExecute(buildTrig)
endif
endmethod
static method build takes rect area, integer tileset, code actionFunc returns nothing
if buildTimer == null then
set buildTimer = CreateTimer()
endif
if buildTrig != null then
call TriggerClearActions(buildTrig)
call DestroyTrigger(buildTrig)
endif
set buildTrig = CreateTrigger()
call TriggerAddAction(buildTrig, actionFunc)
set buildArea = area
set buildTileset = tileset
set buildStairsConsidered = 0
set buildStairsUpPickX = 0
set buildStairsUpPickY = 0
set buildStairsDownPickX = 0
set buildStairsDownPickY = 0
set buildx = 0
set buildy = 0
call TimerStart(buildTimer, 0, false, function thistype.buildSub)
endmethod
private static method clearUnverifiedSub takes nothing returns nothing
local integer j = 0
local integer i = clearUnverifiedCount
loop
exitwhen j >= DUNGEON_SIZE_Y
if not verifymap[i][j] then
set map[i][j] = DUNGEON_EMPTY
endif
set j = j + 1
endloop
set clearUnverifiedCount = clearUnverifiedCount+1
if clearUnverifiedCount < DUNGEON_SIZE_X then
call TriggerExecute(clearUnverifiedTrig)
endif
endmethod
static method clearUnverified takes nothing returns nothing
if clearUnverifiedTrig == null then
set clearUnverifiedTrig = CreateTrigger()
call TriggerAddAction(clearUnverifiedTrig, function thistype.clearUnverifiedSub)
endif
set clearUnverifiedCount = 0
call TriggerExecute(clearUnverifiedTrig)
endmethod
static method isMapUsable takes nothing returns boolean
return usable
endmethod
private static method verifyMapClear takes nothing returns nothing
local integer j = 0
local integer i = veriClearCount
loop
exitwhen j >= DUNGEON_SIZE_Y
set verifymap[i][j] = false
set j = j + 1
endloop
set veriClearCount = veriClearCount+1
if veriClearCount < DUNGEON_SIZE_X then
call TriggerExecute(veriClearTrig)
endif
endmethod
static method verifyMap takes nothing returns nothing
local integer i = 0
local integer j
local integer count = 0
if veriTrig == null then
set veriTrig = CreateTrigger()
call TriggerAddAction(veriTrig, function thistype.verifyMap)
set veriClearTrig = CreateTrigger()
call TriggerAddAction(veriClearTrig, function thistype.verifyMapClear)
endif
if stacksize <= 0 then
set stacksize = 1
set verified = 0
loop
set stackx[0] = GetRandomInt(1,DUNGEON_SIZE_X-2)
set stacky[0] = GetRandomInt(1,DUNGEON_SIZE_Y-2)
exitwhen map[stackx[0]][stacky[0]] >= DUNGEON_ROOM
endloop
set veriClearCount = 0
call TriggerExecute(veriClearTrig)
else
loop
exitwhen stacksize <= 0
exitwhen count > MAX_VERIFY_PER_ITERATION
set stacksize = stacksize-1
set i = stackx[stacksize]
set j = stacky[stacksize]
if map[i][j] >= DUNGEON_ROOM and not verifymap[i][j] then
set verifymap[i][j] = true
set verified = verified + 1
if not verifymap[i][j+1] then
set stackx[stacksize] = i
set stacky[stacksize] = j+1
set stacksize = stacksize+1
endif
if not verifymap[i][j-1] then
set stackx[stacksize] = i
set stacky[stacksize] = j-1
set stacksize = stacksize+1
endif
if not verifymap[i+1][j] then
set stackx[stacksize] = i+1
set stacky[stacksize] = j
set stacksize = stacksize+1
endif
if not verifymap[i-1][j] then
set stackx[stacksize] = i-1
set stacky[stacksize] = j
set stacksize = stacksize+1
endif
endif
set count = count + 1
endloop
endif
if stacksize <= 0 then
if I2R(verified)/I2R(DUNGEON_SIZE_X*DUNGEON_SIZE_Y) > DUNGEON_VERIFICATION_THRESHOLD then
set veriAttempts = 0
set usable = true
return
elseif veriAttempts > MAX_VERIFY_ATTEMPTS then
set veriAttempts = 0
set usable = false
return
else
set veriAttempts = veriAttempts+1
endif
endif
call TriggerExecute(veriTrig)
endmethod
private static method clearSub takes nothing returns nothing
local integer j = 0
local integer i = clearCount
loop
exitwhen j >= DUNGEON_SIZE_Y
set map[i][j] = DUNGEON_EMPTY
set j = j + 1
endloop
set clearCount = clearCount+1
if clearCount < DUNGEON_SIZE_X then
call TriggerExecute(clearTrig)
endif
endmethod
static method clearMap takes nothing returns nothing
if clearTrig == null then
set clearTrig = CreateTrigger()
call TriggerAddAction(clearTrig, function thistype.clearSub)
endif
set clearCount = 0
call TriggerExecute(clearTrig)
endmethod
private static method printSub takes nothing returns nothing
local string s = ""
local integer j = printCount
local integer i = 0
if j >= 0 then
loop
exitwhen i >= DUNGEON_SIZE_X
if map[i][j] == DUNGEON_EMPTY then
set s = s + " "
elseif verifymap[i][j] then
if map[i][j] == DUNGEON_ROOM then
set s = s + "#"
elseif map[i][j] == DUNGEON_CAVE then
set s = s + "+"
elseif map[i][j] == DUNGEON_CORRIDOR then
set s = s + "."
endif
else
set s = s + "X"
endif
set i = i + 1
endloop
call Preload("\")\n\n "+s+" \n\n//")
set printCount = printCount-1
call TriggerExecute(printTrig)
endif
endmethod
static method printMap takes nothing returns nothing
if printTrig == null then
set printTrig = CreateTrigger()
call TriggerAddAction(printTrig, function thistype.printSub)
endif
set printCount = DUNGEON_SIZE_Y-1
call PreloadGenClear()
call PreloadGenStart()
call TriggerExecute(printTrig)
call PreloadGenEnd(DUNGEON_PRINT_DIRECTORY+"layout.txt")
endmethod
endstruct
struct DungeonCavern
integer state //determines the generator phase
boolean doWrite //switches between evolving and writing phase
integer percentAreWalls //determines the original distribution of walls to rooms
integer minx //determines the extends of the current map
integer maxx
integer miny
integer maxy
integer curx
integer highcut
integer lowcut
integer iterations
integer disableLowcutAfter
boolean horizontalCut
boolean finished
trigger trig
static thistype temp = 0
static integer array tempmap[DUNGEON_SIZE_X][DUNGEON_SIZE_Y]
method onDestroy takes nothing returns nothing
call TriggerClearActions(this.trig)
call DestroyTrigger(this.trig)
set this.trig = null
endmethod
method evolve takes integer hc, integer lc returns boolean
local integer i = this.curx
local integer j = this.miny+1
local integer numWalls1
local integer numWalls2
loop
exitwhen j >= this.maxy-1
set numWalls1 = Dungeon.getAdjacentWalls(i,j, 1)
if i >= this.minx+2 and i < this.maxx-2 and j >= this.miny+2 and j < this.maxy-2 then
set numWalls2 = Dungeon.getAdjacentWalls(i,j,2)
else
set numWalls2 = numWalls1+5
endif
if numWalls1 >= hc or numWalls2 <= lc then
set thistype.tempmap[i][j] = DUNGEON_EMPTY
else
set thistype.tempmap[i][j] = DUNGEON_CAVE
endif
set j = j + 1
endloop
set this.curx = i+1
if this.curx >= this.maxx-1 then
return true
endif
return false
endmethod
method write takes nothing returns nothing
local integer i = this.minx
local integer j
loop
exitwhen i >= this.maxx
set j = this.miny
loop
exitwhen j >= this.maxy
set map[i][j] = thistype.tempmap[i][j]
set thistype.tempmap[i][j] = DUNGEON_EMPTY
set j = j + 1
endloop
set i = i + 1
endloop
set curx = this.minx+1
endmethod
method randomize takes nothing returns nothing
local integer i = this.minx+1
local integer j
local integer centery = (this.miny+this.maxy)/2
loop
exitwhen i >= this.maxx-1
set j = this.miny+1
loop
exitwhen j >= this.maxy-1
if map[i][j] == DUNGEON_EMPTY then
if (j == centery and this.horizontalCut) or GetRandomInt(1,100) > this.percentAreWalls then
set map[i][j] = DUNGEON_CAVE
endif
endif
set j = j + 1
endloop
set i = i + 1
endloop
endmethod
private static method generateSub takes nothing returns nothing
local thistype this = thistype.temp
if this.state == 0 then
call this.randomize()
set this.state = 1
elseif this.state >= 1 and this.state <= this.disableLowcutAfter then
if this.doWrite then
call this.write()
set this.state = this.state+1
set this.doWrite = false
else
if this.evolve(this.highcut,this.lowcut) then
set this.doWrite = true
endif
endif
elseif this.state <= this.iterations then
if this.doWrite then
call this.write()
set this.state = this.state+1
set this.doWrite = false
else
if this.evolve(this.highcut,-1) then
set this.doWrite = true
endif
endif
elseif this.state == this.iterations+1 then
set this.finished = true
endif
if not this.finished then
call TriggerExecute(this.trig)
endif
endmethod
method generate takes nothing returns nothing
set thistype.temp = this
call TriggerExecute(this.trig)
endmethod
static method create takes integer x1, integer y1, integer x2, integer y2, integer wallPercent, boolean horizontalBlank, integer cuthigh, integer cutlow, integer phases, integer stopLowcutAfter returns thistype
local thistype this = thistype.allocate()
set this.state = 0
set this.doWrite = false
set this.percentAreWalls = wallPercent
set this.minx = x1
set this.maxx = x2
set this.miny = y1
set this.maxy = y2
set this.curx = x1+1
set this.highcut = cuthigh
set this.lowcut = cutlow
set this.iterations = phases
set this.disableLowcutAfter = stopLowcutAfter
set this.horizontalCut = horizontalBlank
set this.finished = false
set this.trig = CreateTrigger()
call TriggerAddAction(this.trig, function thistype.generateSub)
return this
endmethod
endstruct
struct DungeonUniform
RoomList connected //list of already connected rooms
RoomList unconnected //list of remaining unconnected rooms
boolean allowIrregular //determines if non-rectangular rooms are allowed
real roomPercentage //we stop createing rooms after this percentage of level area has been dug out
integer minSize //minimum room dimension
integer maxWidth //maximum room width
integer maxHeight //maximum room height
real extraChance //chance to create an extra corridor per room
real extraDead //chance to create a deadend corridor per room
integer maxRoomAttempts //maximum number of attempts to create a valid room
integer maxRooms //maximum number of created rooms
integer rooms //counts the number of created rooms
integer dug //counts the number of cells dug
integer state //determines the generator phase
integer minx //determines the extends of the map
integer maxx
integer miny
integer maxy
boolean finished
trigger trig
static thistype temp = 0
method onDestroy takes nothing returns nothing
call this.connected.destroy()
call this.unconnected.destroy()
call TriggerClearActions(this.trig)
call DestroyTrigger(this.trig)
set this.trig = null
endmethod
method digLine takes boolean horizontal, integer x, integer y, integer last returns nothing
local integer i = x
local integer j = y
local integer l = last
if last < x and horizontal then
set l = x
set i = last
elseif last < y and not horizontal then
set l = y
set j = last
endif
loop
if map[i][j] == DUNGEON_EMPTY then
set map[i][j] = DUNGEON_CORRIDOR
set this.dug = this.dug+1
endif
if horizontal then
set i = i + 1
exitwhen i > l
else
set j = j + 1
exitwhen j > l
endif
endloop
endmethod
method digRoom takes integer x1, integer y1, integer x2, integer y2 returns nothing
local integer i = x1
local integer j
loop
exitwhen i > x2
set j = y1
loop
exitwhen j > y2
if map[i][j] != DUNGEON_ROOM then
set map[i][j] = DUNGEON_ROOM
set this.dug = this.dug+1
endif
set j = j + 1
endloop
set i = i + 1
endloop
set this.rooms = this.rooms+1
call this.unconnected.add(Room.create(x1,y1,x2,y2))
endmethod
method generateRoom takes nothing returns boolean
local integer count = 0
local integer x1
local integer y1
local integer x2
local integer y2
loop
exitwhen count > this.maxRoomAttempts
set x1 = GetRandomInt(this.minx+1,this.maxx-2)
set y1 = GetRandomInt(this.miny+1,this.maxy-2)
set x2 = x1+GetRandomInt(this.minSize, this.maxWidth)-1
set y2 = y1+GetRandomInt(this.minSize, this.maxHeight)-1
if not this.allowIrregular then
//snap regular rooms to odd-numbered grid positions
if ModuloInteger(x1,2) == 0 then
set x1=x1-1
endif
if ModuloInteger(y1,2) == 0 then
set y1=y1-1
endif
if ModuloInteger(x2,2) == 0 then
set x2=x2-1
endif
if ModuloInteger(y2,2) == 0 then
set y2=y2-1
endif
endif
if x1 > this.minx and y1 > this.miny and x2 < (this.maxx-1) and y2 < (this.maxy-1) then
if this.allowIrregular or (map[x1][y1] != DUNGEON_ROOM and map[x2][y1] != DUNGEON_ROOM and map[x1][y2] != DUNGEON_ROOM and map[x2][y2] != DUNGEON_ROOM) then
call this.digRoom(x1,y1,x2,y2)
if GetRandomReal(0,1) <= this.extraDead then
set x1 = GetRandomInt(x1,x2)
set y1 = GetRandomInt(y1,y2)
if GetRandomInt(0,1) == 1 then
set x2 = x1+GetRandomInt(-(this.maxWidth), this.maxWidth)
if x2 > this.minx+1 and x2 < this.maxx-2 then
call this.digLine(true,x1,y1,x2)
endif
else
set y2 = y1+GetRandomInt(-(this.maxHeight), this.maxHeight)
if y2 > this.miny+1 and y2 < this.maxy-2 then
call this.digLine(false,x1,y1,y2)
endif
endif
endif
endif
return true
endif
set count = count + 1
endloop
return false
endmethod
method connectRooms takes Room start, Room other returns nothing
local integer count = 0
local integer diffX = other.centerx - start.centerx
local integer diffY = other.centery - start.centery
local integer minCommonX = -1
local integer maxCommonX = -1
local integer minCommonY = -1
local integer maxCommonY = -1
local Room s
local Room o
local integer temp1
local integer temp2
//determine common coordinates
if start.minx <= other.minx then
set s = start
set o = other
else
set s = other
set o = start
endif
if o.minx <= s.maxx then
set minCommonX = o.minx
if s.maxx <= o.maxx then
set maxCommonX = s.maxx
else
set maxCommonX = o.maxx
endif
endif
if start.miny <= other.miny then
set s = start
set o = other
else
set s = other
set o = start
endif
if o.miny <= s.maxy then
set minCommonY = o.miny
if s.maxy <= o.maxy then
set maxCommonY = s.maxy
else
set maxCommonY = o.maxy
endif
endif
if minCommonX >= 0 and maxCommonX >= 0 and minCommonY >= 0 and maxCommonY >= 0 then
//rooms overlap each other; no connection needed
elseif minCommonX >= 0 and maxCommonX >= 0 and minCommonX != maxCommonX then
//dig straight along y-axis
call this.digLine(false, GetRandomInt(minCommonX, maxCommonX), start.centery, other.centery)
elseif minCommonY >= 0 and maxCommonY >= 0 and minCommonY != maxCommonY then
//dig straight along x-axis
call this.digLine(true, start.centerx, GetRandomInt(minCommonY, maxCommonY), other.centerx)
else
//L or S-shaped connections
if IAbsBJ(diffX) > IAbsBJ(diffY) then
if diffX > 0 then
set s = start
set o = other
else
set s = other
set o = start
endif
if s.maxx <= o.minx-4 and GetRandomInt(1,2) <= 1 then //S-shaped connection
set temp1 = GetRandomInt(s.miny, s.maxy)
set temp2 = GetRandomInt(o.miny, o.maxy)
call this.digLine(true, s.centerx, temp1, (s.maxx+o.minx)/2)
call this.digLine(false, (s.maxx+o.minx)/2, temp1, temp2)
call this.digLine(true, (s.maxx+o.minx)/2, temp2, o.centerx)
else //L-shaped connection
set temp1 = GetRandomInt(s.miny, s.maxy)
set temp2 = GetRandomInt(o.minx, o.maxx)
call this.digLine(true, s.centerx, temp1, temp2)
call this.digLine(false, temp2, temp1, o.centery)
endif
else
if diffY > 0 then
set s = start
set o = other
else
set s = other
set o = start
endif
if s.maxy <= o.miny-4 and GetRandomInt(1,2) <= 1 then //S-shaped connection
set temp1 = GetRandomInt(s.minx, s.maxx)
set temp2 = GetRandomInt(o.minx, o.maxx)
call this.digLine(false, temp1, s.centery, (s.maxy+o.miny)/2)
call this.digLine(true, temp1, (s.maxy+o.miny)/2, temp2)
call this.digLine(false, temp2, (s.maxy+o.miny)/2, o.centery)
else //L-shaped connection
set temp1 = GetRandomInt(s.minx, s.maxx)
set temp2 = GetRandomInt(o.miny, o.maxy)
call this.digLine(false, temp1, s.centery, temp2)
call this.digLine(true, temp1, temp2, o.centerx)
endif
endif
endif
endmethod
method generateCorridor takes nothing returns boolean
local real closedist = DUNGEON_SIZE_X+DUNGEON_SIZE_Y
local real distance
local RoomListIterator iter
local Room current = 0
local Room closest = 0
local Room extra = 0
local Room start = 0
if this.unconnected.getFirst() != 0 and this.connected.getFirst() == 0 then
set start = this.unconnected.getFirst()
call this.unconnected.remove(start)
call this.connected.add(start)
endif
if this.connected.getFirst() != 0 and this.unconnected.getFirst() != 0 then
set start = this.connected.getFirst()
set iter = this.unconnected.iterator()
loop
exitwhen iter.hasNoNext()
set current = iter.next()
set distance = SquareRoot((current.centerx-start.centerx)*(current.centerx-start.centerx) + (current.centery-start.centery)*(current.centery-start.centery))
if distance < closedist then
set closedist = distance
set extra = closest
set closest = current
endif
endloop
call iter.destroy()
if closest != 0 then
call this.connectRooms(start, closest)
call this.unconnected.remove(closest)
call this.connected.add(closest)
if extra != 0 and GetRandomReal(0,1) <= this.extraChance then
call this.connectRooms(start, extra)
endif
return true
endif
endif
return false
endmethod
private static method generateSub takes nothing returns nothing
local thistype this = thistype.temp
if this.state == 0 then
if I2R(this.dug)/I2R((this.maxx-this.minx)*(this.maxy-this.miny)) > this.roomPercentage or this.rooms >= this.maxRooms or not this.generateRoom() then
set this.state = 1
endif
elseif this.state == 1 then
if not this.generateCorridor() then
set this.state = 2
endif
elseif this.state == 2 then
set this.finished = true
endif
if not this.finished then
call TriggerExecute(this.trig)
endif
endmethod
method generate takes nothing returns nothing
set thistype.temp = this
call TriggerExecute(this.trig)
endmethod
method connectClosestRoom takes integer x, integer y returns boolean
local RoomListIterator iter = this.connected.iterator()
local Room current = 0
local Room closest = 0
local real distance
local real closedist = DUNGEON_SIZE_X*DUNGEON_SIZE_Y
loop
exitwhen iter.hasNoNext()
set current = iter.next()
set distance = SquareRoot((current.centerx-x)*(current.centerx-x) + (current.centery-y)*(current.centery-y))
if distance < closedist then
set closedist = distance
set closest = current
endif
endloop
call iter.destroy()
if closest != 0 then
set current = Room.create(x,y,x,y)
call this.connectRooms(closest, current)
call current.destroy()
return true
endif
return false
endmethod
static method create takes integer x1, integer y1, integer x2, integer y2, integer roomAttempts, integer roomsMax, real percent, boolean nonRectangular, integer min, integer width, integer height, real extraCorridorChance, real extraDeadendChance returns thistype
local thistype this = thistype.allocate()
set this.connected = RoomList.create()
set this.unconnected = RoomList.create()
set this.minx = x1
set this.maxx = x2
set this.miny = y1
set this.maxy = y2
set this.dug = 0
set this.state = 0
set this.rooms = 0
set this.allowIrregular = nonRectangular
set this.maxRoomAttempts = roomAttempts
set this.maxRooms = roomsMax
set this.roomPercentage = percent
set this.minSize = min
set this.maxWidth = width
set this.maxHeight = height
set this.extraChance = extraCorridorChance
set this.extraDead = extraDeadendChance
set this.finished = false
set this.trig = CreateTrigger()
call TriggerAddAction(this.trig, function thistype.generateSub)
return this
endmethod
endstruct
endlibrary
Attachments
Last edited: