diff --git a/Build/Compilers/Nodebuilders/zdbsp.exe b/Build/Compilers/Nodebuilders/zdbsp.exe index 2fa3102c..75c90626 100644 Binary files a/Build/Compilers/Nodebuilders/zdbsp.exe and b/Build/Compilers/Nodebuilders/zdbsp.exe differ diff --git a/Build/Compilers/ZDoom/acc.exe b/Build/Compilers/ZDoom/acc.exe index a60988bc..2efd005f 100644 Binary files a/Build/Compilers/ZDoom/acc.exe and b/Build/Compilers/ZDoom/acc.exe differ diff --git a/Build/Compilers/ZDoom/zdefs.acs b/Build/Compilers/ZDoom/zdefs.acs index 56e83dcf..e5ea3b20 100644 --- a/Build/Compilers/ZDoom/zdefs.acs +++ b/Build/Compilers/ZDoom/zdefs.acs @@ -969,6 +969,7 @@ #define BLOCKF_USE 128 #define BLOCKF_SIGHT 256 #define BLOCKF_HITSCAN 512 +#define BLOCKF_SOUND 1024 #define FOGP_DENSITY 0 #define FOGP_OUTSIDEDENSITY 1 diff --git a/Build/Compilers/ZDoom/zspecial.acs b/Build/Compilers/ZDoom/zspecial.acs index f159cd1c..023f1b3c 100644 --- a/Build/Compilers/ZDoom/zspecial.acs +++ b/Build/Compilers/ZDoom/zspecial.acs @@ -199,7 +199,7 @@ special // 211:Transfer_CeilingLight, 212:Sector_SetColor(4,5), 213:Sector_SetFade(4), - 214:Sector_SetDamage(3), + 214:Sector_SetDamage(3,5), 215:Teleport_Line(2), 216:Sector_SetGravity(3), 217:Stairs_BuildUpDoom(5), @@ -302,7 +302,7 @@ special -57:SetCVarString(2), -58:GetUserCVarString(2), -59:SetUserCVarString(3), - -60:LineAttack(4,8), + -60:LineAttack(4,9), -61:PlaySound(2,6), -62:StopSound(1,2), -63:strcmp(2,3), @@ -337,6 +337,7 @@ special -91:QuakeEx(8,12), -92:Warp(6,11), -93:GetMaxInventory(2), + -94:SetSectorDamage(2,6), // Zandronum's -100:ResetMap(0), diff --git a/Build/Configurations/Boom_DoomDoom.cfg b/Build/Configurations/Boom_DoomDoom.cfg index ccd8f613..43c5f2e1 100644 --- a/Build/Configurations/Boom_DoomDoom.cfg +++ b/Build/Configurations/Boom_DoomDoom.cfg @@ -32,6 +32,15 @@ include("Includes\\Game_Doom.cfg"); //mxd. No DECORATE support in vanilla decorategames = ""; +//mxd. Sky textures for vanilla maps +defaultskytextures +{ + SKY1 = "E1M1,E1M2,E1M3,E1M4,E1M5,E1M6,E1M7,E1M8,E1M9"; + SKY2 = "E2M1,E2M2,E2M3,E2M4,E2M5,E2M6,E2M7,E2M8,E2M9"; + SKY3 = "E3M1,E3M2,E3M3,E3M4,E3M5,E3M6,E3M7,E3M8,E3M9"; + SKY4 = "E4M1,E4M2,E4M3,E4M4,E4M5,E4M6,E4M7,E4M8,E4M9"; +} + // Default thing filters // (these are not required, just useful for new users) thingsfilters diff --git a/Build/Configurations/Doom_DoomDoom.cfg b/Build/Configurations/Doom_DoomDoom.cfg index 53813a0a..2af0a143 100644 --- a/Build/Configurations/Doom_DoomDoom.cfg +++ b/Build/Configurations/Doom_DoomDoom.cfg @@ -32,6 +32,15 @@ include("Includes\\Game_Doom.cfg"); //mxd. No DECORATE support in vanilla decorategames = ""; +//mxd. Sky textures for vanilla maps +defaultskytextures +{ + SKY1 = "E1M1,E1M2,E1M3,E1M4,E1M5,E1M6,E1M7,E1M8,E1M9"; + SKY2 = "E2M1,E2M2,E2M3,E2M4,E2M5,E2M6,E2M7,E2M8,E2M9"; + SKY3 = "E3M1,E3M2,E3M3,E3M4,E3M5,E3M6,E3M7,E3M8,E3M9"; + SKY4 = "E4M1,E4M2,E4M3,E4M4,E4M5,E4M6,E4M7,E4M8,E4M9"; +} + // Default thing filters // (these are not required, just useful for new users) thingsfilters diff --git a/Build/Configurations/Includes/Game_Doom.cfg b/Build/Configurations/Includes/Game_Doom.cfg index 8fbca08f..a01c3191 100644 --- a/Build/Configurations/Includes/Game_Doom.cfg +++ b/Build/Configurations/Includes/Game_Doom.cfg @@ -1,29 +1,37 @@ // Default lump name for new map - defaultlumpname = "MAP01"; - basegame = 1; //mxd: 0 - UNKNOWN, 1 - DOOM, 2 - HERETIC, 3 - HEXEN, 4 - STRIFE, +defaultlumpname = "MAP01"; +basegame = 1; //mxd: 0 - UNKNOWN, 1 - DOOM, 2 - HERETIC, 3 - HEXEN, 4 - STRIFE - // Decorate actors to include depending on actor game property - decorategames = "doom"; +// Decorate actors to include depending on actor game property +decorategames = "doom"; - // Skill levels - skills - { - include("Doom_misc.cfg", "skills"); - } +//mxd. Sky textures for vanilla maps +defaultskytextures +{ + SKY1 = "MAP01,MAP02,MAP03,MAP04,MAP05,MAP06,MAP07,MAP08,MAP09,MAP10,MAP11"; + SKY2 = "MAP12,MAP13,MAP14,MAP15,MAP16,MAP17,MAP18,MAP19,MAP20"; + SKY3 = "MAP21,MAP22,MAP23,MAP24,MAP25,MAP26,MAP27,MAP28,MAP29,MAP30,MAP31,MAP32"; +} - // Default textures - defaultwalltexture = "STARTAN"; - defaultfloortexture = "FLOOR0_1"; - defaultceilingtexture = "CEIL1_1"; +// Skill levels +skills +{ + include("Doom_misc.cfg", "skills"); +} - // Door making - makedoortrack = "DOORTRAK"; - makedoordoor = "BIGDOOR2"; - makedoorceil = "FLAT20"; +// Default textures +defaultwalltexture = "STARTAN"; +defaultfloortexture = "FLOOR0_1"; +defaultceilingtexture = "CEIL1_1"; - // Default texture sets - // (these are not required, but useful for new users) - texturesets - { - include("Doom_texturesets.cfg"); - } \ No newline at end of file +// Door making +makedoortrack = "DOORTRAK"; +makedoordoor = "BIGDOOR2"; +makedoorceil = "FLAT20"; + +// Default texture sets +// (these are not required, but useful for new users) +texturesets +{ + include("Doom_texturesets.cfg"); +} \ No newline at end of file diff --git a/Build/Configurations/Includes/Game_Heretic.cfg b/Build/Configurations/Includes/Game_Heretic.cfg index f488ec5f..14f481b4 100644 --- a/Build/Configurations/Includes/Game_Heretic.cfg +++ b/Build/Configurations/Includes/Game_Heretic.cfg @@ -1,29 +1,37 @@ // Default lump name for new map - defaultlumpname = "E1M1"; - basegame = 2; +defaultlumpname = "E1M1"; +basegame = 2; - // Decorate actors to include depending on actor game property - decorategames = "heretic raven"; +// Decorate actors to include depending on actor game property +decorategames = "heretic raven"; - // Skill levels - skills - { - include("Heretic_misc.cfg", "skills"); - } +//mxd. Sky textures for vanilla maps +defaultskytextures +{ + SKY1 = "E1M1,E1M2,E1M3,E1M4,E1M5,E1M6,E1M7,E1M8,E1M9,E4M1,E4M2,E4M3,E4M4,E4M5,E4M6,E4M7,E4M8,E4M9,E6M1,E6M2,E6M3"; + SKY2 = "E2M1,E2M2,E2M3,E2M4,E2M5,E2M6,E2M7,E2M8,E2M9"; + SKY3 = "E3M1,E3M2,E3M3,E3M4,E3M5,E3M6,E3M7,E3M8,E3M9,E5M1,E5M2,E5M3,E5M4,E5M5,E5M6,E5M7,E5M8,E5M9"; +} - // Default textures - defaultwalltexture = "GRSTNPB"; - defaultfloortexture = "FLOOR03"; - defaultceilingtexture = "FLAT506"; +// Skill levels +skills +{ + include("Heretic_misc.cfg", "skills"); +} - // Door making - makedoortrack = "WOODWL"; - makedoordoor = "DOORWOOD"; - makedoorceil = "FLOOR04"; - - // Default texture sets - // (these are not required, but useful for new users) - texturesets - { - include("Heretic_texturesets.cfg"); - } \ No newline at end of file +// Default textures +defaultwalltexture = "GRSTNPB"; +defaultfloortexture = "FLOOR03"; +defaultceilingtexture = "FLAT506"; + +// Door making +makedoortrack = "WOODWL"; +makedoordoor = "DOORWOOD"; +makedoorceil = "FLOOR04"; + +// Default texture sets +// (these are not required, but useful for new users) +texturesets +{ + include("Heretic_texturesets.cfg"); +} \ No newline at end of file diff --git a/Build/Configurations/Includes/Game_Hexen.cfg b/Build/Configurations/Includes/Game_Hexen.cfg index c13da6e3..86d87ced 100644 --- a/Build/Configurations/Includes/Game_Hexen.cfg +++ b/Build/Configurations/Includes/Game_Hexen.cfg @@ -1,30 +1,30 @@ // Default lump name for new map - defaultlumpname = "MAP01"; - skyflatname = "F_SKY"; - basegame = 3; +defaultlumpname = "MAP01"; +skyflatname = "F_SKY"; +basegame = 3; - // Decorate actors to include depending on actor game property - decorategames = "hexen raven"; +// Decorate actors to include depending on actor game property +decorategames = "hexen raven"; - // Skill levels - skills - { - include("Hexen_misc.cfg", "skills"); - } +// Skill levels +skills +{ + include("Hexen_misc.cfg", "skills"); +} - // Default textures - defaultwalltexture = "FOREST01"; - defaultfloortexture = "F_010"; - defaultceilingtexture = "F_011"; +// Default textures +defaultwalltexture = "FOREST01"; +defaultfloortexture = "F_010"; +defaultceilingtexture = "F_011"; - // Door making - makedoortrack = "D_END2"; - makedoordoor = "D_WD07"; - makedoorceil = "F_092"; +// Door making +makedoortrack = "D_END2"; +makedoordoor = "D_WD07"; +makedoorceil = "F_092"; - // Default texture sets - // (these are not required, but useful for new users) - texturesets - { - include("Hexen_texturesets.cfg"); - } \ No newline at end of file +// Default texture sets +// (these are not required, but useful for new users) +texturesets +{ + include("Hexen_texturesets.cfg"); +} \ No newline at end of file diff --git a/Build/Configurations/Includes/Game_Strife.cfg b/Build/Configurations/Includes/Game_Strife.cfg index 2a5bb327..159078dd 100644 --- a/Build/Configurations/Includes/Game_Strife.cfg +++ b/Build/Configurations/Includes/Game_Strife.cfg @@ -1,30 +1,37 @@ // Default lump name for new map - defaultlumpname = "MAP01"; - skyflatname = "F_SKY001"; - basegame = 4; +defaultlumpname = "MAP01"; +skyflatname = "F_SKY001"; +basegame = 4; - // Decorate actors to include depending on actor game property - decorategames = "strife"; +// Decorate actors to include depending on actor game property +decorategames = "strife"; - // Skill levels - skills - { - include("Strife_misc.cfg", "skills"); - } +//mxd. Sky textures for vanilla maps +defaultskytextures +{ + SKYMNT01 = "MAP10,MAP11,MAP12,MAP13,MAP14,MAP15,MAP16,MAP17,MAP18,MAP19,MAP20,MAP21,MAP22,MAP23,MAP24,MAP25,MAP26,MAP27,MAP28,MAP29,MAP30,MAP31"; + SKYMNT02 = "MAP01,MAP02,MAP03,MAP04,MAP05,MAP06,MAP07,MAP08,MAP09,MAP32,MAP33,MAP34"; +} - // Default textures - defaultwalltexture = "BRKGRY17"; - defaultfloortexture = "F_NOLINE"; - defaultceilingtexture = "F_DECK"; +// Skill levels +skills +{ + include("Strife_misc.cfg", "skills"); +} - // Door making - makedoortrack = "DORTRK01"; - makedoordoor = "DORML01"; - makedoorceil = "F_UNDOOR"; +// Default textures +defaultwalltexture = "BRKGRY17"; +defaultfloortexture = "F_NOLINE"; +defaultceilingtexture = "F_DECK"; - // Default texture sets - // (these are not required, but useful for new users) - texturesets - { - include("Strife_texturesets.cfg"); - } \ No newline at end of file +// Door making +makedoortrack = "DORTRK01"; +makedoordoor = "DORML01"; +makedoorceil = "F_UNDOOR"; + +// Default texture sets +// (these are not required, but useful for new users) +texturesets +{ + include("Strife_texturesets.cfg"); +} \ No newline at end of file diff --git a/Build/Configurations/Includes/ZDoom_things.cfg b/Build/Configurations/Includes/ZDoom_things.cfg index 3b3388d8..cd8c8677 100644 --- a/Build/Configurations/Includes/ZDoom_things.cfg +++ b/Build/Configurations/Includes/ZDoom_things.cfg @@ -32,20 +32,6 @@ zdoom } } - obstacles - { - blocking = 2; - - 5050 - { - title = "Stalagmite"; - sprite = "SMT2A0"; - class = "Stalagmite"; - width = 16; - height = 48; - } - } - lights { blocking = 2; @@ -1396,6 +1382,20 @@ doom class = "StealthZombieMan"; } } + + obstacles + { + blocking = 2; + + 5050 + { + title = "Stalagmite"; + sprite = "SMT2A0"; + class = "Stalagmite"; + width = 16; + height = 48; + } + } decoration { diff --git a/Build/Configurations/Other Games/Action Doom/Includes/Game_Action2.cfg b/Build/Configurations/Other Games/Action Doom/Includes/Game_Action2.cfg index 7f57b3f2..daca0c09 100644 --- a/Build/Configurations/Other Games/Action Doom/Includes/Game_Action2.cfg +++ b/Build/Configurations/Other Games/Action Doom/Includes/Game_Action2.cfg @@ -1,29 +1,37 @@ // Default lump name for new map - defaultlumpname = "MAP01"; +defaultlumpname = "MAP01"; - // Decorate actors to include depending on actor game property - decorategames = "action2"; // Doesn't actually exist, but used to prevent loading Doom actors when loading zdoom.pk3 +// Decorate actors to include depending on actor game property +decorategames = "action2"; // Doesn't actually exist, but used to prevent loading Doom actors when loading zdoom.pk3 - // Skill levels - skills - { - include("Action2_misc.cfg", "skills"); - } +//mxd. Sky textures for vanilla maps +defaultskytextures +{ + SKY1 = "MAP01,MAP02,MAP03,MAP04,MAP05,MAP06,MAP07,MAP08,MAP09,MAP10,MAP11"; + SKY2 = "MAP12,MAP13,MAP14,MAP15,MAP16,MAP17,MAP18,MAP19,MAP20"; + SKY3 = "MAP21,MAP22,MAP23,MAP24,MAP25,MAP26,MAP27,MAP28,MAP29,MAP30,MAP31,MAP32"; +} - // Door making - makedoortrack = "BLACK"; +// Skill levels +skills +{ + include("Action2_misc.cfg", "skills"); +} - // Default thing filters - // (these are not required, just useful for new users) - /*thingsfilters - { - include("Doom_misc.cfg", "thingsfilters"); - }*/ - +// Door making +makedoortrack = "BLACK"; - // Default texture sets - // (these are not required, but useful for new users) - texturesets - { - include("Action2_texturesets.cfg"); - } \ No newline at end of file +// Default thing filters +// (these are not required, just useful for new users) +/*thingsfilters +{ + include("Doom_misc.cfg", "thingsfilters"); +}*/ + + +// Default texture sets +// (these are not required, but useful for new users) +texturesets +{ + include("Action2_texturesets.cfg"); +} \ No newline at end of file diff --git a/Build/Configurations/Other Games/Chex Quest 3/Includes/Game_Chex3.cfg b/Build/Configurations/Other Games/Chex Quest 3/Includes/Game_Chex3.cfg index c3b3ccd1..3652e796 100644 --- a/Build/Configurations/Other Games/Chex Quest 3/Includes/Game_Chex3.cfg +++ b/Build/Configurations/Other Games/Chex Quest 3/Includes/Game_Chex3.cfg @@ -1,28 +1,34 @@ // Default lump name for new map - defaultlumpname = "MAP01"; +defaultlumpname = "MAP01"; - // Decorate actors to include depending on actor game property - decorategames = "chex"; +// Decorate actors to include depending on actor game property +decorategames = "chex"; - // Skill levels - skills - { - include("Chex_misc.cfg", "skills"); - } +//mxd. Sky textures for vanilla maps +defaultskytextures +{ + SKY1 = "E1M1,E1M2,E1M3,E1M4,E1M5"; +} - // Door making - makedoortrack = "COMPSTA1"; +// Skill levels +skills +{ + include("Chex_misc.cfg", "skills"); +} - // Default thing filters - // (these are not required, just useful for new users) - /*thingsfilters - { - include("Doom_misc.cfg", "thingsfilters"); - }*/ +// Door making +makedoortrack = "COMPSTA1"; - // Default texture sets - // (these are not required, but useful for new users) - texturesets - { - include("Chex3_texturesets.cfg"); - } \ No newline at end of file +// Default thing filters +// (these are not required, just useful for new users) +/*thingsfilters +{ + include("Doom_misc.cfg", "thingsfilters"); +}*/ + +// Default texture sets +// (these are not required, but useful for new users) +texturesets +{ + include("Chex3_texturesets.cfg"); +} \ No newline at end of file diff --git a/Build/Configurations/Other Games/Chex Quest/Includes/Game_Chex.cfg b/Build/Configurations/Other Games/Chex Quest/Includes/Game_Chex.cfg index 23f3e285..b7f240b6 100644 --- a/Build/Configurations/Other Games/Chex Quest/Includes/Game_Chex.cfg +++ b/Build/Configurations/Other Games/Chex Quest/Includes/Game_Chex.cfg @@ -1,28 +1,34 @@ // Default lump name for new map - defaultlumpname = "MAP01"; +defaultlumpname = "MAP01"; - // Decorate actors to include depending on actor game property - decorategames = "chex"; +// Decorate actors to include depending on actor game property +decorategames = "chex"; - // Skill levels - skills - { - include("Chex_misc.cfg", "skills"); - } +//mxd. Sky textures for vanilla maps +defaultskytextures +{ + SKY1 = "E1M1,E1M2,E1M3,E1M4,E1M5"; +} - // Door making - makedoortrack = "COMPSTA1"; +// Skill levels +skills +{ + include("Chex_misc.cfg", "skills"); +} - // Default thing filters - // (these are not required, just useful for new users) - /*thingsfilters - { - include("Doom_misc.cfg", "thingsfilters"); - }*/ +// Door making +makedoortrack = "COMPSTA1"; - // Default texture sets - // (these are not required, but useful for new users) - texturesets - { - include("Chex_texturesets.cfg"); - } \ No newline at end of file +// Default thing filters +// (these are not required, just useful for new users) +/*thingsfilters +{ + include("Doom_misc.cfg", "thingsfilters"); +}*/ + +// Default texture sets +// (these are not required, but useful for new users) +texturesets +{ + include("Chex_texturesets.cfg"); +} \ No newline at end of file diff --git a/Build/Configurations/Other Games/Harmony/Includes/Game_Harmony.cfg b/Build/Configurations/Other Games/Harmony/Includes/Game_Harmony.cfg index d6a38d27..3fa180d6 100644 --- a/Build/Configurations/Other Games/Harmony/Includes/Game_Harmony.cfg +++ b/Build/Configurations/Other Games/Harmony/Includes/Game_Harmony.cfg @@ -1,28 +1,36 @@ // Default lump name for new map - defaultlumpname = "MAP01"; +defaultlumpname = "MAP01"; - // Decorate actors to include depending on actor game property - decorategames = "harmony"; // Doesn't actually exist, but used to prevent loading Doom actors when loading zdoom.pk3 +// Decorate actors to include depending on actor game property +decorategames = "harmony"; // Doesn't actually exist, but used to prevent loading Doom actors when loading zdoom.pk3 - // Skill levels - skills - { - include("Harmony_misc.cfg", "skills"); - } +//mxd. Sky textures for vanilla maps +defaultskytextures +{ + SKY1 = "MAP01,MAP02,MAP03,MAP04,MAP05,MAP06,MAP07,MAP08,MAP09,MAP10,MAP11"; + SKY2 = "MAP12,MAP13,MAP14,MAP15,MAP16,MAP17,MAP18,MAP19,MAP20"; + SKY3 = "MAP21,MAP22,MAP23,MAP24,MAP25,MAP26,MAP27,MAP28,MAP29,MAP30,MAP31,MAP32"; +} - // Door making - makedoortrack = "DOORTRAK"; +// Skill levels +skills +{ + include("Harmony_misc.cfg", "skills"); +} - // Default thing filters - // (these are not required, just useful for new users) - /*thingsfilters - { - include("Doom_misc.cfg", "thingsfilters"); - }*/ +// Door making +makedoortrack = "DOORTRAK"; - // Default texture sets - // (these are not required, but useful for new users) - texturesets - { - include("Harmony_texturesets.cfg"); - } \ No newline at end of file +// Default thing filters +// (these are not required, just useful for new users) +/*thingsfilters +{ + include("Doom_misc.cfg", "thingsfilters"); +}*/ + +// Default texture sets +// (these are not required, but useful for new users) +texturesets +{ + include("Harmony_texturesets.cfg"); +} \ No newline at end of file diff --git a/Build/Scripting/ZDoom_ACS.cfg b/Build/Scripting/ZDoom_ACS.cfg index c67aea90..ea8a23b4 100644 --- a/Build/Scripting/ZDoom_ACS.cfg +++ b/Build/Scripting/ZDoom_ACS.cfg @@ -121,7 +121,7 @@ keywords Death = "Script expression Death"; Default = "default:"; Delay = "void Delay(int tics)"; - Disconnect = "Disconnect script expression"; + Disconnect = "Script expression Disconnect"; Do = "do"; Door_Animated = "Door_Animated(tag, speed, delay, lock)"; Door_Close = "Door_Close(tag, speed, lighttag)"; @@ -261,7 +261,7 @@ keywords Light_Strobe = "Light_Strobe(tag, upper, lower, u-tics, l-tics)"; Light_StrobeDoom = "Light_StrobeDoom(tag, u-tics, l-tics)"; Lightning = "Script expression Lightning"; - LineAttack = "LineAttack(int tid, fixed angle, fixed pitch, int damage[, str pufftype = 'BulletPuff'[, str damagetype = 'None'[, fixed range = 2048[, int flags = 0]]]])\nFires a hitscan attack. If tid is 0, the activator of the script is the source of the attack."; + LineAttack = "LineAttack(int tid, fixed angle, fixed pitch, int damage[, str pufftype = 'BulletPuff'[, str damagetype = 'None'[, fixed range = 2048[, int flags = 0[, int pufftid = 0]]]]])\nFires a hitscan attack. If tid is 0, the activator of the script is the source of the attack."; Line_AlignCeiling = "Line_AlignCeiling(lineid, side)"; Line_AlignFloor = "Line_AlignFloor(lineid, side)"; Line_SetBlocking = "Line_SetBlocking(lineid, setflags, clearflags)"; @@ -390,6 +390,8 @@ keywords SetPlayerProperty = "SetPlayerProperty(who, set, which)"; SetPointer = "bool SetPointer(int assign_slot, int tid[, int pointer_selector[, int flags]])\nSet the value of one of the caller's stored pointers."; SetResultValue = "void SetResultValue(int value)"; + SetSectorDamage = "fixed SetSectorDamage(int tag, int amount, str damagetype, int interval, int leaky)"; + SetSectorTerrain = "fixed SetSectorTerrain(int tag, int plane, str terraintype)"; SetSkyScrollSpeed = "void SetSkyScrollSpeed(int sky, fixed skyspeed)\nChanges the scrolling speed of a sky.\nThis is useful in conjunction with ChangeSky.\nsky: either 1 or 2.\nskyspeed: the desired scrolling speed."; SetActorTeleFog = "void SetActorTeleFog(int tid, str telefogsrcclass, str telefogdestclass"; SetThingSpecial = "void SetThingSpecial(int tid, int special[, int arg0[, int arg1[, int arg2[, int arg3[, int arg4]]]]])\nSets the special for any things with the same TID.\nThis is similar to Thing_SetSpecial, except it can only be used from ACS,\nand it can set all of a thing's special arguments.\nIf tid is 0, then the activator is used."; @@ -547,11 +549,12 @@ constants APROP_Mass; APROP_MasterTID; APROP_MeleeRange; - APROP_NameTag; - APROP_NoTarget; - APROP_NoTrigger; + APROP_Nametag; + APROP_Notarget; + APROP_Notrigger; APROP_PainSound; APROP_Radius; + APROP_Reactiontime; APROP_RenderStyle; APROP_ScaleX; APROP_ScaleY; @@ -566,12 +569,12 @@ constants APROP_TracerTID; APROP_ViewHeight; APROP_Waterlevel; - ARMORINFO_CLASSNAME; - ARMORINFO_SAVEAMOUNT; ARMORINFO_ACTUALSAVEAMOUNT; - ARMORINFO_SAVEPERCENT; + ARMORINFO_CLASSNAME; ARMORINFO_MAXABSORB; ARMORINFO_MAXFULLABSORB; + ARMORINFO_SAVEAMOUNT; + ARMORINFO_SAVEPERCENT; ATTN_IDLE; ATTN_NONE; ATTN_NORM; @@ -590,6 +593,7 @@ constants BLOCKF_PROJECTILES; BLOCKF_RAILING; BLOCKF_SIGHT; + BLOCKF_SOUND; BLOCKF_USE; BT_ALTATTACK; BT_ATTACK; @@ -617,20 +621,19 @@ constants BT_USER4; BT_ZOOM; CARRY; - CHAN_AREA; + CHAN_5; + CHAN_6; + CHAN_7; CHAN_AUTO; CHAN_BODY; CHAN_ITEM; CHAN_LISTENERZ; - CHAN_LOOP; CHAN_MAYBE_LOCAL; CHAN_NOPAUSE; CHAN_UI; CHAN_VOICE; CHAN_WEAPON; - CHAN_5; - CHAN_6; - CHAN_7; + CHANGELEVEL_CHANGESKILL; CHANGELEVEL_KEEPFACING; CHANGELEVEL_NOINTERMISSION; CHANGELEVEL_NOMONSTERS; @@ -670,9 +673,27 @@ constants DAMAGE_NONPLAYERS; DAMAGE_PLAYERS; DAMAGE_SUBCLASSES_PROTECT; + DB_ORDER_ASC; + DB_ORDER_DESC; + EV_Char; + EV_KeyDown; + EV_KeyRepeat; + EV_KeyUp; + EV_LButtonDblClick; + EV_LButtonDown; + EV_LButtonUp; + EV_MButtonDblClick; + EV_MButtonDown; + EV_MButtonUp; + EV_MouseMove; + EV_RButtonDblClick; + EV_RButtonDown; + EV_RButtonUp; + EV_WheelDown; + EV_WheelUp; FALSE; - FHF_NORANDOMPUFFZ; FHF_NOIMPACTDECAL; + FHF_NORANDOMPUFFZ; FOGP_DENSITY; FOGP_OUTSIDEDENSITY; FOGP_SKYFOG; @@ -680,6 +701,45 @@ constants GAME_NET_DEATHMATCH; GAME_SINGLE_PLAYER; GAME_TITLE_MAP; + GK_ALERT; + GK_BACKSPACE; + GK_CESCAPE; + GK_DEL; + GK_DOWN; + GK_END; + GK_ESCAPE; + GK_F1; + GK_F10; + GK_F11; + GK_F12; + GK_F2; + GK_F3; + GK_F4; + GK_F5; + GK_F6; + GK_F7; + GK_F8; + GK_F9; + GK_FORMFEED; + GK_FREE1; + GK_FREE2; + GK_FREE3; + GK_HOME; + GK_LEFT; + GK_LINEFEED; + GK_PGDN; + GK_PGUP; + GK_RETURN; + GK_RIGHT; + GK_TAB; + GK_UP; + GK_VTAB; + GKM_ALT; + GKM_CTRL; + GKM_LBUTTON; + GKM_MBUTTON; + GKM_RBUTTON; + GKM_SHIFT; HUDMSG_ADDBLEND; HUDMSG_ALPHA; HUDMSG_COLORSTRING; @@ -733,66 +793,69 @@ constants MARINEWEAPON_RocketLauncher; MARINEWEAPON_Shotgun; MARINEWEAPON_SuperShotgun; - MF_SPECIAL; - MF_SOLID; - MF_SHOOTABLE; - MF_NOSECTOR; - MF_NOBLOCKMAP; MF_AMBUSH; - MF_JUSTHIT; - MF_JUSTATTACKED; - MF_SPAWNCEILING; - MF_NOGRAVITY; - MF_DROPOFF; - MF_PICKUP; - MF_NOCLIP; - MF_INCHASE; - MF_FLOAT; - MF_TELEPORT; - MF_MISSILE; - MF_DROPPED; - MF_SHADOW; - MF_NOBLOOD; MF_CORPSE; - MF_INFLOAT; - MF_INBOUNCE; - MF_COUNTKILL; MF_COUNTITEM; - MF_SKULLFLY; - MF_NOTDMATCH; - MF_SPAWNSOUNDSOURCE; + MF_COUNTKILL; + MF_DROPOFF; + MF_DROPPED; + MF_FLOAT; MF_FRIENDLY; - MF_UNMORPHED; - MF_NOLIFTDROP; - MF_STEALTH; MF_ICECORPSE; - ML_BLOCKING; - ML_BLOCKMONSTERS; - ML_TWOSIDED; - ML_DONTPEGTOP; - ML_DONTPEGBOTTOM; - ML_SECRET; - ML_SOUNDBLOCK; - ML_DONTDRAW; - ML_MAPPED; - ML_REPEAT_SPECIAL; + MF_INBOUNCE; + MF_INCHASE; + MF_INFLOAT; + MF_JUSTATTACKED; + MF_JUSTHIT; + MF_MISSILE; + MF_NOBLOCKMAP; + MF_NOBLOOD; + MF_NOCLIP; + MF_NOGRAVITY; + MF_NOLIFTDROP; + MF_NOSECTOR; + MF_NOTDMATCH; + MF_PICKUP; + MF_SHADOW; + MF_SHOOTABLE; + MF_SKULLFLY; + MF_SOLID; + MF_SPAWNCEILING; + MF_SPAWNSOUNDSOURCE; + MF_SPECIAL; + MF_STEALTH; + MF_TELEPORT; + MF_UNMORPHED; + ML_3DMIDTEX; ML_ADDTRANS; - ML_MONSTERSCANACTIVATE; + ML_BLOCK_FLOATERS; ML_BLOCK_PLAYERS; ML_BLOCKEVERYTHING; - ML_ZONEBOUNDARY; - ML_RAILING; - ML_BLOCK_FLOATERS; - ML_CLIP_MIDTEX; - ML_WRAP_MIDTEX; - ML_3DMIDTEX; - ML_CHECKSWITCHRANGE; - ML_FIRSTSIDEONLY; - ML_BLOCKPROJECTILE; - ML_BLOCKUSE; - ML_BLOCKSIGHT; ML_BLOCKHITSCAN; + ML_BLOCKING; + ML_BLOCKMONSTERS; + ML_BLOCKPROJECTILE; + ML_BLOCKSIGHT; + ML_BLOCKUSE; + ML_CHECKSWITCHRANGE; + ML_CLIP_MIDTEX; + ML_DONTDRAW; + ML_DONTPEGBOTTOM; + ML_DONTPEGTOP; + ML_FIRSTSIDEONLY; + ML_MAPPED; + ML_MONSTERSCANACTIVATE; + ML_RAILING; + ML_REPEAT_SPECIAL; + ML_SECRET; + ML_SOUNDBLOCK; + ML_TWOSIDED; + ML_WRAP_MIDTEX; + ML_ZONEBOUNDARY; + MOD_BARREL; + MOD_BFG_BOOM; MOD_BFG_SPLASH; + MOD_CHAINSAW; MOD_CRUSH; MOD_DISINTEGRATE; MOD_ELECTRIC; @@ -801,9 +864,14 @@ constants MOD_HIT; MOD_ICE; MOD_LAVA; + MOD_PLASMARIFLE; MOD_POISON; + MOD_R_SPLASH; MOD_RAILGUN; + MOD_ROCKET; MOD_SLIME; + MOD_SPLASH; + MOD_SSHOTGUN; MOD_SUICIDE; MOD_TELEFRAG; MOD_UNKNOWN; @@ -835,6 +903,7 @@ constants MRF_WHENINVULNERABLE; NO; NO_CHANGE; + NO_TEAM; NOT_BOTTOM; NOT_CEILING; NOT_FLOOR; @@ -875,11 +944,11 @@ constants PTROP_NOSAFEGUARDS; PTROP_UNSAFEMASTER; PTROP_UNSAFETARGET; + QF_FULLINTENSITY; + QF_MAX; QF_RELATIVE; QF_SCALEDOWN; QF_SCALEUP; - QF_MAX; - QF_FULLINTENSITY; QF_WAVE; SCROLL; SCROLL_AND_CARRY; @@ -900,21 +969,31 @@ constants SKILL_NORMAL; SKILL_VERY_EASY; SKILL_VERY_HARD; - SPAC_None; - SPAC_Cross; - SPAC_Use; - SPAC_MCross; - SPAC_Impact; - SPAC_Push; - SPAC_PCross; - SPAC_UseThrough; + SOUND_Active; + SOUND_Attack; + SOUND_Bounce; + SOUND_CrushPain; + SOUND_Death; + SOUND_Howl; + SOUND_Pain; + SOUND_See; + SOUND_Use; + SOUND_WallBounce; SPAC_AnyCross; - SPAC_MUse; + SPAC_Cross; + SPAC_Impact; + SPAC_MCross; SPAC_MPush; + SPAC_MUse; + SPAC_None; + SPAC_PCross; + SPAC_Push; + SPAC_Use; SPAC_UseBack; + SPAC_UseThrough; STYLE_Add; - STYLE_AddStencil; STYLE_AddShaded; + STYLE_AddStencil; STYLE_Fuzzy; STYLE_None; STYLE_Normal; @@ -1242,6 +1321,8 @@ constants T_YELLOWKEYCARD; T_YELLOWSKULLKEY; T_ZOMBIE; + TEAM_BLUE; + TEAM_RED; TEXFLAG_ADDOFFSET; TEXFLAG_BOTTOM; TEXFLAG_MIDDLE; @@ -1249,22 +1330,41 @@ constants TEXTURE_BOTTOM; TEXTURE_MIDDLE; TEXTURE_TOP; - WARPF_ABSOLUTEOFFSET; + TPROP_Assister; + TPROP_Carrier; + TPROP_DeathCount; + TPROP_FragCount; + TPROP_IsValid; + TPROP_LoserTheme; + TPROP_Name; + TPROP_NumLivePlayers; + TPROP_NumPlayers; + TPROP_PlayerStartNum; + TPROP_PointCount; + TPROP_ReturnTics; + TPROP_Score; + TPROP_Spread; + TPROP_TeamItem; + TPROP_TextColor; + TPROP_WinCount; + TPROP_WinnerTheme; + TRANSLATION_ICE; + TRUE; WARPF_ABSOLUTEANGLE; - WARPF_USECALLERANGLE; - WARPF_NOCHECKPOSITION; - WARPF_INTERPOLATE; - WARPF_WARPINTERPOLATION; - WARPF_COPYINTERPOLATION; - WARPF_STOP; - WARPF_TOFLOOR; - WARPF_TESTONLY; + WARPF_ABSOLUTEOFFSET; WARPF_ABSOLUTEPOSITION; WARPF_BOB; - WARPF_MOVEPTR; - WARPF_USEPTR; - WARPF_COPYVELOCITY; + WARPF_COPYINTERPOLATION; WARPF_COPYPITCH; - TRUE; + WARPF_COPYVELOCITY; + WARPF_INTERPOLATE; + WARPF_MOVEPTR; + WARPF_NOCHECKPOSITION; + WARPF_STOP; + WARPF_TESTONLY; + WARPF_TOFLOOR; + WARPF_USECALLERANGLE; + WARPF_USEPTR; + WARPF_WARPINTERPOLATION; YES; } \ No newline at end of file diff --git a/Build/Textures/MissingSky3D.png b/Build/Textures/MissingSky3D.png new file mode 100644 index 00000000..cb86d71e Binary files /dev/null and b/Build/Textures/MissingSky3D.png differ diff --git a/Help/gc_basicsettings.html b/Help/gc_basicsettings.html index 9ec97fcd..a27bcba3 100644 --- a/Help/gc_basicsettings.html +++ b/Help/gc_basicsettings.html @@ -23,10 +23,11 @@
game (string)
The name that is displayed in Doom Buider for your Game Configuration.
+ Default value is "<unnamed game>".

