/* ** sbarinfo_parser.cpp ** ** Reads custom status bar definitions. ** **--------------------------------------------------------------------------- ** Copyright 2008 Braden Obrzut ** 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 "doomtype.h" #include "doomstat.h" #include "sc_man.h" #include "v_font.h" #include "w_wad.h" #include "d_player.h" #include "sbar.h" #include "sbarinfo.h" #include "templates.h" #include "m_random.h" #include "gi.h" #include "i_system.h" SBarInfo *SBarInfoScript; static const char *SBarInfoTopLevel[] = { "base", "height", "interpolatehealth", "interpolatearmor", "completeborder", "monospacefonts", "lowerhealthcap", "statusbar", "mugshot", "createpopup", NULL }; static const char *StatusBars[] = { "none", "fullscreen", "normal", "automap", "inventory", "inventoryfullscreen", "popuplog", "popupkeys", "popupstatus", NULL }; static const char *SBarInfoRoutineLevel[] = { "drawimage", "drawnumber", "drawswitchableimage", "drawmugshot", "drawselectedinventory", "drawinventorybar", "drawbar", "drawgem", "drawshader", "drawstring", "drawkeybar", "gamemode", "playerclass", "aspectratio", "isselected", "usessecondaryammo", "hasweaponpiece", "weaponammo", //event "ininventory", NULL }; static void FreeSBarInfoScript() { if (SBarInfoScript != NULL) { delete SBarInfoScript; SBarInfoScript = NULL; } } void SBarInfo::Load() { if(Wads.CheckNumForName("SBARINFO") != -1) { Printf ("ParseSBarInfo: Loading custom status bar definition.\n"); int lastlump, lump; lastlump = 0; while((lump = Wads.FindLump("SBARINFO", &lastlump)) != -1) { if(SBarInfoScript == NULL) SBarInfoScript = new SBarInfo(lump); else //We now have to load multiple SBarInfo Lumps so the 2nd time we need to use this method instead. SBarInfoScript->ParseSBarInfo(lump); } atterm(FreeSBarInfoScript); } } //SBarInfo Script Reader void SBarInfo::ParseSBarInfo(int lump) { gameType = gameinfo.gametype; bool baseSet = false; FScanner sc(lump); sc.SetCMode(true); while(sc.CheckToken(TK_Identifier) || sc.CheckToken(TK_Include)) { if(sc.TokenType == TK_Include) { sc.MustGetToken(TK_StringConst); int lump = Wads.CheckNumForFullName(sc.String, true); if (lump == -1) sc.ScriptError("Lump '%s' not found", sc.String); ParseSBarInfo(lump); continue; } switch(sc.MustMatchString(SBarInfoTopLevel)) { case SBARINFO_BASE: baseSet = true; if(!sc.CheckToken(TK_None)) sc.MustGetToken(TK_Identifier); if(sc.Compare("Doom")) gameType = GAME_Doom; else if(sc.Compare("Heretic")) gameType = GAME_Heretic; else if(sc.Compare("Hexen")) gameType = GAME_Hexen; else if(sc.Compare("Strife")) gameType = GAME_Strife; else if(sc.Compare("None")) gameType = GAME_Any; else sc.ScriptError("Bad game name: %s", sc.String); sc.MustGetToken(';'); break; case SBARINFO_HEIGHT: sc.MustGetToken(TK_IntConst); this->height = sc.Number; sc.MustGetToken(';'); break; case SBARINFO_INTERPOLATEHEALTH: //mimics heretics interpolated health values. if(sc.CheckToken(TK_True)) { interpolateHealth = true; } else { sc.MustGetToken(TK_False); interpolateHealth = false; } if(sc.CheckToken(',')) //speed param { sc.MustGetToken(TK_IntConst); this->interpolationSpeed = sc.Number; } sc.MustGetToken(';'); break; case SBARINFO_INTERPOLATEARMOR: //Since interpolatehealth is such a popular command if(sc.CheckToken(TK_True)) { interpolateArmor = true; } else { sc.MustGetToken(TK_False); interpolateArmor = false; } if(sc.CheckToken(',')) //speed { sc.MustGetToken(TK_IntConst); this->armorInterpolationSpeed = sc.Number; } sc.MustGetToken(';'); break; case SBARINFO_COMPLETEBORDER: //draws the border instead of an HOM if(sc.CheckToken(TK_True)) { completeBorder = true; } else { sc.MustGetToken(TK_False); completeBorder = false; } sc.MustGetToken(';'); break; case SBARINFO_MONOSPACEFONTS: if(sc.CheckToken(TK_True)) { sc.MustGetToken(','); sc.MustGetToken(TK_StringConst); spacingCharacter = sc.String[0]; } else { sc.MustGetToken(TK_False); spacingCharacter = '\0'; sc.MustGetToken(','); sc.MustGetToken(TK_StringConst); //Don't tell anyone we're just ignoring this ;) } sc.MustGetToken(';'); break; case SBARINFO_LOWERHEALTHCAP: if(sc.CheckToken(TK_False)) { lowerHealthCap = false; } else { sc.MustGetToken(TK_True); lowerHealthCap = true; } sc.MustGetToken(';'); break; case SBARINFO_STATUSBAR: { if(!baseSet) //If the user didn't explicitly define a base, do so now. gameType = GAME_Any; int barNum = 0; if(!sc.CheckToken(TK_None)) { sc.MustGetToken(TK_Identifier); barNum = sc.MustMatchString(StatusBars); } this->huds[barNum] = SBarInfoBlock(); if(sc.CheckToken(',')) { while(sc.CheckToken(TK_Identifier)) { if(sc.Compare("forcescaled")) { this->huds[barNum].forceScaled = true; } else if(sc.Compare("fullscreenoffsets")) { this->huds[barNum].fullScreenOffsets = true; } else { sc.ScriptError("Unkown flag '%s'.", sc.String); } if(!sc.CheckToken('|') || !sc.CheckToken(',')) goto FinishStatusBar; //No more args so we must skip over anything else and go to the end. } sc.MustGetToken(TK_FloatConst); this->huds[barNum].alpha = fixed_t(FRACUNIT * sc.Float); } FinishStatusBar: sc.MustGetToken('{'); if(barNum == STBAR_AUTOMAP) { automapbar = true; } ParseSBarInfoBlock(sc, this->huds[barNum]); break; } case SBARINFO_MUGSHOT: { sc.MustGetToken(TK_StringConst); FMugShotState state(sc.String); if(sc.CheckToken(',')) //first loop must be a comma { do { sc.MustGetToken(TK_Identifier); if(sc.Compare("health")) state.bUsesLevels = true; else if(sc.Compare("health2")) state.bUsesLevels = state.bHealth2 = true; else if(sc.Compare("healthspecial")) state.bUsesLevels = state.bHealthSpecial = true; else if(sc.Compare("directional")) state.bDirectional = true; else sc.ScriptError("Unknown MugShot state flag '%s'.", sc.String); } while(sc.CheckToken(',') || sc.CheckToken('|')); } ParseMugShotBlock(sc, state); int index; if((index = FindMugShotStateIndex(state.State)) != -1) //We already had this state, remove the old one. { MugShotStates.Delete(index); } MugShotStates.Push(state); break; } case SBARINFO_CREATEPOPUP: { int pop = 0; sc.MustGetToken(TK_Identifier); if(sc.Compare("log")) pop = 0; else if(sc.Compare("keys")) pop = 1; else if(sc.Compare("status")) pop = 2; else sc.ScriptError("Unkown popup: '%s'", sc.String); Popup &popup = popups[pop]; sc.MustGetToken(','); sc.MustGetToken(TK_IntConst); popup.width = sc.Number; sc.MustGetToken(','); sc.MustGetToken(TK_IntConst); popup.height = sc.Number; sc.MustGetToken(','); if(!sc.CheckToken(TK_None)) { sc.MustGetToken(TK_Identifier); if(sc.Compare("slideinbottom")) { popup.transition = TRANSITION_SLIDEINBOTTOM; sc.MustGetToken(','); sc.MustGetToken(TK_IntConst); popup.speed = sc.Number; } else if(sc.Compare("fade")) { popup.transition = TRANSITION_FADE; sc.MustGetToken(','); sc.MustGetToken(TK_FloatConst); popup.speed = fixed_t(FRACUNIT * (1.0 / (35.0 * sc.Float))); sc.MustGetToken(','); sc.MustGetToken(TK_FloatConst); popup.speed2 = fixed_t(FRACUNIT * (1.0 / (35.0 * sc.Float))); } else sc.ScriptError("Unkown transition type: '%s'", sc.String); } popup.init(); sc.MustGetToken(';'); break; } } } } void SBarInfo::ParseSBarInfoBlock(FScanner &sc, SBarInfoBlock &block) { while(sc.CheckToken(TK_Identifier)) { SBarInfoCommand cmd; switch(cmd.type = sc.MustMatchString(SBarInfoRoutineLevel)) { case SBARINFO_DRAWSWITCHABLEIMAGE: sc.MustGetToken(TK_Identifier); if(sc.Compare("weaponslot")) { cmd.flags = DRAWIMAGE_WEAPONSLOT; sc.MustGetToken(TK_IntConst); cmd.value = sc.Number; } else if(sc.Compare("invulnerable")) { cmd.flags = DRAWIMAGE_INVULNERABILITY; } else if(sc.Compare("keyslot")) { cmd.flags = DRAWIMAGE_KEYSLOT; sc.MustGetToken(TK_IntConst); cmd.value = sc.Number; } else { cmd.setString(sc, sc.String, 0); const PClass* item = PClass::FindClass(sc.String); if(item == NULL || !PClass::FindClass("Inventory")->IsAncestorOf(item)) //must be a kind of Inventory { sc.ScriptError("'%s' is not a type of inventory item.", sc.String); } } if(sc.CheckToken(TK_AndAnd)) { cmd.flags |= DRAWIMAGE_SWITCHABLE_AND; if(cmd.flags & DRAWIMAGE_KEYSLOT) { sc.MustGetToken(TK_IntConst); cmd.special4 = sc.Number; } else { sc.MustGetToken(TK_Identifier); cmd.setString(sc, sc.String, 1); const PClass* item = PClass::FindClass(sc.String); if(item == NULL || !PClass::FindClass("Inventory")->IsAncestorOf(item)) //must be a kind of Inventory { sc.ScriptError("'%s' is not a type of inventory item.", sc.String); } } sc.MustGetToken(','); sc.MustGetToken(TK_StringConst); cmd.special = newImage(sc.String); sc.MustGetToken(','); sc.MustGetToken(TK_StringConst); cmd.special2 = newImage(sc.String); sc.MustGetToken(','); sc.MustGetToken(TK_StringConst); cmd.special3 = newImage(sc.String); sc.MustGetToken(','); } else { sc.MustGetToken(','); sc.MustGetToken(TK_StringConst); cmd.special = newImage(sc.String); sc.MustGetToken(','); } case SBARINFO_DRAWIMAGE: { bool getImage = true; if(sc.CheckToken(TK_Identifier)) { getImage = false; if(sc.Compare("playericon")) cmd.flags |= DRAWIMAGE_PLAYERICON; else if(sc.Compare("ammoicon1")) cmd.flags |= DRAWIMAGE_AMMO1; else if(sc.Compare("ammoicon2")) cmd.flags |= DRAWIMAGE_AMMO2; else if(sc.Compare("armoricon")) cmd.flags |= DRAWIMAGE_ARMOR; else if(sc.Compare("weaponicon")) cmd.flags |= DRAWIMAGE_WEAPONICON; else if(sc.Compare("sigil")) cmd.flags |= DRAWIMAGE_SIGIL; else if(sc.Compare("hexenarmor")) { cmd.flags = DRAWIMAGE_HEXENARMOR; sc.MustGetToken(TK_Identifier); if(sc.Compare("armor")) cmd.value = 0; else if(sc.Compare("shield")) cmd.value = 1; else if(sc.Compare("helm")) cmd.value = 2; else if(sc.Compare("amulet")) cmd.value = 3; else sc.ScriptError("Unkown armor type: '%s'", sc.String); sc.MustGetToken(','); getImage = true; } else if(sc.Compare("translatable")) { cmd.flags |= DRAWIMAGE_TRANSLATABLE; getImage = true; } else { //sc.CheckToken(TK_Identifier); cmd.flags |= DRAWIMAGE_INVENTORYICON; const PClass* item = PClass::FindClass(sc.String); if(item == NULL || !PClass::FindClass("Inventory")->IsAncestorOf(item)) //must be a kind of Inventory { sc.ScriptError("'%s' is not a type of inventory item.", sc.String); } cmd.sprite_index = ((AInventory *)GetDefaultByType(item))->Icon; cmd.image_index = -1; } } if(getImage) { sc.MustGetToken(TK_StringConst); cmd.image_index = newImage(sc.String); cmd.sprite_index.SetInvalid(); } sc.MustGetToken(','); this->getCoordinates(sc, cmd, block.fullScreenOffsets); if(sc.CheckToken(',')) { sc.MustGetToken(TK_Identifier); if(sc.Compare("center")) cmd.flags |= DRAWIMAGE_OFFSET_CENTER; else if(sc.Compare("centerbottom")) cmd.flags |= DRAWIMAGE_OFFSET_CENTERBOTTOM; else sc.ScriptError("Expected 'center' or 'centerbottom' got '%s' instead.", sc.String); } sc.MustGetToken(';'); break; } case SBARINFO_DRAWNUMBER: cmd.special4 = cmd.special3 = -1; sc.MustGetToken(TK_IntConst); cmd.special = sc.Number; sc.MustGetToken(','); sc.MustGetToken(TK_Identifier); cmd.font = V_GetFont(sc.String); if(cmd.font == NULL) sc.ScriptError("Unknown font '%s'.", sc.String); sc.MustGetToken(','); sc.MustGetToken(TK_Identifier); cmd.translation = this->GetTranslation(sc, sc.String); sc.MustGetToken(','); if(sc.CheckToken(TK_IntConst)) { cmd.value = sc.Number; sc.MustGetToken(','); } else { sc.MustGetToken(TK_Identifier); if(sc.Compare("health")) cmd.flags = DRAWNUMBER_HEALTH; else if(sc.Compare("armor")) cmd.flags = DRAWNUMBER_ARMOR; else if(sc.Compare("ammo1")) cmd.flags = DRAWNUMBER_AMMO1; else if(sc.Compare("ammo2")) cmd.flags = DRAWNUMBER_AMMO2; else if(sc.Compare("ammo")) //request the next string to be an ammo type { sc.MustGetToken(TK_Identifier); cmd.setString(sc, sc.String, 0); cmd.flags = DRAWNUMBER_AMMO; const PClass* ammo = PClass::FindClass(sc.String); if(ammo == NULL || !RUNTIME_CLASS(AAmmo)->IsAncestorOf(ammo)) //must be a kind of ammo { sc.ScriptError("'%s' is not a type of ammo.", sc.String); } } else if(sc.Compare("ammocapacity")) { sc.MustGetToken(TK_Identifier); cmd.setString(sc, sc.String, 0); cmd.flags = DRAWNUMBER_AMMOCAPACITY; const PClass* ammo = PClass::FindClass(sc.String); if(ammo == NULL || !RUNTIME_CLASS(AAmmo)->IsAncestorOf(ammo)) //must be a kind of ammo { sc.ScriptError("'%s' is not a type of ammo.", sc.String); } } else if(sc.Compare("frags")) cmd.flags = DRAWNUMBER_FRAGS; else if(sc.Compare("kills")) cmd.flags |= DRAWNUMBER_KILLS; else if(sc.Compare("monsters")) cmd.flags |= DRAWNUMBER_MONSTERS; else if(sc.Compare("items")) cmd.flags |= DRAWNUMBER_ITEMS; else if(sc.Compare("totalitems")) cmd.flags |= DRAWNUMBER_TOTALITEMS; else if(sc.Compare("secrets")) cmd.flags |= DRAWNUMBER_SECRETS; else if(sc.Compare("totalsecrets")) cmd.flags |= DRAWNUMBER_TOTALSECRETS; else if(sc.Compare("armorclass")) cmd.flags |= DRAWNUMBER_ARMORCLASS; else if(sc.Compare("globalvar")) { cmd.flags |= DRAWNUMBER_GLOBALVAR; sc.MustGetToken(TK_IntConst); if(sc.Number < 0 || sc.Number >= NUM_GLOBALVARS) sc.ScriptError("Global variable number out of range: %d", sc.Number); cmd.value = sc.Number; } else if(sc.Compare("globalarray")) //acts like variable[playernumber()] { cmd.flags |= DRAWNUMBER_GLOBALARRAY; sc.MustGetToken(TK_IntConst); if(sc.Number < 0 || sc.Number >= NUM_GLOBALVARS) sc.ScriptError("Global variable number out of range: %d", sc.Number); cmd.value = sc.Number; } else if(sc.Compare("poweruptime")) { cmd.flags |= DRAWNUMBER_POWERUPTIME; sc.MustGetToken(TK_Identifier); cmd.setString(sc, sc.String, 0); const PClass* item = PClass::FindClass(sc.String); if(item == NULL || !PClass::FindClass("PowerupGiver")->IsAncestorOf(item)) { sc.ScriptError("'%s' is not a type of PowerupGiver.", sc.String); } } else { cmd.flags = DRAWNUMBER_INVENTORY; cmd.setString(sc, sc.String, 0); const PClass* item = PClass::FindClass(sc.String); if(item == NULL || !PClass::FindClass("Inventory")->IsAncestorOf(item)) //must be a kind of ammo { sc.ScriptError("'%s' is not a type of inventory item.", sc.String); } } sc.MustGetToken(','); } while(sc.CheckToken(TK_Identifier)) { if(sc.Compare("fillzeros")) cmd.flags |= DRAWNUMBER_FILLZEROS; else if(sc.Compare("whennotzero")) cmd.flags |= DRAWNUMBER_WHENNOTZERO; else if(sc.Compare("drawshadow")) cmd.flags |= DRAWNUMBER_DRAWSHADOW; else sc.ScriptError("Unknown flag '%s'.", sc.String); if(!sc.CheckToken('|')) sc.MustGetToken(','); } this->getCoordinates(sc, cmd, block.fullScreenOffsets); if(sc.CheckToken(',')) { bool needsComma = false; if(sc.CheckToken(TK_IntConst)) //font spacing { cmd.special2 = sc.Number; needsComma = true; } if(!needsComma || sc.CheckToken(',')) //2nd coloring for "low-on" value { sc.MustGetToken(TK_Identifier); cmd.translation2 = this->GetTranslation(sc, sc.String); sc.MustGetToken(','); sc.MustGetToken(TK_IntConst); cmd.special3 = sc.Number; if(sc.CheckToken(',')) //3rd coloring for "high-on" value { sc.MustGetToken(TK_Identifier); cmd.translation3 = this->GetTranslation(sc, sc.String); sc.MustGetToken(','); sc.MustGetToken(TK_IntConst); cmd.special4 = sc.Number; } } } sc.MustGetToken(';'); break; case SBARINFO_DRAWMUGSHOT: if(sc.CheckToken(TK_StringConst)) { cmd.setString(sc, sc.String, 0, 3, true); sc.MustGetToken(','); } sc.MustGetToken(TK_IntConst); //accuracy if(sc.Number < 1 || sc.Number > 9) sc.ScriptError("Expected a number between 1 and 9, got %d instead.", sc.Number); cmd.special = sc.Number; sc.MustGetToken(','); while(sc.CheckToken(TK_Identifier)) { if(sc.Compare("xdeathface")) cmd.flags |= DRAWMUGSHOT_XDEATHFACE; else if(sc.Compare("animatedgodmode")) cmd.flags |= DRAWMUGSHOT_ANIMATEDGODMODE; else if(sc.Compare("disablegrin")) cmd.flags |= DRAWMUGSHOT_DISABLEGRIN; else if(sc.Compare("disableouch")) cmd.flags |= DRAWMUGSHOT_DISABLEOUCH; else if(sc.Compare("disablepain")) cmd.flags |= DRAWMUGSHOT_DISABLEPAIN; else if(sc.Compare("disablerampage")) cmd.flags |= DRAWMUGSHOT_DISABLERAMPAGE; else sc.ScriptError("Unknown flag '%s'.", sc.String); if(!sc.CheckToken('|')) sc.MustGetToken(','); } this->getCoordinates(sc, cmd, block.fullScreenOffsets); sc.MustGetToken(';'); break; case SBARINFO_DRAWSELECTEDINVENTORY: { bool alternateonempty = false; while(true) //go until we get a font (non-flag) { sc.MustGetToken(TK_Identifier); if(sc.Compare("alternateonempty")) { alternateonempty = true; cmd.flags |= DRAWSELECTEDINVENTORY_ALTERNATEONEMPTY; } else if(sc.Compare("artiflash")) { cmd.flags |= DRAWSELECTEDINVENTORY_ARTIFLASH; } else if(sc.Compare("alwaysshowcounter")) { cmd.flags |= DRAWSELECTEDINVENTORY_ALWAYSSHOWCOUNTER; } else { cmd.font = V_GetFont(sc.String); if(cmd.font == NULL) sc.ScriptError("Unknown font '%s'.", sc.String); sc.MustGetToken(','); break; } if(!sc.CheckToken('|')) sc.MustGetToken(','); } sc.MustGetToken(TK_IntConst); cmd.x = sc.Number; sc.MustGetToken(','); sc.MustGetToken(TK_IntConst); cmd.y = sc.Number - (200 - this->height); cmd.special2 = cmd.x + 30; cmd.special3 = cmd.y + 24; cmd.translation = CR_GOLD; if(sc.CheckToken(',')) //more font information { sc.MustGetToken(TK_IntConst); cmd.special2 = sc.Number; sc.MustGetToken(','); sc.MustGetToken(TK_IntConst); cmd.special3 = sc.Number - (200 - this->height); if(sc.CheckToken(',')) { sc.MustGetToken(TK_Identifier); cmd.translation = this->GetTranslation(sc, sc.String); if(sc.CheckToken(',')) { sc.MustGetToken(TK_IntConst); cmd.special4 = sc.Number; } } } if(alternateonempty) { sc.MustGetToken('{'); cmd.subBlock.fullScreenOffsets = block.fullScreenOffsets; this->ParseSBarInfoBlock(sc, cmd.subBlock); } else { sc.MustGetToken(';'); } break; } case SBARINFO_DRAWINVENTORYBAR: sc.MustGetToken(TK_Identifier); if(sc.Compare("Doom")) cmd.special = GAME_Doom; else if(sc.Compare("Heretic")) cmd.special = GAME_Heretic; else if(sc.Compare("Hexen")) cmd.special = GAME_Hexen; else if(sc.Compare("Strife")) cmd.special = GAME_Strife; else sc.ScriptError("Unkown style '%s'.", sc.String); sc.MustGetToken(','); while(sc.CheckToken(TK_Identifier)) { if(sc.Compare("alwaysshow")) { cmd.flags |= DRAWINVENTORYBAR_ALWAYSSHOW; } else if(sc.Compare("noartibox")) { cmd.flags |= DRAWINVENTORYBAR_NOARTIBOX; } else if(sc.Compare("noarrows")) { cmd.flags |= DRAWINVENTORYBAR_NOARROWS; } else if(sc.Compare("alwaysshowcounter")) { cmd.flags |= DRAWINVENTORYBAR_ALWAYSSHOWCOUNTER; } else if(sc.Compare("translucent")) { cmd.flags |= DRAWINVENTORYBAR_TRANSLUCENT; } else { sc.ScriptError("Unknown flag '%s'.", sc.String); } if(!sc.CheckToken('|')) sc.MustGetToken(','); } sc.MustGetToken(TK_IntConst); cmd.value = sc.Number; sc.MustGetToken(','); sc.MustGetToken(TK_Identifier); cmd.font = V_GetFont(sc.String); if(cmd.font == NULL) sc.ScriptError("Unknown font '%s'.", sc.String); sc.MustGetToken(','); this->getCoordinates(sc, cmd, block.fullScreenOffsets); cmd.special2 = cmd.x + 26; cmd.special3 = cmd.y + 22; cmd.translation = CR_GOLD; if(sc.CheckToken(',')) //more font information { sc.MustGetToken(TK_IntConst); cmd.special2 = sc.Number; sc.MustGetToken(','); sc.MustGetToken(TK_IntConst); cmd.special3 = sc.Number - (200 - this->height); if(sc.CheckToken(',')) { sc.MustGetToken(TK_Identifier); cmd.translation = this->GetTranslation(sc, sc.String); if(sc.CheckToken(',')) { sc.MustGetToken(TK_IntConst); cmd.special4 = sc.Number; } } } sc.MustGetToken(';'); break; case SBARINFO_DRAWBAR: sc.MustGetToken(TK_StringConst); cmd.image_index = newImage(sc.String); cmd.sprite_index.SetInvalid(); sc.MustGetToken(','); sc.MustGetToken(TK_StringConst); cmd.special = newImage(sc.String); sc.MustGetToken(','); sc.MustGetToken(TK_Identifier); //yeah, this is the same as drawnumber, there might be a better way to copy it... if(sc.Compare("health")) { cmd.flags = DRAWNUMBER_HEALTH; if(sc.CheckToken(TK_Identifier)) //comparing reference { cmd.setString(sc, sc.String, 0); const PClass* item = PClass::FindClass(sc.String); if(item == NULL || !PClass::FindClass("Inventory")->IsAncestorOf(item)) //must be a kind of inventory { sc.ScriptError("'%s' is not a type of inventory item.", sc.String); } } else cmd.special2 = DRAWBAR_COMPAREDEFAULTS; } else if(sc.Compare("armor")) { cmd.flags = DRAWNUMBER_ARMOR; if(sc.CheckToken(TK_Identifier)) { cmd.setString(sc, sc.String, 0); const PClass* item = PClass::FindClass(sc.String); if(item == NULL || !PClass::FindClass("Inventory")->IsAncestorOf(item)) //must be a kind of inventory { sc.ScriptError("'%s' is not a type of inventory item.", sc.String); } } else cmd.special2 = DRAWBAR_COMPAREDEFAULTS; } else if(sc.Compare("ammo1")) cmd.flags = DRAWNUMBER_AMMO1; else if(sc.Compare("ammo2")) cmd.flags = DRAWNUMBER_AMMO2; else if(sc.Compare("ammo")) //request the next string to be an ammo type { sc.MustGetToken(TK_Identifier); cmd.setString(sc, sc.String, 0); cmd.flags = DRAWNUMBER_AMMO; const PClass* ammo = PClass::FindClass(sc.String); if(ammo == NULL || !RUNTIME_CLASS(AAmmo)->IsAncestorOf(ammo)) //must be a kind of ammo { sc.ScriptError("'%s' is not a type of ammo.", sc.String); } } else if(sc.Compare("frags")) cmd.flags = DRAWNUMBER_FRAGS; else if(sc.Compare("kills")) cmd.flags = DRAWNUMBER_KILLS; else if(sc.Compare("items")) cmd.flags = DRAWNUMBER_ITEMS; else if(sc.Compare("secrets")) cmd.flags = DRAWNUMBER_SECRETS; else if(sc.Compare("poweruptime")) { cmd.flags |= DRAWNUMBER_POWERUPTIME; sc.MustGetToken(TK_Identifier); cmd.setString(sc, sc.String, 0); const PClass* item = PClass::FindClass(sc.String); if(item == NULL || !PClass::FindClass("PowerupGiver")->IsAncestorOf(item)) { sc.ScriptError("'%s' is not a type of PowerupGiver.", sc.String); } } else { cmd.flags = DRAWNUMBER_INVENTORY; cmd.setString(sc, sc.String, 0); const PClass* item = PClass::FindClass(sc.String); if(item == NULL || !RUNTIME_CLASS(AInventory)->IsAncestorOf(item)) { sc.ScriptError("'%s' is not a type of inventory item.", sc.String); } } sc.MustGetToken(','); sc.MustGetToken(TK_Identifier); if(sc.Compare("horizontal")) cmd.special2 += DRAWBAR_HORIZONTAL; else if(!sc.Compare("vertical")) sc.ScriptError("Unknown direction '%s'.", sc.String); sc.MustGetToken(','); while(sc.CheckToken(TK_Identifier)) { if(sc.Compare("reverse")) cmd.special2 += DRAWBAR_REVERSE; else sc.ScriptError("Unkown flag '%s'.", sc.String); if(!sc.CheckToken('|')) sc.MustGetToken(','); } this->getCoordinates(sc, cmd, block.fullScreenOffsets); if(sc.CheckToken(',')) //border { sc.MustGetToken(TK_IntConst); cmd.special3 = sc.Number; } sc.MustGetToken(';'); break; case SBARINFO_DRAWGEM: while(sc.CheckToken(TK_Identifier)) { if(sc.Compare("wiggle")) cmd.flags |= DRAWGEM_WIGGLE; else if(sc.Compare("translatable")) cmd.flags |= DRAWGEM_TRANSLATABLE; else if(sc.Compare("armor")) cmd.flags |= DRAWGEM_ARMOR; else if(sc.Compare("reverse")) cmd.flags |= DRAWGEM_REVERSE; else sc.ScriptError("Unknown drawgem flag '%s'.", sc.String); if(!sc.CheckToken('|')) sc.MustGetToken(','); } sc.MustGetToken(TK_StringConst); //chain cmd.special = newImage(sc.String); sc.MustGetToken(','); sc.MustGetToken(TK_StringConst); //gem cmd.image_index = newImage(sc.String); cmd.sprite_index.SetInvalid(); sc.MustGetToken(','); cmd.special2 = this->getSignedInteger(sc); sc.MustGetToken(','); cmd.special3 = this->getSignedInteger(sc); sc.MustGetToken(','); sc.MustGetToken(TK_IntConst); if(sc.Number < 0) sc.ScriptError("Chain size must be a positive number."); cmd.special4 = sc.Number; sc.MustGetToken(','); this->getCoordinates(sc, cmd, block.fullScreenOffsets); sc.MustGetToken(';'); break; case SBARINFO_DRAWSHADER: sc.MustGetToken(TK_IntConst); cmd.special = sc.Number; if(sc.Number < 1) sc.ScriptError("Width must be greater than 1."); sc.MustGetToken(','); sc.MustGetToken(TK_IntConst); cmd.special2 = sc.Number; if(sc.Number < 1) sc.ScriptError("Height must be greater than 1."); sc.MustGetToken(','); sc.MustGetToken(TK_Identifier); if(sc.Compare("vertical")) cmd.flags |= DRAWSHADER_VERTICAL; else if(!sc.Compare("horizontal")) sc.ScriptError("Unknown direction '%s'.", sc.String); sc.MustGetToken(','); if(sc.CheckToken(TK_Identifier)) { if(!sc.Compare("reverse")) { sc.ScriptError("Exspected 'reverse', got '%s' instead.", sc.String); } cmd.flags |= DRAWSHADER_REVERSE; sc.MustGetToken(','); } this->getCoordinates(sc, cmd, block.fullScreenOffsets); sc.MustGetToken(';'); break; case SBARINFO_DRAWSTRING: sc.MustGetToken(TK_Identifier); cmd.font = V_GetFont(sc.String); if(cmd.font == NULL) sc.ScriptError("Unknown font '%s'.", sc.String); sc.MustGetToken(','); sc.MustGetToken(TK_Identifier); cmd.translation = this->GetTranslation(sc, sc.String); sc.MustGetToken(','); sc.MustGetToken(TK_StringConst); cmd.setString(sc, sc.String, 0, -1, false); sc.MustGetToken(','); this->getCoordinates(sc, cmd, block.fullScreenOffsets); if(sc.CheckToken(',')) //spacing { sc.MustGetToken(TK_IntConst); cmd.special = sc.Number; } sc.MustGetToken(';'); break; case SBARINFO_DRAWKEYBAR: sc.MustGetToken(TK_IntConst); cmd.value = sc.Number; sc.MustGetToken(','); sc.MustGetToken(TK_Identifier); if(sc.Compare("vertical")) cmd.flags |= DRAWKEYBAR_VERTICAL; else if(!sc.Compare("horizontal")) sc.ScriptError("Unknown direction '%s'.", sc.String); sc.MustGetToken(','); while(sc.CheckToken(TK_Identifier)) { if(sc.Compare("reverserows")) cmd.flags |= DRAWKEYBAR_REVERSEROWS; else sc.ScriptError("Unknown flag '%s'.", sc.String); if(!sc.CheckToken('|')) sc.MustGetToken(','); } if(sc.CheckToken(TK_Auto)) cmd.special = -1; else { sc.MustGetToken(TK_IntConst); cmd.special = sc.Number; } sc.MustGetToken(','); this->getCoordinates(sc, cmd, block.fullScreenOffsets); if(sc.CheckToken(',')) { //key offset sc.MustGetToken(TK_IntConst); cmd.special2 = sc.Number; if(sc.CheckToken(',')) { //max per row/column sc.MustGetToken(TK_IntConst); cmd.special3 = sc.Number; sc.MustGetToken(','); //row/column spacing (opposite of previous) if(sc.CheckToken(TK_Auto)) cmd.special4 = -1; else { sc.MustGetToken(TK_IntConst); cmd.special4 = sc.Number; } } } sc.MustGetToken(';'); break; case SBARINFO_GAMEMODE: while(sc.CheckToken(TK_Identifier)) { if(sc.Compare("singleplayer")) cmd.flags |= GAMETYPE_SINGLEPLAYER; else if(sc.Compare("cooperative")) cmd.flags |= GAMETYPE_COOPERATIVE; else if(sc.Compare("deathmatch")) cmd.flags |= GAMETYPE_DEATHMATCH; else if(sc.Compare("teamgame")) cmd.flags |= GAMETYPE_TEAMGAME; //else I'm removing this error to allow cross port compatiblity. If it doesn't know what a gamemode is lets just ignore it. // sc.ScriptError("Unknown gamemode: %s", sc.String); if(sc.CheckToken('{')) break; sc.MustGetToken(','); } cmd.subBlock.fullScreenOffsets = block.fullScreenOffsets; this->ParseSBarInfoBlock(sc, cmd.subBlock); break; case SBARINFO_PLAYERCLASS: cmd.special = cmd.special2 = cmd.special3 = -1; for(int i = 0;i < 3 && sc.CheckToken(TK_Identifier);i++) //up to 3 classes { bool foundClass = false; for(unsigned int c = 0;c < PlayerClasses.Size();c++) { if(stricmp(sc.String, PlayerClasses[c].Type->Meta.GetMetaString(APMETA_DisplayName)) == 0) { foundClass = true; if(i == 0) cmd.special = PlayerClasses[c].Type->ClassIndex; else if(i == 1) cmd.special2 = PlayerClasses[c].Type->ClassIndex; else //should be 2 cmd.special3 = PlayerClasses[c].Type->ClassIndex; break; } } if(!foundClass) sc.ScriptError("Unkown PlayerClass '%s'.", sc.String); if(sc.CheckToken('{') || i == 2) goto FinishPlayerClass; sc.MustGetToken(','); } FinishPlayerClass: cmd.subBlock.fullScreenOffsets = block.fullScreenOffsets; this->ParseSBarInfoBlock(sc, cmd.subBlock); break; case SBARINFO_ASPECTRATIO: sc.MustGetToken(TK_StringConst); if(sc.Compare("4:3")) cmd.value = ASPECTRATIO_4_3; else if(sc.Compare("16:9")) cmd.value = ASPECTRATIO_16_9; else if(sc.Compare("16:10")) cmd.value = ASPECTRATIO_16_10; else if(sc.Compare("5:4")) cmd.value = ASPECTRATIO_5_4; else sc.ScriptError("Unkown aspect ratio: %s", sc.String); sc.MustGetToken('{'); cmd.subBlock.fullScreenOffsets = block.fullScreenOffsets; this->ParseSBarInfoBlock(sc, cmd.subBlock); break; case SBARINFO_ISSELECTED: if(sc.CheckToken(TK_Identifier)) { if(sc.Compare("not")) { cmd.flags |= SBARINFOEVENT_NOT; } else sc.ScriptError("Expected 'not' got '%s' instead.", sc.String); } sc.MustGetToken(TK_StringConst); for(int i = 0;i < 2;i++) { cmd.setString(sc, sc.String, i); const PClass* item = PClass::FindClass(sc.String); if(item == NULL || !RUNTIME_CLASS(AWeapon)->IsAncestorOf(item)) { sc.ScriptError("'%s' is not a type of weapon.", sc.String); } if(sc.CheckToken(',')) sc.MustGetToken(TK_StringConst); else break; } sc.MustGetToken('{'); cmd.subBlock.fullScreenOffsets = block.fullScreenOffsets; this->ParseSBarInfoBlock(sc, cmd.subBlock); break; case SBARINFO_USESSECONDARYAMMO: if(sc.CheckToken(TK_Identifier)) { if(sc.Compare("not")) cmd.flags |= SBARINFOEVENT_NOT; else sc.ScriptError("Exspected 'not' got '%s' instead.", sc.String); } sc.MustGetToken('{'); cmd.subBlock.fullScreenOffsets = block.fullScreenOffsets; this->ParseSBarInfoBlock(sc, cmd.subBlock); break; case SBARINFO_HASWEAPONPIECE: { sc.MustGetToken(TK_Identifier); const PClass* weapon = PClass::FindClass(sc.String); if(weapon == NULL || !RUNTIME_CLASS(AWeapon)->IsAncestorOf(weapon)) //must be a weapon sc.ScriptError("%s is not a kind of weapon.", sc.String); cmd.setString(sc, sc.String, 0); sc.MustGetToken(','); sc.MustGetToken(TK_IntConst); if(sc.Number < 1) sc.ScriptError("Weapon piece number can not be less than 1."); cmd.value = sc.Number; sc.MustGetToken('{'); cmd.subBlock.fullScreenOffsets = block.fullScreenOffsets; this->ParseSBarInfoBlock(sc, cmd.subBlock); break; } case SBARINFO_WEAPONAMMO: sc.MustGetToken(TK_Identifier); if(sc.Compare("not")) { cmd.flags |= SBARINFOEVENT_NOT; sc.MustGetToken(TK_Identifier); } for(int i = 0;i < 2;i++) { cmd.setString(sc, sc.String, i); const PClass* ammo = PClass::FindClass(sc.String); if(ammo == NULL || !RUNTIME_CLASS(AAmmo)->IsAncestorOf(ammo)) //must be a kind of ammo { sc.ScriptError("'%s' is not a type of ammo.", sc.String); } if(sc.CheckToken(TK_OrOr)) { cmd.flags |= SBARINFOEVENT_OR; sc.MustGetToken(TK_Identifier); } else if(sc.CheckToken(TK_AndAnd)) { cmd.flags |= SBARINFOEVENT_AND; sc.MustGetToken(TK_Identifier); } else break; } sc.MustGetToken('{'); cmd.subBlock.fullScreenOffsets = block.fullScreenOffsets; this->ParseSBarInfoBlock(sc, cmd.subBlock); break; case SBARINFO_ININVENTORY: sc.MustGetToken(TK_Identifier); if(sc.Compare("not")) { cmd.flags |= SBARINFOEVENT_NOT; sc.MustGetToken(TK_Identifier); } for(int i = 0;i < 2;i++) { cmd.setString(sc, sc.String, i); const PClass* item = PClass::FindClass(sc.String); if(item == NULL || !RUNTIME_CLASS(AInventory)->IsAncestorOf(item)) { sc.ScriptError("'%s' is not a type of inventory item.", sc.String); } if(sc.CheckToken(TK_OrOr)) { cmd.flags |= SBARINFOEVENT_OR; sc.MustGetToken(TK_Identifier); } else if(sc.CheckToken(TK_AndAnd)) { cmd.flags |= SBARINFOEVENT_AND; sc.MustGetToken(TK_Identifier); } else break; } sc.MustGetToken('{'); cmd.subBlock.fullScreenOffsets = block.fullScreenOffsets; this->ParseSBarInfoBlock(sc, cmd.subBlock); break; } block.commands.Push(cmd); } sc.MustGetToken('}'); } void SBarInfo::ParseMugShotBlock(FScanner &sc, FMugShotState &state) { sc.MustGetToken('{'); while(!sc.CheckToken('}')) { FMugShotFrame frame; bool multiframe = false; if(sc.CheckToken('{')) multiframe = true; do { sc.MustGetToken(TK_Identifier); if(strlen(sc.String) > 5) sc.ScriptError("MugShot frames cannot exceed 5 characters."); frame.Graphic.Push(sc.String); } while(multiframe && sc.CheckToken(',')); if(multiframe) sc.MustGetToken('}'); frame.Delay = getSignedInteger(sc); sc.MustGetToken(';'); state.Frames.Push(frame); } } void SBarInfo::getCoordinates(FScanner &sc, SBarInfoCommand &cmd, bool fullScreenOffsets) { bool negative = false; negative = sc.CheckToken('-'); sc.MustGetToken(TK_IntConst); cmd.x = negative ? -sc.Number : sc.Number; sc.MustGetToken(','); negative = sc.CheckToken('-'); sc.MustGetToken(TK_IntConst); if(!fullScreenOffsets) cmd.y = (negative ? -sc.Number : sc.Number) - (200 - this->height); else cmd.y = negative ? -sc.Number : sc.Number; } int SBarInfo::getSignedInteger(FScanner &sc) { if(sc.CheckToken('-')) { sc.MustGetToken(TK_IntConst); return -sc.Number; } else { sc.MustGetToken(TK_IntConst); return sc.Number; } } int SBarInfo::newImage(const char *patchname) { if(patchname[0] == '\0' || stricmp(patchname, "nullimage") == 0) { return -1; } for(unsigned int i = 0;i < this->Images.Size();i++) //did we already load it? { if(stricmp(this->Images[i], patchname) == 0) { return i; } } return this->Images.Push(patchname); } //converts a string into a tranlation. EColorRange SBarInfo::GetTranslation(FScanner &sc, const char* translation) { EColorRange returnVal = CR_UNTRANSLATED; FString namedTranslation; //we must send in "[translation]" const BYTE *trans_ptr; namedTranslation.Format("[%s]", translation); trans_ptr = (const BYTE *)(&namedTranslation[0]); if((returnVal = V_ParseFontColor(trans_ptr, CR_UNTRANSLATED, CR_UNTRANSLATED)) == CR_UNDEFINED) { sc.ScriptError("Missing definition for color %s.", translation); } return returnVal; } SBarInfo::SBarInfo() //make results more predicable { Init(); } SBarInfo::SBarInfo(int lumpnum) { Init(); ParseSBarInfo(lumpnum); } void SBarInfo::Init() { automapbar = false; interpolateHealth = false; interpolateArmor = false; completeBorder = false; lowerHealthCap = true; interpolationSpeed = 8; armorInterpolationSpeed = 8; height = 0; spacingCharacter = '\0'; } SBarInfo::~SBarInfo() { for (size_t i = 0; i < NUMHUDS; ++i) { huds[i].commands.Clear(); } } void SBarInfoCommand::setString(FScanner &sc, const char* source, int strnum, int maxlength, bool exact) { if(!exact) { if(maxlength != -1 && strlen(source) > (unsigned int) maxlength) { sc.ScriptError("%s is greater than %d characters.", source, maxlength); return; } } else { if(maxlength != -1 && strlen(source) != (unsigned int) maxlength) { sc.ScriptError("%s must be %d characters.", source, maxlength); return; } } string[strnum] = source; } SBarInfoCommand::SBarInfoCommand() //sets the default values for more predicable behavior { type = 0; special = 0; special2 = 0; special3 = 0; special4 = 0; flags = 0; x = 0; y = 0; value = 0; image_index = 0; sprite_index.SetInvalid(); translation = CR_UNTRANSLATED; translation2 = CR_UNTRANSLATED; translation3 = CR_UNTRANSLATED; font = V_GetFont("CONFONT"); } SBarInfoCommand::~SBarInfoCommand() { } SBarInfoBlock::SBarInfoBlock() { forceScaled = false; fullScreenOffsets = false; alpha = FRACUNIT; } //Popup Popup::Popup() { transition = TRANSITION_NONE; height = 320; width = 200; speed = 0; x = 320; y = 200; alpha = FRACUNIT; opened = false; moving = false; } void Popup::init() { x = width; y = height; if(transition == TRANSITION_SLIDEINBOTTOM) { x = 0; } else if(transition == TRANSITION_FADE) { alpha = 0; x = 0; y = 0; } } void Popup::tick() { if(transition == TRANSITION_SLIDEINBOTTOM) { if(moving) { if(opened) y -= clamp(height + (y - height), 1, speed); else y += clamp(height - y, 1, speed); } if(y != 0 && y != height) moving = true; else moving = false; } else if(transition == TRANSITION_FADE) { if(moving) { if(opened) alpha = clamp(alpha + speed, 0, FRACUNIT); else alpha = clamp(alpha - speed2, 0, FRACUNIT); } if(alpha == 0 || alpha == FRACUNIT) moving = false; else moving = true; } else { if(opened) { y = 0; x = 0; } else { y = height; x = width; } moving = false; } } bool Popup::isDoneMoving() { return !moving; } int Popup::getXOffset() { return x; } int Popup::getYOffset() { return y; } int Popup::getAlpha(int maxAlpha) { double a = (double) alpha / (double) FRACUNIT; double b = (double) maxAlpha / (double) FRACUNIT; return fixed_t((a * b) * FRACUNIT); } void Popup::open() { opened = true; moving = true; } void Popup::close() { opened = false; moving = true; }