//------------------------------------------------------------------------- /* Copyright (C) 2016 EDuke32 developers and contributors Copyright (C) 2019 Christoph Oelckers This is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //------------------------------------------------------------------------- #include "ns.h" // Must come before everything else! #include "cheats.h" #include "compat.h" #include "demo.h" #include "duke3d.h" #include "input.h" #include "menus.h" #include "osdcmds.h" #include "savegame.h" #include "game.h" #include "superfasthash.h" #include "gamecvars.h" #include "gamecontrol.h" #include "c_bind.h" #include "menu/menu.h" #include "gstrings.h" #include "version.h" #include "namesdyn.h" #include "menus.h" #include "../../glbackend/glbackend.h" BEGIN_DUKE_NS #define MENU_MARGIN_REGULAR 40 #define MENU_MARGIN_WIDE 32 #define MENU_MARGIN_CENTER 160 #define MENU_HEIGHT_CENTER 100 enum MenuTextFlags_t { MT_Selected = 1 << 0, MT_Disabled = 1 << 1, MT_XCenter = 1 << 2, MT_XRight = 1 << 3, MT_YCenter = 1 << 4, MT_Literal = 1 << 5, MT_RightSide = 1 << 6, }; // common font types // tilenums are set after namesdyn runs. // These are also modifiable by scripts. // emptychar x,y between x,y zoom cursorLeft cursorCenter cursorScale textflags // tilenum shade_deselected shade_disabled pal pal_selected pal_deselected pal_disabled MenuFont_t MF_Redfont = { { 5<<16, 15<<16 }, { 0, 0 }, 65536, 20<<16, 110<<16, 65536, TEXT_BIGALPHANUM | TEXT_UPPERCASE, -1, 10, 0, 0, 0, 0, 1, 0, 0, 1 }; MenuFont_t MF_Bluefont = { { 5<<16, 7<<16 }, { 0, 0 }, 65536, 10<<16, 110<<16, 32768, 0, -1, 10, 0, 0, 10, 10, 16, 0, 0, 16 }; MenuFont_t MF_Minifont = { { 4<<16, 5<<16 }, { 1<<16, 1<<16 }, 65536, 10<<16, 110<<16, 32768, 0, -1, 10, 0, 0, 2, 2, 0, 0, 0, 16 }; /* This function prepares data after ART and CON have been processed. It also initializes some data in loops rather than statically at compile time. */ void Menu_Init(void) { // prepare menu fonts // check if tilenum is -1 in case it was set in EVENT_SETDEFAULTS if ((unsigned)MF_Redfont.tilenum >= MAXTILES) MF_Redfont.tilenum = BIGALPHANUM; if ((unsigned)MF_Bluefont.tilenum >= MAXTILES) MF_Bluefont.tilenum = STARTALPHANUM; if ((unsigned)MF_Minifont.tilenum >= MAXTILES) MF_Minifont.tilenum = MINIFONT; MF_Redfont.emptychar.y = tilesiz[MF_Redfont.tilenum].y << 16; MF_Bluefont.emptychar.y = tilesiz[MF_Bluefont.tilenum].y << 16; MF_Minifont.emptychar.y = tilesiz[MF_Minifont.tilenum].y << 16; if (!minitext_lowercase) MF_Minifont.textflags |= TEXT_UPPERCASE; #if 0 // prepare sound setup #ifndef EDUKE32_STANDALONE if (WW2GI) ME_SOUND_DUKETALK.name = "GI talk:"; else if (NAM) ME_SOUND_DUKETALK.name = "Grunt talk:"; #endif // prepare shareware if (VOLUMEONE) { // blue out episodes beyond the first for (i = 1; i < g_volumeCnt; ++i) { if (MEL_EPISODE[i]) { ME_EPISODE[i].entry = &MEO_EPISODE_SHAREWARE; ME_EPISODE[i].flags |= MEF_LookDisabled; } } M_EPISODE.numEntries = g_volumeCnt; // remove User Map (and spacer) MEOS_NETOPTIONS_EPISODE.numOptions = 1; MenuEntry_DisableOnCondition(&ME_NETOPTIONS_EPISODE, 1); } // prepare pre-Atomic if (!VOLUMEALL || !PLUTOPAK) { // prepare credits M_CREDITS.title = M_CREDITS2.title = M_CREDITS3.title = s_Credits; } #endif } static void Menu_DrawBackground(const DVector2 &origin) { rotatesprite_fs(int(origin.X * 65536) + (MENU_MARGIN_CENTER << 16), int(origin.Y * 65536) + (100 << 16), 65536L, 0, MENUSCREEN, 16, 0, 10 + 64); } static void Menu_DrawTopBar(const DVector2 &origin) { if ((G_GetLogoFlags() & LOGO_NOTITLEBAR) == 0) rotatesprite_fs(int(origin.X*65536) + (MENU_MARGIN_CENTER<<16), int(origin.Y*65536) + (19<<16), MF_Redfont.cursorScale, 0,MENUBAR,16,0,10); } static void Menu_DrawTopBarCaption(const char *caption, const DVector2 &origin) { static char t[64]; size_t const srclen = strlen(caption); size_t const dstlen = min(srclen, ARRAY_SIZE(t)-1); memcpy(t, caption, dstlen); t[dstlen] = '\0'; char *p = &t[dstlen-1]; if (*p == ':') *p = '\0'; captionmenutext(int(origin.X*65536) + (MENU_MARGIN_CENTER<<16), int(origin.Y*65536) + (24<<16) + ((15>>1)<<16), t); } static void Menu_GetFmt(const MenuFont_t* font, uint8_t const status, int32_t* s, int32_t* z) { if (status & MT_Selected) *s = VM_OnEventWithReturn(EVENT_MENUSHADESELECTED, -1, myconnectindex, sintable[((int32_t)totalclock << 5) & 2047] >> 12); else *s = font->shade_deselected; // sum shade values if (status & MT_Disabled) *s += font->shade_disabled; if (FURY && status & MT_Selected) *z += (*z >> 4); } static vec2_t Menu_Text(int32_t x, int32_t y, const MenuFont_t* font, const char* t, uint8_t status, int32_t ydim_upper, int32_t ydim_lower) { int32_t s, p, ybetween = font->between.y; int32_t f = font->textflags; if (status & MT_XCenter) f |= TEXT_XCENTER; if (status & MT_XRight) f |= TEXT_XRIGHT; if (status & MT_YCenter) { f |= TEXT_YCENTER | TEXT_YOFFSETZERO; ybetween = font->emptychar.y; // <^ the battle against 'Q' } if (status & MT_Literal) f |= TEXT_LITERALESCAPE; int32_t z = font->zoom; if (status & MT_Disabled) p = (status & MT_RightSide) ? font->pal_disabled_right : font->pal_disabled; else if (status & MT_Selected) p = (status & MT_RightSide) ? font->pal_selected_right : font->pal_selected; else p = (status & MT_RightSide) ? font->pal_deselected_right : font->pal_deselected; Menu_GetFmt(font, status, &s, &z); return G_ScreenText(font->tilenum, x, y, z, 0, 0, t, s, p, 2 | 8 | 16 | ROTATESPRITE_FULL16, 0, font->emptychar.x, font->emptychar.y, font->between.x, ybetween, f, 0, ydim_upper, xdim - 1, ydim_lower); } static vec2_t mgametextcenterat(int32_t x, int32_t y, char const* t, int32_t f = 0) { return G_ScreenText(MF_Bluefont.tilenum, x, y, MF_Bluefont.zoom, 0, 0, t, 0, MF_Bluefont.pal, 2 | 8 | 16 | ROTATESPRITE_FULL16, 0, MF_Bluefont.emptychar.x, MF_Bluefont.emptychar.y, MF_Bluefont.between.x, MF_Bluefont.between.y, MF_Bluefont.textflags | f | TEXT_XCENTER, 0, 0, xdim - 1, ydim - 1); } static vec2_t mgametextcenter(int32_t x, int32_t y, char const* t, int32_t f = 0) { return mgametextcenterat((MENU_MARGIN_CENTER << 16) + x, y, t, f); } static int32_t Menu_CursorShade(void) { return VM_OnEventWithReturn(EVENT_MENUCURSORSHADE, -1, myconnectindex, 4 - (sintable[((int32_t)totalclock << 4) & 2047] >> 11)); } static void Menu_DrawCursorCommon(int32_t x, int32_t y, int32_t z, int32_t picnum, int32_t ydim_upper = 0, int32_t ydim_lower = ydim - 1) { rotatesprite_(x, y, z, 0, picnum, Menu_CursorShade(), 0, 2 | 8, 0, 0, 0, ydim_upper, xdim - 1, ydim_lower); } static void Menu_DrawCursorLeft(int32_t x, int32_t y, int32_t z) { if (FURY) return; Menu_DrawCursorCommon(x, y, z, VM_OnEventWithReturn(EVENT_MENUCURSORLEFT, -1, myconnectindex, SPINNINGNUKEICON + (((int32_t)totalclock >> 3) % 7))); } static void Menu_DrawCursorRight(int32_t x, int32_t y, int32_t z) { if (FURY) return; Menu_DrawCursorCommon(x, y, z, VM_OnEventWithReturn(EVENT_MENUCURSORRIGHT, -1, myconnectindex, SPINNINGNUKEICON + 6 - ((6 + ((int32_t)totalclock >> 3)) % 7))); } static int Menu_GetFontHeight(int fontnum) { auto& font = fontnum == NIT_BigFont ? MF_Redfont : fontnum == NIT_SmallFont ? MF_Bluefont : MF_Minifont; return font.get_yline(); } //---------------------------------------------------------------------------- // // Implements the native looking menu used for the main menu // and the episode/skill selection screens, i.e. the parts // that need to look authentic // //---------------------------------------------------------------------------- class DukeListMenu : public DListMenu { using Super = DListMenu; protected: void SelectionChanged() override { if (mDesc->mScriptId == 110) { // Hack alert: Ion Fury depends on the skill getting set globally when the selection changes because the script cannot detect actual selection changes. // Yuck! ud.m_player_skill = mDesc->mSelectedItem+1; } } virtual void CallScript(int event, bool getorigin = false) { ud.returnvar[0] = int(origin.X * 65536); ud.returnvar[1] = int(origin.Y * 65536); ud.returnvar[2] = mDesc->mSelectedItem; VM_OnEventWithReturn(event, g_player[screenpeek].ps->i, screenpeek, mDesc->mScriptId); if (getorigin) { origin.X = ud.returnvar[0] / 65536.; origin.Y = ud.returnvar[1] / 65536.; } } void Ticker() override { auto lf = G_GetLogoFlags(); help_disabled = (lf & LOGO_NOHELP); credits_disabled = (lf & LOGO_NOCREDITS); // Lay out the menu. Since scripts are allowed to mess around with the font this needs to be redone each frame. int32_t y_upper = mDesc->mYpos; int32_t y_lower = y_upper + mDesc->mYbotton; int32_t y = 0; int32_t calculatedentryspacing = 0; int32_t const height = Menu_GetFontHeight(mDesc->mNativeFontNum) >> 16; int32_t totalheight = 0, numvalidentries = mDesc->mItems.Size(); for (unsigned e = 0; e < mDesc->mItems.Size(); ++e) { auto entry = mDesc->mItems[e]; entry->mHidden = false; if (entry->GetAction(nullptr) == NAME_HelpMenu && help_disabled) { entry->mHidden = true; numvalidentries--; continue; } else if (entry->GetAction(nullptr) == NAME_CreditsMenu && credits_disabled) { entry->mHidden = true; numvalidentries--; continue; } entry->SetHeight(height); totalheight += height; } if (mDesc->mSpacing <= 0) calculatedentryspacing = std::max(0, (y_lower - y_upper - totalheight) / (numvalidentries > 1 ? numvalidentries - 1 : 1)); if (calculatedentryspacing <= 0) calculatedentryspacing = mDesc->mSpacing; // totalHeight calculating pass int totalHeight; for (unsigned e = 0; e < mDesc->mItems.Size(); ++e) { auto entry = mDesc->mItems[e]; if (!entry->mHidden) { entry->SetY(y_upper + y); y += height; totalHeight = y; y += calculatedentryspacing; } } } void PreDraw() override { CallScript(CurrentMenu == this ? EVENT_DISPLAYMENU : EVENT_DISPLAYINACTIVEMENU, true); Super::PreDraw(); } void PostDraw() override { CallScript(CurrentMenu == this ? EVENT_DISPLAYMENUREST : EVENT_DISPLAYINACTIVEMENUREST, false); } }; class DukeNewGameCustomSubMenu : public DukeListMenu { virtual void CallScript(int event, bool getorigin) override { // This needs to get the secondary ID to the script. ud.returnvar[3] = mDesc->mSecondaryId; DukeListMenu::CallScript(event, getorigin); } }; class DukeMainMenu : public DukeListMenu { void PreDraw() override { DukeListMenu::PreDraw(); if ((G_GetLogoFlags() & LOGO_NOGAMETITLE) == 0) { rotatesprite_fs(int(origin.X * 65536) + (MENU_MARGIN_CENTER<<16), int(origin.Y * 65536) + ((28)<<16), 65536L,0,INGAMEDUKETHREEDEE,0,0,10); if (PLUTOPAK) // JBF 20030804 rotatesprite_fs(int(origin.X * 65536) + ((MENU_MARGIN_CENTER+100)<<16), int(origin.Y * 65536) + (36<<16), 65536L,0,PLUTOPAKSPRITE+2,(sintable[((int32_t) totalclock<<4)&2047]>>11),0,2+8); } } }; //---------------------------------------------------------------------------- // // Hack to display Ion Fury's credits screens // //---------------------------------------------------------------------------- class DukeImageScreen : public ImageScreen { public: DukeImageScreen(FImageScrollerDescriptor::ScrollerItem* desc) : ImageScreen(desc) {} void CallScript(int event, bool getorigin = false) { ud.returnvar[0] = int(origin.X * 65536); ud.returnvar[1] = int(origin.Y * 65536); ud.returnvar[2] = 0; VM_OnEventWithReturn(event, g_player[screenpeek].ps->i, screenpeek, mDesc->scriptID); if (getorigin) { origin.X = ud.returnvar[0] / 65536.; origin.Y = ud.returnvar[1] / 65536.; } } void Drawer() override { // Hack alert: The Ion Fury scripts - being true to the entire design here, take the current menu value // not from the passed variable but instead from the global current_menu, so we have to temporarily alter that here. // Ugh. (Talk about "broken by design"...) auto cm = g_currentMenu; g_currentMenu = mDesc->scriptID; auto o = origin; CallScript(EVENT_DISPLAYMENU, true); ImageScreen::Drawer(); CallScript(EVENT_DISPLAYMENUREST, false); g_currentMenu = cm; origin = o; } }; class DDukeImageScrollerMenu : public DImageScrollerMenu { ImageScreen* newImageScreen(FImageScrollerDescriptor::ScrollerItem* desc) override { return new DukeImageScreen(desc); } }; //---------------------------------------------------------------------------- // // Menu related game interface functions // //---------------------------------------------------------------------------- void GameInterface::DrawNativeMenuText(int fontnum, int state, double xpos, double ypos, float fontscale, const char* text, int flags) { int ydim_upper = 0; int ydim_lower = ydim - 1; //int32_t const indent = 0; // not set for any relevant menu int x = int(xpos * 65536); uint8_t status = 0; if (state == NIT_SelectedState) status |= MT_Selected; if (state == NIT_InactiveState) status |= MT_Disabled; if (flags & LMF_Centered) status |= MT_XCenter; bool const dodraw = true; MenuFont_t& font = fontnum == NIT_BigFont ? MF_Redfont : fontnum == NIT_SmallFont ? MF_Bluefont : MF_Minifont; int32_t const height = font.get_yline(); status |= MT_YCenter; int32_t const y_internal = int(ypos * 65536) + ((height >> 17) << 16);// -menu->scrollPos; vec2_t textsize; if (dodraw) textsize = Menu_Text(x, y_internal, &font, text, status, ydim_upper, ydim_lower); if (dodraw && (status & MT_Selected) && state != 1) { if (status & MT_XCenter) { Menu_DrawCursorLeft(x + font.cursorCenterPosition, y_internal, font.cursorScale); Menu_DrawCursorRight(x - font.cursorCenterPosition, y_internal, font.cursorScale); } else Menu_DrawCursorLeft(x /*+ indent*/ - font.cursorLeftPosition, y_internal, font.cursorScale); } } void GameInterface::MenuOpened() { S_PauseSounds(true); if ((!g_netServer && ud.multimode < 2)) { ready2send = 0; totalclock = ototalclock; screenpeek = myconnectindex; } auto& gm = g_player[myconnectindex].ps->gm; if (gm & MODE_GAME) { gm |= MODE_MENU; } } void GameInterface::MenuSound(EMenuSounds snd) { switch (snd) { case CursorSound: S_PlaySound(KICK_HIT, CHAN_AUTO, CHANF_UI); break; case AdvanceSound: S_PlaySound(PISTOL_BODYHIT, CHAN_AUTO, CHANF_UI); break; case CloseSound: S_PlaySound(EXITMENUSOUND, CHAN_AUTO, CHANF_UI); break; default: return; } } void GameInterface::MenuClosed() { auto& gm = g_player[myconnectindex].ps->gm; if (gm & MODE_GAME) { if (gm & MODE_MENU) I_ClearAllInput(); // The following lines are here so that you cannot close the menu when no game is running. gm &= ~MODE_MENU; if ((!g_netServer && ud.multimode < 2) && ud.recstat != 2) { ready2send = 1; totalclock = ototalclock; CAMERACLOCK = (int32_t)totalclock; CAMERADIST = 65536; // Reset next-viewscreen-redraw counter. // XXX: are there any other cases like that in need of handling? if (g_curViewscreen >= 0) actor[g_curViewscreen].t_data[0] = (int32_t)totalclock; } G_UpdateScreenArea(); S_PauseSounds(false); } } bool GameInterface::CanSave() { if (ud.recstat == 2) return false; auto &myplayer = *g_player[myconnectindex].ps; if (sprite[myplayer.i].extra <= 0) { P_DoQuote(QUOTE_SAVE_DEAD, &myplayer); return false; } return true; } void GameInterface::CustomMenuSelection(int menu, int item) { ud.returnvar[0] = item; ud.returnvar[1] = -1; VM_OnEventWithReturn(EVENT_NEWGAMECUSTOM, -1, myconnectindex, menu); } void GameInterface::StartGame(FGameStartup& gs) { int32_t skillsound = PISTOL_BODYHIT; switch (gs.Skill) { case 0: skillsound = JIBBED_ACTOR6; break; case 1: skillsound = BONUS_SPEECH1; break; case 2: skillsound = DUKE_GETWEAPON2; break; case 3: skillsound = JIBBED_ACTOR5; break; } ud.m_player_skill = gs.Skill + 1; if (menu_sounds && skillsound >= 0 && SoundEnabled()) { S_PlaySound(skillsound, CHAN_AUTO, CHANF_UI); while (S_CheckSoundPlaying(skillsound)) { S_Update(); gameHandleEvents(); } } ud.m_respawn_monsters = (gs.Skill == 3); ud.m_monsters_off = ud.monsters_off = 0; ud.m_respawn_items = 0; ud.m_respawn_inventory = 0; ud.multimode = 1; ud.m_volume_number = gs.Episode; m_level_number = gs.Level; G_NewGame_EnterLevel(); } FSavegameInfo GameInterface::GetSaveSig() { return { SAVESIG_DN3D, MINSAVEVER_DN3D, SAVEVER_DN3D }; } void GameInterface::DrawMenuCaption(const DVector2& origin, const char* text) { Menu_DrawTopBar(origin); Menu_DrawTopBarCaption(text, origin); } //---------------------------------------------------------------------------- // // // //---------------------------------------------------------------------------- static void shadowminitext(int32_t x, int32_t y, const char* t, int32_t p) { int32_t f = 0; if (!minitext_lowercase) f |= TEXT_UPPERCASE; G_ScreenTextShadow(1, 1, MINIFONT, x, y, 65536, 0, 0, t, 0, p, 2 | 8 | 16 | ROTATESPRITE_FULL16, 0, 4 << 16, 8 << 16, 1 << 16, 0, f, 0, 0, xdim - 1, ydim - 1); } //---------------------------------------------------------------------------- // // allows the front end to override certain fullscreen image menus // with custom implementations. // // This is needed because the credits screens in Duke Nukem // are eithrr done by providing an image or by printing text, based on the version used. // //---------------------------------------------------------------------------- bool GameInterface::DrawSpecialScreen(const DVector2 &origin, int tilenum) { // Older versions of Duke Nukem create the credits screens manually. // On the latest version there's real graphics for this. bool haveCredits = VOLUMEALL && PLUTOPAK; int32_t m, l; if (!haveCredits) { if (tilenum == CREDITSTEXT1) { Menu_DrawBackground(origin); m = int(origin.X * 65536) + (20 << 16); l = int(origin.Y * 65536) + (33 << 16); shadowminitext(m, l, "Original Concept", 12); l += 7 << 16; shadowminitext(m, l, "Todd Replogle and Allen H. Blum III", 12); l += 7 << 16; l += 3 << 16; shadowminitext(m, l, "Produced & Directed By", 12); l += 7 << 16; shadowminitext(m, l, "Greg Malone", 12); l += 7 << 16; l += 3 << 16; shadowminitext(m, l, "Executive Producer", 12); l += 7 << 16; shadowminitext(m, l, "George Broussard", 12); l += 7 << 16; l += 3 << 16; shadowminitext(m, l, "BUILD Engine", 12); l += 7 << 16; shadowminitext(m, l, "Ken Silverman", 12); l += 7 << 16; l += 3 << 16; shadowminitext(m, l, "Game Programming", 12); l += 7 << 16; shadowminitext(m, l, "Todd Replogle", 12); l += 7 << 16; l += 3 << 16; shadowminitext(m, l, "3D Engine/Tools/Net", 12); l += 7 << 16; shadowminitext(m, l, "Ken Silverman", 12); l += 7 << 16; l += 3 << 16; shadowminitext(m, l, "Network Layer/Setup Program", 12); l += 7 << 16; shadowminitext(m, l, "Mark Dochtermann", 12); l += 7 << 16; l += 3 << 16; shadowminitext(m, l, "Map Design", 12); l += 7 << 16; shadowminitext(m, l, "Allen H. Blum III", 12); l += 7 << 16; shadowminitext(m, l, "Richard Gray", 12); l += 7 << 16; m = int(origin.X * 65536) + (180 << 16); l = int(origin.Y * 65536) + (33 << 16); shadowminitext(m, l, "3D Modeling", 12); l += 7 << 16; shadowminitext(m, l, "Chuck Jones", 12); l += 7 << 16; shadowminitext(m, l, "Sapphire Corporation", 12); l += 7 << 16; l += 3 << 16; shadowminitext(m, l, "Artwork", 12); l += 7 << 16; shadowminitext(m, l, "Dirk Jones, Stephen Hornback", 12); l += 7 << 16; shadowminitext(m, l, "James Storey, David Demaret", 12); l += 7 << 16; shadowminitext(m, l, "Douglas R. Wood", 12); l += 7 << 16; l += 3 << 16; shadowminitext(m, l, "Sound Engine", 12); l += 7 << 16; shadowminitext(m, l, "Jim Dose", 12); l += 7 << 16; l += 3 << 16; shadowminitext(m, l, "Sound & Music Development", 12); l += 7 << 16; shadowminitext(m, l, "Robert Prince", 12); l += 7 << 16; shadowminitext(m, l, "Lee Jackson", 12); l += 7 << 16; l += 3 << 16; shadowminitext(m, l, "Voice Talent", 12); l += 7 << 16; shadowminitext(m, l, "Lani Minella - Voice Producer", 12); l += 7 << 16; shadowminitext(m, l, "Jon St. John as \"Duke Nukem\"", 12); l += 7 << 16; l += 3 << 16; shadowminitext(m, l, "Graphic Design", 12); l += 7 << 16; shadowminitext(m, l, "Packaging, Manual, Ads", 12); l += 7 << 16; shadowminitext(m, l, "Robert M. Atkins", 12); l += 7 << 16; shadowminitext(m, l, "Michael Hadwin", 12); l += 7 << 16; return true; } else if (tilenum == CREDITSTEXT2__STATIC) { Menu_DrawBackground(origin); m = int(origin.X * 65536) + (20 << 16); l = int(origin.Y * 65536) + (33 << 16); shadowminitext(m, l, "Special Thanks To", 12); l += 7 << 16; shadowminitext(m, l, "Steven Blackburn, Tom Hall", 12); l += 7 << 16; shadowminitext(m, l, "Scott Miller, Joe Siegler", 12); l += 7 << 16; shadowminitext(m, l, "Terry Nagy, Colleen Compton", 12); l += 7 << 16; shadowminitext(m, l, "HASH, Inc., FormGen, Inc.", 12); l += 7 << 16; l += 3 << 16; shadowminitext(m, l, "The 3D Realms Beta Testers", 12); l += 7 << 16; l += 3 << 16; shadowminitext(m, l, "Nathan Anderson, Wayne Benner", 12); l += 7 << 16; shadowminitext(m, l, "Glenn Brensinger, Rob Brown", 12); l += 7 << 16; shadowminitext(m, l, "Erik Harris, Ken Heckbert", 12); l += 7 << 16; shadowminitext(m, l, "Terry Herrin, Greg Hively", 12); l += 7 << 16; shadowminitext(m, l, "Hank Leukart, Eric Baker", 12); l += 7 << 16; shadowminitext(m, l, "Jeff Rausch, Kelly Rogers", 12); l += 7 << 16; shadowminitext(m, l, "Mike Duncan, Doug Howell", 12); l += 7 << 16; shadowminitext(m, l, "Bill Blair", 12); l += 7 << 16; m = int(origin.X * 65536) + (160 << 16); l = int(origin.Y * 65536) + (33 << 16); shadowminitext(m, l, "Company Product Support", 12); l += 7 << 16; l += 3 << 16; shadowminitext(m, l, "The following companies were cool", 12); l += 7 << 16; shadowminitext(m, l, "enough to give us lots of stuff", 12); l += 7 << 16; shadowminitext(m, l, "during the making of Duke Nukem 3D.", 12); l += 7 << 16; l += 3 << 16; shadowminitext(m, l, "Altec Lansing Multimedia", 12); l += 7 << 16; shadowminitext(m, l, "for tons of speakers and the", 12); l += 7 << 16; shadowminitext(m, l, "THX-licensed sound system.", 12); l += 7 << 16; shadowminitext(m, l, "For info call 1-800-548-0620", 12); l += 7 << 16; l += 3 << 16; shadowminitext(m, l, "Creative Labs, Inc.", 12); l += 7 << 16; l += 3 << 16; shadowminitext(m, l, "Thanks for the hardware, guys.", 12); l += 7 << 16; return true; } else if (tilenum == CREDITSTEXT3) { Menu_DrawBackground(origin); mgametextcenter(int(origin.X * 65536), int(origin.Y * 65536) + (50 << 16), "Duke Nukem 3D is a trademark of\n" "3D Realms Entertainment" "\n" "Duke Nukem 3D\n" "(C) 1996 3D Realms Entertainment"); if (VOLUMEONE) { mgametextcenter(int(origin.X * 65536), int(origin.Y * 65536) + (106 << 16), "Please read LICENSE.DOC for shareware\n" "distribution grants and restrictions."); } mgametextcenter(int(origin.X * 65536), int(origin.Y * 65536) + ((VOLUMEONE ? 134 : 115) << 16), "Made in Dallas, Texas USA"); return true; } } return false; } void GameInterface::DrawCenteredTextScreen(const DVector2 &origin, const char *text, int position, bool bg) { if (bg) Menu_DrawBackground(origin); else { // Only used for the confirmation screen. int lines = 1; for (int i = 0; text[i]; i++) if (text[i] == '\n') lines++; int height = lines * Menu_GetFontHeight(NIT_SmallFont); position -= height >> 17; Menu_DrawCursorLeft(160 << 16, 130 << 16, 65536); } mgametextcenter(int(origin.X * 65536), int((origin.Y + position) * 65536), text); } void GameInterface::DrawPlayerSprite(const DVector2& origin, bool onteam) { rotatesprite_fs(int(origin.X * 65536) + (260<<16), int(origin.Y*65536) + ((24+(tilesiz[APLAYER].y>>1))<<16), 49152L,0,1441-((((4-((int32_t) totalclock>>4)))&3)*5),0,onteam ? G_GetTeamPalette(playerteam) : G_CheckPlayerColor(playercolor),10); } void GameInterface::QuitToTitle() { g_player[myconnectindex].ps->gm = MODE_DEMO; if (ud.recstat == 1) G_CloseDemoWrite(); artClearMapArt(); } END_DUKE_NS //---------------------------------------------------------------------------- // // Class registration // //---------------------------------------------------------------------------- static TMenuClassDescriptor _mm("Duke.MainMenu"); static TMenuClassDescriptor _lm("Duke.ListMenu"); static TMenuClassDescriptor _ngcsm("Duke.NewGameCustomSubMenu"); static TMenuClassDescriptor _ism("Duke.ImageScrollerMenu"); void RegisterDukeMenus() { menuClasses.Push(&_mm); menuClasses.Push(&_lm); menuClasses.Push(&_ngcsm); menuClasses.Push(&_ism); }