enabledbydefault (boolean) - GZDB only.
This game configuration is available by default. You can enable and disable game configurations using Game Configurations window.
- The default value is false.
+ Default value is false.

actionspecialhelp (string) - GZDB only.
The URL used to display action special help. "%K" wildcard is replaced by id property defined in action definition.
@@ -38,7 +39,7 @@ basegame (integer) [0 .. 4] - GZDB only.
Indicates which game the current configuration is based on. Used to load game-specific GLDEFS lumps (DOOMDEFS, HTICDEFS, HEXNDEFS or STRFDEFS).
Possible values: 1 (DOOM), 2 (HERETIC), 3 (HEXEN) or 4 (STRIFE).
- The default value is 0 (don't load game-specific lumps).
+ Default value is 0 (don't load game-specific lumps).

engine (string)
@@ -52,7 +53,7 @@
testshortpaths (boolean)
Set to true to use MSDOS 8.3 format paths and filenames by default. The user can still change this in the Game Configurations window.
- The default value is false.
+ Default value is false.

defaultsavecompiler (string)
Name of the Nodebuilder Compiler Configuration structure to use as the default settings for the compiler that is used when saving the map. The user can still change this in the Game Configurations window.
@@ -67,7 +68,7 @@ This defines the skill options the user has available with this game engine/project. The settings in this structure are expected to be numbers with string values (the descriptive name for the skill level).

Example: -
+  
 skills
 {
 	1 = "I'm too young to die";
@@ -79,7 +80,8 @@ skills
 

linetagindicatesectors (boolean)
- When true, Doom Builder will highlight sectors associated with the same tag number when a line is highlighted. This is only really useful for Doom format maps, because Hexen format and UDMF format has no single tag on linedefs (in those formats, the arguments of the linedef's action can be tags, which also works to highlight sectors).
The default is false.
+ When true, Doom Builder will highlight sectors associated with the same tag number when a line is highlighted. This is only really useful for Doom format maps, because Hexen format and UDMF format has no single tag on linedefs (in those formats, the arguments of the linedef's action can be tags, which also works to highlight sectors).
+Default value is false.

singlesidedflag (integer or string)
This lets Doom Builder know the linedef flag that indicates a line with only one side. Doom Builder will set this flag value on a linedef when it changes a line to become single sided and removes the flag from a linedef when it becomes double sided. Plugins can also use this information to perform operations on linedefs. For map formats that use numeric flags (Doom and Hexen) this must be an integer flag value. For map formats that use named flags (UDMF), this must be a string indicating the name of the flag.
@@ -91,22 +93,28 @@ skills This lets Doom Builder know the linedef flag that indicates a line which blocks players and monsters. Doom Builder uses this to give the line a special color and plugins can use this information to perform operations related to blocking sound lines. For map formats that use numeric flags (Doom and Hexen) this must be an integer specifying the flag value of the Impassable flag. For map formats that use named flags (UDMF), this must be a string indicating the name of the Impassable flag.

defaultwalltexture (string) - GZDB only.
- Name of a texture to use on sidedefs when creating a new sector.
+ Name of a texture to use on sidedefs when creating a new sector.
+"STARTAN".

defaultfloortexture (string) - GZDB only.
- Name of a flat to use on the floor when creating a new sector.
+ Name of a flat to use on the floor when creating a new sector.
+ Default value is "FLOOR0_1".

defaultceilingtexture (string) - GZDB only.
- Name of a flat to use on the ceiling when creating a new sector.
+ Name of a flat to use on the ceiling when creating a new sector.
+ Default value is "CEIL1_1".

makedoortrack (string)
Name of a texture to use on the walls when making a door.
+ Default value is "-" (no texture).

makedoordoor (string) - GZDB only.
Name of a texture to use as the door texture when making a door.
+ Default value is "-" (no texture).

makedoorceil (string) - GZDB only.
Name of a texture to use as the door's ceiling texture when making a door.
+ Default value is "-" (no texture).

makedooraction (integer)
Linedef action number to put on the lines when making a door.
@@ -115,13 +123,28 @@ skills Arguments for the linedef action number to put on the lines when making a door.

doomlightlevels (boolean)
- Set this to false to use linear lighting in Doom Builder. Normally Doom Builder uses a simulation of Doom's light levels.
Default value is true.
+ Set this to false to use linear lighting in Doom Builder. Normally Doom Builder uses a simulation of Doom's light levels. +
Default value is true.

start3dmode (integer)
Thing type number that Doom Builder will use to keep your Visual Mode camera position stored in the map. Doom Builder will place a single thing of this type in your map and move it along as you move in Visual Mode.

skyflatname (string)
- Name of the flat that is interpreted as sky (meaning there is no ceiling). Doom Builder and plugins can use this information for various purposes.
+ Name of the flat that is interpreted as sky (meaning there is no ceiling). Doom Builder and plugins can use this information for various purposes. +
Default value is "F_SKY1".
+
+ defaultskytextures (structure) - GZDB only.
+ Defines the relationship between map names and sky texture names used by vanilla maps.
+
+ Example: +
+defaultskytextures
+{
+	SKY1 = "MAP01,MAP02,MAP03,MAP04,MAP05";
+	SKY2 = "MAP12,MAP13,MAP14,MAP15,MAP16";
+	SKY3 = "MAP21,MAP22,MAP23,MAP24,MAP25";
+}
+

longtexturenames (boolean) - GZDB only.
Enables support for long (> 8 chars) texture names. This is used by GZDoom Builder to limit the input fields in the user interface and to check the validity of texture names in resources. This setting should only be enabled for UDMF game configurations. Enabling this setting will make maps incompatible with Doom Builder 2 and can lead to problems in Slade 3 This does NOT determine the actual limitation on the texture names in the map file format.
Default value is false.
diff --git a/Help/gc_resourcesettings.html b/Help/gc_resourcesettings.html index 0ce4142f..fba8476e 100644 --- a/Help/gc_resourcesettings.html +++ b/Help/gc_resourcesettings.html @@ -21,7 +21,7 @@ decorategames (string)
Fill this to the game names to support DECORATE actors from. Only the DECORATE actors who's game name is in this string will be loaded. If this setting is not set, DECORATE lumps are not loaded.

- Example: + Example:
 decorategames = "heretic raven";
 
@@ -41,7 +41,7 @@ decorategames = "heretic raven"; textures (structure)
This lists the marker lump names that indicate the begin and end of a list of textures that Doom Builder should load. There must be a separate structure for each range, for which the structure name doesn't matter. The range must have a 'start' setting and an 'end' setting of which the values must be the names of the start and end lumps (strings). Please note that PNAMES, TEXTURE1 and TEXTURE2 lumps do not need to be in the game configuration, they are always loaded when available.

- Example: + Example:
 textures
 {
@@ -56,7 +56,7 @@ textures
 		patches (structure)
This lists the marker lump names that indicate the begin and end of a list of patches that Doom Builder should load. There must be a separate structure for each range, for which the structure name doesn't matter. The range must have a 'start' setting and an 'end' setting of which the values must be the names of the start and end lumps (strings). Note that Doom Builder does not load all patches, only those that are used by the textures.

- Example: + Example:
 patches
 {
@@ -77,7 +77,7 @@ patches
 		sprites (structure)
This lists the marker lump names that indicate the begin and end of a list of sprites that Doom Builder should load. There must be a separate structure for each range, for which the structure name doesn't matter. The range must have a 'start' setting and an 'end' setting of which the values must be the names of the start and end lumps (strings). Note that Doom Builder does not load all sprites, only those that are used by the things.

- Example: + Example:
 sprites
 {
@@ -92,7 +92,7 @@ sprites
 		flats (structure)
This lists the marker lump names that indicate the begin and end of a list of flats that Doom Builder should load. There must be a separate structure for each range, for which the structure name doesn't matter. The range must have a 'start' setting and an 'end' setting of which the values must be the names of the start and end lumps (strings).

- Example: + Example:
 flats
 {
@@ -107,7 +107,7 @@ flats
 		colormaps (structure)
This lists the marker lump names that indicate the begin and end of a list of colormaps that Doom Builder should load. There must be a separate structure for each range, for which the structure name doesn't matter. The range must have a 'start' setting and an 'end' setting of which the values must be the names of the start and end lumps (strings).

- Example: + Example:
 colormaps
 {
diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj
index 0a8f1056..6741d324 100644
--- a/Source/Core/Builder.csproj
+++ b/Source/Core/Builder.csproj
@@ -46,8 +46,9 @@
     ..\..\Build\Builder.xml
     
     
-    3
+    4
     true
+    1591
   
   
     true
@@ -863,6 +864,7 @@
     
     
     
+    
     
     
     
@@ -1120,10 +1122,12 @@
     
     
     
+    
     
     
     
     
+    
     
     
     
diff --git a/Source/Core/Config/GameConfiguration.cs b/Source/Core/Config/GameConfiguration.cs
index 8ce41c29..06ea0f17 100644
--- a/Source/Core/Config/GameConfiguration.cs
+++ b/Source/Core/Config/GameConfiguration.cs
@@ -77,6 +77,7 @@ namespace CodeImp.DoomBuilder.Config
 		private readonly bool linetagindicatesectors;
 		private readonly string decorategames;
 		private string skyflatname;
+		private Dictionary defaultskytextures; //mxd 
 		private readonly int maxtexturenamelength;
 		private readonly bool longtexturenames; //mxd
 		private readonly int leftboundary;
@@ -193,6 +194,7 @@ namespace CodeImp.DoomBuilder.Config
 		public bool LineTagIndicatesSectors { get { return linetagindicatesectors ; } }
 		public string DecorateGames { get { return decorategames; } }
 		public string SkyFlatName { get { return skyflatname; } internal set { skyflatname = value; } } //mxd. Added setter
+		public Dictionary DefaultSkyTextures { get { return defaultskytextures; } } //mxd
 		public int MaxTextureNameLength { get { return maxtexturenamelength; } }
 		public bool UseLongTextureNames { get { return longtexturenames; } } //mxd
 		public int LeftBoundary { get { return leftboundary; } }
@@ -307,6 +309,7 @@ namespace CodeImp.DoomBuilder.Config
 			this.linedefrenderstyles = new Dictionary(StringComparer.Ordinal); //mxd
 			this.sectorrenderstyles = new Dictionary(StringComparer.Ordinal); //mxd
 			this.thingrenderstyles = new Dictionary(StringComparer.Ordinal); //mxd
+			this.defaultskytextures = new Dictionary(StringComparer.OrdinalIgnoreCase); //mxd
 			
 			// Read general settings
 			configname = cfg.ReadSetting("game", "");
@@ -423,6 +426,9 @@ namespace CodeImp.DoomBuilder.Config
 			LoadTextureSets();
 			LoadThingFilters();
 
+			//mxd. Vanilla sky textures
+			LoadDefaultSkies();
+
 			// Make door flags
 			LoadMakeDoorFlags();
 		}
@@ -895,6 +901,40 @@ namespace CodeImp.DoomBuilder.Config
 			}
 		}
 
+		//mxd
+		private void LoadDefaultSkies()
+		{
+			IDictionary dic = cfg.ReadSetting("defaultskytextures", new Hashtable());
+			char[] separator = new []{ ',' };
+			foreach(DictionaryEntry de in dic)
+			{
+				string skytex = de.Key.ToString();
+				if(defaultskytextures.ContainsKey(skytex))
+				{
+					General.ErrorLogger.Add(ErrorType.Warning, "Sky texture \"" + skytex + "\" is double-defined in the current game configuration!");
+					continue;
+				}
+
+				string[] maps = de.Value.ToString().Split(separator, StringSplitOptions.RemoveEmptyEntries);
+				if(maps.Length == 0)
+				{
+					General.ErrorLogger.Add(ErrorType.Warning, "Sky texture \"" + skytex + "\" has no map names defined in the current game configuration!");
+					continue;
+				}
+
+				foreach(string map in maps)
+				{
+					if(defaultskytextures.ContainsKey(map))
+					{
+						General.ErrorLogger.Add(ErrorType.Warning, "Map \"" + map + "\" is double-defined in the \"DefaultSkyTextures\" block of current game configuration!");
+						continue;
+					}
+
+					defaultskytextures[map] = skytex;
+				}
+			}
+		}
+
 		//mxd
 		private void LoadStringDictionary(Dictionary target, string settingname) 
 		{
diff --git a/Source/Core/Config/ProgramConfiguration.cs b/Source/Core/Config/ProgramConfiguration.cs
index 41b95af2..ef6afab4 100644
--- a/Source/Core/Config/ProgramConfiguration.cs
+++ b/Source/Core/Config/ProgramConfiguration.cs
@@ -97,6 +97,7 @@ namespace CodeImp.DoomBuilder.Config
 		private ModelRenderMode gzDrawModelsMode;
 		private LightRenderMode gzDrawLightsMode;
 		private bool gzDrawFog;
+		private bool gzDrawSky;
 		private bool gzToolbarGZDoom;
 		private bool gzSynchCameras;
 		private bool gzShowEventLines;
@@ -111,6 +112,7 @@ namespace CodeImp.DoomBuilder.Config
 		private string lastUsedConfigName;
 		private string lastUsedMapFolder;
 		private bool gzMarkExtraFloors;
+		private bool gzdoomrenderingeffects = true; //mxd
 		private int maxRecentFiles;
 		private bool autoClearSideTextures;
 		private bool storeSelectedEditTab;
@@ -187,6 +189,7 @@ namespace CodeImp.DoomBuilder.Config
 		public ModelRenderMode GZDrawModelsMode { get { return gzDrawModelsMode; } internal set { gzDrawModelsMode = value; } }
 		public LightRenderMode GZDrawLightsMode { get { return gzDrawLightsMode; } internal set { gzDrawLightsMode = value; } }
 		public bool GZDrawFog { get { return gzDrawFog; } internal set { gzDrawFog = value; } }
+		public bool GZDrawSky { get { return gzDrawSky; } internal set { gzDrawSky = value; } }
 		public bool GZToolbarGZDoom { get { return gzToolbarGZDoom; } internal set { gzToolbarGZDoom = value; } }
 		public bool GZSynchCameras { get { return gzSynchCameras; } internal set { gzSynchCameras = value; } }
 		public bool GZShowEventLines { get { return gzShowEventLines; } internal set { gzShowEventLines = value; } }
@@ -201,6 +204,7 @@ namespace CodeImp.DoomBuilder.Config
 		public string LastUsedConfigName { get { return lastUsedConfigName; } internal set { lastUsedConfigName = value; } }
 		public string LastUsedMapFolder { get { return lastUsedMapFolder; } internal set { lastUsedMapFolder = value; } }
 		public bool GZMarkExtraFloors { get { return gzMarkExtraFloors; } internal set { gzMarkExtraFloors = value; } }
+		public bool GZDoomRenderingEffects { get { return gzdoomrenderingeffects; } set { gzdoomrenderingeffects = value; } } //mxd
 		public int MaxRecentFiles { get { return maxRecentFiles; } internal set { maxRecentFiles = General.Clamp(value, 8, 25); } }
 		public bool AutoClearSidedefTextures { get { return autoClearSideTextures; } internal set { autoClearSideTextures = value; } }
 		public bool StoreSelectedEditTab { get { return storeSelectedEditTab; } internal set { storeSelectedEditTab = value; } }
@@ -300,6 +304,7 @@ namespace CodeImp.DoomBuilder.Config
 				gzDrawModelsMode = (ModelRenderMode)cfg.ReadSetting("gzdrawmodels", (int)ModelRenderMode.ALL);
 				gzDrawLightsMode = (LightRenderMode)cfg.ReadSetting("gzdrawlights", (int)LightRenderMode.ALL);
 				gzDrawFog = cfg.ReadSetting("gzdrawfog", false);
+				gzDrawSky = cfg.ReadSetting("gzdrawsky", true);
 				gzToolbarGZDoom = cfg.ReadSetting("gztoolbargzdoom", true);
 				gzSynchCameras = cfg.ReadSetting("gzsynchcameras", true);
 				gzShowEventLines = cfg.ReadSetting("gzshoweventlines", true);
@@ -395,6 +400,7 @@ namespace CodeImp.DoomBuilder.Config
 			cfg.WriteSetting("gzdrawmodels", (int)gzDrawModelsMode);
 			cfg.WriteSetting("gzdrawlights", (int)gzDrawLightsMode);
 			cfg.WriteSetting("gzdrawfog", gzDrawFog);
+			cfg.WriteSetting("gzdrawsky", gzDrawSky);
 			cfg.WriteSetting("gzsynchcameras", gzSynchCameras);
 			cfg.WriteSetting("gzshoweventlines", gzShowEventLines);
 			cfg.WriteSetting("gzoldhighlightmode", gzOldHighlightMode);
diff --git a/Source/Core/Controls/LinedefInfoPanel.cs b/Source/Core/Controls/LinedefInfoPanel.cs
index 8f9ea493..5913272c 100644
--- a/Source/Core/Controls/LinedefInfoPanel.cs
+++ b/Source/Core/Controls/LinedefInfoPanel.cs
@@ -299,6 +299,16 @@ namespace CodeImp.DoomBuilder.Controls
 
 					//mxd. Sector index
 					frontpanel.Text += ". Sector " + l.Front.Sector.Index + " ";
+
+					//visibility
+					frontTopUDMFOffsetLabel.Visible = false;
+					frontTopUDMFScaleLabel.Visible = false;
+
+					frontMidUDMFOffsetLabel.Visible = false;
+					frontMidUDMFScaleLabel.Visible = false;
+
+					frontBottomUDMFOffsetLabel.Visible = false;
+					frontBottomUDMFScaleLabel.Visible = false;
 				}
 
 				//mxd. Set texture names, update panel sizes
diff --git a/Source/Core/Controls/ScintillaControl.cs b/Source/Core/Controls/ScintillaControl.cs
index 526be916..2e56febb 100644
--- a/Source/Core/Controls/ScintillaControl.cs
+++ b/Source/Core/Controls/ScintillaControl.cs
@@ -135,10 +135,6 @@ namespace CodeImp.DoomBuilder.Controls
 		// on to the other controls on the parent form
 		private readonly Dictionary ignoredkeys;
 		
-		// States
-		private ScriptMarginType indexmargintype;
-		private ScriptIndicatorStyle indexindicatorstyle;
-		
 		#endregion
 
 		#region ================== Properties
@@ -162,16 +158,6 @@ namespace CodeImp.DoomBuilder.Controls
 			set { EOLMode = (int)value; }
 		}
 
-		/// 
-		/// The type of a margin.
-		/// 
-		public ScriptMarginType MarginType { get { return indexmargintype; } }
-
-		/// 
-		/// The type of a margin.
-		/// 
-		public ScriptIndicatorStyle IndicatorStyle { get { return indexindicatorstyle; } }
-
 		/// 
 		/// Are there any redoable actions in the undo history?
 		/// 
diff --git a/Source/Core/Data/DataManager.cs b/Source/Core/Data/DataManager.cs
index 1c87d84e..333d22b6 100644
--- a/Source/Core/Data/DataManager.cs
+++ b/Source/Core/Data/DataManager.cs
@@ -18,6 +18,9 @@
 
 using System;
 using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
 using System.IO;
 using System.Threading;
 using System.Windows.Forms;
@@ -29,6 +32,8 @@ using CodeImp.DoomBuilder.IO;
 using CodeImp.DoomBuilder.Map;
 using CodeImp.DoomBuilder.Windows;
 using CodeImp.DoomBuilder.ZDoom;
+using SlimDX;
+using SlimDX.Direct3D9;
 
 #endregion
 
@@ -70,6 +75,7 @@ namespace CodeImp.DoomBuilder.Data
 		private MapInfo mapinfo;
 		private Dictionary> reverbs; // 
 		private Dictionary glowingflats; // Texture name hash, Glowing Flat Data
+		private Dictionary skyboxes; 
 		private List soundsequences;
 		
 		// Background loading
@@ -92,6 +98,9 @@ namespace CodeImp.DoomBuilder.Data
 		private ImageData whitetexture;
 		private ImageData blacktexture; //mxd
 
+		//mxd. Sky textures
+		private CubeTexture skybox; // GZDoom skybox
+
 		//mxd. Comment icons
 		private ImageData[] commenttextures;
 		
@@ -139,6 +148,7 @@ namespace CodeImp.DoomBuilder.Data
 		public ImageData WhiteTexture { get { return whitetexture; } }
 		public ImageData BlackTexture { get { return blacktexture; } } //mxd
 		public ImageData[] CommentTextures { get { return commenttextures; } } //mxd
+		internal CubeTexture SkyBox { get { return skybox; } } //mxd
 		public List ThingCategories { get { return thingcategories; } }
 		public ICollection ThingTypes { get { return thingtypes.Values; } }
 		public DecorateParser Decorate { get { return decorate; } }
@@ -169,8 +179,10 @@ namespace CodeImp.DoomBuilder.Data
 			//mxd.
 			modeldefentries = new Dictionary();
 			gldefsentries = new Dictionary();
-			reverbs = new Dictionary>();
+			reverbs = new Dictionary>(StringComparer.Ordinal);
 			glowingflats = new Dictionary();
+			skyboxes = new Dictionary(StringComparer.Ordinal);
+
 			soundsequences = new List();
 
 			// Load special images (mxd: the rest is loaded in LoadInternalTextures())
@@ -224,8 +236,17 @@ namespace CodeImp.DoomBuilder.Data
 				blacktexture = null; //mxd
 				unknownimage.Dispose(); //mxd
 				unknownimage = null; //mxd
-				foreach(ImageData i in commenttextures) i.Dispose(); //mxd
+				for(int i = 0; i < commenttextures.Length; i++) //mxd
+				{
+					commenttextures[i].Dispose();
+					commenttextures[i] = null;
+				}
 				commenttextures = null;
+				if(skybox != null) //mxd
+				{
+					skybox.Dispose();
+					skybox = null;
+				}
 				
 				// Done
 				isdisposed = true;
@@ -252,7 +273,7 @@ namespace CodeImp.DoomBuilder.Data
 		}
 
 		// This loads all data resources
-		internal void Load(DataLocationList locations)
+		private void Load(DataLocationList locations)
 		{
 			Dictionary texturesonly = new Dictionary();
 			Dictionary colormapsonly = new Dictionary();
@@ -474,6 +495,9 @@ namespace CodeImp.DoomBuilder.Data
 				// Add to all
 				alltextures.AddFlat(img.Value);
 			}
+
+			//mxd. Create skybox texture(s)
+			SetupSkybox();
 			
 			// Start background loading
 			StartBackgroundLoader();
@@ -483,7 +507,8 @@ namespace CodeImp.DoomBuilder.Data
 				colormapcount + " colormaps, " + spritecount + " sprites, " + 
 				thingcount + " decorate things, " + modeldefentries.Count + " model/voxel deinitions, " + 
 				gldefsentries.Count + " dynamic light definitions, " + 
-				glowingflats.Count + " glowing flat definitions, " + reverbs.Count + " sound environment definitions");
+				glowingflats.Count + " glowing flat definitions, " + skyboxes.Count + " skybox definitions, " +
+				reverbs.Count + " sound environment definitions");
 		}
 		
 		// This unloads all data
@@ -1724,7 +1749,7 @@ namespace CodeImp.DoomBuilder.Data
 		//mxd. This creates  dictionary. Should be called after all DECORATE actors are parsed
 		private Dictionary CreateActorsByClassList() 
 		{
-			Dictionary actors = new Dictionary(StringComparer.Ordinal);
+			Dictionary actors = new Dictionary(StringComparer.OrdinalIgnoreCase);
 			if(string.IsNullOrEmpty(General.Map.Config.DecorateGames)) return actors;
 
 			//read our new shiny ClassNames for default game things
@@ -1732,11 +1757,9 @@ namespace CodeImp.DoomBuilder.Data
 			{
 				if(!string.IsNullOrEmpty(ti.Value.ClassName))
 				{
-					string classname = ti.Value.ClassName.ToLowerInvariant();
-
-					if(actors.ContainsKey(classname) && actors[classname] != ti.Key)
+					if(actors.ContainsKey(ti.Value.ClassName) && actors[ti.Value.ClassName] != ti.Key)
 						General.ErrorLogger.Add(ErrorType.Warning, "actor '" + ti.Value.ClassName + "' has several editor numbers! Only the last one (" + ti.Key + ") will be used.");
-					actors[classname] = ti.Key;
+					actors[ti.Value.ClassName] = ti.Key;
 				}
 			}
 
@@ -1794,7 +1817,10 @@ namespace CodeImp.DoomBuilder.Data
 				return;
 			}
 
-			//rebuild geometry if in Visual mode
+			// Rebuild skybox texture
+			SetupSkybox();
+
+			// Rebuild geometry if in Visual mode
 			if(General.Editing.Mode != null && General.Editing.Mode.GetType().Name == "BaseVisualMode") 
 			{
 				General.Editing.Mode.OnReloadResources();
@@ -1820,7 +1846,7 @@ namespace CodeImp.DoomBuilder.Data
 				foreach(KeyValuePair group in streams) 
 				{
 					// Parse the data
-					if(parser.Parse(group.Value, currentreader.Location.location + "\\" + group.Key, true)) 
+					if(parser.Parse(group.Value, Path.Combine(currentreader.Location.location, group.Key), true)) 
 					{
 						foreach(KeyValuePair g in parser.Entries) 
 						{
@@ -1946,7 +1972,7 @@ namespace CodeImp.DoomBuilder.Data
 		//mxd. This parses gldefs. Should be called after all DECORATE actors are parsed
 		private void LoadGldefs(Dictionary actorsbyclass) 
 		{
-			//if no actors defined in DECORATE or game config...
+			// Skip if no actors defined in DECORATE or game config...
 			if(actorsbyclass.Count == 0) return;
 
 			GldefsParser parser = new GldefsParser { OnInclude = ParseFromLocation };
@@ -1971,10 +1997,10 @@ namespace CodeImp.DoomBuilder.Data
 				}
 			}
 
-			//create Gldefs Entries dictionary
+			// Create Gldefs Entries dictionary
 			foreach(KeyValuePair e in parser.Objects) //
 			{ 
-				//if we have decorate actor and light definition for given ClassName...
+				// If we have decorate actor and light definition for given ClassName...
 				if(actorsbyclass.ContainsKey(e.Key) && parser.LightsByName.ContainsKey(e.Value)) 
 					gldefsentries[actorsbyclass[e.Key]] = parser.LightsByName[e.Value];
 				else if(!decorate.AllActorsByClass.ContainsKey(e.Key))
@@ -1983,45 +2009,52 @@ namespace CodeImp.DoomBuilder.Data
 
 			// Grab them glowy flats!
 			glowingflats = parser.GlowingFlats;
+
+			// And skyboxes
+			skyboxes = parser.Skyboxes;
 		}
 
 		//mxd. This loads (Z)MAPINFO
 		private void LoadMapInfo(out Dictionary spawnnums, out Dictionary doomednums)
 		{
 			MapinfoParser parser = new MapinfoParser { OnInclude = ParseFromLocation };
-			
+
+			// Parse mapinfo 
 			foreach(DataReader dr in containers)
 			{
 				currentreader = dr;
 
 				Dictionary streams = dr.GetMapinfoData();
-				foreach(KeyValuePair group in streams) 
+				foreach(KeyValuePair group in streams)
 				{
 					// Parse the data
-					parser.Parse(group.Value, Path.Combine(currentreader.Location.location, group.Key), General.Map.Options.LevelName, false);
+					parser.Parse(group.Value, Path.Combine(dr.Location.location, group.Key), General.Map.Options.LevelName, false);
 
 					//MAPINFO lumps are interdependable. Can't carry on...
 					if(parser.HasError)
 					{
 						parser.LogError();
-
-						// No nulls allowed!
-						spawnnums = new Dictionary();
-						doomednums = new Dictionary();
-						mapinfo = new MapInfo();
-
-						return;
+						break;
 					}
 				}
 			}
-
-			// Set the output values
-			spawnnums = parser.SpawnNums;
-			doomednums = parser.DoomEdNums;
-
-			// Store to our MapInfo property
+			
+			if(!parser.HasError)
+			{
+				// Store parsed data
+				spawnnums = parser.SpawnNums;
+				doomednums = parser.DoomEdNums;
+				mapinfo = parser.MapInfo;
+			}
+			else
+			{
+				// No nulls allowed!
+				spawnnums = new Dictionary();
+				doomednums = new Dictionary();
+				mapinfo = new MapInfo();
+			}
+			
 			currentreader = null;
-			mapinfo = parser.MapInfo ?? new MapInfo();
 		}
 
 		private void ParseFromLocation(ZDTextParser parser, string location, bool clearerrors)
@@ -2260,6 +2293,438 @@ namespace CodeImp.DoomBuilder.Data
 				updatedusedtextures = true;
 			}
 		}
+
+		#endregion
+
+		#region ================== mxd. Skybox Making
+
+		private void SetupSkybox()
+		{
+			// Get rid of old texture
+			if(skybox != null) skybox.Dispose(); skybox = null;
+
+			// Determine which texture name to use
+			string skytex = string.Empty;
+			if(!string.IsNullOrEmpty(mapinfo.Sky1))
+			{
+				skytex = mapinfo.Sky1;
+			}
+			// Use vanilla sky only when current map doesn't have a MAPINFO entry
+			else if(!mapinfo.IsDefined)
+			{
+				if(General.Map.Config.DefaultSkyTextures.ContainsKey(General.Map.Options.CurrentName))
+					skytex = General.Map.Config.DefaultSkyTextures[General.Map.Options.CurrentName];
+				else
+					skytex = General.GetByIndex(General.Map.Config.DefaultSkyTextures, 0).Value;
+			}
+			
+			// Create sky texture
+			if(!string.IsNullOrEmpty(skytex))
+			{
+				if(skyboxes.ContainsKey(skytex))
+				{
+					// Create cubemap texture
+					skybox = (skyboxes[skytex].Textures.Count == 6 ? MakeSkyBox6(skyboxes[skytex]) : MakeSkyBox3(skyboxes[skytex]));
+				}
+				else
+				{
+					// Create classic texture
+					ImageData tex = GetTextureImage(skytex);
+					if(!(tex is UnknownImage))
+					{
+						if(!tex.IsImageLoaded) tex.LoadImage();
+						if(!tex.LoadFailed)
+						{
+							skybox = MakeClassicSkyBox(new Bitmap(tex.GetBitmap()), true);
+						}
+					}
+				}
+			}
+
+			// Sky texture will be missing in ZDoom. Use internal texture
+			if(skybox == null)
+			{
+				ImageData tex = LoadInternalTexture("MissingSky3D.png");
+				tex.CreateTexture();
+				skybox = MakeClassicSkyBox(new Bitmap(tex.GetBitmap()), false);
+				tex.Dispose();
+			}
+		}
+
+		private static CubeTexture MakeClassicSkyBox(Bitmap img, bool dogradients)
+		{
+			// CubeTexture must be square with power of 2 sides
+			int targetwidth = General.NextPowerOf2(img.Width);
+			int targetheight = General.NextPowerOf2(img.Height);
+
+			// Get averaged top and bottom colors
+			Color topcolor, bottomcolor;
+			if(dogradients)
+			{
+				int tr = 0, tg = 0, tb = 0, br = 0, bg = 0, bb = 0;
+				for(int i = 0; i < img.Width; i++)
+				{
+					Color c = img.GetPixel(i, 0);
+					tr += c.R;
+					tg += c.G;
+					tb += c.B;
+
+					c = img.GetPixel(i, img.Height - 1);
+					br += c.R;
+					bg += c.G;
+					bb += c.B;
+				}
+
+				topcolor = Color.FromArgb(255, tr / img.Width, tg / img.Width, tb / img.Width);
+				bottomcolor = Color.FromArgb(255, br / img.Width, bg / img.Width, bb / img.Width);
+			}
+			else
+			{
+				// This should be built-in sky texture
+				Color c = img.GetPixel(img.Width / 2, 0);
+				topcolor = Color.FromArgb(255, c);
+
+				c = img.GetPixel(img.Width / 2, img.Height - 1);
+				bottomcolor = Color.FromArgb(255, c);
+			}
+
+			// Make it Po2
+			if(img.Width != targetwidth || img.Height != targetheight) img = ResizeImage(img, targetwidth, targetheight);
+
+			// Make it square
+			if(targetwidth > targetheight)
+			{
+				int c = targetwidth / targetheight;
+				Bitmap result = new Bitmap(targetwidth, targetwidth, img.PixelFormat);
+
+				// Tile vertically
+				using(Graphics g = Graphics.FromImage(result))
+				{
+					for(int i = 0; i < c; i++) g.DrawImage(img, 0, targetheight * i);
+				}
+
+				img = result;
+			}
+			else if(targetwidth < targetheight)
+			{
+				int c = targetheight / targetwidth;
+				Bitmap result = new Bitmap(targetheight, targetheight);
+
+				// Tile horizontally
+				using(Graphics g = Graphics.FromImage(result))
+				{
+					for(int i = 0; i < c; i++) g.DrawImage(img, targetwidth * i, 0);
+				}
+
+				img = result;
+			}
+
+			// Make top and bottom images
+			Bitmap top = new Bitmap(img.Width, img.Height);
+			using(Graphics g = Graphics.FromImage(top))
+			{
+				using(SolidBrush b = new SolidBrush(topcolor))
+				{
+					g.FillRectangle(b, 0, 0, img.Width, img.Height);
+				}
+			}
+
+			Bitmap bottom = new Bitmap(img.Width, img.Height);
+			using(Graphics g = Graphics.FromImage(bottom))
+			{
+				using(SolidBrush b = new SolidBrush(bottomcolor))
+				{
+					g.FillRectangle(b, 0, 0, img.Width, img.Height);
+				}
+			}
+
+			// Apply top/bottom gradients
+			if(dogradients)
+			{
+				using(Graphics g = Graphics.FromImage(img))
+				{
+					int gradientheight = img.Height / 6;
+					Rectangle area = new Rectangle(0, 0, img.Width, gradientheight);
+					using(LinearGradientBrush b = new LinearGradientBrush(area, topcolor, Color.FromArgb(0, topcolor), 90f))
+					{
+						g.FillRectangle(b, area);
+					}
+
+					area = new Rectangle(0, img.Height - gradientheight, img.Width, gradientheight);
+					using(LinearGradientBrush b = new LinearGradientBrush(area, Color.FromArgb(0, bottomcolor), bottomcolor, 90f))
+					{
+						area.Y += 1;
+						g.FillRectangle(b, area);
+					}
+				}
+			}
+
+			// Make cubemap texture
+			CubeTexture cubemap = new CubeTexture(General.Map.Graphics.Device, img.Width, 1, Usage.None, Format.A8R8G8B8, Pool.Managed);
+
+			// Draw faces
+			img.RotateFlip(RotateFlipType.Rotate180FlipX);
+			DrawCubemapFace(cubemap, CubeMapFace.NegativeX, img);
+
+			img.RotateFlip(RotateFlipType.Rotate90FlipNone);
+			DrawCubemapFace(cubemap, CubeMapFace.NegativeY, img);
+
+			img.RotateFlip(RotateFlipType.Rotate90FlipNone);
+			DrawCubemapFace(cubemap, CubeMapFace.PositiveX, img);
+
+			img.RotateFlip(RotateFlipType.Rotate90FlipNone);
+			DrawCubemapFace(cubemap, CubeMapFace.PositiveY, img);
+
+			DrawCubemapFace(cubemap, CubeMapFace.PositiveZ, top);
+			DrawCubemapFace(cubemap, CubeMapFace.NegativeZ, bottom);
+
+			// All done...
+			return cubemap;
+		}
+
+		// Makes CubeTexture from 6 images
+		private CubeTexture MakeSkyBox6(SkyboxInfo info)
+		{
+			// Gather images. They should be defined in this order: North, East, South, West, Top, Bottom
+			Bitmap[] sides = new Bitmap[6];
+			int targetsize = 0;
+			for(int i = 0; i < info.Textures.Count; i++)
+			{
+				ImageData tex = GetTextureImage(info.Textures[i]);
+				if(!(tex is UnknownImage))
+				{
+					if(!tex.IsImageLoaded) tex.LoadImage();
+					if(!tex.LoadFailed)
+					{
+						sides[i] = new Bitmap(tex.GetBitmap());
+						targetsize = Math.Max(targetsize, Math.Max(sides[i].Width, sides[i].Height));
+					}
+				}
+
+				if(sides[i] == null)
+				{
+					General.ErrorLogger.Add(ErrorType.Error, "Unable to create \"" + info.Name + "\" skybox: unable to find \"" + info.Textures[i] + "\" texture");
+					return null;
+				}
+			}
+
+			// All images must be square and have the same size
+			if(targetsize == 0)
+			{
+				General.ErrorLogger.Add(ErrorType.Error, "Unable to create \"" + info.Name + "\" skybox: invalid texture size");
+				return null;
+			}
+
+			// Make it Po2
+			targetsize = General.NextPowerOf2(targetsize);
+
+			for(int i = 0; i < sides.Length; i++)
+			{
+				if(sides[i].Width != targetsize || sides[i].Height != targetsize)
+					sides[i] = ResizeImage(sides[i], targetsize, targetsize);
+			}
+
+			// Return cubemap texture
+			return MakeSkyBox(sides, targetsize, info.FlipTop);
+		}
+
+		// Makes CubeTexture from 3 images
+		private CubeTexture MakeSkyBox3(SkyboxInfo info)
+		{
+			// Gather images. They should be defined in this order: Sides, Top, Bottom
+			Bitmap[] sides = new Bitmap[6];
+			int targetsize = 0;
+
+			// Create NWSE images from the side texture
+			ImageData side = GetTextureImage(info.Textures[0]);
+			if(!(side is UnknownImage))
+			{
+				if(!side.IsImageLoaded) side.LoadImage();
+				if(!side.LoadFailed)
+				{
+					// This should be 4x1 format image. If it's not, resize it
+					Bitmap sideimg = new Bitmap(side.GetBitmap());
+					targetsize = Math.Max(sideimg.Width / 4, sideimg.Height);
+
+					if(targetsize == 0)
+					{
+						General.ErrorLogger.Add(ErrorType.Error, "Unable to create \"" + info.Name + "\" skybox: invalid texture size");
+						return null;
+					}
+
+					// Make it Po2
+					targetsize = General.NextPowerOf2(targetsize);
+
+					// Resize if needed
+					if(sideimg.Width != targetsize * 4 || sideimg.Height != targetsize)
+					{
+						sideimg = ResizeImage(sideimg, targetsize * 4, targetsize);
+					}
+
+					// Chop into tiny pieces
+					for(int i = 0; i < 4; i++)
+					{
+						// Create square image
+						Bitmap img = new Bitmap(targetsize, targetsize);
+						using(Graphics g = Graphics.FromImage(img))
+						{
+							// Copy area from the side image
+							g.DrawImage(sideimg, 0, 0, new Rectangle(targetsize * i, 0, targetsize, targetsize), GraphicsUnit.Pixel);
+						}
+
+						// Add to collection
+						sides[i] = img;
+					}
+				}
+			}
+
+			// Sanity check...
+			if(sides[0] == null || sides[1] == null || sides[2] == null || sides[3] == null)
+			{
+				General.ErrorLogger.Add(ErrorType.Error, "Unable to create \"" + info.Name + "\" skybox: unable to find \"" + info.Textures[0] + "\" texture");
+				return null;
+			}
+
+			// Create top
+			ImageData top = GetTextureImage(info.Textures[1]);
+			if(!(top is UnknownImage))
+			{
+				if(!top.IsImageLoaded) top.LoadImage();
+				if(!top.LoadFailed)
+				{
+					// Copy bitmap 
+					Bitmap topimg = new Bitmap(top.GetBitmap());
+					
+					// Resize if needed
+					if(topimg.Width != targetsize || topimg.Height != targetsize)
+						topimg = ResizeImage(topimg, targetsize, targetsize);
+
+					// Add to collection
+					sides[4] = topimg;
+				}
+			}
+
+			// Sanity check...
+			if(sides[4] == null)
+			{
+				General.ErrorLogger.Add(ErrorType.Error, "Unable to create \"" + info.Name + "\" skybox: unable to find \"" + info.Textures[1] + "\" texture");
+				return null;
+			}
+
+			// Create bottom
+			ImageData bottom = GetTextureImage(info.Textures[2]);
+			if(!(bottom is UnknownImage))
+			{
+				if(!bottom.IsImageLoaded) bottom.LoadImage();
+				if(!bottom.LoadFailed)
+				{
+					// Copy bitmap 
+					Bitmap bottomimg = new Bitmap(bottom.GetBitmap());
+
+					// Resize if needed
+					if(bottomimg.Width != targetsize || bottomimg.Height != targetsize)
+						bottomimg = ResizeImage(bottomimg, targetsize, targetsize);
+
+					// Add to collection
+					sides[5] = bottomimg;
+				}
+			}
+
+			// Sanity check...
+			if(sides[5] == null)
+			{
+				General.ErrorLogger.Add(ErrorType.Error, "Unable to create \"" + info.Name + "\" skybox: unable to find \"" + info.Textures[2] + "\" texture");
+				return null;
+			}
+
+			// Return cubemap texture
+			return MakeSkyBox(sides, targetsize, info.FlipTop);
+		}
+
+		// Makes CubeTexture from 6 images.
+		// sides[] must contain 6 square Po2 images in this order: North, East, South, West, Top, Bottom
+		private static CubeTexture MakeSkyBox(Bitmap[] sides, int targetsize, bool fliptop)
+		{
+			CubeTexture cubemap = new CubeTexture(General.Map.Graphics.Device, targetsize, 1, Usage.None, Format.A8R8G8B8, Pool.Managed);
+
+			// Draw faces
+			sides[3].RotateFlip(RotateFlipType.Rotate180FlipX);
+			DrawCubemapFace(cubemap, CubeMapFace.NegativeX, sides[3]); // West
+
+			sides[0].RotateFlip(RotateFlipType.Rotate90FlipX);
+			DrawCubemapFace(cubemap, CubeMapFace.NegativeY, sides[0]); // North
+
+			sides[1].RotateFlip(RotateFlipType.RotateNoneFlipX);
+			DrawCubemapFace(cubemap, CubeMapFace.PositiveX, sides[1]); // East
+
+			sides[2].RotateFlip(RotateFlipType.Rotate270FlipX);
+			DrawCubemapFace(cubemap, CubeMapFace.PositiveY, sides[2]); // South
+
+			sides[4].RotateFlip(fliptop ? RotateFlipType.Rotate90FlipX : RotateFlipType.Rotate90FlipNone);
+			DrawCubemapFace(cubemap, CubeMapFace.PositiveZ, sides[4]); // Top
+
+			sides[5].RotateFlip(RotateFlipType.Rotate270FlipX);
+			DrawCubemapFace(cubemap, CubeMapFace.NegativeZ, sides[5]); // Bottom
+
+			// All done...
+			return cubemap;
+		}
+
+		private static void DrawCubemapFace(CubeTexture texture, CubeMapFace face, Bitmap image)
+		{
+			DataRectangle rect = texture.LockRectangle(face, 0, LockFlags.NoSystemLock);
+			SurfaceDescription desc = texture.GetLevelDescription(0);
+
+			if(rect.Data.CanWrite)
+			{
+				for(int row = 0; row < desc.Height; row++)
+				{
+					int rowstart = row * rect.Pitch;
+					rect.Data.Seek(rowstart, SeekOrigin.Begin);
+
+					for(int col = 0; col < desc.Width; col++)
+					{
+						Color color = image.GetPixel(row, col);
+
+						rect.Data.WriteByte(color.B);
+						rect.Data.WriteByte(color.G);
+						rect.Data.WriteByte(color.R);
+						rect.Data.WriteByte(color.A);
+					}
+				}
+			}
+			else
+			{
+				General.ErrorLogger.Add(ErrorType.Error, "Skybox creation failed: CubeTexture is unwritable...");
+			}
+
+			texture.UnlockRectangle(face, 0);
+		}
+
+		private static Bitmap ResizeImage(Image image, int width, int height)
+		{
+			var destrect = new Rectangle(0, 0, width, height);
+			var destimage = new Bitmap(width, height);
+
+			destimage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
+
+			using(var graphics = Graphics.FromImage(destimage))
+			{
+				graphics.CompositingMode = CompositingMode.SourceCopy;
+				graphics.CompositingQuality = CompositingQuality.HighQuality;
+				graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
+				graphics.SmoothingMode = SmoothingMode.HighQuality;
+				graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
+
+				using(var wrapmode = new ImageAttributes())
+				{
+					wrapmode.SetWrapMode(WrapMode.TileFlipXY);
+					graphics.DrawImage(image, destrect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapmode);
+				}
+			}
+
+			return destimage;
+		}
 		
 		#endregion
 	}
diff --git a/Source/Core/Data/PK3StructuredReader.cs b/Source/Core/Data/PK3StructuredReader.cs
index e1366d47..d181b461 100644
--- a/Source/Core/Data/PK3StructuredReader.cs
+++ b/Source/Core/Data/PK3StructuredReader.cs
@@ -531,16 +531,16 @@ namespace CodeImp.DoomBuilder.Data
 			// Error when suspended
 			if(issuspended) throw new Exception("Data reader is suspended");
 
-			//mapinfo should be in root folder
+			// Mapinfo should be in root folder
 			Dictionary streams = new Dictionary(StringComparer.Ordinal);
-			string[] files = GetAllFiles("", false);
 
-			//try to find (z)mapinfo
+			// Try to find (z)mapinfo
+			string[] files = GetAllFiles("", false);
 			foreach(string s in files)
 			{
 				string filename = Path.GetFileNameWithoutExtension(s.ToLowerInvariant());
 				if(filename == "zmapinfo" || filename == "mapinfo")
-					streams.Add(s, LoadFile(s));
+					streams[s] = LoadFile(s);
 			}
 
 			return streams;
diff --git a/Source/Core/Data/SpriteImage.cs b/Source/Core/Data/SpriteImage.cs
index b61bae00..b3b8f784 100644
--- a/Source/Core/Data/SpriteImage.cs
+++ b/Source/Core/Data/SpriteImage.cs
@@ -30,8 +30,8 @@ namespace CodeImp.DoomBuilder.Data
 	{
 		#region ================== Variables
 
-		protected int offsetx;
-		protected int offsety;
+		private int offsetx;
+		private int offsety;
 		
 		#endregion
 
diff --git a/Source/Core/GZBuilder/Data/MapInfo.cs b/Source/Core/GZBuilder/Data/MapInfo.cs
index f3aa1378..d7aef9a6 100644
--- a/Source/Core/GZBuilder/Data/MapInfo.cs
+++ b/Source/Core/GZBuilder/Data/MapInfo.cs
@@ -1,30 +1,69 @@
-using SlimDX;
+#region ================== Namespaces
+
+using SlimDX;
+
+#endregion
 
 namespace CodeImp.DoomBuilder.GZBuilder.Data 
 {
-	public sealed class MapInfo 
+	public sealed class MapInfo
 	{
-		public string Sky1;
-		public float Sky1ScrollSpeed;
-		public string Sky2;
-		public float Sky2ScrollSpeed;
-		public bool DoubleSky;
-		public bool HasFadeColor;
-		public Color4 FadeColor;
-		public bool HasOutsideFogColor;
-		public Color4 OutsideFogColor;
-		public int FogDensity;
-		public int OutsideFogDensity;
+		#region ================== Variables
 
-		public bool EvenLighting;
-		public bool SmoothLighting;
-		public int VertWallShade;
-		public int HorizWallShade;
+		private bool isdefined;
+
+		private string title;
+		private string sky1;
+		private float sky1scrollspeed;
+		private string sky2;
+		private float sky2scrollspeed;
+		private bool doublesky;
+		private bool hasfadecolor;
+		private Color4 fadecolor;
+		private bool hasoutsidefogcolor;
+		private Color4 outsidefogcolor;
+		private int fogdensity;
+		private int outsidefogdensity;
+
+		private bool evenlighting;
+		private bool smoothlighting;
+		private int vertwallshade;
+		private int horizwallshade;
+
+		#endregion
+
+		#region ================== Properties
+
+		public bool IsDefined { get { return isdefined; } }
+
+		public string Title { get { return title; } internal set { title = value; isdefined = true; } }
+		public string Sky1 { get { return sky1; } internal set { sky1 = value; isdefined = true; } }
+		public float Sky1ScrollSpeed { get { return sky1scrollspeed; } internal set { sky1scrollspeed = value; isdefined = true; } }
+		public string Sky2 { get { return sky2; } internal set { sky2 = value; isdefined = true; } }
+		public float Sky2ScrollSpeed { get { return sky2scrollspeed; } internal set { sky2scrollspeed = value; isdefined = true; } }
+		public bool DoubleSky { get { return doublesky; } internal set { doublesky = value; isdefined = true; } }
+		public bool HasFadeColor { get { return hasfadecolor; } internal set { hasfadecolor = value; isdefined = true; } }
+		public Color4 FadeColor { get { return fadecolor; } internal set { fadecolor = value; isdefined = true; } }
+		public bool HasOutsideFogColor { get { return hasoutsidefogcolor; } internal set { hasoutsidefogcolor = value; isdefined = true; } }
+		public Color4 OutsideFogColor { get { return outsidefogcolor; } internal set { outsidefogcolor = value; isdefined = true; } }
+		public int FogDensity { get { return fogdensity; } internal set { fogdensity = value; isdefined = true; } }
+		public int OutsideFogDensity { get { return outsidefogdensity; } internal set { outsidefogdensity = value; isdefined = true; } }
+
+		public bool EvenLighting { get { return evenlighting; } internal set { evenlighting = value; isdefined = true; } }
+		public bool SmoothLighting { get { return smoothlighting; } internal set { smoothlighting = value; isdefined = true; } }
+		public int VertWallShade { get { return vertwallshade; } internal set { vertwallshade = value; isdefined = true; } }
+		public int HorizWallShade { get { return horizwallshade; } internal set { horizwallshade = value; isdefined = true; } }
+
+		#endregion
+
+		#region ================== Constructor
 
 		public MapInfo() 
 		{
-			VertWallShade = 16;
-			HorizWallShade = -16;
+			vertwallshade = 16;
+			horizwallshade = -16;
 		}
+
+		#endregion
 	}
 }
diff --git a/Source/Core/GZBuilder/Data/SkyboxInfo.cs b/Source/Core/GZBuilder/Data/SkyboxInfo.cs
new file mode 100644
index 00000000..4e41caa3
--- /dev/null
+++ b/Source/Core/GZBuilder/Data/SkyboxInfo.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+
+namespace CodeImp.DoomBuilder.GZBuilder.Data
+{
+	public sealed class SkyboxInfo
+	{
+		private readonly string name;
+		public string Name { get { return name; } }
+		public readonly List Textures;
+		public bool FlipTop;
+
+		public SkyboxInfo(string name)
+		{
+			this.name = name;
+			Textures = new List();
+		}
+	}
+}
diff --git a/Source/Core/GZBuilder/GZDoom/GldefsParser.cs b/Source/Core/GZBuilder/GZDoom/GldefsParser.cs
index 63ac4e8e..9b8c297d 100644
--- a/Source/Core/GZBuilder/GZDoom/GldefsParser.cs
+++ b/Source/Core/GZBuilder/GZDoom/GldefsParser.cs
@@ -46,18 +46,20 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 
 		#region ================== Variables
 
-		private readonly Dictionary lightsByName; //LightName, light definition
+		private readonly Dictionary lightsbyname; //LightName, light definition
 		private readonly Dictionary objects; //ClassName, LightName
 		private readonly Dictionary glowingflats;
+		private readonly Dictionary skyboxes;
 		private readonly HashSet parsedlumps;
 
 		#endregion
 
 		#region ================== Properties
 
-		public Dictionary LightsByName { get { return lightsByName; } }
-		public Dictionary Objects { get { return objects; } }
+		internal Dictionary LightsByName { get { return lightsbyname; } }
+		internal Dictionary Objects { get { return objects; } }
 		internal Dictionary GlowingFlats { get { return glowingflats; } }
+		internal Dictionary Skyboxes { get { return skyboxes; } }
 
 		#endregion
 
@@ -70,9 +72,10 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 			specialtokens = ",{}\n";
 			
 			parsedlumps = new HashSet(StringComparer.OrdinalIgnoreCase);
-			lightsByName = new Dictionary(StringComparer.Ordinal); //LightName, Light params
-			objects = new Dictionary(StringComparer.Ordinal); //ClassName, LightName
+			lightsbyname = new Dictionary(StringComparer.OrdinalIgnoreCase); //LightName, Light params
+			objects = new Dictionary(StringComparer.OrdinalIgnoreCase); //ClassName, LightName
 			glowingflats = new Dictionary(); // Texture name hash, Glowing Flat Data
+			skyboxes = new Dictionary(StringComparer.OrdinalIgnoreCase);
 		}
 
 		#endregion
@@ -91,649 +94,708 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 			// Continue until at the end of the stream
 			while(SkipWhitespace(true)) 
 			{
-				string token = StripTokenQuotes(ReadToken()).ToLowerInvariant(); //Quotes can be anywhere! ANYWHERE!!! And GZDoom will still parse data correctly
+				string token = StripTokenQuotes(ReadToken()).ToLowerInvariant(); // Quotes can be anywhere! ANYWHERE!!! And GZDoom will still parse data correctly
 				if(string.IsNullOrEmpty(token)) break;
 
-				//got light structure
-				if(token == GldefsLightType.POINT || token == GldefsLightType.PULSE || token == GldefsLightType.FLICKER 
-					|| token == GldefsLightType.FLICKER2 || token == GldefsLightType.SECTOR) 
+				switch(token)
 				{
-					string lightType = token;
-					DynamicLightData light = new DynamicLightData { Type = GldefsLightType.GLDEFS_TO_GZDOOM_LIGHT_TYPE[lightType] };
+					case GldefsLightType.POINT: case GldefsLightType.PULSE: case GldefsLightType.SECTOR:
+					case GldefsLightType.FLICKER: case GldefsLightType.FLICKER2: 
+						if(!ParseLight(token)) return false;
+						break;
 
-					//find classname
-					SkipWhitespace(true);
-					string lightName = StripTokenQuotes(ReadToken()).ToLowerInvariant();
+					case "object":
+						if(!ParseObject()) return false;
+						break;
 
-					if(string.IsNullOrEmpty(lightName))
-					{
-						ReportError("Expected " + token + " name");
-						return false;
-					}
+					case "glow":
+						if(!ParseGlowingFlats()) return false;
+						break;
 
-					//now find opening brace
-					if(!NextTokenIs("{", false))
-					{
-						ReportError("Expected opening brace");
-						return false;
-					}
+					case "skybox":
+						if(!ParseSkybox()) return false;
+						break;
 
-					//read gldefs light structure
-					while(SkipWhitespace(true)) 
-					{
-						token = ReadToken().ToLowerInvariant();
-						if(!string.IsNullOrEmpty(token)) 
+					case "#include":
+						if(!ParseInclude(clearerrors)) return false;
+
+						// Set our buffers back to continue parsing
+						datastream = localstream;
+						datareader = localreader;
+						sourcename = localsourcename;
+						break;
+
+					case "$gzdb_skip": return !this.HasError;
+					
+					default:
+						// Unknown structure!
+						SkipStructure();
+						break;
+				}
+			}
+
+			// All done
+			return !this.HasError;
+		}
+
+		private bool ParseLight(string lighttype)
+		{
+			DynamicLightData light = new DynamicLightData { Type = GldefsLightType.GLDEFS_TO_GZDOOM_LIGHT_TYPE[lighttype] };
+
+			// Find classname
+			SkipWhitespace(true);
+			string lightname = StripTokenQuotes(ReadToken()).ToLowerInvariant();
+
+			if(string.IsNullOrEmpty(lightname))
+			{
+				ReportError("Expected " + lighttype + " name");
+				return false;
+			}
+
+			// Now find opening brace
+			if(!NextTokenIs("{", false))
+			{
+				ReportError("Expected opening brace");
+				return false;
+			}
+
+			// Read gldefs light structure
+			while(SkipWhitespace(true))
+			{
+				string token = ReadToken().ToLowerInvariant();
+				if(string.IsNullOrEmpty(token)) continue;
+
+				switch(token)
+				{
+					case "color":
+						SkipWhitespace(true);
+						token = StripTokenQuotes(ReadToken());
+						if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out light.Color.Red))
 						{
-//color
-							if(token == "color") 
+							// Not numeric!
+							ReportError("Expected Red color value, but got '" + token + "'");
+							return false;
+						}
+
+						SkipWhitespace(true);
+						token = StripTokenQuotes(ReadToken());
+						if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out light.Color.Green))
+						{
+							// Not numeric!
+							ReportError("Expected Green color value, but got '" + token + "'");
+							return false;
+						}
+
+						SkipWhitespace(true);
+						token = StripTokenQuotes(ReadToken());
+						if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out light.Color.Blue))
+						{
+							// Not numeric!
+							ReportError("Expected Blue color value, but got '" + token + "'");
+							return false;
+						}
+					break;
+
+					case "size":
+						if(lighttype != GldefsLightType.SECTOR)
+						{
+							SkipWhitespace(true);
+
+							token = StripTokenQuotes(ReadToken());
+							if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out light.PrimaryRadius))
 							{
-								SkipWhitespace(true);
+								// Not numeric!
+								ReportError("Expected Size value, but got '" + token + "'");
+								return false;
+							}
+							light.PrimaryRadius *= 2;
 
