/* ** p_udmf.cpp ** ** UDMF text map parser ** **--------------------------------------------------------------------------- ** Copyright 2008 Christoph Oelckers ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **--------------------------------------------------------------------------- ** */ #include "doomstat.h" #include "p_setup.h" #include "p_lnspec.h" #include "templates.h" #include "i_system.h" #include "gi.h" #include "r_sky.h" #include "g_level.h" #include "v_palette.h" #include "p_udmf.h" #include "r_state.h" #include "r_data/colormaps.h" #include "w_wad.h" #include "p_tags.h" #include "p_terrain.h" #include "p_spec.h" #include "g_levellocals.h" #include "info.h" #include "vm.h" //=========================================================================== // // Maps for Hexen namespace need to filter out all extended line and sector // specials to comply with the spec. // //=========================================================================== static char HexenLineSpecialOk[]={ 1,1,1,1,1,1,1,1,1,0, // 0-9 1,1,1,1,0,0,0,0,0,0, // 10-19 1,1,1,1,1,1,1,1,1,1, // 20-29 1,1,1,0,0,1,1,0,0,0, // 30-39 1,1,1,1,1,1,1,0,0,0, // 40-49 0,0,0,0,0,0,0,0,0,0, // 50-59 1,1,1,1,1,1,1,1,1,1, // 60-69 1,1,1,1,1,1,0,0,0,0, // 70-79 1,1,1,1,0,0,0,0,0,0, // 80-89 1,1,1,1,1,1,1,0,0,0, // 90-99 1,1,1,1,0,0,0,0,0,1, // 100-109 1,1,1,1,1,1,1,0,0,0, // 110-119 1,0,0,0,0,0,0,0,0,1, // 120-129 1,1,1,1,1,1,1,1,1,0, // 130-139 1 // 140 is the highest valid special in Hexen. }; static char HexenSectorSpecialOk[256]={ 1,1,1,1,1,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,1,1,0,0, 0,0,0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1,1,1, 1,1,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,1,1, 1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1, }; static inline bool P_IsThingSpecial(int specnum) { return (specnum >= Thing_Projectile && specnum <= Thing_SpawnNoFog) || specnum == Thing_SpawnFacing || specnum == Thing_ProjectileIntercept || specnum == Thing_ProjectileAimed; } enum { Dm=1, Ht=2, Hx=4, St=8, Zd=16, Zdt=32, Va=64, // will be extended later. Unknown namespaces will always be treated like the base // namespace for each game }; void SetTexture (sector_t *sector, int index, int position, const char *name, FMissingTextureTracker &, bool truncate); void P_ProcessSideTextures(bool checktranmap, side_t *sd, sector_t *sec, intmapsidedef_t *msd, int special, int tag, short *alpha, FMissingTextureTracker &); void P_AdjustLine (line_t *ld); void P_FinishLoadingLineDef(line_t *ld, int alpha); void SpawnMapThing(int index, FMapThing *mt, int position); extern bool ForceNodeBuild; extern TArray MapThingsConverted; extern TArray linemap; #define CHECK_N(f) if (!(namespace_bits&(f))) break; //=========================================================================== // // Common parsing routines // //=========================================================================== //=========================================================================== // // Skip a key or block // //=========================================================================== void UDMFParserBase::Skip() { if (developer >= DMSG_WARNING) sc.ScriptMessage("Ignoring unknown UDMF key \"%s\".", sc.String); if(sc.CheckToken('{')) { int level = 1; while(sc.GetToken()) { if (sc.TokenType == '}') { level--; if(level == 0) { sc.UnGet(); break; } } else if (sc.TokenType == '{') { level++; } } } else { sc.MustGetToken('='); do { sc.MustGetAnyToken(); } while(sc.TokenType != ';'); } } //=========================================================================== // // Parses a 'key = value' line of the map // //=========================================================================== FName UDMFParserBase::ParseKey(bool checkblock, bool *isblock) { sc.MustGetString(); FName key = sc.String; if (checkblock) { if (sc.CheckToken('{')) { if (isblock) *isblock = true; return key; } else if (isblock) *isblock = false; } sc.MustGetToken('='); sc.Number = 0; sc.Float = 0; sc.MustGetAnyToken(); if (sc.TokenType == '+' || sc.TokenType == '-') { bool neg = (sc.TokenType == '-'); sc.MustGetAnyToken(); if (sc.TokenType != TK_IntConst && sc.TokenType != TK_FloatConst) { sc.ScriptMessage("Numeric constant expected"); } if (neg) { sc.Number = -sc.Number; sc.Float = -sc.Float; } } if (sc.TokenType == TK_StringConst) { parsedString = sc.String; } int savedtoken = sc.TokenType; sc.MustGetToken(';'); sc.TokenType = savedtoken; return key; } //=========================================================================== // // Syntax checks // //=========================================================================== int UDMFParserBase::CheckInt(const char *key) { if (sc.TokenType != TK_IntConst) { sc.ScriptMessage("Integer value expected for key '%s'", key); } return sc.Number; } double UDMFParserBase::CheckFloat(const char *key) { if (sc.TokenType != TK_IntConst && sc.TokenType != TK_FloatConst) { sc.ScriptMessage("Floating point value expected for key '%s'", key); } return sc.Float; } double UDMFParserBase::CheckCoordinate(const char *key) { if (sc.TokenType != TK_IntConst && sc.TokenType != TK_FloatConst) { sc.ScriptMessage("Floating point value expected for key '%s'", key); } if (sc.Float < -32768 || sc.Float > 32768) { sc.ScriptMessage("Value %f out of range for a coordinate '%s'. Valid range is ]-32768 .. 32768]", sc.Float, key); BadCoordinates = true; // If this happens the map must not allowed to be started. } return sc.Float; } DAngle UDMFParserBase::CheckAngle(const char *key) { return DAngle(CheckFloat(key)).Normalized360(); } bool UDMFParserBase::CheckBool(const char *key) { if (sc.TokenType == TK_True) return true; if (sc.TokenType == TK_False) return false; sc.ScriptMessage("Boolean value expected for key '%s'", key); return false; } const char *UDMFParserBase::CheckString(const char *key) { if (sc.TokenType != TK_StringConst) { sc.ScriptMessage("String value expected for key '%s'", key); } return parsedString; } //=========================================================================== // // Storage of UDMF user properties // //=========================================================================== typedef TMap FUDMFKeyMap; static FUDMFKeyMap UDMFKeys[4]; // Things must be handled differently void P_ClearUDMFKeys() { for(int i=0;i<4;i++) { UDMFKeys[i].Clear(); } } static int udmfcmp(const void *a, const void *b) { FUDMFKey *A = (FUDMFKey*)a; FUDMFKey *B = (FUDMFKey*)b; return int(A->Key) - int(B->Key); } void FUDMFKeys::Sort() { qsort(&(*this)[0], Size(), sizeof(FUDMFKey), udmfcmp); } FUDMFKey *FUDMFKeys::Find(FName key) { if (!mSorted) { mSorted = true; Sort(); } int min = 0, max = Size()-1; while (min <= max) { int mid = (min + max) / 2; if ((*this)[mid].Key == key) { return &(*this)[mid]; } else if ((*this)[mid].Key <= key) { min = mid + 1; } else { max = mid - 1; } } return NULL; } //=========================================================================== // // Retrieves UDMF user properties // //=========================================================================== int GetUDMFInt(int type, int index, FName key) { assert(type >=0 && type <=3); FUDMFKeys *pKeys = UDMFKeys[type].CheckKey(index); if (pKeys != NULL) { FUDMFKey *pKey = pKeys->Find(key); if (pKey != NULL) { return pKey->IntVal; } } return 0; } DEFINE_ACTION_FUNCTION(FLevelLocals, GetUDMFInt) { PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals); PARAM_INT(type); PARAM_INT(index); PARAM_NAME(key); ACTION_RETURN_INT(GetUDMFInt(type, index, key)); } double GetUDMFFloat(int type, int index, FName key) { assert(type >=0 && type <=3); FUDMFKeys *pKeys = UDMFKeys[type].CheckKey(index); if (pKeys != NULL) { FUDMFKey *pKey = pKeys->Find(key); if (pKey != NULL) { return pKey->FloatVal; } } return 0; } DEFINE_ACTION_FUNCTION(FLevelLocals, GetUDMFFloat) { PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals); PARAM_INT(type); PARAM_INT(index); PARAM_NAME(key); ACTION_RETURN_FLOAT(GetUDMFFloat(type, index, key)); } FString GetUDMFString(int type, int index, FName key) { assert(type >= 0 && type <= 3); FUDMFKeys *pKeys = UDMFKeys[type].CheckKey(index); if (pKeys != NULL) { FUDMFKey *pKey = pKeys->Find(key); if (pKey != NULL) { return pKey->StringVal; } } return ""; } DEFINE_ACTION_FUNCTION(FLevelLocals, GetUDMFString) { PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals); PARAM_INT(type); PARAM_INT(index); PARAM_NAME(key); ACTION_RETURN_STRING(GetUDMFString(type, index, key)); } //=========================================================================== // // UDMF parser // //=========================================================================== struct UDMFScroll { bool ceiling; int index; double x, y; FName type; }; class UDMFParser : public UDMFParserBase { bool isTranslated; bool isExtended; bool floordrop; TArray ParsedLines; TArray ParsedSides; TArray ParsedSideTextures; TArray ParsedSectors; TArray ParsedVertices; TArray UDMFScrollers; FDynamicColormap *fogMap = nullptr, *normMap = nullptr; FMissingTextureTracker &missingTex; public: UDMFParser(FMissingTextureTracker &missing) : missingTex(missing) { linemap.Clear(); } void ReadUserKey(FUDMFKey &ukey) { switch (sc.TokenType) { case TK_IntConst: ukey = sc.Number; break; case TK_FloatConst: ukey = sc.Float; break; default: case TK_StringConst: ukey = parsedString; break; case TK_True: ukey = 1; break; case TK_False: ukey = 0; break; } } void AddUserKey(FName key, int kind, int index) { FUDMFKeys &keyarray = UDMFKeys[kind][index]; for(unsigned i=0; i < keyarray.Size(); i++) { if (keyarray[i].Key == key) { ReadUserKey(keyarray[i]); return; } } FUDMFKey ukey; ukey.Key = key; ReadUserKey(ukey); keyarray.Push(ukey); } //=========================================================================== // // Parse a thing block // //=========================================================================== void ParseThing(FMapThing *th) { FString arg0str, arg1str; memset(th, 0, sizeof(*th)); th->Gravity = 1; th->RenderStyle = STYLE_Count; th->Alpha = -1; th->Health = 1; th->FloatbobPhase = -1; sc.MustGetToken('{'); while (!sc.CheckToken('}')) { FName key = ParseKey(); switch(key) { case NAME_Id: th->thingid = CheckInt(key); break; case NAME_X: th->pos.X = CheckCoordinate(key); break; case NAME_Y: th->pos.Y = CheckCoordinate(key); break; case NAME_Height: th->pos.Z = CheckCoordinate(key); break; case NAME_Angle: th->angle = (short)CheckInt(key); break; case NAME_Type: th->EdNum = (short)CheckInt(key); th->info = DoomEdMap.CheckKey(th->EdNum); break; case NAME_Conversation: CHECK_N(Zd | Zdt) th->Conversation = CheckInt(key); break; case NAME_Special: CHECK_N(Hx | Zd | Zdt | Va) th->special = CheckInt(key); break; case NAME_Gravity: CHECK_N(Zd | Zdt) th->Gravity = CheckFloat(key); break; case NAME_Arg0: case NAME_Arg1: case NAME_Arg2: case NAME_Arg3: case NAME_Arg4: CHECK_N(Hx | Zd | Zdt | Va) th->args[int(key)-int(NAME_Arg0)] = CheckInt(key); break; case NAME_Arg0Str: CHECK_N(Zd); arg0str = CheckString(key); th->arg0str = arg0str; break; case NAME_Arg1Str: CHECK_N(Zd); arg1str = CheckString(key); break; case NAME_Skill1: case NAME_Skill2: case NAME_Skill3: case NAME_Skill4: case NAME_Skill5: case NAME_Skill6: case NAME_Skill7: case NAME_Skill8: case NAME_Skill9: case NAME_Skill10: case NAME_Skill11: case NAME_Skill12: case NAME_Skill13: case NAME_Skill14: case NAME_Skill15: case NAME_Skill16: if (CheckBool(key)) th->SkillFilter |= (1<<(int(key)-NAME_Skill1)); else th->SkillFilter &= ~(1<<(int(key)-NAME_Skill1)); break; case NAME_Class1: case NAME_Class2: case NAME_Class3: case NAME_Class4: case NAME_Class5: case NAME_Class6: case NAME_Class7: case NAME_Class8: case NAME_Class9: case NAME_Class10: case NAME_Class11: case NAME_Class12: case NAME_Class13: case NAME_Class14: case NAME_Class15: case NAME_Class16: CHECK_N(Hx | Zd | Zdt | Va) if (CheckBool(key)) th->ClassFilter |= (1<<(int(key)-NAME_Class1)); else th->ClassFilter &= ~(1<<(int(key)-NAME_Class1)); break; case NAME_Ambush: Flag(th->flags, MTF_AMBUSH, key); break; case NAME_Dormant: CHECK_N(Hx | Zd | Zdt | Va) Flag(th->flags, MTF_DORMANT, key); break; case NAME_Single: Flag(th->flags, MTF_SINGLE, key); break; case NAME_Coop: Flag(th->flags, MTF_COOPERATIVE, key); break; case NAME_Dm: Flag(th->flags, MTF_DEATHMATCH, key); break; case NAME_Translucent: CHECK_N(St | Zd | Zdt | Va) Flag(th->flags, MTF_SHADOW, key); break; case NAME_Invisible: CHECK_N(St | Zd | Zdt | Va) Flag(th->flags, MTF_ALTSHADOW, key); break; case NAME_Friend: // This maps to Strife's friendly flag CHECK_N(Dm | Zd | Zdt | Va) Flag(th->flags, MTF_FRIENDLY, key); break; case NAME_Strifeally: CHECK_N(St | Zd | Zdt | Va) Flag(th->flags, MTF_FRIENDLY, key); break; case NAME_Standing: CHECK_N(St | Zd | Zdt | Va) Flag(th->flags, MTF_STANDSTILL, key); break; case NAME_Countsecret: CHECK_N(Zd | Zdt | Va) Flag(th->flags, MTF_SECRET, key); break; case NAME_Floatbobphase: CHECK_N(Zd | Zdt) th->FloatbobPhase = CheckInt(key); break; case NAME_Renderstyle: { FName style = CheckString(key); switch (style) { case NAME_None: th->RenderStyle = STYLE_None; break; case NAME_Normal: th->RenderStyle = STYLE_Normal; break; case NAME_Fuzzy: th->RenderStyle = STYLE_Fuzzy; break; case NAME_SoulTrans: th->RenderStyle = STYLE_SoulTrans; break; case NAME_OptFuzzy: th->RenderStyle = STYLE_OptFuzzy; break; case NAME_Stencil: th->RenderStyle = STYLE_Stencil; break; case NAME_AddStencil: th->RenderStyle = STYLE_AddStencil; break; case NAME_Translucent: th->RenderStyle = STYLE_Translucent; break; case NAME_Add: case NAME_Additive: th->RenderStyle = STYLE_Add; break; case NAME_Shaded: th->RenderStyle = STYLE_Shaded; break; case NAME_AddShaded: th->RenderStyle = STYLE_AddShaded; break; case NAME_TranslucentStencil: th->RenderStyle = STYLE_TranslucentStencil; break; case NAME_Shadow: th->RenderStyle = STYLE_Shadow; break; case NAME_Subtract: case NAME_Subtractive: th->RenderStyle = STYLE_Subtract; break; case NAME_ColorBlend: th->RenderStyle = STYLE_ColorBlend; break; case NAME_ColorAdd: th->RenderStyle = STYLE_ColorAdd; break; case NAME_Multiply: th->RenderStyle = STYLE_Multiply; break; default: break; } } break; case NAME_Alpha: th->Alpha = CheckFloat(key); break; case NAME_FillColor: th->fillcolor = CheckInt(key); break; case NAME_Health: th->Health = CheckFloat(key); break; case NAME_Score: th->score = CheckInt(key); break; case NAME_Pitch: th->pitch = (short)CheckInt(key); break; case NAME_Roll: th->roll = (short)CheckInt(key); break; case NAME_ScaleX: th->Scale.X = CheckFloat(key); break; case NAME_ScaleY: th->Scale.Y = CheckFloat(key); break; case NAME_Scale: th->Scale.X = th->Scale.Y = CheckFloat(key); break; case NAME_FriendlySeeBlocks: CHECK_N(Zd | Zdt) th->friendlyseeblocks = CheckInt(key); break; default: CHECK_N(Zd | Zdt) if (0 == strnicmp("user_", key.GetChars(), 5)) { // Custom user key - Sets an actor's user variable directly FUDMFKey ukey; ukey.Key = key; ReadUserKey(ukey); MapThingsUserData.Push(ukey); } break; } } if (arg0str.IsNotEmpty() && (P_IsACSSpecial(th->special) || th->special == 0)) { th->args[0] = -FName(arg0str); } if (arg1str.IsNotEmpty() && (P_IsThingSpecial(th->special) || th->special == 0)) { th->args[1] = -FName(arg1str); } // Thing specials are only valid in namespaces with Hexen-type specials // and in ZDoomTranslated - which will use the translator on them. if (namespc == NAME_ZDoomTranslated) { maplinedef_t mld; line_t ld; if (th->special != 0) // if special is 0, keep the args (e.g. for bridge things) { // The trigger type is ignored here. mld.flags = 0; mld.special = th->special; mld.tag = th->args[0]; P_TranslateLineDef(&ld, &mld); th->special = ld.special; memcpy(th->args, ld.args, sizeof (ld.args)); } } else if (isTranslated) { th->special = 0; memset(th->args, 0, sizeof (th->args)); } } //=========================================================================== // // Parse a linedef block // //=========================================================================== void ParseLinedef(line_t *ld, int index) { bool passuse = false; bool strifetrans = false; bool strifetrans2 = false; FString arg0str, arg1str; int lineid = -1; // forZDoomTranslated namespace FString tagstring; memset(ld, 0, sizeof(*ld)); ld->alpha = 1.; ld->portalindex = UINT_MAX; ld->portaltransferred = UINT_MAX; ld->sidedef[0] = ld->sidedef[1] = NULL; if (level.flags2 & LEVEL2_CLIPMIDTEX) ld->flags |= ML_CLIP_MIDTEX; if (level.flags2 & LEVEL2_WRAPMIDTEX) ld->flags |= ML_WRAP_MIDTEX; if (level.flags2 & LEVEL2_CHECKSWITCHRANGE) ld->flags |= ML_CHECKSWITCHRANGE; sc.MustGetToken('{'); while (!sc.CheckToken('}')) { FName key = ParseKey(); // This switch contains all keys of the UDMF base spec switch(key) { case NAME_V1: ld->v1 = (vertex_t*)(intptr_t)CheckInt(key); // must be relocated later continue; case NAME_V2: ld->v2 = (vertex_t*)(intptr_t)CheckInt(key); // must be relocated later continue; case NAME_Special: ld->special = CheckInt(key); if (namespc == NAME_Hexen) { if (ld->special < 0 || ld->special > 140 || !HexenLineSpecialOk[ld->special]) ld->special = 0; // NULL all specials which don't exist in Hexen } continue; case NAME_Id: lineid = CheckInt(key); tagManager.AddLineID(index, lineid); continue; case NAME_Sidefront: ld->sidedef[0] = (side_t*)(intptr_t)(1 + CheckInt(key)); continue; case NAME_Sideback: ld->sidedef[1] = (side_t*)(intptr_t)(1 + CheckInt(key)); continue; case NAME_Arg0: case NAME_Arg1: case NAME_Arg2: case NAME_Arg3: case NAME_Arg4: ld->args[int(key)-int(NAME_Arg0)] = CheckInt(key); continue; case NAME_Arg0Str: CHECK_N(Zd); arg0str = CheckString(key); continue; case NAME_Arg1Str: CHECK_N(Zd); arg1str = CheckString(key); continue; case NAME_Blocking: Flag(ld->flags, ML_BLOCKING, key); continue; case NAME_Blockmonsters: Flag(ld->flags, ML_BLOCKMONSTERS, key); continue; case NAME_Twosided: Flag(ld->flags, ML_TWOSIDED, key); continue; case NAME_Dontpegtop: Flag(ld->flags, ML_DONTPEGTOP, key); continue; case NAME_Dontpegbottom: Flag(ld->flags, ML_DONTPEGBOTTOM, key); continue; case NAME_Secret: Flag(ld->flags, ML_SECRET, key); continue; case NAME_Blocksound: Flag(ld->flags, ML_SOUNDBLOCK, key); continue; case NAME_Dontdraw: Flag(ld->flags, ML_DONTDRAW, key); continue; case NAME_Mapped: Flag(ld->flags, ML_MAPPED, key); continue; case NAME_Jumpover: CHECK_N(St | Zd | Zdt | Va) Flag(ld->flags, ML_RAILING, key); continue; case NAME_Blockfloaters: CHECK_N(St | Zd | Zdt | Va) Flag(ld->flags, ML_BLOCK_FLOATERS, key); continue; case NAME_Translucent: CHECK_N(St | Zd | Zdt | Va) strifetrans = CheckBool(key); continue; case NAME_Transparent: CHECK_N(St | Zd | Zdt | Va) strifetrans2 = CheckBool(key); continue; case NAME_Passuse: CHECK_N(Dm | Zd | Zdt | Va) passuse = CheckBool(key); continue; default: break; } // This switch contains all keys of the UDMF base spec which only apply to Hexen format specials if (!isTranslated) switch (key) { case NAME_Playercross: Flag(ld->activation, SPAC_Cross, key); continue; case NAME_Playeruse: Flag(ld->activation, SPAC_Use, key); continue; case NAME_Playeruseback: Flag(ld->activation, SPAC_UseBack, key); continue; case NAME_Monstercross: Flag(ld->activation, SPAC_MCross, key); continue; case NAME_Impact: Flag(ld->activation, SPAC_Impact, key); continue; case NAME_Playerpush: Flag(ld->activation, SPAC_Push, key); continue; case NAME_Missilecross: Flag(ld->activation, SPAC_PCross, key); continue; case NAME_Monsteruse: Flag(ld->activation, SPAC_MUse, key); continue; case NAME_Monsterpush: Flag(ld->activation, SPAC_MPush, key); continue; case NAME_Repeatspecial: Flag(ld->flags, ML_REPEAT_SPECIAL, key); continue; default: break; } // This switch contains all keys which are ZDoom specific if (namespace_bits & (Zd|Zdt|Va)) switch(key) { case NAME_Alpha: ld->setAlpha(CheckFloat(key)); continue; case NAME_Renderstyle: { const char *str = CheckString(key); if (!stricmp(str, "translucent")) ld->flags &= ~ML_ADDTRANS; else if (!stricmp(str, "add")) ld->flags |= ML_ADDTRANS; else sc.ScriptMessage("Unknown value \"%s\" for 'renderstyle'\n", str); continue; } case NAME_Anycross: Flag(ld->activation, SPAC_AnyCross, key); continue; case NAME_Monsteractivate: Flag(ld->flags, ML_MONSTERSCANACTIVATE, key); continue; case NAME_Blockplayers: Flag(ld->flags, ML_BLOCK_PLAYERS, key); continue; case NAME_Blockeverything: Flag(ld->flags, ML_BLOCKEVERYTHING, key); continue; case NAME_Zoneboundary: Flag(ld->flags, ML_ZONEBOUNDARY, key); continue; case NAME_Clipmidtex: Flag(ld->flags, ML_CLIP_MIDTEX, key); continue; case NAME_Wrapmidtex: Flag(ld->flags, ML_WRAP_MIDTEX, key); continue; case NAME_Midtex3d: Flag(ld->flags, ML_3DMIDTEX, key); continue; case NAME_Checkswitchrange: Flag(ld->flags, ML_CHECKSWITCHRANGE, key); continue; case NAME_Firstsideonly: Flag(ld->flags, ML_FIRSTSIDEONLY, key); continue; case NAME_blockprojectiles: Flag(ld->flags, ML_BLOCKPROJECTILE, key); continue; case NAME_blockuse: Flag(ld->flags, ML_BLOCKUSE, key); continue; case NAME_blocksight: Flag(ld->flags, ML_BLOCKSIGHT, key); continue; case NAME_blockhitscan: Flag(ld->flags, ML_BLOCKHITSCAN, key); continue; // [TP] Locks the special with a key case NAME_Locknumber: ld->locknumber = CheckInt(key); continue; // [TP] Causes a 3d midtex to behave like an impassible line case NAME_Midtex3dimpassible: Flag(ld->flags, ML_3DMIDTEX_IMPASS, key); continue; case NAME_Revealed: Flag(ld->flags, ML_REVEALED, key); continue; case NAME_AutomapStyle: ld->automapstyle = AutomapLineStyle(CheckInt(key)); continue; case NAME_MoreIds: // delay parsing of the tag string until parsing of the sector is complete // This ensures that the ID is always the first tag in the list. tagstring = CheckString(key); break; case NAME_Health: ld->health = CheckInt(key); break; case NAME_DamageSpecial: Flag(ld->activation, SPAC_Damage, key); break; case NAME_DeathSpecial: Flag(ld->activation, SPAC_Death, key); break; case NAME_HealthGroup: ld->healthgroup = CheckInt(key); break; default: break; } if ((namespace_bits & (Zd | Zdt)) && !strnicmp("user_", key.GetChars(), 5)) { AddUserKey(key, UDMF_Line, index); } } if (tagstring.IsNotEmpty()) { FScanner sc; sc.OpenString("tagstring", tagstring); // scan the string as long as valid numbers can be found while (sc.CheckNumber()) { if (sc.Number != 0) tagManager.AddLineID(index, sc.Number); } } if (isTranslated) { int saved = ld->flags; maplinedef_t mld; memset(&mld, 0, sizeof(mld)); mld.special = ld->special; mld.tag = ld->args[0]; P_TranslateLineDef(ld, &mld); ld->flags = saved | (ld->flags&(ML_MONSTERSCANACTIVATE|ML_REPEAT_SPECIAL|ML_FIRSTSIDEONLY)); } if (passuse && (ld->activation & SPAC_Use)) { ld->activation = (ld->activation & ~SPAC_Use) | SPAC_UseThrough; } if (strifetrans && ld->alpha == 1.) { ld->alpha = 0.75; } if (strifetrans2 && ld->alpha == 1.) { ld->alpha = 0.25; } if (ld->sidedef[0] == NULL) { ld->sidedef[0] = (side_t*)(intptr_t)(1); Printf("Line %d has no first side.\n", index); } if (arg0str.IsNotEmpty() && (P_IsACSSpecial(ld->special) || ld->special == 0)) { ld->args[0] = -FName(arg0str); } if (arg1str.IsNotEmpty() && (P_IsThingSpecial(ld->special) || ld->special == 0)) { ld->args[1] = -FName(arg1str); } if ((ld->flags & ML_3DMIDTEX_IMPASS) && !(ld->flags & ML_3DMIDTEX)) // [TP] { Printf ("Line %d has midtex3dimpassible without midtex3d.\n", index); } } //=========================================================================== // // Parse a sidedef block // //=========================================================================== void ParseSidedef(side_t *sd, intmapsidedef_t *sdt, int index) { double texOfs[2]={0,0}; memset(sd, 0, sizeof(*sd)); sdt->bottomtexture = "-"; sdt->toptexture = "-"; sdt->midtexture = "-"; sd->SetTextureXScale(1.); sd->SetTextureYScale(1.); sd->UDMFIndex = index; sc.MustGetToken('{'); while (!sc.CheckToken('}')) { FName key = ParseKey(); switch(key) { case NAME_Offsetx: texOfs[0] = CheckInt(key); continue; case NAME_Offsety: texOfs[1] = CheckInt(key); continue; case NAME_Texturetop: sdt->toptexture = CheckString(key); continue; case NAME_Texturebottom: sdt->bottomtexture = CheckString(key); continue; case NAME_Texturemiddle: sdt->midtexture = CheckString(key); continue; case NAME_Sector: sd->sector = (sector_t*)(intptr_t)CheckInt(key); continue; default: break; } if (namespace_bits & (Zd|Zdt|Va)) switch(key) { case NAME_offsetx_top: sd->SetTextureXOffset(side_t::top, CheckFloat(key)); continue; case NAME_offsety_top: sd->SetTextureYOffset(side_t::top, CheckFloat(key)); continue; case NAME_offsetx_mid: sd->SetTextureXOffset(side_t::mid, CheckFloat(key)); continue; case NAME_offsety_mid: sd->SetTextureYOffset(side_t::mid, CheckFloat(key)); continue; case NAME_offsetx_bottom: sd->SetTextureXOffset(side_t::bottom, CheckFloat(key)); continue; case NAME_offsety_bottom: sd->SetTextureYOffset(side_t::bottom, CheckFloat(key)); continue; case NAME_scalex_top: sd->SetTextureXScale(side_t::top, CheckFloat(key)); continue; case NAME_scaley_top: sd->SetTextureYScale(side_t::top, CheckFloat(key)); continue; case NAME_scalex_mid: sd->SetTextureXScale(side_t::mid, CheckFloat(key)); continue; case NAME_scaley_mid: sd->SetTextureYScale(side_t::mid, CheckFloat(key)); continue; case NAME_scalex_bottom: sd->SetTextureXScale(side_t::bottom, CheckFloat(key)); continue; case NAME_scaley_bottom: sd->SetTextureYScale(side_t::bottom, CheckFloat(key)); continue; case NAME_light: sd->SetLight(CheckInt(key)); continue; case NAME_lightabsolute: Flag(sd->Flags, WALLF_ABSLIGHTING, key); continue; case NAME_lightfog: Flag(sd->Flags, WALLF_LIGHT_FOG, key); continue; case NAME_nofakecontrast: Flag(sd->Flags, WALLF_NOFAKECONTRAST, key); continue; case NAME_smoothlighting: Flag(sd->Flags, WALLF_SMOOTHLIGHTING, key); continue; case NAME_Wrapmidtex: Flag(sd->Flags, WALLF_WRAP_MIDTEX, key); continue; case NAME_Clipmidtex: Flag(sd->Flags, WALLF_CLIP_MIDTEX, key); continue; case NAME_Nodecals: Flag(sd->Flags, WALLF_NOAUTODECALS, key); continue; case NAME_nogradient_top: Flag(sd->textures[side_t::top].flags, side_t::part::NoGradient, key); break; case NAME_flipgradient_top: Flag(sd->textures[side_t::top].flags, side_t::part::FlipGradient, key); break; case NAME_clampgradient_top: Flag(sd->textures[side_t::top].flags, side_t::part::ClampGradient, key); break; case NAME_useowncolors_top: Flag(sd->textures[side_t::top].flags, side_t::part::UseOwnColors, key); break; case NAME_uppercolor_top: sd->SetSpecialColor(side_t::top, 0, CheckInt(key)); break; case NAME_lowercolor_top: sd->SetSpecialColor(side_t::top, 1, CheckInt(key)); break; case NAME_nogradient_mid: Flag(sd->textures[side_t::mid].flags, side_t::part::NoGradient, key); break; case NAME_flipgradient_mid: Flag(sd->textures[side_t::mid].flags, side_t::part::FlipGradient, key); break; case NAME_clampgradient_mid: Flag(sd->textures[side_t::mid].flags, side_t::part::ClampGradient, key); break; case NAME_useowncolors_mid: Flag(sd->textures[side_t::mid].flags, side_t::part::UseOwnColors, key); break; case NAME_uppercolor_mid: sd->SetSpecialColor(side_t::mid, 0, CheckInt(key)); break; case NAME_lowercolor_mid: sd->SetSpecialColor(side_t::mid, 1, CheckInt(key)); break; case NAME_nogradient_bottom: Flag(sd->textures[side_t::bottom].flags, side_t::part::NoGradient, key); break; case NAME_flipgradient_bottom: Flag(sd->textures[side_t::bottom].flags, side_t::part::FlipGradient, key); break; case NAME_clampgradient_bottom: Flag(sd->textures[side_t::bottom].flags, side_t::part::ClampGradient, key); break; case NAME_useowncolors_bottom: Flag(sd->textures[side_t::bottom].flags, side_t::part::UseOwnColors, key); break; case NAME_uppercolor_bottom: sd->SetSpecialColor(side_t::bottom, 0, CheckInt(key)); break; case NAME_lowercolor_bottom: sd->SetSpecialColor(side_t::bottom, 1, CheckInt(key)); break; default: break; } if ((namespace_bits & (Zd | Zdt)) && !strnicmp("user_", key.GetChars(), 5)) { AddUserKey(key, UDMF_Side, index); } } // initialization of these is delayed to allow separate offsets and add them with the global ones. sd->AddTextureXOffset(side_t::top, texOfs[0]); sd->AddTextureXOffset(side_t::mid, texOfs[0]); sd->AddTextureXOffset(side_t::bottom, texOfs[0]); sd->AddTextureYOffset(side_t::top, texOfs[1]); sd->AddTextureYOffset(side_t::mid, texOfs[1]); sd->AddTextureYOffset(side_t::bottom, texOfs[1]); } //=========================================================================== // // parse a sector block // //=========================================================================== void ParseSector(sector_t *sec, int index) { PalEntry lightcolor = -1; PalEntry fadecolor = -1; int fogdensity = -1; int desaturation = -1; int fplaneflags = 0, cplaneflags = 0; double fp[4] = { 0 }, cp[4] = { 0 }; FString tagstring; // Brand new UDMF scroller properties double scroll_ceil_x = 0; double scroll_ceil_y = 0; FName scroll_ceil_type = NAME_None; double scroll_floor_x = 0; double scroll_floor_y = 0; FName scroll_floor_type = NAME_None; memset(sec, 0, sizeof(*sec)); sec->lightlevel = 160; sec->SetXScale(sector_t::floor, 1.); // [RH] floor and ceiling scaling sec->SetYScale(sector_t::floor, 1.); sec->SetXScale(sector_t::ceiling, 1.); sec->SetYScale(sector_t::ceiling, 1.); sec->SetAlpha(sector_t::floor, 1.); sec->SetAlpha(sector_t::ceiling, 1.); sec->thinglist = nullptr; sec->touching_thinglist = nullptr; // phares 3/14/98 sec->sectorportal_thinglist = nullptr; sec->touching_renderthings = nullptr; sec->seqType = (level.flags & LEVEL_SNDSEQTOTALCTRL) ? 0 : -1; sec->nextsec = -1; //jff 2/26/98 add fields to support locking out sec->prevsec = -1; // stair retriggering until build completes sec->heightsec = NULL; // sector used to get floor and ceiling height sec->sectornum = index; sec->damageinterval = 32; sec->terrainnum[sector_t::ceiling] = sec->terrainnum[sector_t::floor] = -1; memset(sec->SpecialColors, -1, sizeof(sec->SpecialColors)); if (floordrop) sec->Flags = SECF_FLOORDROP; // killough 3/7/98: end changes sec->gravity = 1.; // [RH] Default sector gravity of 1.0 sec->ZoneNumber = 0xFFFF; // killough 8/28/98: initialize all sectors to normal friction sec->friction = ORIG_FRICTION; sec->movefactor = ORIG_FRICTION_FACTOR; sc.MustGetToken('{'); while (!sc.CheckToken('}')) { FName key = ParseKey(); switch(key) { case NAME_Heightfloor: sec->SetPlaneTexZ(sector_t::floor, CheckCoordinate(key)); continue; case NAME_Heightceiling: sec->SetPlaneTexZ(sector_t::ceiling, CheckCoordinate(key)); continue; case NAME_Texturefloor: SetTexture(sec, index, sector_t::floor, CheckString(key), missingTex, false); continue; case NAME_Textureceiling: SetTexture(sec, index, sector_t::ceiling, CheckString(key), missingTex, false); continue; case NAME_Lightlevel: sec->lightlevel = sector_t::ClampLight(CheckInt(key)); continue; case NAME_Special: sec->special = (short)CheckInt(key); if (isTranslated) sec->special = P_TranslateSectorSpecial(sec->special); else if (namespc == NAME_Hexen) { if (sec->special < 0 || sec->special > 140 || !HexenSectorSpecialOk[sec->special]) sec->special = 0; // NULL all unknown specials } continue; case NAME_Id: tagManager.AddSectorTag(index, CheckInt(key)); continue; default: break; } if (namespace_bits & (Zd|Zdt|Va)) switch(key) { case NAME_Xpanningfloor: sec->SetXOffset(sector_t::floor, CheckFloat(key)); continue; case NAME_Ypanningfloor: sec->SetYOffset(sector_t::floor, CheckFloat(key)); continue; case NAME_Xpanningceiling: sec->SetXOffset(sector_t::ceiling, CheckFloat(key)); continue; case NAME_Ypanningceiling: sec->SetYOffset(sector_t::ceiling, CheckFloat(key)); continue; case NAME_Xscalefloor: sec->SetXScale(sector_t::floor, CheckFloat(key)); continue; case NAME_Yscalefloor: sec->SetYScale(sector_t::floor, CheckFloat(key)); continue; case NAME_Xscaleceiling: sec->SetXScale(sector_t::ceiling, CheckFloat(key)); continue; case NAME_Yscaleceiling: sec->SetYScale(sector_t::ceiling, CheckFloat(key)); continue; case NAME_Rotationfloor: sec->SetAngle(sector_t::floor, CheckAngle(key)); continue; case NAME_Rotationceiling: sec->SetAngle(sector_t::ceiling, CheckAngle(key)); continue; case NAME_Lightfloor: sec->SetPlaneLight(sector_t::floor, CheckInt(key)); continue; case NAME_Lightceiling: sec->SetPlaneLight(sector_t::ceiling, CheckInt(key)); continue; case NAME_Alphafloor: sec->SetAlpha(sector_t::floor, CheckFloat(key)); continue; case NAME_Alphaceiling: sec->SetAlpha(sector_t::ceiling, CheckFloat(key)); continue; case NAME_Renderstylefloor: { const char *str = CheckString(key); if (!stricmp(str, "translucent")) sec->ChangeFlags(sector_t::floor, PLANEF_ADDITIVE, 0); else if (!stricmp(str, "add")) sec->ChangeFlags(sector_t::floor, 0, PLANEF_ADDITIVE); else sc.ScriptMessage("Unknown value \"%s\" for 'renderstylefloor'\n", str); continue; } case NAME_Renderstyleceiling: { const char *str = CheckString(key); if (!stricmp(str, "translucent")) sec->ChangeFlags(sector_t::ceiling, PLANEF_ADDITIVE, 0); else if (!stricmp(str, "add")) sec->ChangeFlags(sector_t::ceiling, 0, PLANEF_ADDITIVE); else sc.ScriptMessage("Unknown value \"%s\" for 'renderstyleceiling'\n", str); continue; } case NAME_Lightfloorabsolute: if (CheckBool(key)) sec->ChangeFlags(sector_t::floor, 0, PLANEF_ABSLIGHTING); else sec->ChangeFlags(sector_t::floor, PLANEF_ABSLIGHTING, 0); continue; case NAME_Lightceilingabsolute: if (CheckBool(key)) sec->ChangeFlags(sector_t::ceiling, 0, PLANEF_ABSLIGHTING); else sec->ChangeFlags(sector_t::ceiling, PLANEF_ABSLIGHTING, 0); continue; case NAME_Gravity: sec->gravity = CheckFloat(key); continue; case NAME_Lightcolor: lightcolor = CheckInt(key); continue; case NAME_Fadecolor: fadecolor = CheckInt(key); continue; case NAME_Color_Floor: sec->SpecialColors[sector_t::floor] = CheckInt(key) | 0xff000000; break; case NAME_Color_Ceiling: sec->SpecialColors[sector_t::ceiling] = CheckInt(key) | 0xff000000; break; case NAME_Color_Walltop: sec->SpecialColors[sector_t::walltop] = CheckInt(key) | 0xff000000; break; case NAME_Color_Wallbottom: sec->SpecialColors[sector_t::wallbottom] = CheckInt(key) | 0xff000000; break; case NAME_Color_Sprites: sec->SpecialColors[sector_t::sprites] = CheckInt(key) | 0xff000000; break; case NAME_Desaturation: desaturation = int(255*CheckFloat(key) + FLT_EPSILON); // FLT_EPSILON to avoid rounding errors with numbers slightly below a full integer. continue; case NAME_fogdensity: fogdensity = CheckInt(key); break; case NAME_Silent: Flag(sec->Flags, SECF_SILENT, key); continue; case NAME_NoRespawn: Flag(sec->Flags, SECF_NORESPAWN, key); continue; case NAME_Nofallingdamage: Flag(sec->Flags, SECF_NOFALLINGDAMAGE, key); continue; case NAME_Dropactors: Flag(sec->Flags, SECF_FLOORDROP, key); continue; case NAME_SoundSequence: sec->SeqName = CheckString(key); sec->seqType = -1; continue; case NAME_hidden: Flag(sec->MoreFlags, SECMF_HIDDEN, key); break; case NAME_Waterzone: Flag(sec->MoreFlags, SECMF_UNDERWATER, key); break; case NAME_floorplane_a: fplaneflags |= 1; fp[0] = CheckFloat(key); break; case NAME_floorplane_b: fplaneflags |= 2; fp[1] = CheckFloat(key); break; case NAME_floorplane_c: fplaneflags |= 4; fp[2] = CheckFloat(key); break; case NAME_floorplane_d: fplaneflags |= 8; fp[3] = CheckFloat(key); break; case NAME_ceilingplane_a: cplaneflags |= 1; cp[0] = CheckFloat(key); break; case NAME_ceilingplane_b: cplaneflags |= 2; cp[1] = CheckFloat(key); break; case NAME_ceilingplane_c: cplaneflags |= 4; cp[2] = CheckFloat(key); break; case NAME_ceilingplane_d: cplaneflags |= 8; cp[3] = CheckFloat(key); break; case NAME_damageamount: sec->damageamount = CheckInt(key); break; case NAME_damagetype: sec->damagetype = CheckString(key); break; case NAME_damageinterval: sec->damageinterval = CheckInt(key); if (sec->damageinterval < 1) sec->damageinterval = 1; break; case NAME_leakiness: sec->leakydamage = CheckInt(key); break; case NAME_damageterraineffect: Flag(sec->Flags, SECF_DMGTERRAINFX, key); break; case NAME_damagehazard: Flag(sec->Flags, SECF_HAZARD, key); break; case NAME_floorterrain: sec->terrainnum[sector_t::floor] = P_FindTerrain(CheckString(key)); break; case NAME_ceilingterrain: sec->terrainnum[sector_t::ceiling] = P_FindTerrain(CheckString(key)); break; case NAME_floor_reflect: sec->reflect[sector_t::floor] = (float)CheckFloat(key); break; case NAME_ceiling_reflect: sec->reflect[sector_t::ceiling] = (float)CheckFloat(key); break; case NAME_floorglowcolor: sec->planes[sector_t::floor].GlowColor = CheckInt(key); break; case NAME_floorglowheight: sec->planes[sector_t::floor].GlowHeight = (float)CheckFloat(key); break; case NAME_ceilingglowcolor: sec->planes[sector_t::ceiling].GlowColor = CheckInt(key); break; case NAME_ceilingglowheight: sec->planes[sector_t::ceiling].GlowHeight = (float)CheckFloat(key); break; case NAME_Noattack: Flag(sec->Flags, SECF_NOATTACK, key); break; case NAME_MoreIds: // delay parsing of the tag string until parsing of the sector is complete // This ensures that the ID is always the first tag in the list. tagstring = CheckString(key); break; case NAME_portal_ceil_blocksound: Flag(sec->planes[sector_t::ceiling].Flags, PLANEF_BLOCKSOUND, key); break; case NAME_portal_ceil_disabled: Flag(sec->planes[sector_t::ceiling].Flags, PLANEF_DISABLED, key); break; case NAME_portal_ceil_nopass: Flag(sec->planes[sector_t::ceiling].Flags, PLANEF_NOPASS, key); break; case NAME_portal_ceil_norender: Flag(sec->planes[sector_t::ceiling].Flags, PLANEF_NORENDER, key); break; case NAME_portal_ceil_overlaytype: if (!stricmp(CheckString(key), "translucent")) sec->planes[sector_t::ceiling].Flags &= ~PLANEF_ADDITIVE; else if (!stricmp(CheckString(key), "additive")) sec->planes[sector_t::ceiling].Flags |= PLANEF_ADDITIVE; break; case NAME_portal_floor_blocksound: Flag(sec->planes[sector_t::floor].Flags, PLANEF_BLOCKSOUND, key); break; case NAME_portal_floor_disabled: Flag(sec->planes[sector_t::floor].Flags, PLANEF_DISABLED, key); break; case NAME_portal_floor_nopass: Flag(sec->planes[sector_t::floor].Flags, PLANEF_NOPASS, key); break; case NAME_portal_floor_norender: Flag(sec->planes[sector_t::floor].Flags, PLANEF_NORENDER, key); break; case NAME_portal_floor_overlaytype: if (!stricmp(CheckString(key), "translucent")) sec->planes[sector_t::floor].Flags &= ~PLANEF_ADDITIVE; else if (!stricmp(CheckString(key), "additive")) sec->planes[sector_t::floor].Flags |= PLANEF_ADDITIVE; break; case NAME_scroll_ceil_x: scroll_ceil_x = CheckFloat(key); break; case NAME_scroll_ceil_y: scroll_ceil_y = CheckFloat(key); break; case NAME_scroll_ceil_type: scroll_ceil_type = CheckString(key); break; case NAME_scroll_floor_x: scroll_floor_x = CheckFloat(key); break; case NAME_scroll_floor_y: scroll_floor_y = CheckFloat(key); break; case NAME_scroll_floor_type: scroll_floor_type = CheckString(key); break; // These two are used by Eternity for something I do not understand. //case NAME_portal_ceil_useglobaltex: //case NAME_portal_floor_useglobaltex: case NAME_HealthFloor: sec->healthfloor = CheckInt(key); break; case NAME_HealthCeiling: sec->healthceiling = CheckInt(key); break; case NAME_Health3D: sec->health3d = CheckInt(key); break; case NAME_HealthFloorGroup: sec->healthfloorgroup = CheckInt(key); break; case NAME_HealthCeilingGroup: sec->healthceilinggroup = CheckInt(key); break; case NAME_Health3DGroup: sec->health3dgroup = CheckInt(key); break; default: break; } if ((namespace_bits & (Zd | Zdt)) && !strnicmp("user_", key.GetChars(), 5)) { AddUserKey(key, UDMF_Sector, index); } } if (tagstring.IsNotEmpty()) { FScanner sc; sc.OpenString("tagstring", tagstring); // scan the string as long as valid numbers can be found while (sc.CheckNumber()) { if (sc.Number != 0) tagManager.AddSectorTag(index, sc.Number); } } if (sec->damageamount == 0) { // If no damage is set, clear all other related properties so that they do not interfere // with other means of setting them. sec->damagetype = NAME_None; sec->damageinterval = 0; sec->leakydamage = 0; sec->Flags &= ~SECF_DAMAGEFLAGS; } // Cannot be initialized yet because they need the final sector array. if (scroll_ceil_type != NAME_None) { UDMFScrollers.Push({ true, index, scroll_ceil_x, scroll_ceil_y, scroll_ceil_type }); } if (scroll_floor_type != NAME_None) { UDMFScrollers.Push({ false, index, scroll_floor_x, scroll_floor_y, scroll_floor_type }); } // Reset the planes to their defaults if not all of the plane equation's parameters were found. if (fplaneflags != 15) { sec->floorplane.SetAtHeight(sec->GetPlaneTexZ(sector_t::floor), sector_t::floor); } else { // normalize the vector, it must have a length of 1 DVector3 n = DVector3(fp[0], fp[1], fp[2]).Unit(); sec->floorplane.set(n.X, n.Y, n.Z, fp[3]); } if (cplaneflags != 15) { sec->ceilingplane.SetAtHeight(sec->GetPlaneTexZ(sector_t::ceiling), sector_t::ceiling); } else { DVector3 n = DVector3(cp[0], cp[1], cp[2]).Unit(); sec->ceilingplane.set(n.X, n.Y, n.Z, cp[3]); } if (lightcolor == ~0u && fadecolor == ~0u && desaturation == -1 && fogdensity == -1) { // [RH] Sectors default to white light with the default fade. // If they are outside (have a sky ceiling), they use the outside fog. sec->Colormap.LightColor = PalEntry(255, 255, 255); if (level.outsidefog != 0xff000000 && (sec->GetTexture(sector_t::ceiling) == skyflatnum || (sec->special & 0xff) == Sector_Outside)) { sec->Colormap.FadeColor.SetRGB(level.outsidefog); } else { sec->Colormap.FadeColor.SetRGB(level.fadeto); } } else { sec->Colormap.LightColor.SetRGB(lightcolor); if (fadecolor == ~0u) { if (level.outsidefog != 0xff000000 && (sec->GetTexture(sector_t::ceiling) == skyflatnum || (sec->special & 0xff) == Sector_Outside)) fadecolor = level.outsidefog; else fadecolor = level.fadeto; } sec->Colormap.FadeColor.SetRGB(fadecolor); sec->Colormap.Desaturation = clamp(desaturation, 0, 255); sec->Colormap.FogDensity = clamp(fogdensity, 0, 512) / 2; } } //=========================================================================== // // parse a vertex block // //=========================================================================== void ParseVertex(vertex_t *vt, vertexdata_t *vd) { vt->set(0, 0); vd->zCeiling = vd->zFloor = vd->flags = 0; sc.MustGetToken('{'); double x = 0, y = 0; while (!sc.CheckToken('}')) { FName key = ParseKey(); switch (key) { case NAME_X: x = CheckCoordinate(key); break; case NAME_Y: y = CheckCoordinate(key); break; case NAME_ZCeiling: vd->zCeiling = CheckCoordinate(key); vd->flags |= VERTEXFLAG_ZCeilingEnabled; break; case NAME_ZFloor: vd->zFloor = CheckCoordinate(key); vd->flags |= VERTEXFLAG_ZFloorEnabled; break; default: break; } } vt->set(x, y); } //=========================================================================== // // Processes the linedefs after the map has been loaded // //=========================================================================== void ProcessLineDefs() { int sidecount = 0; for(unsigned i = 0, skipped = 0; i < ParsedLines.Size();) { // Relocate the vertices intptr_t v1i = intptr_t(ParsedLines[i].v1); intptr_t v2i = intptr_t(ParsedLines[i].v2); if ((unsigned)v1i >= level.vertexes.Size() || (unsigned)v2i >= level.vertexes.Size()) { I_Error ("Line %d has invalid vertices: %zd and/or %zd.\nThe map only contains %u vertices.", i+skipped, v1i, v2i, level.vertexes.Size()); } else if (v1i == v2i || (level.vertexes[v1i].fX() == level.vertexes[v2i].fX() && level.vertexes[v1i].fY() == level.vertexes[v2i].fY())) { Printf ("Removing 0-length line %d\n", i+skipped); ParsedLines.Delete(i); ForceNodeBuild = true; skipped++; } else { ParsedLines[i].v1 = &level.vertexes[v1i]; ParsedLines[i].v2 = &level.vertexes[v2i]; if (ParsedLines[i].sidedef[0] != NULL) sidecount++; if (ParsedLines[i].sidedef[1] != NULL) sidecount++; linemap.Push(i+skipped); i++; } } unsigned numlines = ParsedLines.Size(); level.sides.Alloc(sidecount); level.lines.Alloc(numlines); int line, side; auto lines = &level.lines[0]; auto sides = &level.sides[0]; for(line = 0, side = 0; line < (int)numlines; line++) { short tempalpha[2] = { SHRT_MIN, SHRT_MIN }; lines[line] = ParsedLines[line]; for(int sd = 0; sd < 2; sd++) { if (lines[line].sidedef[sd] != NULL) { int mapside = int(intptr_t(lines[line].sidedef[sd]))-1; if (mapside < sidecount) { sides[side] = ParsedSides[mapside]; sides[side].linedef = &lines[line]; sides[side].sector = &level.sectors[intptr_t(sides[side].sector)]; lines[line].sidedef[sd] = &sides[side]; P_ProcessSideTextures(!isExtended, &sides[side], sides[side].sector, &ParsedSideTextures[mapside], lines[line].special, lines[line].args[0], &tempalpha[sd], missingTex); side++; } else { lines[line].sidedef[sd] = NULL; } } } P_AdjustLine(&lines[line]); P_FinishLoadingLineDef(&lines[line], tempalpha[0]); } const int sideDelta = level.sides.Size() - side; assert(sideDelta >= 0); if (sideDelta < 0) { Printf("Map had %d invalid side references\n", abs(sideDelta)); } else if (sideDelta > 0) { level.sides.Resize(side); } } //=========================================================================== // // Main parsing function // //=========================================================================== void ParseTextMap(MapData *map) { isTranslated = true; isExtended = false; floordrop = false; sc.OpenMem(Wads.GetLumpFullName(map->lumpnum), map->Read(ML_TEXTMAP)); sc.SetCMode(true); if (sc.CheckString("namespace")) { sc.MustGetStringName("="); sc.MustGetString(); namespc = sc.String; switch(namespc) { case NAME_ZDoom: namespace_bits = Zd; isTranslated = false; break; case NAME_ZDoomTranslated: level.flags2 |= LEVEL2_DUMMYSWITCHES; namespace_bits = Zdt; break; case NAME_Vavoom: namespace_bits = Va; isTranslated = false; break; case NAME_Hexen: namespace_bits = Hx; isTranslated = false; break; case NAME_Doom: namespace_bits = Dm; P_LoadTranslator("xlat/doom_base.txt"); level.flags2 |= LEVEL2_DUMMYSWITCHES; floordrop = true; break; case NAME_Heretic: namespace_bits = Ht; P_LoadTranslator("xlat/heretic_base.txt"); level.flags2 |= LEVEL2_DUMMYSWITCHES; floordrop = true; break; case NAME_Strife: namespace_bits = St; P_LoadTranslator("xlat/strife_base.txt"); level.flags2 |= LEVEL2_DUMMYSWITCHES|LEVEL2_RAILINGHACK; floordrop = true; break; default: Printf("Unknown namespace %s. Using defaults for %s\n", sc.String, GameTypeName()); switch (gameinfo.gametype) { default: // Shh, GCC case GAME_Doom: case GAME_Chex: namespace_bits = Dm; P_LoadTranslator("xlat/doom_base.txt"); break; case GAME_Heretic: namespace_bits = Ht; P_LoadTranslator("xlat/heretic_base.txt"); break; case GAME_Strife: namespace_bits = St; P_LoadTranslator("xlat/strife_base.txt"); break; case GAME_Hexen: namespace_bits = Hx; isTranslated = false; break; } } sc.MustGetStringName(";"); } else { Printf("Map does not define a namespace.\n"); } while (sc.GetString()) { if (sc.Compare("thing")) { FMapThing th; unsigned userdatastart = MapThingsUserData.Size(); ParseThing(&th); MapThingsConverted.Push(th); if (userdatastart < MapThingsUserData.Size()) { // User data added MapThingsUserDataIndex[MapThingsConverted.Size()-1] = userdatastart; // Mark end of the user data for this map thing FUDMFKey ukey; ukey.Key = NAME_None; ukey = 0; MapThingsUserData.Push(ukey); } } else if (sc.Compare("linedef")) { line_t li; ParseLinedef(&li, ParsedLines.Size()); ParsedLines.Push(li); } else if (sc.Compare("sidedef")) { side_t si; intmapsidedef_t st; ParseSidedef(&si, &st, ParsedSides.Size()); ParsedSides.Push(si); ParsedSideTextures.Push(st); } else if (sc.Compare("sector")) { sector_t sec; memset(&sec, 0, sizeof(sector_t)); ParseSector(&sec, ParsedSectors.Size()); ParsedSectors.Push(sec); } else if (sc.Compare("vertex")) { vertex_t vt; vertexdata_t vd; ParseVertex(&vt, &vd); ParsedVertices.Push(vt); vertexdatas.Push(vd); } else { Skip(); } } // Catch bogus maps here rather than during nodebuilding if (ParsedVertices.Size() == 0) I_Error("Map has no vertices."); if (ParsedSectors.Size() == 0) I_Error("Map has no sectors. "); if (ParsedLines.Size() == 0) I_Error("Map has no linedefs."); if (ParsedSides.Size() == 0) I_Error("Map has no sidedefs."); if (BadCoordinates) I_Error("Map has out of range coordinates"); // Create the real vertices level.vertexes.Alloc(ParsedVertices.Size()); memcpy(&level.vertexes[0], &ParsedVertices[0], level.vertexes.Size() * sizeof(vertex_t)); // Create the real sectors level.sectors.Alloc(ParsedSectors.Size()); memcpy(&level.sectors[0], &ParsedSectors[0], level.sectors.Size() * sizeof(sector_t)); level.sectors[0].e = new extsector_t[level.sectors.Size()]; for(unsigned i = 0; i < level.sectors.Size(); i++) { level.sectors[i].e = &level.sectors[0].e[i]; } // Now create the scrollers. for (auto &scroll : UDMFScrollers) { const double scrollfactor = 1 / 3.2; // I hope this is correct, it's just a guess taken from Eternity's code. if (scroll.type == NAME_Both || scroll.type == NAME_Visual) { P_CreateScroller(scroll.ceiling ? EScroll::sc_ceiling : EScroll::sc_floor, scroll.x * scrollfactor, scroll.y * scrollfactor, -1, scroll.index, 0); } if (scroll.type == NAME_Both || scroll.type == NAME_Physical) { // sc_carry_ceiling doesn't do anything yet. P_CreateScroller(scroll.ceiling ? EScroll::sc_carry_ceiling : EScroll::sc_carry, scroll.x * scrollfactor, scroll.y * scrollfactor, -1, scroll.index, 0); } } // Create the real linedefs and decompress the sidedefs ProcessLineDefs(); } }; void P_ParseTextMap(MapData *map, FMissingTextureTracker &missingtex) { UDMFParser parse(missingtex); parse.ParseTextMap(map); }