-								token = StripTokenQuotes(ReadToken());
-								if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out light.Color.Red)) 
-								{
-									// Not numeric!
-									ReportError("Expected Red color value, but got '" + token + "'");
-									return false;
-								}
+						}
+						else
+						{
+							ReportError("'" + token + "' is not valid property for " + lighttype);
+							return false;
+						}
+					break;
 
-								SkipWhitespace(true);
+					case "offset":
+						SkipWhitespace(true);
+						token = StripTokenQuotes(ReadToken());
+						if(!ReadSignedFloat(token, ref light.Offset.X))
+						{
+							// Not numeric!
+							ReportError("Expected Offset X value, but got '" + token + "'");
+							return false;
+						}
 
-								token = StripTokenQuotes(ReadToken());
-								if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out light.Color.Green)) 
-								{
-									// Not numeric!
-									ReportError("Expected Green color value, but got '" + token + "'");
-									return false;
-								}
+						SkipWhitespace(true);
+						token = StripTokenQuotes(ReadToken());
+						if(!ReadSignedFloat(token, ref light.Offset.Z))
+						{
+							// Not numeric!
+							ReportError("Expected Offset Y value, but got '" + token + "'");
+							return false;
+						}
 
-								SkipWhitespace(true);
+						SkipWhitespace(true);
+						token = StripTokenQuotes(ReadToken());
+						if(!ReadSignedFloat(token, ref light.Offset.Y))
+						{
+							// Not numeric!
+							ReportError("Expected Offset Z value, but got '" + token + "'");
+							return false;
+						}
+					break;
 
-								token = StripTokenQuotes(ReadToken());
-								if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out light.Color.Blue)) 
-								{
-									// Not numeric!
-									ReportError("Expected Blue color value, but got '" + token + "'");
-									return false;
-								}
-//size
-							} 
-							else if(token == "size") 
+					case "subtractive":
+					{
+						SkipWhitespace(true);
+
+						token = StripTokenQuotes(ReadToken());
+						int i;
+						if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out i))
+						{
+							// Not numeric!
+							ReportError("expected Subtractive value, but got '" + token + "'");
+							return false;
+						}
+
+						light.Subtractive = (i == 1);
+					}
+					break;
+
+					case "dontlightself":
+					{
+						SkipWhitespace(true);
+
+						token = StripTokenQuotes(ReadToken());
+						int i;
+						if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out i))
+						{
+							// Not numeric!
+							ReportError("Expected DontLightSelf value, but got '" + token + "'");
+							return false;
+						}
+
+						light.DontLightSelf = (i == 1);
+					}
+					break;
+
+					case "interval":
+						if(lighttype == GldefsLightType.PULSE || lighttype == GldefsLightType.FLICKER2)
+						{
+							SkipWhitespace(true);
+
+							token = StripTokenQuotes(ReadToken());
+							float interval;
+							if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out interval))
 							{
-								if(lightType != GldefsLightType.SECTOR) 
-								{
-									SkipWhitespace(true);
+								// Not numeric!
+								ReportError("Expected Interval value, but got '" + token + "'");
+								return false;
+							}
 
-									token = StripTokenQuotes(ReadToken());
-									if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out light.PrimaryRadius)) 
-									{
-										// Not numeric!
-										ReportError("Expected Size value, but got '" + token + "'");
-										return false;
-									}
-									light.PrimaryRadius *= 2;
+							if(interval == 0) LogWarning("Interval value should be greater than zero");
 
-								} 
-								else 
-								{
-									ReportError("'" + token + "' is not valid property for " + lightType);
-									return false;
-								}
-//offset
-							} 
-							else if(token == "offset") 
+							//I wrote logic for dynamic lights animation first, so here I modify gldefs settings to fit in existing logic
+							if(lighttype == GldefsLightType.PULSE)
 							{
-								SkipWhitespace(true);
-
-								token = StripTokenQuotes(ReadToken());
-								if(!ReadSignedFloat(token, ref light.Offset.X)) 
-								{
-									// Not numeric!
-									ReportError("Expected Offset X value, but got '" + token + "'");
-									return false;
-								}
-
-								SkipWhitespace(true);
-
-								token = StripTokenQuotes(ReadToken());
-								if(!ReadSignedFloat(token, ref light.Offset.Z)) 
-								{
-									// Not numeric!
-									ReportError("Expected Offset Y value, but got '" + token + "'");
-									return false;
-								}
-
-								SkipWhitespace(true);
-
-								token = StripTokenQuotes(ReadToken());
-								if(!ReadSignedFloat(token, ref light.Offset.Y)) 
-								{
-									// Not numeric!
-									ReportError("Expected Offset Z value, but got '" + token + "'");
-									return false;
-								}
-//subtractive
-							} 
-							else if(token == "subtractive") 
+								light.Interval = (int)(interval * 35); //measured in tics (35 per second) in PointLightPulse, measured in seconds in gldefs' PulseLight
+							}
+							else //FLICKER2. Seems like PointLightFlickerRandom to me
 							{
-								SkipWhitespace(true);
-
-								token = StripTokenQuotes(ReadToken());
-								int i;
-								if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out i)) 
-								{
-									// Not numeric!
-									ReportError("expected Subtractive value, but got '" + token + "'");
-									return false;
-								}
-
-								light.Subtractive = i == 1;
-//dontlightself
-							} 
-							else if(token == "dontlightself") 
-							{
-								SkipWhitespace(true);
-
-								token = StripTokenQuotes(ReadToken());
-								int i;
-								if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out i)) 
-								{
-									// Not numeric!
-									ReportError("Expected DontLightSelf value, but got '" + token + "'");
-									return false;
-								}
-
-								light.DontLightSelf = (i == 1);
-//interval
-							} 
-							else if(token == "interval") 
-							{
-								if(lightType == GldefsLightType.PULSE || lightType == GldefsLightType.FLICKER2) 
-								{
-									SkipWhitespace(true);
-
-									token = StripTokenQuotes(ReadToken());
-									float interval;
-									if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out interval)) 
-									{
-										// Not numeric!
-										ReportError("Expected Interval value, but got '" + token + "'");
-										return false;
-									}
-
-									if(interval == 0) LogWarning("Interval value should be greater than zero");
-
-									//I wrote logic for dynamic lights animation first, so here I modify gldefs settings to fit in existing logic
-									if(lightType == GldefsLightType.PULSE) 
-									{
-										light.Interval = (int)(interval * 35); //measured in tics (35 per second) in PointLightPulse, measured in seconds in gldefs' PulseLight
-									} 
-									else //FLICKER2. Seems like PointLightFlickerRandom to me
-									{ 
-										light.Interval = (int)(interval * 350); //0.1 is one second for FlickerLight2
-									}
-								} 
-								else 
-								{
-									ReportError("'" + token + "' is not valid property for " + lightType);
-									return false;
-								}
-//secondarysize
-							} 
-							else if(token == "secondarysize") 
-							{
-								if(lightType == GldefsLightType.PULSE || lightType == GldefsLightType.FLICKER || lightType == GldefsLightType.FLICKER2) 
-								{
-									SkipWhitespace(true);
-
-									token = StripTokenQuotes(ReadToken());
-									if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out light.SecondaryRadius)) 
-									{
-										// Not numeric!
-										ReportError("Expected SecondarySize value, but got '" + token + "'");
-										return false;
-									}
-									light.SecondaryRadius *= 2;
-								} 
-								else 
-								{
-									ReportError("'" + token + "' is not valid property for " + lightType);
-									return false;
-								}
-//chance
-							} 
-							else if(token == "chance") 
-							{
-								if(lightType == GldefsLightType.FLICKER) 
-								{
-									SkipWhitespace(true);
-
-									token = StripTokenQuotes(ReadToken());
-									float chance;
-									if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out chance)) 
-									{
-										// Not numeric!
-										ReportError("Expected Chance value, but got '" + token + "'");
-										return false;
-									}
-
-									//transforming from 0.0 .. 1.0 to 0 .. 359 to fit in existing logic
-									light.Interval = (int)(chance * 359.0f);
-								} 
-								else 
-								{
-									ReportError("'" + token + "' is not valid property for " + lightType);
-									return false;
-								}
-//scale
-							} 
-							else if(token == "scale") 
-							{
-								if(lightType == GldefsLightType.SECTOR) 
-								{
-									SkipWhitespace(true);
-
-									token = StripTokenQuotes(ReadToken());
-									float scale;
-									if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out scale)) 
-									{
-										// Not numeric!
-										ReportError("Expected Scale value, but got '" + token + "'");
-										return false;
-									}
-
-									if(scale > 1.0f) 
-									{
-										ReportError("Scale must be in 0.0 - 1.0 range, but is " + scale);
-										return false;
-									}
-
-									//sector light doesn't have animation, so we will store it's size in Interval
-									//transforming from 0.0 .. 1.0 to 0 .. 10 to preserve value.
-									light.Interval = (int)(scale * 10.0f);
-								} 
-								else 
-								{
-									ReportError("'" + token + "' is not valid property for " + lightType);
-									return false;
-								}
-							} 
-							//end of structure
-							else if(token == "}") 
-							{
-								bool skip = false;
-
-								//general checks
-								if(light.Color.Red == 0.0f && light.Color.Green == 0.0f && light.Color.Blue == 0.0f) 
-								{
-									LogWarning("'" + lightName + "' light Color is " + light.Color.Red + "," + light.Color.Green + "," + light.Color.Blue + ". It won't be shown in GZDoom");
-									skip = true;
-								}
-
-								//light-type specific checks
-								if(light.Type == DynamicLightType.NORMAL && light.PrimaryRadius == 0) 
-								{
-									LogWarning("'" + lightName + "' light Size is 0. It won't be shown in GZDoom");
-									skip = true;
-								}
-
-								if(light.Type == DynamicLightType.FLICKER || light.Type == DynamicLightType.PULSE || light.Type == DynamicLightType.RANDOM) 
-								{
-									if(light.PrimaryRadius == 0 && light.SecondaryRadius == 0)
-									{
-										LogWarning("'" + lightName + "' light Size and SecondarySize are 0. This light won't be shown in GZDoom");
-										skip = true;
-									}
-								}
-
-								//offset it slightly to avoid shading glitches
-								if(light.Offset.Z == 0.0f) light.Offset.Z = 0.1f;
-
-								// Add to the collection?
-								if(!skip) lightsByName[lightName] = light;
-
-								//break out of this parsing loop
-								break; 
+								light.Interval = (int)(interval * 350); //0.1 is one second for FlickerLight2
 							}
 						}
+						else
+						{
+							ReportError("'" + token + "' is not valid property for " + lighttype);
+							return false;
+						}
+					break;
+
+					case "secondarysize":
+						if(lighttype == GldefsLightType.PULSE || lighttype == GldefsLightType.FLICKER || lighttype == GldefsLightType.FLICKER2)
+						{
+							SkipWhitespace(true);
+
+							token = StripTokenQuotes(ReadToken());
+							if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out light.SecondaryRadius))
+							{
+								// Not numeric!
+								ReportError("Expected SecondarySize value, but got '" + token + "'");
+								return false;
+							}
+
+							light.SecondaryRadius *= 2;
+						}
+						else
+						{
+							ReportError("'" + token + "' is not valid property for " + lighttype);
+							return false;
+						}
+					break;
+
+					case "chance":
+						if(lighttype == GldefsLightType.FLICKER)
+						{
+							SkipWhitespace(true);
+
+							token = StripTokenQuotes(ReadToken());
+							float chance;
+							if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out chance))
+							{
+								// Not numeric!
+								ReportError("Expected Chance value, but got '" + token + "'");
+								return false;
+							}
+
+							// Transforming from 0.0 .. 1.0 to 0 .. 359 to fit in existing logic
+							light.Interval = (int)(chance * 359.0f);
+						}
+						else
+						{
+							ReportError("'" + token + "' is not valid property for " + lighttype);
+							return false;
+						}
+					break;
+
+					case "scale":
+						if(lighttype == GldefsLightType.SECTOR)
+						{
+							SkipWhitespace(true);
+
+							token = StripTokenQuotes(ReadToken());
+							float scale;
+							if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out scale))
+							{
+								// Not numeric!
+								ReportError("Expected Scale value, but got '" + token + "'");
+								return false;
+							}
+
+							if(scale > 1.0f)
+							{
+								ReportError("Scale must be in 0.0 - 1.0 range, but is " + scale);
+								return false;
+							}
+
+							//sector light doesn't have animation, so we will store it's size in Interval
+							//transforming from 0.0 .. 1.0 to 0 .. 10 to preserve value.
+							light.Interval = (int)(scale * 10.0f);
+						}
+						else
+						{
+							ReportError("'" + token + "' is not valid property for " + lighttype);
+							return false;
+						}
+					break;
+
+					case "}":
+					{
+						bool skip = false;
+
+						// General checks
+						if(light.Color.Red == 0.0f && light.Color.Green == 0.0f && light.Color.Blue == 0.0f)
+						{
+							LogWarning("'" + lightname + "' light Color is " + light.Color.Red + "," + light.Color.Green + "," + light.Color.Blue + ". It won't be shown in GZDoom");
+							skip = true;
+						}
+
+						// Light-type specific checks
+						if(light.Type == DynamicLightType.NORMAL && light.PrimaryRadius == 0)
+						{
+							LogWarning("'" + lightname + "' light Size is 0. It won't be shown in GZDoom");
+							skip = true;
+						}
+
+						if(light.Type == DynamicLightType.FLICKER || light.Type == DynamicLightType.PULSE || light.Type == DynamicLightType.RANDOM)
+						{
+							if(light.PrimaryRadius == 0 && light.SecondaryRadius == 0)
+							{
+								LogWarning("'" + lightname + "' light Size and SecondarySize are 0. This light won't be shown in GZDoom");
+								skip = true;
+							}
+						}
+
+						// Offset it slightly to avoid shading glitches
+						if(light.Offset.Z == 0.0f) light.Offset.Z = 0.1f;
+
+						// Add to the collection?
+						if(!skip) lightsbyname[lightname] = light;
+
+						// Break out of this parsing loop
+						return true;
 					}
-				} 
-				else if(token == "object") 
+				}
+			}
+
+			// All done here
+			return true;
+		}
+
+		private bool ParseObject()
+		{
+			SkipWhitespace(true);
+
+			// Read object class
+			string objectclass = StripTokenQuotes(ReadToken()).ToLowerInvariant();
+
+			if(string.IsNullOrEmpty(objectclass))
+			{
+				ReportError("Expected object class");
+				return false;
+			}
+
+			// Now find opening brace
+			if(!NextTokenIs("{", false))
+			{
+				ReportError("Expected opening brace");
+				return false;
+			}
+
+			int bracescount = 1;
+			bool foundlight = false;
+			bool foundframe = false;
+
+			// Read frames structure
+			while(SkipWhitespace(true))
+			{
+				string token = ReadToken();
+				if(string.IsNullOrEmpty(token)) continue;
+
+				token = StripTokenQuotes(token).ToLowerInvariant();
+				if(!foundlight && !foundframe && token == "frame")
 				{
 					SkipWhitespace(true);
+					token = ReadToken().ToLowerInvariant(); // Should be frame name
 
-					//read object class
-					string objectClass = StripTokenQuotes(ReadToken()).ToLowerInvariant();
+					// Use this frame if it's 4 characters long or it's the first frame
+					foundframe = (token.Length == 4 || (token.Length > 4 && token[4] == 'a'));
+				}
+				else if(!foundlight && foundframe && token == "light") // Just use first light and be done with it
+				{
+					SkipWhitespace(true);
+					token = ReadToken().ToLowerInvariant(); // Should be light name
 
-					if(string.IsNullOrEmpty(objectClass))
+					if(!string.IsNullOrEmpty(token))
 					{
-						ReportError("Expected object class");
-						return false;
-					}
-
-					//now find opening brace
-					if(!NextTokenIs("{", false))
-					{
-						ReportError("Expected opening brace");
-						return false;
-					}
-
-					int bracesCount = 1;
-					bool foundLight = false;
-					bool foundFrame = false;
-
-					//read frames structure
-					while(SkipWhitespace(true)) 
-					{
-						token = ReadToken();
-						if(!string.IsNullOrEmpty(token)) 
+						if(lightsbyname.ContainsKey(token))
 						{
-							token = StripTokenQuotes(token).ToLowerInvariant();
-							if(!foundLight && !foundFrame && token == "frame") 
-							{
-								SkipWhitespace(true);
-								token = ReadToken().ToLowerInvariant(); //should be frame name
-
-								//use this frame if it's 4 characters long or it's the first frame
-								foundFrame = (token.Length == 4 || (token.Length > 4 && token[4] == 'a'));
-							} 
-							else if(!foundLight && foundFrame && token == "light") //just use first light and be done with it
-							{ 
-								SkipWhitespace(true);
-								token = ReadToken().ToLowerInvariant(); //should be light name
-
-								if(!string.IsNullOrEmpty(token)) 
-								{
-									if(lightsByName.ContainsKey(token)) 
-									{
-										objects[objectClass] = token;
-										foundLight = true;
-									} 
-									else 
-									{
-										LogWarning("Light declaration not found for light '" + token + "'");
-									}
-								}
-							} 
-							else if(token == "{") //continue in this loop until object structure ends
-							{ 
-								bracesCount++;
-							} 
-							else if(token == "}") 
-							{
-								if(--bracesCount < 1) break; //This was Cave Johnson. And we are done here.
-							}
+							objects[objectclass] = token;
+							foundlight = true;
+						}
+						else
+						{
+							LogWarning("Light declaration not found for light '" + token + "'");
 						}
 					}
 				}
-				//Glowing flats block start
-				else if(token == "glow")
+				else if(token == "{") // Continue in this loop until object structure ends
 				{
-					// Next sould be opening brace
-					if(!NextTokenIs("{", false))
-					{
-						ReportError("Expected opening brace");
-						return false;
-					}
+					bracescount++;
+				}
+				else if(token == "}")
+				{
+					if(--bracescount < 1) break; // This was Cave Johnson. And we are done here.
+				}
+			}
 
-					// Parse inner blocks
-					while(SkipWhitespace(true))
-					{
-						token = ReadToken().ToLowerInvariant();
-						if(token == "}")
+			// All done here
+			return true;
+		}
+
+		private bool ParseGlowingFlats()
+		{
+			// Next sould be opening brace
+			if(!NextTokenIs("{", false))
+			{
+				ReportError("Expected opening brace");
+				return false;
+			}
+
+			// Parse inner blocks
+			while(SkipWhitespace(true))
+			{
+				string token = ReadToken().ToLowerInvariant();
+				if(token == "}") break; // End of Glow structure
+				
+				switch(token)
+				{
+					case "walls":
+					case "flats":
+						if(!NextTokenIs("{", false))
 						{
-							// End of Glow structure
-							break;
+							ReportError("Expected opening brace");
+							return false;
 						}
-						else if(token == "flats" || token == "walls") 
+
+						while(SkipWhitespace(true))
 						{
-							// Next sould be opening brace
-							if(!NextTokenIs("{", false))
-							{
-								ReportError("Expected opening brace");
-								return false;
-							}
-
-							// Read flat names
-							while(SkipWhitespace(true))
-							{
-								token = ReadToken();
-								if(token == "}") break;
-
-								// Add glow data
-								glowingflats[General.Map.Data.GetFullLongFlatName(Lump.MakeLongName(token, General.Map.Options.UseLongTextureNames))] = new GlowingFlatData {
-									Height = DEFAULT_GLOW_HEIGHT * 2,
-									Fullbright = true,
-									Color = new PixelColor(255, 255, 255, 255),
-									CalculateTextureColor = true
-								};
-							}
-						} 
-						// GLOOME subtractive flats
-						else if(token == "subflats" || token == "subwalls")
-						{
-							// Next sould be opening brace
-							if(!NextTokenIs("{", false))
-							{
-								ReportError("Expected opening brace");
-								return false;
-							}
-
-							// Read flat names
-							while(SkipWhitespace(true))
-							{
-								token = ReadToken();
-								if(token == "}") break;
-
-								// Add glow data
-								glowingflats[General.Map.Data.GetFullLongFlatName(Lump.MakeLongName(token, General.Map.Options.UseLongTextureNames))] = new GlowingFlatData {
-									Height = DEFAULT_GLOW_HEIGHT * 2,
-									Fullblack = true,
-									Subtractive = true,
-									Color = new PixelColor(255, 0, 0, 0),
-									CalculateTextureColor = false
-								};
-							}
-						}
-						else if(token == "texture" || token == "subtexture")
-						{
-							int color;
-							int glowheight = DEFAULT_GLOW_HEIGHT;
-							bool subtractivetexture = (token == "subtexture");
-							string texturename = StripTokenQuotes(ReadToken(false));
-
-							if(string.IsNullOrEmpty(texturename))
-							{
-								ReportError("expected " + token + " name");
-								return false;
-							}
-							
-							// Now we should find a comma
-							if(!NextTokenIs(",", false))
-							{
-								ReportError("Expected a comma");
-								return false;
-							}
-
-							// Next is color
-							SkipWhitespace(true);
 							token = ReadToken();
+							if(token == "}") break;
 
-							if(!int.TryParse(token, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out color)) 
+							// Add glow data
+							long flatnamehash = General.Map.Data.GetFullLongFlatName(Lump.MakeLongName(token, General.Map.Options.UseLongTextureNames));
+							glowingflats[flatnamehash] = new GlowingFlatData
+														 {
+                                                             Height = DEFAULT_GLOW_HEIGHT * 2,
+                                                             Fullbright = true,
+                                                             Color = new PixelColor(255, 255, 255, 255),
+                                                             CalculateTextureColor = true
+                                                         };
+						}
+					break;
+
+					case "subwalls":
+					case "subflats":
+						if(!NextTokenIs("{", false))
+						{
+							ReportError("Expected opening brace");
+							return false;
+						}
+
+						while(SkipWhitespace(true))
+						{
+							token = ReadToken();
+							if(token == "}") break;
+
+							// Add glow data
+							long flatnamehash = General.Map.Data.GetFullLongFlatName(Lump.MakeLongName(token, General.Map.Options.UseLongTextureNames));
+							glowingflats[flatnamehash] = new GlowingFlatData
+														 {
+                                                             Height = DEFAULT_GLOW_HEIGHT * 2,
+                                                             Fullblack = true,
+                                                             Subtractive = true,
+                                                             Color = new PixelColor(255, 0, 0, 0),
+                                                             CalculateTextureColor = false
+                                                         };
+						}
+					break;
+
+					case "subtexture":
+					case "texture":
+					{
+						int color;
+						int glowheight = DEFAULT_GLOW_HEIGHT;
+						bool subtractivetexture = (token == "subtexture");
+						string texturename = StripTokenQuotes(ReadToken(false));
+
+						if(string.IsNullOrEmpty(texturename))
+						{
+							ReportError("expected " + token + " name");
+							return false;
+						}
+
+						// Now we should find a comma
+						if(!NextTokenIs(",", false))
+						{
+							ReportError("Expected a comma");
+							return false;
+						}
+
+						// Next is color
+						SkipWhitespace(true);
+						token = ReadToken();
+
+						if(!int.TryParse(token, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out color))
+						{
+							//probably it's a color name?
+							Color c = Color.FromName(token); //should be similar to C++ color name detection, I suppose
+							if(c.IsKnownColor)
 							{
-								//probably it's a color name?
-								Color c = Color.FromName(token); //should be similar to C++ color name detection, I suppose
-								if(c.IsKnownColor)
-								{
-									color = PixelColor.FromColor(c).ToInt();
-								}
-								else
-								{
-									ReportError("expected glow color value, but got '" + token + "'");
-									return false;
-								}
+								color = PixelColor.FromColor(c).ToInt();
 							}
+							else
+							{
+								ReportError("expected glow color value, but got '" + token + "'");
+								return false;
+							}
+						}
 
-							// The glow data is valid at thispoint. Let's get texture hash 
-							long texturehash = General.Map.Data.GetFullLongFlatName(Lump.MakeLongName(texturename, General.Map.Options.UseLongTextureNames));
+						// The glow data is valid at thispoint. Let's get texture hash 
+						long texturehash = General.Map.Data.GetFullLongFlatName(Lump.MakeLongName(texturename, General.Map.Options.UseLongTextureNames));
+
+						// Now we can find a comma
+						if(!NextTokenIs(",", false))
+						{
+							// Add glow data
+							glowingflats[texturehash] = new GlowingFlatData
+							                            {
+								                            Height = glowheight * 2,
+								                            Subtractive = subtractivetexture,
+								                            Color = PixelColor.FromInt(color).WithAlpha(255),
+								                            CalculateTextureColor = false
+							                            };
+							continue;
+						}
+
+						// Can be glow height
+						SkipWhitespace(true);
+						token = ReadToken();
+
+						int h;
+						if(int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out h))
+						{
+							// Can't pass glowheight directly cause TryParse will unconditionally set it to 0
+							glowheight = h;
 
 							// Now we can find a comma
 							if(!NextTokenIs(",", false))
 							{
 								// Add glow data
-								glowingflats[texturehash] = new GlowingFlatData {
-									Height = glowheight * 2,
-									Subtractive = subtractivetexture,
-									Color = PixelColor.FromInt(color).WithAlpha(255),
-									CalculateTextureColor = false
-								};
-
+								glowingflats[texturehash] = new GlowingFlatData
+								                            {
+									                            Height = glowheight * 2,
+									                            Subtractive = subtractivetexture,
+									                            Color = PixelColor.FromInt(color).WithAlpha(255),
+									                            CalculateTextureColor = false
+								                            };
 								continue;
 							}
 
-							// Can be glow height
+							// Read the flag
 							SkipWhitespace(true);
-							token = ReadToken();
-
-							int h;
-							if(int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out h))
-							{
-								// Can't pass glowheight directly cause TryParse will unconditionally set it to 0
-								glowheight = h;
-								
-								// Now we can find a comma
-								if(!NextTokenIs(",", false))
-								{
-									// Add glow data
-									glowingflats[texturehash] = new GlowingFlatData {
-										Height = glowheight * 2,
-										Subtractive = subtractivetexture,
-										Color = PixelColor.FromInt(color).WithAlpha(255),
-										CalculateTextureColor = false
-									};
-
-									continue;
-								}
-
-								// Read the flag
-								SkipWhitespace(true);
-								token = ReadToken().ToLowerInvariant();
-							}
-
-							// Next is "fullbright" or "fullblack" flag
-							bool fullbright = (token == "fullbright");
-							bool fullblack = (!subtractivetexture && token == "fullblack");
-
-							if(!fullblack && !fullbright)
-							{
-								string expectedflags = (subtractivetexture ? "'fullbright'" : "'fullbright' or 'fullblack'");
-								ReportError("expected " + expectedflags + " flag, but got '" + token + "'");
-								return false;
-							}
-
-							// Add glow data
-							glowingflats[texturehash] = new GlowingFlatData {
-								Height = glowheight * 2,
-								Fullbright = fullbright,
-								Fullblack = fullblack,
-								Subtractive = subtractivetexture,
-								Color = PixelColor.FromInt(color).WithAlpha(255),
-								CalculateTextureColor = false
-							};
+							token = ReadToken().ToLowerInvariant();
 						}
+
+						// Next is "fullbright" or "fullblack" flag
+						bool fullbright = (token == "fullbright");
+						bool fullblack = (!subtractivetexture && token == "fullblack");
+
+						if(!fullblack && !fullbright)
+						{
+							string expectedflags = (subtractivetexture ? "'fullbright'" : "'fullbright' or 'fullblack'");
+							ReportError("expected " + expectedflags + " flag, but got '" + token + "'");
+							return false;
+						}
+
+						// Add glow data
+						glowingflats[texturehash] = new GlowingFlatData
+						                            {
+							                            Height = glowheight * 2,
+							                            Fullbright = fullbright,
+							                            Fullblack = fullblack,
+							                            Subtractive = subtractivetexture,
+							                            Color = PixelColor.FromInt(color).WithAlpha(255),
+							                            CalculateTextureColor = false
+						                            };
 					}
-				}
-				else if(token == "#include") 
-				{
-					//INFO: ZDoom GLDEFS include paths can't be relative ("../glstuff.txt") 
-					//or absolute ("d:/project/glstuff.txt") 
-					//or have backward slashes ("info\glstuff.txt")
-					//include paths are relative to the first parsed entry, not the current one 
-					//also include paths may or may not be quoted
-					SkipWhitespace(true);
-					string includelump = StripTokenQuotes(ReadToken(false)); // Don't skip newline
-
-					// Sanity checks
-					if(string.IsNullOrEmpty(includelump))
-					{
-						ReportError("Expected file name to include");
-						return false;
-					}
-
-					// Check invalid path chars
-					if(!CheckInvalidPathChars(token)) return false;
-
-					// Absolute paths are not supported...
-					if(Path.IsPathRooted(includelump))
-					{
-						ReportError("Absolute include paths are not supported by ZDoom");
-						return false;
-					}
-
-					// Relative paths are not supported
-					if(includelump.StartsWith(RELATIVE_PATH_MARKER) || includelump.StartsWith(CURRENT_FOLDER_PATH_MARKER) ||
-					   includelump.StartsWith(ALT_RELATIVE_PATH_MARKER) || includelump.StartsWith(ALT_CURRENT_FOLDER_PATH_MARKER))
-					{
-						ReportError("Relative include paths are not supported by ZDoom");
-						return false;
-					}
-
-					// Backward slashes are not supported
-					if(includelump.Contains(Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture)))
-					{
-						ReportError("Only forward slashes are supported by ZDoom");
-						return false;
-					}
-				
-					// Already parsed?
-					if(parsedlumps.Contains(includelump))
-					{
-						ReportError("Already parsed '" + includelump + "'. Check your #include directives");
-						return false;
-					}
-
-					// Add to collection
-					parsedlumps.Add(includelump);
-
-					// Callback to parse this file
-					if(OnInclude != null) OnInclude(this, includelump, clearerrors);
-
-					// Bail out on error
-					if(this.HasError) return false;
-
-					// Set our buffers back to continue parsing
-					datastream = localstream;
-					datareader = localreader;
-					sourcename = localsourcename;
-				} 
-				else if(token == "$gzdb_skip") //mxd
-				{
 					break;
 				}
-				else 
-				{
-					// Unknown structure!
-					string token2;
-					do 
-					{
-						if(!SkipWhitespace(true)) break;
-						token2 = ReadToken();
-						if(string.IsNullOrEmpty(token2)) break;
-					}
-					while(token2 != "{");
-					int scopelevel = 1;
-					do 
-					{
-						if(!SkipWhitespace(true)) break;
-						token2 = ReadToken();
-						if(string.IsNullOrEmpty(token2)) break;
-						if(token2 == "{") scopelevel++;
-						if(token2 == "}") scopelevel--;
-					}
-					while(scopelevel > 0);
-				}
 			}
 
+			// All done here
+			return true;
+		}
+
+		private bool ParseSkybox()
+		{
+			SkipWhitespace(true);
+			string name = StripTokenQuotes(ReadToken());
+
+			if(string.IsNullOrEmpty(name))
+			{
+				ReportError("Expected skybox name");
+				return false;
+			}
+
+			if(skyboxes.ContainsKey(name)) LogWarning("Skybox \"" + name + "\" is double-defined");
+
+			SkyboxInfo info = new SkyboxInfo(name.ToUpperInvariant()); 
+
+			// FlipTop / opening brace
+			SkipWhitespace(true);
+			string token = ReadToken();
+			if(token.ToLowerInvariant() == "fliptop")
+			{
+				info.FlipTop = true;
+				if(!NextTokenIs("{")) return false;
+			}
+			else if(token != "{")
+			{
+				ReportError("Expected opening brace or \"fliptop\" keyword");
+				return false;
+			}
+
+			// Read skybox texture names
+			while(SkipWhitespace(true))
+			{
+				token = ReadToken();
+				if(token == "}") break;
+				info.Textures.Add(token);
+			}
+
+			// Sanity check. Should have 3 or 6 textrues
+			if(info.Textures.Count != 3 && info.Textures.Count != 6)
+			{
+				ReportError("Expected 3 or 6 skybox textures");
+				return false;
+			}
+
+			// Add to collection
+			skyboxes[name] = info;
+
+			// All done here
+			return true;
+		}
+
+		private bool ParseInclude(bool clearerrors)
+		{
+			//INFO: GZDoom GLDEFS include paths can't be relative ("../glstuff.txt") 
+			//or absolute ("d:/project/glstuff.txt") 
+			//or have backward slashes ("info\glstuff.txt")
+			//include paths are relative to the first parsed entry, not the current one 
+			//also include paths may or may not be quoted
+			SkipWhitespace(true);
+			string includelump = StripTokenQuotes(ReadToken(false)); // Don't skip newline
+
+			// Sanity checks
+			if(string.IsNullOrEmpty(includelump))
+			{
+				ReportError("Expected file name to include");
+				return false;
+			}
+
+			// Check invalid path chars
+			if(!CheckInvalidPathChars(includelump)) return false;
+
+			// Absolute paths are not supported...
+			if(Path.IsPathRooted(includelump))
+			{
+				ReportError("Absolute include paths are not supported by GZDoom");
+				return false;
+			}
+
+			// Relative paths are not supported
+			if(includelump.StartsWith(RELATIVE_PATH_MARKER) || includelump.StartsWith(CURRENT_FOLDER_PATH_MARKER) ||
+			   includelump.StartsWith(ALT_RELATIVE_PATH_MARKER) || includelump.StartsWith(ALT_CURRENT_FOLDER_PATH_MARKER))
+			{
+				ReportError("Relative include paths are not supported by GZDoom");
+				return false;
+			}
+
+			// Backward slashes are not supported
+			if(includelump.Contains(Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture)))
+			{
+				ReportError("Only forward slashes are supported by GZDoom");
+				return false;
+			}
+
+			// Already parsed?
+			if(parsedlumps.Contains(includelump))
+			{
+				ReportError("Already parsed '" + includelump + "'. Check your #include directives");
+				return false;
+			}
+
+			// Add to collection
+			parsedlumps.Add(includelump);
+
+			// Callback to parse this file
+			if(OnInclude != null) OnInclude(this, includelump, clearerrors);
+
+			// All done here
 			return !this.HasError;
 		}
 
diff --git a/Source/Core/GZBuilder/GZDoom/MapinfoParser.cs b/Source/Core/GZBuilder/GZDoom/MapinfoParser.cs
index 2d295dc2..6bb8ce3f 100644
--- a/Source/Core/GZBuilder/GZDoom/MapinfoParser.cs
+++ b/Source/Core/GZBuilder/GZDoom/MapinfoParser.cs
@@ -61,7 +61,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 
 		override public bool Parse(Stream stream, string sourcefilename, bool clearerrors)
 		{
-			if(string.IsNullOrEmpty(mapname)) throw new NotSupportedException("MapName is required!");
+			if(string.IsNullOrEmpty(mapname)) throw new NotSupportedException("Map name required!");
 			return Parse(stream, sourcefilename, mapname, clearerrors);
 		}
 
@@ -70,274 +70,375 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 			this.mapname = mapname.ToLowerInvariant();
 			if(!base.Parse(stream, sourcefilename, clearerrors)) return false;
 
+			// Keep local data
+			Stream localstream = datastream;
+			string localsourcename = sourcename;
+			BinaryReader localreader = datareader;
+
+			// Classic format skip stoppers...
+			HashSet breakat = new HashSet { "map", "defaultmap", "adddefaultmap" };
+
 			while(SkipWhitespace(true)) 
 			{
-				string token = ReadToken();
-				if(!string.IsNullOrEmpty(token)) 
+				string token = ReadToken().ToLowerInvariant();
+				if(string.IsNullOrEmpty(token)) break;
+				bool stopparsing = false;
+
+				switch(token)
 				{
-					token = token.ToLowerInvariant();
-					if(ParseBlock(token, clearerrors)) break;
+					case "adddefaultmap":
+						// Parse properties
+						if(!ParseMapBlock()) return false;
+						break;
+
+					case "defaultmap":
+						// Reset MapInfo
+						mapinfo = new MapInfo();
+
+						// Parse properties
+						if(!ParseMapBlock()) return false;
+						break;
+
+					case "map":
+						// Get map lump name
+						SkipWhitespace(true);
+						token = ReadToken().ToLowerInvariant();
+						if(token != this.mapname) 
+						{
+							// Map number? Try to build map name from it...
+							int n;
+							if(int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out n))
+							{
+								token = ((n > 0 && n < 10) ? "map0" + n : "map" + n);
+							}
+
+							// Still no dice?
+							if(token != this.mapname)
+							{
+								SkipStructure(breakat);
+								continue; // Not finished, search for next "map", "defaultmap" or "adddefaultmap" block
+							}
+						}
+
+						// Try to get map name
+						SkipWhitespace(true);
+						token = ReadToken();
+						if(token.ToLowerInvariant() == "lookup")
+						{
+							// No dice...
+							SkipWhitespace(true);
+							ReadToken();
+						}
+						else
+						{
+							mapinfo.Title = StripTokenQuotes(token);
+						}
+
+						// Parse properties
+						if(!ParseMapBlock()) return false;
+						break;
+
+					case "include":
+						if(!ParseInclude(clearerrors)) return false;
+
+						// Set our buffers back to continue parsing
+						datastream = localstream;
+						datareader = localreader;
+						sourcename = localsourcename;
+						break;
+
+					case "gameinfo":
+						if(!ParseGameInfo()) return false;
+						break;
+
+					case "doomednums":
+						if(!ParseDoomEdNums()) return false;
+						break;
+
+					case "spawnnums":
+						if(!ParseSpawnNums()) return false;
+						break;
+
+					case "$gzdb_skip":
+						stopparsing = true; // Finished with this file
+						break; 
 				}
+
+				if(stopparsing) break;
 			}
 
-			//check values
+			// Check values
 			if(mapinfo.FadeColor.Red > 0 || mapinfo.FadeColor.Green > 0 || mapinfo.FadeColor.Blue > 0)
 				mapinfo.HasFadeColor = true;
 
 			if(mapinfo.OutsideFogColor.Red > 0 || mapinfo.OutsideFogColor.Green > 0 || mapinfo.OutsideFogColor.Blue > 0)
 				mapinfo.HasOutsideFogColor = true;
 
-			//Cannot fail here
+			// All done
+			return !this.HasError;
+		}
+
+		private bool ParseInclude(bool clearerrors)
+		{
+			SkipWhitespace(true);
+			string includelump = StripTokenQuotes(ReadToken(false)); // Don't skip newline
+
+			//INFO: ZDoom MAPINFO include paths can't be relative ("../mapstuff.txt") 
+			//or absolute ("d:/project/mapstuff.txt") 
+			//or have backward slashes ("info\mapstuff.txt")
+			//include paths are relative to the first parsed entry, not the current one 
+			//also include paths may or may not be quoted
+			if(!string.IsNullOrEmpty(includelump))
+			{
+				// Absolute paths are not supported...
+				if(Path.IsPathRooted(includelump))
+				{
+					ReportError("Absolute include paths are not supported by ZDoom");
+					return false;
+				}
+
+				// Relative paths are not supported
+				if(includelump.StartsWith(RELATIVE_PATH_MARKER) || includelump.StartsWith(CURRENT_FOLDER_PATH_MARKER) ||
+				   includelump.StartsWith(ALT_RELATIVE_PATH_MARKER) || includelump.StartsWith(ALT_CURRENT_FOLDER_PATH_MARKER))
+				{
+					ReportError("Relative include paths are not supported by ZDoom");
+					return false;
+				}
+
+				// Backward slashes are not supported
+				if(includelump.Contains(Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture)))
+				{
+					ReportError("Only forward slashes are supported by ZDoom");
+					return false;
+				}
+
+				// Check invalid path chars
+				if(!CheckInvalidPathChars(includelump)) return false;
+
+				// Already parsed?
+				if(parsedlumps.Contains(includelump))
+				{
+					ReportError("Already parsed '" + includelump + "'. Check your include directives");
+					return false;
+				}
+
+				// Add to collection
+				parsedlumps.Add(includelump);
+
+				// Callback to parse this file
+				if(OnInclude != null)
+				{
+					OnInclude(this, includelump, clearerrors);
+
+					// Bail out on error
+					if(this.HasError) return false;
+				}
+			}
+			else
+			{
+				ReportError("Expected filename to include");
+				return false;
+			}
+
+			// All done here
 			return true;
 		}
 
-		//returns true if parsing is finished
-		private bool ParseBlock(string token, bool clearerrors) 
+		private bool ParseGameInfo()
 		{
-			// Keep local data
-			Stream localstream = datastream;
-			string localsourcename = sourcename;
-			BinaryReader localreader = datareader;
-			
-			if(token == "map" || token == "defaultmap" || token == "adddefaultmap") 
-			{
-				switch(token)
-				{
-					case "map":
-						//get map name
-						SkipWhitespace(true);
-						token = ReadToken().ToLowerInvariant();
-						if(token != mapname) return false; //not finished, search for next "map", "defaultmap" or "adddefaultmap" block
-						break;
+			if(!NextTokenIs("{")) return false; // Finished with this file
 
-					case "defaultmap":
-						//reset MapInfo
-						mapinfo = new MapInfo();
-						break;
+			while(SkipWhitespace(true))
+			{
+				string token = ReadToken();
+				if(string.IsNullOrEmpty(token))
+				{
+					ReportError("Failed to find the end of GameInfo block");
+					return false; // Finished with this file
 				}
 
-				// Track brace level
-				int bracelevel = 0;
+				if(token == "}") break;
 
-				//search for required keys
-				while(SkipWhitespace(true)) 
+				if(token.ToLowerInvariant() == "skyflatname")
 				{
-					token = ReadToken().ToLowerInvariant();
-//sky1 or sky2
-					if(token == "sky1" || token == "sky2") 
+					if(!NextTokenIs("=")) return false; // Finished with this file
+					SkipWhitespace(true);
+					string skyflatname = StripTokenQuotes(ReadToken());
+					if(string.IsNullOrEmpty(skyflatname))
 					{
-						string skyType = token;
-						SkipWhitespace(true);
-						token = StripTokenQuotes(ReadToken()).ToLowerInvariant();
-
-						//new format
-						if(token == "=") 
-						{
-							SkipWhitespace(true);
-
-							//should be sky texture name
-							token = StripTokenQuotes(ReadToken());
-							bool gotComma = (token.IndexOf(",", StringComparison.Ordinal) != -1);
-							if(gotComma) token = token.Replace(",", "");
-							string skyTexture = StripTokenQuotes(token).ToLowerInvariant();
-
-							if(!string.IsNullOrEmpty(skyTexture)) 
-							{
-								if(skyType == "sky1")
-									mapinfo.Sky1 = skyTexture;
-								else
-									mapinfo.Sky2 = skyTexture;
-
-								//check if we have scrollspeed
-								SkipWhitespace(true);
-								token = StripTokenQuotes(ReadToken());
-
-								if(!gotComma && token == ",") 
-								{
-									gotComma = true;
-									SkipWhitespace(true);
-									token = ReadToken();
-								}
-
-								if(gotComma) 
-								{
-									float scrollSpeed = 0;
-									if(!ReadSignedFloat(token, ref scrollSpeed)) 
-									{
-										// Not numeric!
-										ReportError("Expected " + skyType + " scroll speed value, but got '" + token + "'");
-										return false;
-									}
-
-									if(skyType == "sky1")
-										mapinfo.Sky1ScrollSpeed = scrollSpeed;
-									else
-										mapinfo.Sky2ScrollSpeed = scrollSpeed;
-								} 
-								else 
-								{
-									datastream.Seek(-token.Length - 1, SeekOrigin.Current); //step back and try parsing this token again
-								}
-							} 
-							else 
-							{
-								ReportError("Expected " + skyType + " texture name");
-								return false;
-							}
-						}
-						//old format
-						else 
-						{
-							//token should be sky1/2 name
-							if(!string.IsNullOrEmpty(token)) 
-							{
-								if(skyType == "sky1")
-									mapinfo.Sky1 = token;
-								else
-									mapinfo.Sky2 = token;
-
-								//try to read scroll speed
-								SkipWhitespace(true);
-								token = StripTokenQuotes(ReadToken());
-
-								float scrollSpeed = 0;
-								if(!ReadSignedFloat(token, ref scrollSpeed)) 
-								{
-									// Not numeric!
-									datastream.Seek(-token.Length - 1, SeekOrigin.Current); //step back and try parsing this token again
-									continue;
-								}
-
-								if(skyType == "sky1")
-									mapinfo.Sky1ScrollSpeed = scrollSpeed;
-								else
-									mapinfo.Sky2ScrollSpeed = scrollSpeed;
-
-							} 
-							else 
-							{
-								ReportError("Expected " + skyType + " texture name");
-								return false;
-							}
-						}
+						ReportError("Unable to get SkyFlatName value");
+						return false; // Finished with this file
 					}
-//fade or outsidefog
-					else if(token == "fade" || token == "outsidefog") 
-					{
-						string fadeType = token;
-						SkipWhitespace(true);
-						token = StripTokenQuotes(ReadToken()).ToLowerInvariant();
 
-						//new format?
-						if(token == "=") 
+					General.Map.Config.SkyFlatName = skyflatname.ToUpperInvariant();
+				}
+			}
+
+			// All done here
+			return true;
+		}
+
+		private bool ParseDoomEdNums()
+		{
+			if(!NextTokenIs("{")) return false; // Finished with this file
+
+			while(SkipWhitespace(true))
+			{
+				string token = ReadToken();
+				if(string.IsNullOrEmpty(token))
+				{
+					ReportError("Failed to find the end of DoomEdNums block");
+					return false; // Finished with this file
+				}
+
+				if(token == "}") break;
+
+				// First must be a number
+				int id;
+				if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out id))
+				{
+					// Not numeric!
+					ReportError("Expected DoomEdNums entry number, but got '" + token + "'");
+					return false; // Finished with this file
+				}
+
+				// Then "="
+				if(!NextTokenIs("=")) return false; // Finished with this file
+
+				// Then actor class
+				SkipWhitespace(false);
+				string classname = StripTokenQuotes(ReadToken());
+				if(string.IsNullOrEmpty(classname))
+				{
+					ReportError("Unable to get DoomEdNums entry class definition");
+					return false; // Finished with this file
+				}
+
+				// Possible special and args. We'll skip them
+				for(int i = 0; i < 6; i++)
+				{
+					if(!NextTokenIs(",", false)) break;
+
+					// Read special name or arg value
+					if(!SkipWhitespace(true) || string.IsNullOrEmpty(ReadToken())) return false;
+				}
+
+				// Add to collection?
+				if(id != 0) doomednums[id] = classname.ToLowerInvariant();
+			}
+
+			// All done here
+			return true;
+		}
+
+		private bool ParseSpawnNums()
+		{
+			if(!NextTokenIs("{")) return false; // Finished with this file
+
+			while(SkipWhitespace(true))
+			{
+				string token = ReadToken();
+				if(string.IsNullOrEmpty(token))
+				{
+					ReportError("Failed to find the end of SpawnNums block");
+					return false; // Finished with this file
+				}
+
+				if(token == "}") break;
+
+				// First must be a number
+				int id;
+				if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out id))
+				{
+					// Not numeric!
+					ReportError("Expected SpawnNums number, but got '" + token + "'");
+					return false; // Finished with this file
+				}
+
+				// Then "="
+				if(!NextTokenIs("=")) return false; // Finished with this file
+
+				// Then actor class
+				SkipWhitespace(false);
+				token = StripTokenQuotes(ReadToken());
+				if(string.IsNullOrEmpty(token))
+				{
+					ReportError("Unable to get SpawnNums entry class definition");
+					return false;
+				}
+
+				// Add to collection
+				spawnnums[id] = token.ToLowerInvariant();
+			}
+
+			// All done here
+			return true;
+		}
+
+		#endregion
+
+		#region ================== Map block parsing
+
+		private bool ParseMapBlock()
+		{
+			bool classicformat = !NextTokenIs("{", false);
+			
+			// Track brace level
+			int bracelevel = 0;
+
+			// Parse required values
+			while(SkipWhitespace(true))
+			{
+				string token = ReadToken().ToLowerInvariant();
+				switch(token)
+				{
+					//TODO: are there any other blocks available in the classic format?..
+					case "map": case "defaultmap": case "adddefaultmap":
+						if(classicformat)
 						{
-							SkipWhitespace(true);
-							token = ReadToken();
+							// We parsed too greadily, step back
+							DataStream.Seek(-token.Length - 1, SeekOrigin.Current);
+
+							// Finished with this block
+							return true;
 						}
-
-						//get the color value
-						string colorVal = StripTokenQuotes(token).ToLowerInvariant().Replace(" ", "");
-						if(!string.IsNullOrEmpty(colorVal)) 
-						{
-							Color4 color = new Color4();
-							//try to get the color...
-							if(GetColor(colorVal, ref color)) 
-							{
-								if(fadeType == "fade")
-									mapinfo.FadeColor = color;
-								else
-									mapinfo.OutsideFogColor = color;
-							} 
-							else //...or not
-							{ 
-								ReportError("Failed to parse " + fadeType + " value from string '" + colorVal + "'");
-								return false;
-							}
-						} 
-						else 
-						{
-							ReportError("Expected " + fadeType + " color value");
-							return false;
-						}
-					}
-//vertwallshade or horizwallshade
-					else if(token == "vertwallshade" || token == "horizwallshade") 
-					{
-						string shadeType = token;
-						SkipWhitespace(true);
-						token = StripTokenQuotes(ReadToken());
-
-						//new format
-						if(token == "=") 
-						{
-							SkipWhitespace(true);
-							token = StripTokenQuotes(ReadToken());
-						}
-
-						int val = 0;
-						if(!ReadSignedInt(token, ref val)) 
-						{
-							// Not numeric!
-							ReportError("Expected " + shadeType + " value, but got '" + token + "'");
-							return false;
-						}
-
-						if(shadeType == "vertwallshade")
-							mapinfo.VertWallShade = General.Clamp(val, -255, 255);
 						else
-							mapinfo.HorizWallShade = General.Clamp(val, -255, 255);
-					}
-//fogdensity or outsidefogdensity
-					else if(token == "fogdensity" || token == "outsidefogdensity") 
-					{
-						string densityType = token;
-						SkipWhitespace(true);
-						token = StripTokenQuotes(ReadToken());
-
-						//new format
-						if(token == "=") 
 						{
-							SkipWhitespace(true);
-							token = StripTokenQuotes(ReadToken());
-						}
-
-						int val;
-						if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out val)) 
-						{
-							// Not numeric!
-							ReportError("Expected " + densityType + " value, but got '" + token + "'");
+							ReportError("Unexpected token \"" + token + "\"");
 							return false;
 						}
+					
+					case "sky2": case "sky1":
+						if(!ParseSky(token)) return false;
+						break;
 
-						if(densityType == "fogdensity")
-							mapinfo.FogDensity = (int)(1024 * (256.0f / val));
-						else
-							mapinfo.OutsideFogDensity = (int)(1024 * (256.0f / val));
-					}
-//doublesky
-					else if(token == "doublesky") 
-					{
+					case "outsidefog": case "fade":
+						if(!ParseFade(token)) return false;
+						break;
+
+					case "horizwallshade": case "vertwallshade":
+						if(!ParseWallShade(token)) return false;
+						break;
+
+					case "outsidefogdensity": case "fogdensity":
+						if(!ParseFogDensity(token)) return false;
+						break;
+
+					case "doublesky":
 						mapinfo.DoubleSky = true;
-					}
-//evenlighting
-					else if(token == "evenlighting") 
-					{
+						break;
+
+					case "evenlighting":
 						mapinfo.EvenLighting = true;
-					}
-//smoothlighting
-					else if(token == "smoothlighting") 
-					{
+						break;
+
+					case "smoothlighting":
 						mapinfo.SmoothLighting = true;
-					}
-//block end
-					else if(token == "}") 
-					{
-						return ParseBlock(token, clearerrors);
-					}
-//child block
-					else if(token == "{")
-					{
-						// Skip inner properties
+						break;
+
+					case "}": return true; // Block end
+
+					case "{": // Skip inner blocks
 						bracelevel++;
 						if(bracelevel > 1)
 						{
@@ -349,192 +450,206 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 								else if(token == "}") bracelevel--;
 							} while(!string.IsNullOrEmpty(token) && bracelevel > 1);
 						}
-					}
+						break;
 				}
 			}
-			else if(token == "include") // It's "include", not "#include". Cause fuck consistency.
+
+			// All done here
+			return true;
+		}
+
+		private bool ParseSky(string skytype)
+		{
+			SkipWhitespace(true);
+			string token = StripTokenQuotes(ReadToken());
+
+			// New format
+			if(token == "=")
 			{
 				SkipWhitespace(true);
-				string includelump = StripTokenQuotes(ReadToken(false)); // Don't skip newline
 
-				//INFO: ZDoom MAPINFO include paths can't be relative ("../mapstuff.txt") 
-				//or absolute ("d:/project/mapstuff.txt") 
-				//or have backward slashes ("info\mapstuff.txt")
-				//include paths are relative to the first parsed entry, not the current one 
-				//also include paths may or may not be quoted
-				if(!string.IsNullOrEmpty(includelump)) 
+				// Should be sky texture name
+				token = StripTokenQuotes(ReadToken());
+				bool gotcomma = (token.IndexOf(",", StringComparison.Ordinal) != -1);
+				if(gotcomma) token = token.Replace(",", "");
+				string skytexture = token.ToUpperInvariant();
+
+				if(string.IsNullOrEmpty(skytexture))
 				{
-					// Absolute paths are not supported...
-					if(Path.IsPathRooted(includelump))
-					{
-						ReportError("Absolute include paths are not supported by ZDoom");
-						return false;
-					}
-
-					// Relative paths are not supported
-					if(includelump.StartsWith(RELATIVE_PATH_MARKER) || includelump.StartsWith(CURRENT_FOLDER_PATH_MARKER) ||
-					   includelump.StartsWith(ALT_RELATIVE_PATH_MARKER) || includelump.StartsWith(ALT_CURRENT_FOLDER_PATH_MARKER))
-					{
-						ReportError("Relative include paths are not supported by ZDoom");
-						return false;
-					}
-
-					// Backward slashes are not supported
-					if(includelump.Contains(Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture)))
-					{
-						ReportError("Only forward slashes are supported by ZDoom");
-						return false;
-					}
-
-					// Already parsed?
-					if(parsedlumps.Contains(includelump))
-					{
-						ReportError("Already parsed '" + includelump + "'. Check your include directives");
-						return false;
-					}
-
-					// Add to collection
-					parsedlumps.Add(includelump);
-
-					// Callback to parse this file
-					if(OnInclude != null) OnInclude(this, includelump, clearerrors);
-
-					// Bail out on error
-					if(this.HasError) return false;
-
-					// Set our buffers back to continue parsing
-					datastream = localstream;
-					datareader = localreader;
-					sourcename = localsourcename;
-				} 
-				else 
-				{
-					ReportError("Expected filename to include");
+					ReportError("Expected " + skytype + " texture name");
 					return false;
 				}
-			}
-			else if(token == "gameinfo")
-			{
-				if(!NextTokenIs("{")) return false; // Finished with this file
 
-				while(SkipWhitespace(true))
+				if(skytype == "sky1")
+					mapinfo.Sky1 = skytexture;
+				else
+					mapinfo.Sky2 = skytexture;
+
+				// Check if we have scrollspeed
+				SkipWhitespace(true);
+				token = StripTokenQuotes(ReadToken());
+
+				if(!gotcomma && token == ",")
 				{
-					token = ReadToken();
-					if(string.IsNullOrEmpty(token))
-					{
-						ReportError("Failed to find the end of GameInfo block");
-						return false; // Finished with this file
-					}
-					if(token == "}") break;
-
-					if(token == "skyflatname")
-					{
-						if(!NextTokenIs("=")) return false; // Finished with this file
-						SkipWhitespace(true);
-						string skyflatname = StripTokenQuotes(ReadToken());
-						if(string.IsNullOrEmpty(skyflatname)) 
-						{
-							ReportError("Unable to get SkyFlatName value");
-							return false; // Finished with this file
-						}
-
-						General.Map.Config.SkyFlatName = skyflatname.ToUpperInvariant();
-					}
-				}
-			}
-			else if(token == "doomednums")
-			{
-				if(!NextTokenIs("{")) return false; // Finished with this file
-
-				while(SkipWhitespace(true)) 
-				{
-					token = ReadToken();
-					if(string.IsNullOrEmpty(token)) 
-					{
-						ReportError("Failed to find the end of DoomEdNums block");
-						return false; // Finished with this file
-					}
-					if(token == "}") break;
-
-					// First must be a number
-					int id;
-					if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out id)) 
-					{
-						// Not numeric!
-						ReportError("Expected DoomEdNums entry number, but got '" + token + "'");
-						return false; // Finished with this file
-					}
-
-					// Then "="
-					if(!NextTokenIs("=")) return false; // Finished with this file
-
-					// Then actor class
-					SkipWhitespace(false);
-					string classname = StripTokenQuotes(ReadToken());
-					if(string.IsNullOrEmpty(classname)) 
-					{
-						ReportError("Unable to get DoomEdNums entry class definition");
-						return false; // Finished with this file
-					}
-
-					// Possible special and args. We'll skip them
-					for(int i = 0; i < 6; i++)
-					{
-						if(!NextTokenIs(",", false)) break;
-
-						// Read special name or arg value
-						if(!SkipWhitespace(true) || string.IsNullOrEmpty(ReadToken())) return false;
-					}
-
-					// Add to collection?
-					if(id != 0) doomednums[id] = classname.ToLowerInvariant();
-				}
-			} 
-			else if(token == "spawnnums")
-			{
-				if(!NextTokenIs("{")) return false; // Finished with this file
-
-				while(SkipWhitespace(true))
-				{
-					token = ReadToken();
-					if(string.IsNullOrEmpty(token)) 
-					{
-						ReportError("Failed to find the end of SpawnNums block");
-						return false; // Finished with this file
-					}
-					if(token == "}") break;
-
-					// First must be a number
-					int id;
-					if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out id)) 
-					{
-						// Not numeric!
-						ReportError("Expected SpawnNums number, but got '" + token + "'");
-						return false; // Finished with this file
-					}
-
-					// Then "="
-					if(!NextTokenIs("=")) return false; // Finished with this file
-
-					// Then actor class
-					SkipWhitespace(false);
+					gotcomma = true;
+					SkipWhitespace(true);
 					token = StripTokenQuotes(ReadToken());
-					if(string.IsNullOrEmpty(token))
+				}
+
+				if(gotcomma)
+				{
+					float scrollspeed = 0;
+					if(!ReadSignedFloat(token, ref scrollspeed))
 					{
-						ReportError("Unable to get SpawnNums entry class definition");
+						// Not numeric!
+						ReportError("Expected " + skytype + " scroll speed value, but got '" + token + "'");
 						return false;
 					}
 
-					// Add to collection
-					spawnnums[id] = token.ToLowerInvariant();
+					if(skytype == "sky1")
+						mapinfo.Sky1ScrollSpeed = scrollspeed;
+					else
+						mapinfo.Sky2ScrollSpeed = scrollspeed;
 				}
-			} 
-			else if(token == "$gzdb_skip")
+				else
+				{
+					datastream.Seek(-token.Length - 1, SeekOrigin.Current); // Step back and try parsing this token again
+				}
+			}
+			// Old format
+			else
 			{
-				return true; // Finished with this file
+				// Token should be sky1/2 name
+				if(string.IsNullOrEmpty(token))
+				{
+					ReportError("Expected " + skytype + " texture name");
+					return false;
+				}
+
+				if(skytype == "sky1")
+					mapinfo.Sky1 = token.ToUpperInvariant();
+				else
+					mapinfo.Sky2 = token.ToUpperInvariant();
+
+				// Try to read scroll speed
+				SkipWhitespace(true);
+				token = StripTokenQuotes(ReadToken());
+
+				float scrollspeed = 0;
+				if(!ReadSignedFloat(token, ref scrollspeed))
+				{
+					// Not numeric!
+					datastream.Seek(-token.Length - 1, SeekOrigin.Current); // Step back and try parsing this token again
+					return true;
+				}
+
+				if(skytype == "sky1")
+					mapinfo.Sky1ScrollSpeed = scrollspeed;
+				else
+					mapinfo.Sky2ScrollSpeed = scrollspeed;
 			}
 
-			return false; // Not done yet
+			// All done here
+			return true;
+		}
+
+		private bool ParseFade(string fadetype)
+		{
+			SkipWhitespace(true);
+			string token = StripTokenQuotes(ReadToken());
+
+			// New format?
+			if(token == "=")
+			{
+				SkipWhitespace(true);
+				token = StripTokenQuotes(ReadToken());
+			}
+
+			// Get the color value
+			string colorval = StripTokenQuotes(token).ToLowerInvariant().Replace(" ", "");
+
+			if(string.IsNullOrEmpty(colorval))
+			{
+				ReportError("Expected " + fadetype + " color value");
+				return false;
+			}
+
+			Color4 color = new Color4();
+			
+			// Try to get the color...
+			if(GetColor(colorval, ref color))
+			{
+				if(fadetype == "fade")
+					mapinfo.FadeColor = color;
+				else
+					mapinfo.OutsideFogColor = color;
+			}
+			else //...or not
+			{
+				ReportError("Failed to parse " + fadetype + " value from string '" + colorval + "'");
+				return false;
+			}
+
+			// All done here
+			return true;
+		}
+
+		private bool ParseWallShade(string shadetype)
+		{
+			SkipWhitespace(true);
+			string token = StripTokenQuotes(ReadToken());
+
+			// New format
+			if(token == "=")
+			{
+				SkipWhitespace(true);
+				token = StripTokenQuotes(ReadToken());
+			}
+
+			int val = 0;
+			if(!ReadSignedInt(token, ref val))
+			{
+				// Not numeric!
+				ReportError("Expected " + shadetype + " value, but got '" + token + "'");
+				return false;
+			}
+
+			if(shadetype == "vertwallshade")
+				mapinfo.VertWallShade = General.Clamp(val, -255, 255);
+			else
+				mapinfo.HorizWallShade = General.Clamp(val, -255, 255);
+
+			// All done here
+			return true;
+		}
+
+		private bool ParseFogDensity(string densitytype)
+		{
+			SkipWhitespace(true);
+			string token = StripTokenQuotes(ReadToken());
+
+			// New format
+			if(token == "=")
+			{
+				SkipWhitespace(true);
+				token = StripTokenQuotes(ReadToken());
+			}
+
+			int val;
+			if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out val))
+			{
+				// Not numeric!
+				ReportError("Expected " + densitytype + " value, but got '" + token + "'");
+				return false;
+			}
+
+			if(densitytype == "fogdensity")
+				mapinfo.FogDensity = (int)(1024 * (256.0f / val));
+			else
+				mapinfo.OutsideFogDensity = (int)(1024 * (256.0f / val));
+
+			// All done here
+			return true;
 		}
 
 		#endregion
diff --git a/Source/Core/GZBuilder/GZGeneral.cs b/Source/Core/GZBuilder/GZGeneral.cs
index ea104c48..779c0669 100644
--- a/Source/Core/GZBuilder/GZGeneral.cs
+++ b/Source/Core/GZBuilder/GZGeneral.cs
@@ -2,6 +2,7 @@
 
 using CodeImp.DoomBuilder.Actions;
 using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Editing;
 using CodeImp.DoomBuilder.Windows;
 using CodeImp.DoomBuilder.GZBuilder.Data;
 
@@ -73,22 +74,40 @@ namespace CodeImp.DoomBuilder.GZBuilder
 		[BeginAction("gztogglelights")]
 		private static void ToggleLightsRenderingMode() 
 		{
-			switch(General.Settings.GZDrawLightsMode) 
+			if(General.Editing.Mode is ClassicMode)
 			{
-				case LightRenderMode.NONE:
-					General.Settings.GZDrawLightsMode = LightRenderMode.ALL;
-					General.MainWindow.DisplayStatus(StatusType.Action, "Dynamic lights rendering mode: ALL");
-					break;
+				switch(General.Settings.GZDrawLightsMode)
+				{
+					case LightRenderMode.NONE:
+						General.Settings.GZDrawLightsMode = LightRenderMode.ALL;
+						General.MainWindow.DisplayStatus(StatusType.Action, "Dynamic lights rendering mode: ALL");
+						break;
 
-				case LightRenderMode.ALL:
-					General.Settings.GZDrawLightsMode = LightRenderMode.ALL_ANIMATED;
-					General.MainWindow.DisplayStatus(StatusType.Action, "Models rendering mode: ANIMATED");
-					break;
+					default:
+						General.Settings.GZDrawLightsMode = LightRenderMode.NONE;
+						General.MainWindow.DisplayStatus(StatusType.Action, "Dynamic lights rendering mode: NONE");
+						break;
+				}
+			}
+			else
+			{
+				switch(General.Settings.GZDrawLightsMode)
+				{
+					case LightRenderMode.NONE:
+						General.Settings.GZDrawLightsMode = LightRenderMode.ALL;
+						General.MainWindow.DisplayStatus(StatusType.Action, "Dynamic lights rendering mode: ALL");
+						break;
 
-				case LightRenderMode.ALL_ANIMATED:
-					General.Settings.GZDrawLightsMode = LightRenderMode.NONE;
-					General.MainWindow.DisplayStatus(StatusType.Action, "Models rendering mode: NONE");
-					break;
+					case LightRenderMode.ALL:
+						General.Settings.GZDrawLightsMode = LightRenderMode.ALL_ANIMATED;
+						General.MainWindow.DisplayStatus(StatusType.Action, "Dynamic lights rendering mode: ANIMATED");
+						break;
+
+					case LightRenderMode.ALL_ANIMATED:
+						General.Settings.GZDrawLightsMode = LightRenderMode.NONE;
+						General.MainWindow.DisplayStatus(StatusType.Action, "Dynamic lights rendering mode: NONE");
+						break;
+				}
 			}
 			
 			General.MainWindow.RedrawDisplay();
@@ -104,17 +123,28 @@ namespace CodeImp.DoomBuilder.GZBuilder
 			General.MainWindow.UpdateGZDoomPanel();
 		}
 
+		[BeginAction("gztogglesky")]
+		private static void ToggleSky()
+		{
+			General.Settings.GZDrawSky = !General.Settings.GZDrawSky;
+			General.MainWindow.DisplayStatus(StatusType.Action, "Sky rendering is " + (General.Settings.GZDrawSky ? "ENABLED" : "DISABLED"));
+			General.MainWindow.RedrawDisplay();
+			General.MainWindow.UpdateGZDoomPanel();
+		}
+
 		[BeginAction("gztogglefx")]
 		private static void ToggleFx() 
 		{
 			int on = 0;
 			on += General.Settings.GZDrawFog ? 1 : -1;
+			on += General.Settings.GZDrawSky ? 1 : -1;
 			on += General.Settings.GZDrawLightsMode != LightRenderMode.NONE ? 1 : -1;
 			on += General.Settings.GZDrawModelsMode != ModelRenderMode.NONE ? 1 : -1;
 
 			bool enable = (on < 0);
 
 			General.Settings.GZDrawFog = enable;
+			General.Settings.GZDrawSky = enable;
 			General.Settings.GZDrawLightsMode = (enable ? LightRenderMode.ALL : LightRenderMode.NONE);
 			General.Settings.GZDrawModelsMode = (enable ? ModelRenderMode.ALL : ModelRenderMode.NONE);
 			General.MainWindow.DisplayStatus(StatusType.Action, "Advanced effects are " + (enable ? "ENABLED" : "DISABLED") );
diff --git a/Source/Core/General/General.cs b/Source/Core/General/General.cs
index e5ba1a53..9599e32f 100644
--- a/Source/Core/General/General.cs
+++ b/Source/Core/General/General.cs
@@ -1808,7 +1808,7 @@ namespace CodeImp.DoomBuilder
 		}
 
 		// This returns the next power of 2
-		public static int NextPowerOf2(int v)
+		/*public static int NextPowerOf2(int v)
 		{
 			int p = 0;
 
@@ -1817,6 +1817,20 @@ namespace CodeImp.DoomBuilder
 
 			// Return power
 			return (int)Math.Pow(2, p);
+		}*/
+
+		//mxd. This returns the next power of 2. Taken from http://bits.stephan-brumme.com/roundUpToNextPowerOfTwo.html
+		public static int NextPowerOf2(int x)
+		{
+			x--;
+			x |= x >> 1;  // handle  2 bit numbers
+			x |= x >> 2;  // handle  4 bit numbers
+			x |= x >> 4;  // handle  8 bit numbers
+			x |= x >> 8;  // handle 16 bit numbers
+			x |= x >> 16; // handle 32 bit numbers
+			x++;
+
+			return x;
 		}
 		
 		// Convert bool to integer
diff --git a/Source/Core/Geometry/Tools.cs b/Source/Core/Geometry/Tools.cs
index fc133d3e..2214e0ed 100644
--- a/Source/Core/Geometry/Tools.cs
+++ b/Source/Core/Geometry/Tools.cs
@@ -2127,6 +2127,7 @@ namespace CodeImp.DoomBuilder.Geometry
 		public static List GetDynamicLightShapes()
 		{
 			List circles = new List();
+			const int linealpha = 128;
 			foreach(Thing t in General.Map.Map.Things)
 			{
 				int lightid = Array.IndexOf(GZBuilder.GZGeneral.GZ_LIGHTS, t.Type);
@@ -2171,15 +2172,15 @@ namespace CodeImp.DoomBuilder.Geometry
 				switch(t.Type)
 				{
 					case 1502: // Vavoom light
-						color = new PixelColor(255, 255, 255, 255);
+						color = new PixelColor(linealpha, 255, 255, 255);
 						break;
 
 					case 1503: // Vavoom colored light
-						color = new PixelColor(255, (byte)t.Args[1], (byte)t.Args[2], (byte)t.Args[3]);
+						color = new PixelColor(linealpha, (byte)t.Args[1], (byte)t.Args[2], (byte)t.Args[3]);
 						break;
 
 					default:
-						color = new PixelColor(255, (byte)t.Args[0], (byte)t.Args[1], (byte)t.Args[2]);
+						color = new PixelColor(linealpha, (byte)t.Args[0], (byte)t.Args[1], (byte)t.Args[2]);
 						break;
 				}
 
diff --git a/Source/Core/Map/Linedef.cs b/Source/Core/Map/Linedef.cs
index 969dc159..eabc3d71 100644
--- a/Source/Core/Map/Linedef.cs
+++ b/Source/Core/Map/Linedef.cs
@@ -297,7 +297,7 @@ namespace CodeImp.DoomBuilder.Map
 		}
 		
 		// This copies all properties to another line
-		new public void CopyPropertiesTo(Linedef l)
+		public void CopyPropertiesTo(Linedef l)
 		{
 			l.BeforePropsChange();
 			
diff --git a/Source/Core/Properties/Resources.Designer.cs b/Source/Core/Properties/Resources.Designer.cs
index 5343bf50..96a42b66 100644
--- a/Source/Core/Properties/Resources.Designer.cs
+++ b/Source/Core/Properties/Resources.Designer.cs
@@ -1,7 +1,7 @@
 //------------------------------------------------------------------------------
 // 
 //     This code was generated by a tool.
-//     Runtime Version:2.0.50727.5485
+//     Runtime Version:2.0.50727.5466
 //
 //     Changes to this file may cause incorrect behavior and will be lost if
 //     the code is regenerated.
@@ -38,7 +38,7 @@ namespace CodeImp.DoomBuilder.Properties {
         [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
         internal static global::System.Resources.ResourceManager ResourceManager {
             get {
-                if(object.ReferenceEquals(resourceMan, null)) {
+                if (object.ReferenceEquals(resourceMan, null)) {
                     global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CodeImp.DoomBuilder.Properties.Resources", typeof(Resources).Assembly);
                     resourceMan = temp;
                 }
@@ -837,6 +837,13 @@ namespace CodeImp.DoomBuilder.Properties {
             }
         }
         
+        internal static System.Drawing.Bitmap Sky {
+            get {
+                object obj = ResourceManager.GetObject("Sky", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
         internal static System.Drawing.Bitmap SlimDX_small {
             get {
                 object obj = ResourceManager.GetObject("SlimDX_small", resourceCulture);
diff --git a/Source/Core/Properties/Resources.resx b/Source/Core/Properties/Resources.resx
index 6664860d..37c09402 100644
--- a/Source/Core/Properties/Resources.resx
+++ b/Source/Core/Properties/Resources.resx
@@ -544,4 +544,7 @@
   
     ..\Resources\GridIncrease.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
   
+  
+    ..\Resources\Sky.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+  
 
\ No newline at end of file
diff --git a/Source/Core/Rendering/Display2DShader.cs b/Source/Core/Rendering/Display2DShader.cs
index 680ead9d..48ad5f96 100644
--- a/Source/Core/Rendering/Display2DShader.cs
+++ b/Source/Core/Rendering/Display2DShader.cs
@@ -113,10 +113,10 @@ namespace CodeImp.DoomBuilder.Rendering
 		// This sets up the render pipeline
 		public override void BeginPass(int index)
 		{
-			Device device = manager.D3DDevice.Device;
-
 			if(!manager.Enabled)
 			{
+				Device device = manager.D3DDevice.Device;
+				
 				// Sampler settings
 				if(General.Settings.ClassicBilinear)
 				{
diff --git a/Source/Core/Rendering/Renderer3D.cs b/Source/Core/Rendering/Renderer3D.cs
index 789dee6c..5c3f7158 100644
--- a/Source/Core/Rendering/Renderer3D.cs
+++ b/Source/Core/Rendering/Renderer3D.cs
@@ -43,6 +43,9 @@ namespace CodeImp.DoomBuilder.Rendering
 		private const float FOG_RANGE = 0.9f;
 		internal const float GZDOOM_VERTICAL_VIEW_STRETCH = 1.2f;
 		internal const float GZDOOM_INVERTED_VERTICAL_VIEW_STRETCH = 1.0f / GZDOOM_VERTICAL_VIEW_STRETCH;
+
+		private const int SHADERPASS_LIGHT = 17; //mxd
+		private const int SHADERPASS_SKYBOX = 5; //mxd
 		
 		#endregion
 
@@ -92,6 +95,9 @@ namespace CodeImp.DoomBuilder.Rendering
 		//mxd. Translucent geometry to be rendered. Must be sorted by camera distance.
 		private List translucentgeo;
 
+		//mxd. Geometry to be rendered as skybox.
+		private List skygeo;
+
 		//mxd. Solid things to be rendered (currently(?) there won't be any). Must be sorted by sector.
 		private Dictionary> solidthings;
 
@@ -400,6 +406,7 @@ namespace CodeImp.DoomBuilder.Rendering
 			solidgeo = new Dictionary>(); //mxd
 			maskedgeo = new Dictionary>(); //mxd
 			translucentgeo = new List(); //mxd
+			skygeo = new List(); //mxd
 
 			solidthings = new Dictionary>(); //mxd
 			maskedthings = new Dictionary>(); //mxd
@@ -427,6 +434,14 @@ namespace CodeImp.DoomBuilder.Rendering
 			graphics.Device.SetRenderState(RenderState.TextureFactor, -1);
 			graphics.Shaders.World3D.Begin();
 
+			//mxd. SKY PASS
+			if(skygeo.Count > 0)
+			{
+				world = Matrix.Identity;
+				ApplyMatrices3D();
+				RenderSky(skygeo);
+			}
+
 			// SOLID PASS
 			world = Matrix.Identity;
 			ApplyMatrices3D();
@@ -510,6 +525,7 @@ namespace CodeImp.DoomBuilder.Rendering
 			solidgeo = null;
 			maskedgeo = null;
 			translucentgeo = null;
+			skygeo = null;
 
 			solidthings = null;
 			maskedthings = null;
@@ -951,6 +967,7 @@ namespace CodeImp.DoomBuilder.Rendering
 				// If the camera is inside a sector, compare z coordinates
 				translucentgeo.Sort(delegate(VisualGeometry vg1, VisualGeometry vg2)
 				{
+					if(vg1 == vg2) return 0;
 					float camdist1, camdist2;
 
 					if((vg1.GeometryType == VisualGeometryType.FLOOR || vg1.GeometryType == VisualGeometryType.CEILING)
@@ -978,8 +995,12 @@ namespace CodeImp.DoomBuilder.Rendering
 			}
 			else
 			{
-				translucentgeo.Sort((vg1, vg2) => (int)((General.Map.VisualCamera.Position - vg2.BoundingBox[0]).GetLengthSq()
-													  - (General.Map.VisualCamera.Position - vg1.BoundingBox[0]).GetLengthSq()));
+				translucentgeo.Sort(delegate(VisualGeometry vg1, VisualGeometry vg2)
+				{
+					if(vg1 == vg2) return 0;
+					return (int)((General.Map.VisualCamera.Position - vg2.BoundingBox[0]).GetLengthSq()
+					           - (General.Map.VisualCamera.Position - vg1.BoundingBox[0]).GetLengthSq());
+				});
 			}
 
 			ImageData curtexture;
@@ -1105,8 +1126,12 @@ namespace CodeImp.DoomBuilder.Rendering
 				graphics.Device.SetRenderState(RenderState.CullMode, Cull.None); //mxd. Disable backside culling, because otherwise sprites with positive ScaleY and negative ScaleX will be facing away from the camera...
 
 				// Sort geometry by camera distance. First vertex of the BoundingBox is it's center
-				thingspass.Sort((vt1, vt2) => (int)((General.Map.VisualCamera.Position - vt2.BoundingBox[0]).GetLengthSq()
-												  - (General.Map.VisualCamera.Position - vt1.BoundingBox[0]).GetLengthSq()));
+				thingspass.Sort(delegate(VisualThing vt1, VisualThing vt2)
+				{
+					if(vt1 == vt2) return 0;
+					return (int)((General.Map.VisualCamera.Position - vt2.BoundingBox[0]).GetLengthSq()
+							   - (General.Map.VisualCamera.Position - vt1.BoundingBox[0]).GetLengthSq());
+				});
 
 				// Reset vars
 				currentpass = RenderPass.Solid;
@@ -1279,7 +1304,7 @@ namespace CodeImp.DoomBuilder.Rendering
 			if(geometrytolit.Count == 0) return;
 			
 			graphics.Shaders.World3D.World = Matrix.Identity;
-			graphics.Shaders.World3D.BeginPass(17);
+			graphics.Shaders.World3D.BeginPass(SHADERPASS_LIGHT);
 
 			VisualSector sector = null;
 
@@ -1565,6 +1590,57 @@ namespace CodeImp.DoomBuilder.Rendering
 			graphics.Shaders.World3D.EndPass();
 		}
 
+		//mxd
+		private void RenderSky(IEnumerable geo)
+		{
+			VisualSector sector = null;
+			
+			// Set render settings
+			graphics.Shaders.World3D.BeginPass(SHADERPASS_SKYBOX);
+			graphics.Shaders.World3D.Texture1 = General.Map.Data.SkyBox;
+			graphics.Shaders.World3D.World = world;
+			graphics.Shaders.World3D.CameraPosition = new Vector4(cameraposition.x, cameraposition.y, cameraposition.z, 0f);
+
+			foreach(VisualGeometry g in geo)
+			{
+				// Changing sector?
+				if(!object.ReferenceEquals(g.Sector, sector))
+				{
+					// Update the sector if needed
+					if(g.Sector.NeedsUpdateGeo) g.Sector.Update();
+
+					// Only do this sector when a vertexbuffer is created
+					//mxd. No Map means that sector was deleted recently, I suppose
+					if(g.Sector.GeometryBuffer != null && g.Sector.Sector.Map != null)
+					{
+						// Change current sector
+						sector = g.Sector;
+
+						// Set stream source
+						graphics.Device.SetStreamSource(0, sector.GeometryBuffer, 0, WorldVertex.Stride);
+					}
+					else
+					{
+						sector = null;
+					}
+				}
+
+				if(sector != null)
+				{
+					// Set the colors to use
+					graphics.Shaders.World3D.HighlightColor = CalculateHighlightColor((g == highlighted) && showhighlight, (g.Selected && showselection));
+
+					// Apply changes
+					graphics.Shaders.World3D.ApplySettings();
+
+					// Render!
+					graphics.Device.DrawPrimitives(PrimitiveType.TriangleList, g.VertexOffset, g.Triangles);
+				}
+			}
+
+			graphics.Shaders.World3D.EndPass();
+		}
+
 		//mxd. This gets color from dynamic lights based on distance to thing. 
 		//thing position must be in absolute cordinates 
 		//(thing.Position.Z value is relative to floor of the sector the thing is in)
@@ -1595,6 +1671,7 @@ namespace CodeImp.DoomBuilder.Rendering
 		// This calculates the highlight/selection color
 		private Color4 CalculateHighlightColor(bool ishighlighted, bool isselected)
 		{
+			if(!ishighlighted && !isselected) return new Color4(); //mxd
 			Color4 highlightcolor = isselected ? General.Colors.Selection.ToColorValue() : General.Colors.Highlight.ToColorValue();
 			highlightcolor.Alpha = ishighlighted ? highlightglowinv : highlightglow;
 			return highlightcolor;
@@ -1627,24 +1704,34 @@ namespace CodeImp.DoomBuilder.Rendering
 			// Must have a texture and vertices
 			if(g.Texture != null && g.Triangles > 0)
 			{
-				switch(g.RenderPass)
+				if(g.RenderAsSky && General.Settings.GZDrawSky)
 				{
-					case RenderPass.Solid:
-						if(!solidgeo.ContainsKey(g.Texture)) solidgeo.Add(g.Texture, new List());
-						solidgeo[g.Texture].Add(g);
-						break;
+					skygeo.Add(g);
+				}
+				else
+				{
+					switch(g.RenderPass)
+					{
+						case RenderPass.Solid:
+							if(!solidgeo.ContainsKey(g.Texture))
+								solidgeo.Add(g.Texture, new List());
+							solidgeo[g.Texture].Add(g);
+							break;
 
-					case RenderPass.Mask:
-						if(!maskedgeo.ContainsKey(g.Texture)) maskedgeo.Add(g.Texture, new List());
-						maskedgeo[g.Texture].Add(g);
-						break;
+						case RenderPass.Mask:
+							if(!maskedgeo.ContainsKey(g.Texture))
+								maskedgeo.Add(g.Texture, new List());
+							maskedgeo[g.Texture].Add(g);
+							break;
 
-					case RenderPass.Additive: case RenderPass.Alpha:
-						translucentgeo.Add(g);
-						break;
+						case RenderPass.Additive:
+						case RenderPass.Alpha:
+							translucentgeo.Add(g);
+							break;
 
-					default:
-						throw new NotImplementedException("Geometry rendering of " + g.RenderPass + " render pass is not implemented!");
+						default:
+							throw new NotImplementedException("Geometry rendering of " + g.RenderPass + " render pass is not implemented!");
+					}
 				}
 			}
 		}
diff --git a/Source/Core/Rendering/Things2DShader.cs b/Source/Core/Rendering/Things2DShader.cs
index 51e78001..1f1fd495 100644
--- a/Source/Core/Rendering/Things2DShader.cs
+++ b/Source/Core/Rendering/Things2DShader.cs
@@ -137,10 +137,10 @@ namespace CodeImp.DoomBuilder.Rendering
 		// This sets up the render pipeline
 		public override void BeginPass(int index)
 		{
-			Device device = manager.D3DDevice.Device;
-
 			if(!manager.Enabled)
 			{
+				Device device = manager.D3DDevice.Device;
+				
 				// Sampler settings
 				if(General.Settings.ClassicBilinear)
 				{
diff --git a/Source/Core/Rendering/World3DShader.cs b/Source/Core/Rendering/World3DShader.cs
index 00b4db5b..a2d023ec 100644
--- a/Source/Core/Rendering/World3DShader.cs
+++ b/Source/Core/Rendering/World3DShader.cs
@@ -67,7 +67,7 @@ namespace CodeImp.DoomBuilder.Rendering
 			}
 		}
 
-		public Texture Texture1 { set { if(manager.Enabled) effect.SetTexture(texture1, value); settingschanged = true; } }
+		public BaseTexture Texture1 { set { if(manager.Enabled) effect.SetTexture(texture1, value); settingschanged = true; } }
 
 		//mxd
 		private Color4 vertexcolor;
@@ -184,7 +184,7 @@ namespace CodeImp.DoomBuilder.Rendering
 				lightPositionAndRadiusHandle = effect.GetParameter(null, "lightPosAndRadius");
 				lightColorHandle = effect.GetParameter(null, "lightColor");
 				//fog
-				camPosHandle = effect.GetParameter(null, "cameraPos");
+				camPosHandle = effect.GetParameter(null, "campos");
 
 				world = effect.GetParameter(null, "world");
 			}
@@ -285,10 +285,10 @@ namespace CodeImp.DoomBuilder.Rendering
 		// This sets up the render pipeline
 		public override void BeginPass(int index)
 		{
-			Device device = manager.D3DDevice.Device;
-
 			if(!manager.Enabled)
 			{
+				Device device = manager.D3DDevice.Device;
+				
 				// Sampler settings
 				if(General.Settings.VisualBilinear)
 				{
diff --git a/Source/Core/Resources/Actions.cfg b/Source/Core/Resources/Actions.cfg
index 08bb9c97..1e2d752c 100644
--- a/Source/Core/Resources/Actions.cfg
+++ b/Source/Core/Resources/Actions.cfg
@@ -1155,6 +1155,16 @@ gztogglefog
 	allowscroll = false;
 }
 
+gztogglesky
+{
+	title = "Toggle sky rendering";
+	category = "gzdoombuilder";
+	description = "Toggles sky rendering in Visual mode.";
+	allowkeys = true;
+	allowmouse = true;
+	allowscroll = false;
+}
+
 gztogglefx
 {
 	title = "Toggle models, dynamic lights and fog rendering";
diff --git a/Source/Core/Resources/MissingSky3D.png b/Source/Core/Resources/MissingSky3D.png
new file mode 100644
index 00000000..cb86d71e
Binary files /dev/null and b/Source/Core/Resources/MissingSky3D.png differ
diff --git a/Source/Core/Resources/Sky.png b/Source/Core/Resources/Sky.png
new file mode 100644
index 00000000..0d331c8f
Binary files /dev/null and b/Source/Core/Resources/Sky.png differ
diff --git a/Source/Core/Resources/world3d.fx b/Source/Core/Resources/world3d.fx
index 0710f0ab..256786b1 100644
--- a/Source/Core/Resources/world3d.fx
+++ b/Source/Core/Resources/world3d.fx
@@ -18,8 +18,21 @@ struct PixelData
 	float2 uv		  : TEXCOORD0;
 };
 
-//mxd
-// Pixel input data for light pass
+//mxd. Vertex input data for sky rendering
+struct SkyVertexData
+{
+	float3 pos		: POSITION;
+	float2 uv			: TEXCOORD0;
+};
+
+//mxd. Pixel input data for sky rendering
+struct SkyPixelData
+{
+	float4 pos		: POSITION;
+	float3 tex		: TEXCOORD0;
+};
+
+//mxd. Pixel input data for light pass
 struct LitPixelData
 {
 	float4 pos		  : POSITION;
@@ -44,7 +57,10 @@ float4 lightPosAndRadius;
 float4 lightColor; //also used as fog color
 
 //fog
-const float4 cameraPos;  //w is set to fade factor (distance, at wich fog color completely overrides pixel color)
+const float4 campos;  //w is set to fade factor (distance, at wich fog color completely overrides pixel color)
+
+//sky
+static const float4 skynormal = float4(0.0f, 1.0f, 0.0f, 0.0f);
 
 // Texture input
 const texture texture1;
@@ -66,6 +82,17 @@ sampler2D texturesamp = sampler_state
 	MaxAnisotropy = maxanisotropysetting;
 };
 
+//mxd. Skybox texture sampler settings
+samplerCUBE skysamp = sampler_state
+{
+	Texture = ;
+	MagFilter = magfiltersettings;
+	MinFilter = minfiltersettings;
+	MipFilter = mipfiltersettings;
+	MipMapLodBias = 0.0f;
+	MaxAnisotropy = maxanisotropysetting;
+};
+
 // Vertex shader
 PixelData vs_main(VertexData vd) 
 {
@@ -100,7 +127,7 @@ LitPixelData vs_customvertexcolor_fog(VertexData vd)
 	
 	// Fill pixel data input
 	pd.pos = mul(float4(vd.pos, 1.0f), worldviewproj);
-	pd.pos_w = mul(float4(vd.pos, 1.0f), world);
+	pd.pos_w = mul(float4(vd.pos, 1.0f), world).xyz;
 	pd.color = vertexColor;
 	pd.uv = vd.uv;
 	pd.normal = vd.normal;
@@ -114,7 +141,7 @@ LitPixelData vs_lightpass(VertexData vd)
 {
 	LitPixelData pd;
 	pd.pos = mul(float4(vd.pos, 1.0f), worldviewproj);
-	pd.pos_w = mul(float4(vd.pos, 1.0f), world);
+	pd.pos_w = mul(float4(vd.pos, 1.0f), world).xyz;
 	pd.color = vd.color;
 	pd.uv = vd.uv;
 	pd.normal = vd.normal;
@@ -165,8 +192,8 @@ float4 ps_fullbright_highlight(PixelData pd) : COLOR
 //mxd. This adds fog color to current pixel color
 float4 getFogColor(LitPixelData pd, float4 color)
 {
-	float fogdist = max(16.0f, distance(pd.pos_w, cameraPos.xyz));
-	float fogfactor = exp2(cameraPos.w * fogdist);
+	float fogdist = max(16.0f, distance(pd.pos_w, campos.xyz));
+	float fogfactor = exp2(campos.w * fogdist);
 
 	color.rgb = lerp(lightColor.rgb, color.rgb, fogfactor);
 	return color;
@@ -238,6 +265,23 @@ float4 ps_lightpass(LitPixelData pd) : COLOR
 	return lightColorMod; //should never get here
 }
 
+//mxd. Vertex skybox shader
+SkyPixelData vs_skybox(SkyVertexData vd)
+{
+	SkyPixelData pd;
+	pd.pos = mul(float4(vd.pos, 1.0f), worldviewproj);
+	float3 worldpos = mul(float4(vd.pos, 1.0f), world).xyz;
+	pd.tex = reflect(worldpos - campos.xyz, normalize(mul(skynormal, world).xyz));
+	return pd;
+}
+
+//mxd. Pixel skybox shader
+float4 ps_skybox(SkyPixelData pd) : COLOR
+{
+	float4 ncolor = texCUBE(skysamp, pd.tex);
+	return float4(highlightcolor.rgb * highlightcolor.a + (ncolor.rgb - 0.4f * highlightcolor.a), 1.0f);
+}
+
 // Technique for shader model 2.0
 technique SM20 
 {
@@ -277,7 +321,12 @@ technique SM20
 		PixelShader = compile ps_2_0 ps_main();
 	}
 	
-	pass p5 {} //mxd. need this only to maintain offset
+	//mxd. Skybox shader
+	pass p5 
+	{
+		VertexShader = compile vs_2_0 vs_skybox();
+		PixelShader  = compile ps_2_0 ps_skybox();
+	}
 	
 	// Normal with highlight
 	pass p6 
diff --git a/Source/Core/VisualModes/VisualGeometry.cs b/Source/Core/VisualModes/VisualGeometry.cs
index 19e7a52e..28380a6e 100644
--- a/Source/Core/VisualModes/VisualGeometry.cs
+++ b/Source/Core/VisualModes/VisualGeometry.cs
@@ -75,6 +75,7 @@ namespace CodeImp.DoomBuilder.VisualModes
 		private Vector3D[] boundingBox;
 		protected VisualGeometryType geometrytype;
 		protected string partname; //UDMF part name
+		protected bool renderassky;
 		
 		#endregion
 
@@ -89,6 +90,7 @@ namespace CodeImp.DoomBuilder.VisualModes
 		public Vector3D[] BoundingBox { get { return boundingBox; } }
 		public VisualGeometryType GeometryType { get { return geometrytype; } }
 		public float FogFactor { get { return fogfactor; } set { fogfactor = value; } }
+		public bool RenderAsSky { get { return renderassky; } }
 
 		/// 
 		/// Render pass in which this geometry must be rendered. Default is Solid.
@@ -131,7 +133,6 @@ namespace CodeImp.DoomBuilder.VisualModes
 		/// 
 		/// This creates visual geometry that is bound to a sidedef. This geometry is only visible when the sidedef is visible. It is automatically back-face culled during rendering and automatically XY intersection tested as well as back-face culled during object picking.
 		/// 
-		/// 
 		protected VisualGeometry(VisualSector vs, Sidedef sd)
 		{
 			this.sector = vs;
diff --git a/Source/Core/VisualModes/VisualVertex.cs b/Source/Core/VisualModes/VisualVertex.cs
index 42a089cb..ee9ec9b7 100644
--- a/Source/Core/VisualModes/VisualVertex.cs
+++ b/Source/Core/VisualModes/VisualVertex.cs
@@ -1,14 +1,14 @@
 using System;
+using CodeImp.DoomBuilder.Geometry;
 using CodeImp.DoomBuilder.Map;
 using SlimDX;
-using CodeImp.DoomBuilder.Geometry;
 
 namespace CodeImp.DoomBuilder.VisualModes
 {
 	public class VisualVertexPair
 	{
-		private VisualVertex floorvert;
-		private VisualVertex ceilvert;
+		private readonly VisualVertex floorvert;
+		private readonly VisualVertex ceilvert;
 
 		public VisualVertex[] Vertices { get { return new[] { floorvert, ceilvert }; } }
 		public VisualVertex FloorVertex { get { return floorvert; } }
@@ -37,18 +37,17 @@ namespace CodeImp.DoomBuilder.VisualModes
 		}
 	}
 
-	public abstract class VisualVertex : IVisualPickable, IComparable
+	public abstract class VisualVertex : IVisualPickable
 	{
 		//Constants
 		public const float DEFAULT_SIZE = 6.0f;
 		
 		//Variables
-		protected Vertex vertex;
+		protected readonly Vertex vertex;
 		private Matrix position;
-		private float cameradistance;
 		protected bool selected;
 		protected bool changed;
-		protected bool ceilingVertex;
+		protected readonly bool ceilingVertex;
 		protected bool haveOffset;
 
 		//Properties
@@ -90,13 +89,5 @@ namespace CodeImp.DoomBuilder.VisualModes
 		{
 			return false;
 		}
-
-		/// 
-		/// This sorts things by distance from the camera. Farthest first.
-		/// 
-		public int CompareTo(VisualVertex other) 
-		{
-			return Math.Sign(other.cameradistance - this.cameradistance);
-		}
 	}
 }
diff --git a/Source/Core/Windows/MainForm.Designer.cs b/Source/Core/Windows/MainForm.Designer.cs
index 1e639619..f45d93c4 100644
--- a/Source/Core/Windows/MainForm.Designer.cs
+++ b/Source/Core/Windows/MainForm.Designer.cs
@@ -196,6 +196,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.modelsshowfiltered = new System.Windows.Forms.ToolStripMenuItem();
 			this.modelsshowall = new System.Windows.Forms.ToolStripMenuItem();
 			this.buttontogglefog = new System.Windows.Forms.ToolStripButton();
+			this.buttontogglesky = new System.Windows.Forms.ToolStripButton();
 			this.buttontoggleeventlines = new System.Windows.Forms.ToolStripButton();
 			this.buttontogglevisualvertices = new System.Windows.Forms.ToolStripButton();
 			this.separatorgzmodes = new System.Windows.Forms.ToolStripSeparator();
@@ -1257,6 +1258,7 @@ namespace CodeImp.DoomBuilder.Windows
             this.dynamiclightmode,
             this.modelrendermode,
             this.buttontogglefog,
+			this.buttontogglesky,
             this.buttontoggleeventlines,
             this.buttontogglevisualvertices,
             this.separatorgzmodes,
@@ -1825,6 +1827,18 @@ namespace CodeImp.DoomBuilder.Windows
 			this.buttontogglefog.Text = "Toggle Fog Rendering";
 			this.buttontogglefog.Click += new System.EventHandler(this.InvokeTaggedAction);
 			// 
+			// buttontogglesky
+			// 
+			this.buttontogglesky.CheckOnClick = true;
+			this.buttontogglesky.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+			this.buttontogglesky.Image = global::CodeImp.DoomBuilder.Properties.Resources.Sky;
+			this.buttontogglesky.ImageTransparentColor = System.Drawing.Color.Magenta;
+			this.buttontogglesky.Name = "buttontogglesky";
+			this.buttontogglesky.Size = new System.Drawing.Size(23, 20);
+			this.buttontogglesky.Tag = "builder_gztogglesky";
+			this.buttontogglesky.Text = "Toggle Sky Rendering";
+			this.buttontogglesky.Click += new System.EventHandler(this.InvokeTaggedAction);
+			// 
 			// buttontoggleeventlines
 			// 
 			this.buttontoggleeventlines.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
@@ -2616,6 +2630,7 @@ namespace CodeImp.DoomBuilder.Windows
 		private System.Windows.Forms.ToolStripSeparator seperatorgeometry;
 		private System.Windows.Forms.ToolStripButton buttontogglefx;
 		private System.Windows.Forms.ToolStripButton buttontogglefog;
+		private System.Windows.Forms.ToolStripButton buttontogglesky;
 		private System.Windows.Forms.ToolStripStatusLabel warnsLabel;
 		private System.Windows.Forms.ToolStripMenuItem itemReloadModedef;
 		private System.Windows.Forms.ToolStripMenuItem itemReloadGldefs;
diff --git a/Source/Core/Windows/MainForm.cs b/Source/Core/Windows/MainForm.cs
index d1085a1d..23822067 100644
--- a/Source/Core/Windows/MainForm.cs
+++ b/Source/Core/Windows/MainForm.cs
@@ -420,8 +420,11 @@ namespace CodeImp.DoomBuilder.Windows
 			// Map opened?
 			if(General.Map != null)
 			{
+				// Get nice name
+				string maptitle = (!string.IsNullOrEmpty(General.Map.Data.MapInfo.Title) ? ": " + General.Map.Data.MapInfo.Title : "");
+				
 				// Show map name and filename in caption
-				this.Text = (mapchanged ? "\u25CF " : "") + General.Map.FileTitle + " (" + General.Map.Options.CurrentName + ") - " + Application.ProductName;
+				this.Text = (mapchanged ? "\u25CF " : "") + General.Map.FileTitle + " (" + General.Map.Options.CurrentName + maptitle + ") - " + Application.ProductName;
 			}
 			else
 			{
@@ -2035,6 +2038,7 @@ namespace CodeImp.DoomBuilder.Windows
 			dynamiclightmode.Visible = General.Settings.GZToolbarGZDoom && maploaded;
 			buttontogglefx.Visible = General.Settings.GZToolbarGZDoom && maploaded;
 			buttontogglefog.Visible = General.Settings.GZToolbarGZDoom && maploaded;
+			buttontogglesky.Visible = General.Settings.GZToolbarGZDoom && maploaded;
 			buttontoggleeventlines.Visible = General.Settings.GZToolbarGZDoom && maploaded;
 			buttontogglevisualvertices.Visible = General.Settings.GZToolbarGZDoom && maploaded;
 			separatorgzmodes.Visible = General.Settings.GZToolbarGZDoom && maploaded;
@@ -2188,6 +2192,7 @@ namespace CodeImp.DoomBuilder.Windows
 				}
 				
 				buttontogglefog.Checked = General.Settings.GZDrawFog;
+				buttontogglesky.Checked = General.Settings.GZDrawSky;
 				buttontoggleeventlines.Checked = General.Settings.GZShowEventLines;
 				buttontogglevisualvertices.Visible = General.Map.UDMF;
 				buttontogglevisualvertices.Checked = General.Settings.GZShowVisualVertices;
diff --git a/Source/Core/ZDoom/ZDTextParser.cs b/Source/Core/ZDoom/ZDTextParser.cs
index e28f31cf..710a6896 100644
--- a/Source/Core/ZDoom/ZDTextParser.cs
+++ b/Source/Core/ZDoom/ZDTextParser.cs
@@ -17,6 +17,7 @@
 #region ================== Namespaces
 
 using System;
+using System.Collections.Generic;
 using System.Globalization;
 using System.Text;
 using System.IO;
@@ -87,12 +88,21 @@ namespace CodeImp.DoomBuilder.ZDoom
 			//mxd. Clear error status?
 			if(clearerrors) ClearError();
 
-			//mxd. Integrity check
-			if(stream == null || stream.Length == 0)
+			//mxd. Integrity checks
+			if(stream == null)
 			{
-				ReportError("Unable to load '" + sourcefilename + "'");
+				ReportError("Unable to load \"" + sourcefilename + "\"");
 				return false;
 			}
+
+			if(stream.Length == 0)
+			{
+				if(!string.IsNullOrEmpty(sourcename))
+					LogWarning("Include file \"" + sourcefilename + "\" is empty");
+				else
+					LogWarning("File is empty");
+				return true;
+			}
 			
 			datastream = stream;
 			datareader = new BinaryReader(stream, Encoding.ASCII);
@@ -470,6 +480,35 @@ namespace CodeImp.DoomBuilder.ZDoom
 			if(success) value = val * sign;
 			return success;
 		}
+
+		//mxd
+		protected void SkipStructure() { SkipStructure(new HashSet()); }
+		protected void SkipStructure(HashSet breakat)
+		{
+			string token;
+			do
+			{
+				if(!SkipWhitespace(true)) break;
+				token = ReadToken();
+				if(string.IsNullOrEmpty(token)) break;
+				if(breakat.Contains(token))
+				{
+					DataStream.Seek(-token.Length - 1, SeekOrigin.Current);
+					return;
+				}
+			}
+			while(token != "{");
+			int scopelevel = 1;
+			do
+			{
+				if(!SkipWhitespace(true)) break;
+				token = ReadToken();
+				if(string.IsNullOrEmpty(token)) break;
+				if(token == "{") scopelevel++;
+				if(token == "}") scopelevel--;
+			}
+			while(scopelevel > 0);
+		}
 		
 		// This reports an error
 		protected internal void ReportError(string message)
@@ -603,7 +642,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 				switch(num)
 				{
 					case 34: case 60: case 62: case 124:
-						ReportError("unsupported character \"" + c + "\" in path \"" + path + "\".");
+						ReportError("Unsupported character \"" + c + "\" in path \"" + path + "\"");
 						return false;
 
 					default:
diff --git a/Source/Plugins/BuilderModes/IO/WavefrontExporter.cs b/Source/Plugins/BuilderModes/IO/WavefrontExporter.cs
index f66d84f3..0a306f5f 100644
--- a/Source/Plugins/BuilderModes/IO/WavefrontExporter.cs
+++ b/Source/Plugins/BuilderModes/IO/WavefrontExporter.cs
@@ -197,7 +197,7 @@ namespace CodeImp.DoomBuilder.BuilderModes.IO
 			BaseVisualMode mode = new BaseVisualMode();
 			bool renderingEffectsDisabled = false;
 
-			if(!BaseVisualMode.GZDoomRenderingEffects) 
+			if(!General.Settings.GZDoomRenderingEffects) 
 			{
 				renderingEffectsDisabled = true;
 				mode.ToggleGZDoomRenderingEffects();
diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs
index f8c33d67..c231860d 100644
--- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs
@@ -95,8 +95,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		private readonly List copybuffer;
 		private Type lasthighlighttype;
 
-		private static bool gzdoomRenderingEffects = true; //mxd
-
 		//mxd. Moved here from Tools
 		private struct SidedefAlignJob
 		{
@@ -159,7 +157,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		}
 
 		public object HighlightedTarget { get { return target.picked; } } //mxd
-		public static bool GZDoomRenderingEffects { get { return gzdoomRenderingEffects; } } //mxd
 		public bool UseSelectionFromClassicMode { get { return useSelectionFromClassicMode; } } //mxd
 
 		new public IRenderer3D Renderer { get { return renderer; } }
@@ -777,7 +774,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			//mxd
 			Sector[] sectorsWithEffects = null;
 
-			if(!gzdoomRenderingEffects) 
+			if(!General.Settings.GZDoomRenderingEffects) 
 			{
 				//store all sectors with effects
 				if(sectordata != null && sectordata.Count > 0) 
@@ -820,7 +817,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				vertices.Clear();
 			}
 
-			if(!gzdoomRenderingEffects) return; //mxd
+			if(!General.Settings.GZDoomRenderingEffects) return; //mxd
 			
 			// Find all sector who's tag is not 0 and hash them so that we can find them quicly
 			foreach(Sector s in General.Map.Map.Sectors)
@@ -1431,7 +1428,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			base.OnUndoEnd();
 
 			//mxd. Effects may've become invalid
-			if(gzdoomRenderingEffects && sectordata != null && sectordata.Count > 0)
+			if(General.Settings.GZDoomRenderingEffects && sectordata != null && sectordata.Count > 0)
 				RebuildElementData();
 
 			//mxd. As well as geometry...
@@ -3371,10 +3368,10 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		[BeginAction("togglegzdoomgeometryeffects")]
 		public void ToggleGZDoomRenderingEffects() 
 		{
-			gzdoomRenderingEffects = !gzdoomRenderingEffects;
+			General.Settings.GZDoomRenderingEffects = !General.Settings.GZDoomRenderingEffects;
 			RebuildElementData();
 			UpdateChangedObjects();
-			General.Interface.DisplayStatus(StatusType.Info, "(G)ZDoom geometry effects are " + (gzdoomRenderingEffects ? "ENABLED" : "DISABLED"));
+			General.Interface.DisplayStatus(StatusType.Info, "(G)ZDoom geometry effects are " + (General.Settings.GZDoomRenderingEffects ? "ENABLED" : "DISABLED"));
 		}
 
 		//mxd
diff --git a/Source/Plugins/BuilderModes/VisualModes/SectorData.cs b/Source/Plugins/BuilderModes/VisualModes/SectorData.cs
index a9170001..6ac4eef1 100644
--- a/Source/Plugins/BuilderModes/VisualModes/SectorData.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/SectorData.cs
@@ -327,9 +327,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 					SectorLevel l = lightlevels[i];
 					SectorLevel pl = lightlevels[i + 1];
 
-					// Glow levels should not affect light transfer
-					if(l.type == SectorLevelType.Glow) continue;
-
 					// Glow levels don't cast light
 					if(pl.type == SectorLevelType.Glow && lightlevels.Count > i + 2) pl = lightlevels[i + 2];
 
diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualCeiling.cs b/Source/Plugins/BuilderModes/VisualModes/VisualCeiling.cs
index c90ac301..983542a9 100644
--- a/Source/Plugins/BuilderModes/VisualModes/VisualCeiling.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/VisualCeiling.cs
@@ -117,43 +117,35 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			else
 				texscale = new Vector2D(1.0f / 64.0f, 1.0f / 64.0f);
 
-			//mxd. Sky is always bright
-			int color;
-			if(s.CeilTexture == General.Map.Config.SkyFlatName)
+			// Determine brightness
+			int color = PixelColor.FromInt(level.color).WithAlpha((byte)General.Clamp(level.alpha, 0, 255)).ToInt();
+
+			//mxd. Top extrafloor level should calculate fogdensity
+			//from the brightness of the level above it
+			int targetbrightness;
+			if(extrafloor != null && !extrafloor.VavoomType && !level.disablelighting)
 			{
-				color = -1; // That's white. With alpha. Not very impressive, eh?
-				fogfactor = 0; // No fog
+				targetbrightness = 0;
+				SectorData sd = mode.GetSectorData(this.Sector.Sector);
+				for(int i = 0; i < sd.LightLevels.Count - 1; i++)
+				{
+					if(sd.LightLevels[i] == level)
+					{
+						targetbrightness = sd.LightLevels[i + 1].brightnessbelow;
+						break;
+					}
+				}
 			}
 			else
 			{
-				color = PixelColor.FromInt(level.color).WithAlpha((byte)General.Clamp(level.alpha, 0, 255)).ToInt();
-
-				//mxd. Top extrafloor level should calculate fogdensity
-				//from the brightness of the level above it
-				int targetbrightness;
-				if(extrafloor != null && !extrafloor.VavoomType && !level.disablelighting)
-				{
-					targetbrightness = 0;
-					SectorData sd = mode.GetSectorData(this.Sector.Sector);
-					for(int i = 0; i < sd.LightLevels.Count - 1; i++)
-					{
-						if(sd.LightLevels[i] == level)
-						{
-							targetbrightness = sd.LightLevels[i + 1].brightnessbelow;
-							break;
-						}
-					}
-				}
-				else
-				{
-					targetbrightness = level.brightnessbelow;
-				}
-
-				fogfactor = CalculateFogDensity(targetbrightness);
+				targetbrightness = level.brightnessbelow;
 			}
 
+			//mxd. Determine fog density
+			fogfactor = CalculateFogDensity(targetbrightness);
+
 			// Make vertices
-			ReadOnlyCollection triverts = base.Sector.Sector.Triangles.Vertices;
+			ReadOnlyCollection triverts = Sector.Sector.Triangles.Vertices;
 			WorldVertex[] verts = new WorldVertex[triverts.Count];
 			for(int i = 0; i < triverts.Count; i++)
 			{
@@ -196,12 +188,32 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			{
 				this.RenderPass = RenderPass.Solid;
 			}
+
+			//mxd. Update sky render flag
+			UpdateSkyRenderFlag();
 			
 			// Apply vertices
 			base.SetVertices(verts);
 			return (verts.Length > 0);
 		}
-		
+
+		//mxd
+		private void UpdateSkyRenderFlag()
+		{
+			bool isrenderedassky = renderassky;
+			renderassky = (level.sector.CeilTexture == General.Map.Config.SkyFlatName);
+			if(isrenderedassky != renderassky && Sector.Sides != null)
+			{
+				// Upper/middle geometry may need updating...
+				foreach(Sidedef side in level.sector.Sidedefs)
+				{
+					VisualSidedefParts parts = Sector.GetSidedefParts(side);
+					if(parts.upper != null) parts.upper.UpdateSkyRenderFlag();
+					else if(parts.middlesingle != null) parts.middlesingle.UpdateSkyRenderFlag();
+				}
+			}
+		}
+
 		#endregion
 
 		#region ================== Methods
diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualFloor.cs b/Source/Plugins/BuilderModes/VisualModes/VisualFloor.cs
index 2dc3efd6..1e0ec9c3 100644
--- a/Source/Plugins/BuilderModes/VisualModes/VisualFloor.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/VisualFloor.cs
@@ -119,43 +119,35 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			else
 				texscale = new Vector2D(1.0f / 64.0f, 1.0f / 64.0f);
 
-			//mxd. Sky is always bright
-			int color;
-			if(s.FloorTexture == General.Map.Config.SkyFlatName)
+			// Determine brightness
+			int color = PixelColor.FromInt(level.color).WithAlpha((byte)General.Clamp(level.alpha, 0, 255)).ToInt();
+
+			//mxd. Top extrafloor level should calculate fogdensity
+			//from the brightness of the level above it
+			int targetbrightness;
+			if(extrafloor != null && extrafloor.VavoomType && !level.disablelighting)
 			{
-				color = -1; // That's white. With alpha. Not very impressive, eh?
-				fogfactor = 0; // No fog
+				targetbrightness = 0;
+				SectorData sd = mode.GetSectorData(this.Sector.Sector);
+				for(int i = 0; i < sd.LightLevels.Count - 1; i++)
+				{
+					if(sd.LightLevels[i] == level)
+					{
+						targetbrightness = sd.LightLevels[i + 1].brightnessbelow;
+						break;
+					}
+				}
 			}
 			else
 			{
-				color = PixelColor.FromInt(level.color).WithAlpha((byte)General.Clamp(level.alpha, 0, 255)).ToInt();
-
-				//mxd. Top extrafloor level should calculate fogdensity
-				//from the brightness of the level above it
-				int targetbrightness;
-				if(extrafloor != null && extrafloor.VavoomType && !level.disablelighting)
-				{
-					targetbrightness = 0;
-					SectorData sd = mode.GetSectorData(this.Sector.Sector);
-					for(int i = 0; i < sd.LightLevels.Count - 1; i++)
-					{
-						if(sd.LightLevels[i] == level)
-						{
-							targetbrightness = sd.LightLevels[i + 1].brightnessbelow;
-							break;
-						}
-					}
-				}
-				else
-				{
-					targetbrightness = level.brightnessbelow;
-				}
-
-				fogfactor = CalculateFogDensity(targetbrightness);
+				targetbrightness = level.brightnessbelow;
 			}
 
+			//mxd. Determine fog density
+			fogfactor = CalculateFogDensity(targetbrightness);
+
 			// Make vertices
-			ReadOnlyCollection triverts = base.Sector.Sector.Triangles.Vertices;
+			ReadOnlyCollection triverts = Sector.Sector.Triangles.Vertices;
 			WorldVertex[] verts = new WorldVertex[triverts.Count];
 			for(int i = 0; i < triverts.Count; i++)
 			{
@@ -198,12 +190,31 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			{
 				this.RenderPass = RenderPass.Solid;
 			}
+
+			//mxd. Update sky render flag
+			UpdateSkyRenderFlag();
 			
 			// Apply vertices
 			base.SetVertices(verts);
 			return (verts.Length > 0);
 		}
-		
+
+		//mxd
+		private void UpdateSkyRenderFlag()
+		{
+			bool isrenderedassky = renderassky;
+			renderassky = (level.sector.FloorTexture == General.Map.Config.SkyFlatName || level.sector.LongFloorTexture == MapSet.EmptyLongName);
+			if(isrenderedassky != renderassky && Sector.Sides != null)
+			{
+				// Middle geometry may need updating...
+				foreach(Sidedef side in level.sector.Sidedefs)
+				{
+					VisualSidedefParts parts = Sector.GetSidedefParts(side);
+					if(parts.middlesingle != null) parts.middlesingle.UpdateSkyRenderFlag();
+				}
+			}
+		}
+
 		#endregion
 		
 		#region ================== Methods
diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualMiddleSingle.cs b/Source/Plugins/BuilderModes/VisualModes/VisualMiddleSingle.cs
index 99f83faf..bf974713 100644
--- a/Source/Plugins/BuilderModes/VisualModes/VisualMiddleSingle.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/VisualMiddleSingle.cs
@@ -62,6 +62,9 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			Vector2D vl, vr;
 
+			//mxd. Apply sky hack?
+			UpdateSkyRenderFlag();
+
 			//mxd. lightfog flag support
 			int lightvalue;
 			bool lightabsolute;
@@ -201,7 +204,13 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			base.SetVertices(null); //mxd
 			return false;
 		}
-		
+
+		//mxd
+		internal void UpdateSkyRenderFlag()
+		{
+			renderassky = (Sidedef.LongMiddleTexture == MapSet.EmptyLongName && Sidedef.Sector != null && Sidedef.Sector.CeilTexture == General.Map.Config.SkyFlatName);
+		}
+
 		#endregion
 		
 		#region ================== Methods
diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualUpper.cs b/Source/Plugins/BuilderModes/VisualModes/VisualUpper.cs
index f9627b56..a131dc5c 100644
--- a/Source/Plugins/BuilderModes/VisualModes/VisualUpper.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/VisualUpper.cs
@@ -61,6 +61,9 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		public override bool Setup()
 		{
 			Vector2D vl, vr;
+
+			//mxd. Apply sky hack?
+			UpdateSkyRenderFlag();
 			
 			//mxd. lightfog flag support
 			int lightvalue;
@@ -196,6 +199,14 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			base.SetVertices(null); //mxd
 			return false;
 		}
+
+		//mxd
+		internal void UpdateSkyRenderFlag()
+		{
+			renderassky = (Sidedef.Other != null && Sidedef.Sector != null && Sidedef.Other.Sector != null &&
+						   Sidedef.Sector.CeilTexture == General.Map.Config.SkyFlatName &&
+						   Sidedef.Other.Sector.CeilTexture == General.Map.Config.SkyFlatName);
+		}
 		
 		#endregion