bool ConfirmScreen(const char *info, const char *msg) { int w = gGraphicsDevice.cachedConfig.Res.x; int h = gGraphicsDevice.cachedConfig.Res.y; ClearScreen(&gGraphicsDevice); FontStr(info, Vec2iNew((w - FontStrW(info)) / 2, (h - FontH()) / 2)); FontStr(msg, Vec2iNew((w - FontStrW(msg)) / 2, (h + FontH()) / 2)); BlitFlip(&gGraphicsDevice, &gConfig.Graphics); int c = GetKey(&gEventHandlers); return (c == 'Y' || c == 'y'); }
bool ConfirmScreen(const char *info, const char *msg) { int w = gGraphicsDevice.cachedConfig.Res.x; int h = gGraphicsDevice.cachedConfig.Res.y; ClearScreen(&gGraphicsDevice); FontStr(info, Vec2iNew((w - FontStrW(info)) / 2, (h - FontH()) / 2)); FontStr(msg, Vec2iNew((w - FontStrW(msg)) / 2, (h + FontH()) / 2)); BlitFlip(&gGraphicsDevice); SDL_Keycode k = SDL_GetKeyFromScancode(GetKey(&gEventHandlers)); return k == SDLK_y; }
static void DeathmatchFinalScoresDraw(void *data) { UNUSED(data); // This will only draw once const int w = gGraphicsDevice.cachedConfig.Res.x; const int h = gGraphicsDevice.cachedConfig.Res.y; GraphicsBlitBkg(&gGraphicsDevice); // Work out the highest kills int maxKills = 0; for (int i = 0; i < (int)gPlayerDatas.size; i++) { const PlayerData *p = CArrayGet(&gPlayerDatas, i); if (p->kills > maxKills) { maxKills = p->kills; } } // Draw players and their names spread evenly around the screen. CASSERT( gPlayerDatas.size >= 2 && gPlayerDatas.size <= 4, "Unimplemented number of players for deathmatch"); #define LAST_MAN_TEXT "Last man standing!" for (int i = 0; i < (int)gPlayerDatas.size; i++) { const Vec2i pos = Vec2iNew( w / 4 + (i & 1) * w / 2, gPlayerDatas.size == 2 ? h / 2 : h / 4 + (i / 2) * h / 2); const PlayerData *p = CArrayGet(&gPlayerDatas, i); DisplayCharacterAndName(pos, &p->Char, p->name); // Kills char s[16]; sprintf(s, "Kills: %d", p->kills); FontStrMask( s, Vec2iNew(pos.x - FontStrW(s) / 2, pos.y + 20), p->kills == maxKills ? colorGreen : colorWhite); // Last man standing? if (p->Lives > 0) { FontStrMask( LAST_MAN_TEXT, Vec2iNew(pos.x - FontStrW(LAST_MAN_TEXT) / 2, pos.y + 30), colorGreen); } } }
bool ConfirmScreen(const char *info, const char *msg) { int w = gGraphicsDevice.cachedConfig.Res.x; int h = gGraphicsDevice.cachedConfig.Res.y; ClearScreen(&gGraphicsDevice); FontStr(info, svec2i((w - FontStrW(info)) / 2, (h - FontH()) / 2)); FontStr(msg, svec2i((w - FontStrW(msg)) / 2, (h + FontH()) / 2)); WindowContextPreRender(&gGraphicsDevice.gameWindow); BlitUpdateFromBuf(&gGraphicsDevice, gGraphicsDevice.screen); WindowContextPostRender(&gGraphicsDevice.gameWindow); SDL_Keycode k = SDL_GetKeyFromScancode(GetKey(&gEventHandlers)); return k == SDLK_y; }
static void ShowPlayerScore(const Vec2i pos, const int score) { char s[16]; sprintf(s, "Score: %d", score); const Vec2i scorePos = Vec2iNew(pos.x - FontStrW(s) / 2, pos.y + 20); FontStr(s, scorePos); }
static void DrawSpawnerName( const TObject *obj, DrawBuffer *b, const Vec2i offset) { const char *name = obj->Class->u.PickupClass->Name; const Vec2i textPos = Vec2iNew( obj->tileItem.x - b->xTop + offset.x - FontStrW(name) / 2, obj->tileItem.y - b->yTop + offset.y); FontStr(name, textPos); }
static void DogfightFinalScoresDraw(void *data) { UNUSED(data); // This will only draw once const int w = gGraphicsDevice.cachedConfig.Res.x; const int h = gGraphicsDevice.cachedConfig.Res.y; GraphicsBlitBkg(&gGraphicsDevice); // Work out who's the winner, or if it's a tie int maxScore = 0; int playersWithMaxScore = 0; for (int i = 0; i < (int)gPlayerDatas.size; i++) { const PlayerData *p = CArrayGet(&gPlayerDatas, i); if (p->RoundsWon > maxScore) { maxScore = p->RoundsWon; playersWithMaxScore = 1; } else if (p->RoundsWon == maxScore) { playersWithMaxScore++; } } const bool isTie = playersWithMaxScore == (int)gPlayerDatas.size; // Draw players and their names spread evenly around the screen. // If it's a tie, display the message in the centre, // otherwise display the winner just below the winning player #define DRAW_TEXT "It's a draw!" #define WINNER_TEXT "Winner!" CASSERT( gPlayerDatas.size >= 2 && gPlayerDatas.size <= 4, "Unimplemented number of players for dogfight"); for (int i = 0; i < (int)gPlayerDatas.size; i++) { const Vec2i pos = Vec2iNew( w / 4 + (i & 1) * w / 2, gPlayerDatas.size == 2 ? h / 2 : h / 4 + (i / 2) * h / 2); const PlayerData *p = CArrayGet(&gPlayerDatas, i); DisplayCharacterAndName(pos, &p->Char, p->name); ShowPlayerScore(pos, p->RoundsWon); if (!isTie && maxScore == p->RoundsWon) { FontStrMask( WINNER_TEXT, Vec2iNew(pos.x - FontStrW(WINNER_TEXT) / 2, pos.y + 30), colorGreen); } } if (isTie) { FontStrCenter(DRAW_TEXT); } }
static void DrawSpawnerName( const TObject *obj, DrawBuffer *b, const struct vec2i offset) { const char *name = obj->Class->u.PickupClass->Name; const struct vec2i textPos = svec2i( (int)obj->thing.Pos.x - b->xTop + offset.x - FontStrW(name) / 2, (int)obj->thing.Pos.y - b->yTop + offset.y); FontStr(name, textPos); }
// Display a character and the player name above it, with the character // centered around the target position void DisplayCharacterAndName(Vec2i pos, Character *c, char *name) { Vec2i namePos; // Move the point down a bit since the default character draw point is at // its feet pos.y += 8; namePos = Vec2iAdd(pos, Vec2iNew(-FontStrW(name) / 2, -30)); DrawCharacterSimple( c, pos, DIRECTION_DOWN, STATE_IDLE, -1, GUNSTATE_READY, &c->table); FontStr(name, namePos); }
static void DrawObjectiveName( const TTileItem *ti, DrawBuffer *b, const Vec2i offset) { const int objective = ObjectiveFromTileItem(ti->flags); const Objective *o = CArrayGet(&gMission.missionData->Objectives, objective); const char *typeName = ObjectiveTypeStr(o->Type); const Vec2i textPos = Vec2iNew( ti->x - b->xTop + offset.x - FontStrW(typeName) / 2, ti->y - b->yTop + offset.y); FontStr(typeName, textPos); }
static void DrawObjectiveName( const Thing *ti, DrawBuffer *b, const struct vec2i offset) { const int objective = ObjectiveFromThing(ti->flags); const Objective *o = CArrayGet(&gMission.missionData->Objectives, objective); const char *typeName = ObjectiveTypeStr(o->Type); const struct vec2i textPos = svec2i( (int)ti->Pos.x - b->xTop + offset.x - FontStrW(typeName) / 2, (int)ti->Pos.y - b->yTop + offset.y); FontStr(typeName, textPos); }
static int DisplayEntry(int x, int y, int idx, struct Entry *e, int hilite) { char s[10]; #define INDEX_OFFSET 15 #define SCORE_OFFSET 40 #define MISSIONS_OFFSET 60 #define MISSION_OFFSET 80 #define NAME_OFFSET 85 sprintf(s, "%d.", idx + 1); DisplayAt(x + INDEX_OFFSET - FontStrW(s), y, s, hilite); sprintf(s, "%d", e->score); DisplayAt(x + SCORE_OFFSET - FontStrW(s), y, s, hilite); sprintf(s, "%d", e->missions); DisplayAt(x + MISSIONS_OFFSET - FontStrW(s), y, s, hilite); sprintf(s, "(%d)", e->lastMission + 1); DisplayAt(x + MISSION_OFFSET - FontStrW(s), y, s, hilite); DisplayAt(x + NAME_OFFSET, y, e->name, hilite); return 1 + FontH(); }
static void DrawChatter( const TTileItem *ti, DrawBuffer *b, const Vec2i offset) { const TActor *a = CArrayGet(&gActors, ti->id); // Draw character text if (strlen(a->Chatter) > 0) { const Vec2i textPos = Vec2iNew( a->tileItem.x - b->xTop + offset.x - FontStrW(a->Chatter) / 2, a->tileItem.y - b->yTop + offset.y - ACTOR_HEIGHT); FontStr(a->Chatter, textPos); } }
static void DrawObjectiveCounts(HUD *hud) { int x = 5 + GAUGE_WIDTH; int y = hud->device->cachedConfig.Res.y - 5 - FontH(); for (int i = 0; i < (int)gMission.missionData->Objectives.size; i++) { MissionObjective *mo = CArrayGet(&gMission.missionData->Objectives, i); const ObjectiveDef *o = CArrayGet(&gMission.Objectives, i); // Don't draw anything for optional objectives if (mo->Required == 0) { continue; } // Objective color dot Draw_Rect(x, y + 3, 2, 2, o->color); x += 5; char s[32]; int itemsLeft = mo->Required - o->done; if (itemsLeft > 0) { if (!(mo->Flags & OBJECTIVE_UNKNOWNCOUNT)) { sprintf(s, "%s: %d", ObjectiveTypeStr(mo->Type), itemsLeft); } else { sprintf(s, "%s: ?", ObjectiveTypeStr(mo->Type)); } } else { strcpy(s, "Done"); } FontStr(s, Vec2iNew(x, y)); DrawNumUpdate( CArrayGet(&hud->objectiveUpdates, i), "%d", o->done, Vec2iNew(x + FontStrW(s) - 8, y), 0); x += 40; } }
static void VictoryDraw(void *data) { const VictoryData *vd = data; const int w = gGraphicsDevice.cachedConfig.Res.x; FontOpts opts = FontOptsNew(); opts.HAlign = ALIGN_CENTER; opts.Area = gGraphicsDevice.cachedConfig.Res; int y = 30; // Congratulations text #define CONGRATULATIONS "Congratulations, you have completed " FontStrOpt(CONGRATULATIONS, svec2i(0, y), opts); y += 15; opts.Mask = colorRed; FontStrOpt(vd->Campaign->Setting.Title, svec2i(0, y), opts); y += 15; // Final words struct vec2i pos = svec2i((w - FontStrW(vd->FinalWords)) / 2, y); pos = FontChMask('"', pos, colorDarker); pos = FontStrMask(vd->FinalWords, pos, colorPurple); FontChMask('"', pos, colorDarker); }
void MenuDisplayPlayerControls( menu_t *menu, GraphicsDevice *g, Vec2i pos, Vec2i size, void *data) { UNUSED(g); char s[256]; MenuDisplayPlayerControlsData *d = data; Vec2i textPos = Vec2iNew(0, pos.y + size.y - FontH()); int textWidth = 0; UNUSED(menu); switch (d->pData->inputDevice) { case INPUT_DEVICE_KEYBOARD: { input_keys_t *keys = &d->inputConfig->PlayerKeys[d->pData->deviceIndex].Keys; sprintf(s, "(%s, %s, %s, %s, %s and %s)", SDL_GetKeyName(keys->left), SDL_GetKeyName(keys->right), SDL_GetKeyName(keys->up), SDL_GetKeyName(keys->down), SDL_GetKeyName(keys->button1), SDL_GetKeyName(keys->button2)); textWidth = FontStrW(s); } break; case INPUT_DEVICE_MOUSE: sprintf(s, "(mouse wheel to scroll, left and right click)"); break; case INPUT_DEVICE_JOYSTICK: sprintf(s, "(%s)", InputDeviceName(d->pData->inputDevice, d->pData->deviceIndex)); break; case INPUT_DEVICE_NET: sprintf(s, "(Network)"); break; case INPUT_DEVICE_AI: sprintf(s, "(Computer)"); break; default: assert(0 && "unknown device"); break; } // If the text is too long, split the text with a newline textWidth = FontStrW(s); if (textWidth < 125) { textPos.x = pos.x - textWidth / 2; FontStr(s, textPos); } else { // find the first whitespace before half of the string, split there, // and print two lines char *secondLine; for (secondLine = &s[strlen(s) / 2]; secondLine > s; secondLine--) { if (isspace(*secondLine)) { *secondLine = '\0'; secondLine++; break; } } textWidth = FontStrW(s); textPos.x = pos.x - textWidth / 2; textPos.y -= FontH(); FontStr(s, textPos); textWidth = FontStrW(secondLine); textPos.x = pos.x - textWidth / 2; textPos.y += FontH(); FontStr(secondLine, textPos); } }
static void VictoryDraw(void *data) { // This will only draw once const CampaignOptions *c = data; GraphicsBlitBkg(&gGraphicsDevice); const int w = gGraphicsDevice.cachedConfig.Res.x; const int h = gGraphicsDevice.cachedConfig.Res.y; FontOpts opts = FontOptsNew(); opts.HAlign = ALIGN_CENTER; opts.Area = gGraphicsDevice.cachedConfig.Res; int y = 100; // Congratulations text #define CONGRATULATIONS "Congratulations, you have completed " FontStrOpt(CONGRATULATIONS, Vec2iNew(0, y), opts); y += 15; opts.Mask = colorRed; FontStrOpt(c->Setting.Title, Vec2iNew(0, y), opts); // Display players switch (gPlayerDatas.size) { case 1: { const PlayerData *p = CArrayGet(&gPlayerDatas, 0); DisplayCharacterAndName(Vec2iNew(w / 4, h / 4), &p->Char, p->name); } break; case 2: { // side by side const PlayerData *p1 = CArrayGet(&gPlayerDatas, 0); DisplayCharacterAndName( Vec2iNew(w / 8, h / 4), &p1->Char, p1->name); const PlayerData *p2 = CArrayGet(&gPlayerDatas, 1); DisplayCharacterAndName( Vec2iNew(w / 8 + w / 2, h / 4), &p2->Char, p2->name); } break; case 3: // fallthrough case 4: { // 2x2 const PlayerData *p1 = CArrayGet(&gPlayerDatas, 0); DisplayCharacterAndName( Vec2iNew(w / 8, h / 8), &p1->Char, p1->name); const PlayerData *p2 = CArrayGet(&gPlayerDatas, 1); DisplayCharacterAndName( Vec2iNew(w / 8 + w / 2, h / 8), &p2->Char, p2->name); const PlayerData *p3 = CArrayGet(&gPlayerDatas, 2); DisplayCharacterAndName( Vec2iNew(w / 8, h / 8 + h / 4), &p3->Char, p3->name); if (gPlayerDatas.size == 4) { const PlayerData *p4 = CArrayGet(&gPlayerDatas, 3); DisplayCharacterAndName( Vec2iNew(w / 8 + w / 2, h / 8 + h / 4), &p4->Char, p4->name); } } break; default: CASSERT(false, "not implemented"); break; } // Final words const char *finalWordsSingle[] = { "Ha, next time I'll use my good hand", "Over already? I was just warming up...", "There's just no good opposition to be found these days!", "Well, maybe I'll just do my monthly reload then", "Woof woof", "I'll just bury the bones in the back yard, he-he", "I just wish they'd let me try bare-handed", "Rambo? Who's Rambo?", "<in Austrian accent:> I'll be back", "Gee, my trigger finger is sore", "I need more practice. I think I missed a few shots at times" }; const char *finalWordsMulti[] = { "United we stand, divided we conquer", "Nothing like good teamwork, is there?", "Which way is the camera?", "We eat bullets for breakfast and have grenades as dessert", "We're so cool we have to wear mittens", }; const char *finalWords; if (gPlayerDatas.size == 1) { const int numWords = sizeof finalWordsSingle / sizeof(char *); finalWords = finalWordsSingle[rand() % numWords]; } else { const int numWords = sizeof finalWordsMulti / sizeof(char *); finalWords = finalWordsMulti[rand() % numWords]; } Vec2i pos = Vec2iNew((w - FontStrW(finalWords)) / 2, h / 2 + 20); pos = FontChMask('"', pos, colorDarker); pos = FontStrMask(finalWords, pos, colorPurple); FontChMask('"', pos, colorDarker); }
void HUDDraw(HUD *hud, const input_device_e pausingDevice) { char s[50]; int flags = 0; const int numPlayersAlive = GetNumPlayers(PLAYER_ALIVE_OR_DYING, false, false); const int numLocalPlayers = GetNumPlayers(PLAYER_ANY, false, true); const int numLocalPlayersAlive = GetNumPlayers(PLAYER_ALIVE_OR_DYING, false, true); Rect2i r; r.Size = Vec2iNew( hud->device->cachedConfig.Res.x, hud->device->cachedConfig.Res.y); if (numLocalPlayersAlive <= 1) { flags = 0; } else if ( ConfigGetEnum(&gConfig, "Interface.Splitscreen") == SPLITSCREEN_NEVER) { flags |= HUDFLAGS_SHARE_SCREEN; } else if (numLocalPlayers == 2) { r.Size.x /= 2; flags |= HUDFLAGS_HALF_SCREEN; } else if (numLocalPlayers == 3 || numLocalPlayers == 4) { r.Size.x /= 2; r.Size.y /= 2; flags |= HUDFLAGS_QUARTER_SCREEN; } else { assert(0 && "not implemented"); } int idx = 0; for (int i = 0; i < (int)gPlayerDatas.size; i++, idx++) { const PlayerData *p = CArrayGet(&gPlayerDatas, i); if (!p->IsLocal) { idx--; continue; } int drawFlags = flags; r.Pos = Vec2iZero(); if (idx & 1) { r.Pos.x = r.Size.x; drawFlags |= HUDFLAGS_PLACE_RIGHT; } if (idx >= 2) { r.Pos.y = r.Size.y; drawFlags |= HUDFLAGS_PLACE_BOTTOM; } TActor *player = NULL; if (IsPlayerAlive(p)) { player = ActorGetByUID(p->ActorUID); } DrawPlayerStatus(hud, p, player, drawFlags, r); DrawScoreUpdate(&hud->scoreUpdates[idx], drawFlags); DrawHealthUpdate(&hud->healthUpdates[idx], drawFlags); DrawAmmoUpdate(&hud->ammoUpdates[idx], drawFlags); } // Only draw radar once if shared if (ConfigGetBool(&gConfig, "Interface.ShowHUDMap") && (flags & HUDFLAGS_SHARE_SCREEN) && IsAutoMapEnabled(gCampaign.Entry.Mode)) { DrawSharedRadar(hud->device, RADAR_SCALE, hud->showExit); } if (numPlayersAlive == 0) { if (AreAllPlayersDeadAndNoLives()) { if (!IsPVP(gCampaign.Entry.Mode)) { FontStrCenter("Game Over!"); } else { FontStrCenter("All Kill!"); } } } else if (hud->mission->state == MISSION_STATE_PICKUP) { int timeLeft = gMission.pickupTime + PICKUP_LIMIT - gMission.time; sprintf(s, "Pickup in %d seconds\n", (timeLeft + (FPS_FRAMELIMIT - 1)) / FPS_FRAMELIMIT); FontStrCenter(s); } if (pausingDevice != INPUT_DEVICE_UNSET) { Vec2i pos = Vec2iScaleDiv(Vec2iMinus( gGraphicsDevice.cachedConfig.Res, FontStrSize("Foo\nPress foo or bar to unpause\nBaz")), 2); const int x = pos.x; FontStr("<Paused>", pos); pos.y += FontH(); pos = FontStr("Press ", pos); color_t c = colorWhite; const char *buttonName = InputGetButtonNameColor(pausingDevice, 0, CMD_ESC, &c); pos = FontStrMask(buttonName, pos, c); FontStr(" again to quit", pos); pos.x = x; pos.y += FontH(); pos = FontStr("Press ", pos); buttonName = InputGetButtonNameColor( pausingDevice, 0, CMD_BUTTON1, &c); pos = FontStrMask(buttonName, pos, c); pos = FontStr(" or ", pos); buttonName = InputGetButtonNameColor( pausingDevice, 0, CMD_BUTTON2, &c); pos = FontStrMask(buttonName, pos, c); FontStr(" to unpause", pos); } if (hud->messageTicks > 0 || hud->messageTicks == -1) { // Draw the message centered, and just below the automap Vec2i pos = Vec2iNew( (hud->device->cachedConfig.Res.x - FontStrW(hud->message)) / 2, AUTOMAP_SIZE + AUTOMAP_PADDING + AUTOMAP_PADDING); FontStrMask(hud->message, pos, colorCyan); } if (ConfigGetBool(&gConfig, "Interface.ShowFPS")) { FPSCounterDraw(&hud->fpsCounter); } if (ConfigGetBool(&gConfig, "Interface.ShowTime")) { WallClockDraw(&hud->clock); } DrawKeycards(hud); // Draw elapsed mission time as MM:SS int missionTimeSeconds = gMission.time / FPS_FRAMELIMIT; sprintf(s, "%d:%02d", missionTimeSeconds / 60, missionTimeSeconds % 60); FontOpts opts = FontOptsNew(); opts.HAlign = ALIGN_CENTER; opts.Area = hud->device->cachedConfig.Res; opts.Pad.y = 5; FontStrOpt(s, Vec2iZero(), opts); if (HasObjectives(gCampaign.Entry.Mode)) { DrawObjectiveCounts(hud); } }
static void DrawNumUpdate( const HUDNumUpdate *update, const char *formatText, int currentValue, Vec2i pos, int flags) { if (update->Timer <= 0 || update->Amount == 0) { return; } color_t color = update->Amount > 0 ? colorGreen : colorRed; char s[50]; if (!(flags & HUDFLAGS_PLACE_RIGHT)) { // Find the right position to draw the update // Make sure the update is displayed lined up with the lowest digits // Find the position of where the normal text is displayed, // and move to its right sprintf(s, formatText, currentValue); pos.x += FontStrW(s); // Then find the size of the update, and move left sprintf(s, "%s%d", update->Amount > 0 ? "+" : "", update->Amount); pos.x -= FontStrW(s); // The final position should ensure the score update's lowest digit // lines up with the normal score's lowest digit } else { sprintf(s, "%s%d", update->Amount > 0 ? "+" : "", update->Amount); } // Now animate the score update based on its stage int timer = update->TimerMax - update->Timer; if (timer < NUM_UPDATE_POP_UP_DURATION_MS) { // update is still popping up // calculate height int popupHeight = timer * NUM_UPDATE_POP_UP_HEIGHT / NUM_UPDATE_POP_UP_DURATION_MS; pos.y -= popupHeight; } else if (timer < NUM_UPDATE_POP_UP_DURATION_MS + NUM_UPDATE_FALL_DOWN_DURATION_MS) { // update is falling down // calculate height timer -= NUM_UPDATE_POP_UP_DURATION_MS; timer = NUM_UPDATE_FALL_DOWN_DURATION_MS - timer; int popupHeight = timer * NUM_UPDATE_POP_UP_HEIGHT / NUM_UPDATE_FALL_DOWN_DURATION_MS; pos.y -= popupHeight; } else { // Change alpha so that the update fades away color.a = (Uint8)(update->Timer * 255 / update->TimerMax); } FontOpts opts = FontOptsNew(); if (flags & HUDFLAGS_PLACE_RIGHT) { opts.HAlign = ALIGN_END; } if (flags & HUDFLAGS_PLACE_BOTTOM) { opts.VAlign = ALIGN_END; pos.y += BOTTOM_PADDING; } opts.Area = gGraphicsDevice.cachedConfig.Res; opts.Pad = pos; opts.Mask = color; opts.Blend = true; FontStrOpt(s, Vec2iZero(), opts); }
static void PlayerListCustomDraw( const menu_t *menu, GraphicsDevice *g, const struct vec2i pos, const struct vec2i size, const void *data) { UNUSED(menu); UNUSED(g); // Draw players starting from the index // TODO: custom columns const PlayerList *pl = data; // First draw the headers const int xStart = pos.x + 80 + (size.x - 320) / 2; int x = xStart; int y = pos.y; FontStrMask("Player", svec2i(x, y), colorPurple); x += 100; FontStrMask("Score", svec2i(x, y), colorPurple); x += 32; FontStrMask("Kills", svec2i(x, y), colorPurple); y += FontH() * 2 + PLAYER_LIST_ROW_HEIGHT + 4; // Then draw the player list int maxScore = -1; for (int i = pl->scroll; i < MIN((int)pl->playerUIDs.size, pl->scroll + PlayerListMaxRows(pl)); i++) { const int *playerUID = CArrayGet(&pl->playerUIDs, i); PlayerData *p = PlayerDataGetByUID(*playerUID); if (p == NULL) { continue; } if (maxScore < GetModeScore(p)) { maxScore = GetModeScore(p); } x = xStart; // Highlight local players using different coloured text const color_t textColor = p->IsLocal ? colorPurple : colorWhite; // Draw the players offset on alternate rows DisplayCharacterAndName( svec2i(x + (i & 1) * 16, y + 4), &p->Char, DIRECTION_DOWN, p->name, textColor); // Draw score x += 100; char buf[256]; sprintf(buf, "%d", p->Totals.Score); FontStrMask(buf, svec2i(x, y), textColor); // Draw kills x += 32; sprintf(buf, "%d", p->Totals.Kills); FontStrMask(buf, svec2i(x, y), textColor); // Draw winner/award text x += 32; if (pl->showWinners && GetModeScore(p) == maxScore) { FontStrMask("Winner!", svec2i(x, y), colorGreen); } else if (pl->showLastMan && p->Lives > 0 && gCampaign.Entry.Mode == GAME_MODE_DEATHMATCH) { // Only show last man standing on deathmatch mode FontStrMask("Last man standing!", svec2i(x, y), colorGreen); } y += PLAYER_LIST_ROW_HEIGHT; } // Draw indicator arrows if there's enough to scroll if (pl->scroll > 0) { FontStr("^", svec2i( CENTER_X(pos, size, FontStrW("^")), pos.y + FontH())); } if (pl->scroll < PlayerListMaxScroll(pl)) { FontStr("v", svec2i( CENTER_X(pos, size, FontStrW("v")), pos.y + size.y - FontH())); } // Finally draw any custom stuff if (pl->drawFunc) { pl->drawFunc(pl->data); } }
static void MenuDisplaySubmenus(const MenuSystem *ms) { int x = 0, yStart = 0; const menu_t *menu = ms->current; switch (menu->type) { // TODO: refactor the three menu types (normal, options, campaign) into one case MENU_TYPE_NORMAL: case MENU_TYPE_OPTIONS: { int iStart = 0; int iEnd = (int)menu->u.normal.subMenus.size; int numMenuLines = 0; int maxIEnd = (int)menu->u.normal.subMenus.size; if (menu->u.normal.maxItems > 0) { // Calculate first/last indices if (menu->u.normal.scroll != 0) { iStart = menu->u.normal.scroll; } maxIEnd = iStart + menu->u.normal.maxItems; } // Count the number of menu items that can fit // This is to account for multi-line items for (iEnd = iStart; iEnd < maxIEnd && iEnd < (int)menu->u.normal.subMenus.size; iEnd++) { const menu_t *subMenu = CArrayGet(&menu->u.normal.subMenus, iEnd); const int numLines = FontStrNumLines(subMenu->name); if (menu->u.normal.maxItems > 0 && numMenuLines + numLines > menu->u.normal.maxItems) { break; } numMenuLines += numLines; } int maxWidth = 0; for (int i = 0; i < (int)menu->u.normal.subMenus.size; i++) { const menu_t *subMenu = CArrayGet(&menu->u.normal.subMenus, i); const int width = FontStrW(subMenu->name); if (width > maxWidth) { maxWidth = width; } } // Limit max width if it is larger than the menu system size maxWidth = MIN(ms->size.x, maxWidth); const bool isCentered = menu->type == MENU_TYPE_NORMAL; switch (ms->align) { case MENU_ALIGN_CENTER: x = MS_CENTER_X(*ms, maxWidth); if (!isCentered) { x -= 20; } break; case MENU_ALIGN_LEFT: x = ms->pos.x; break; default: assert(0 && "unknown alignment"); break; } yStart = MS_CENTER_Y(*ms, numMenuLines * FontH()); if (menu->u.normal.maxItems > 0) { // Display scroll arrows if (menu->u.normal.scroll != 0) { DisplayMenuItem( Vec2iNew( MS_CENTER_X(*ms, FontW('^')), yStart - 2 - FontH()), "^", 0, 0, colorBlack); } if (iEnd < (int)menu->u.normal.subMenus.size - 1) { DisplayMenuItem( Vec2iNew( MS_CENTER_X(*ms, FontW('v')), yStart + numMenuLines*FontH() + 2), "v", 0, 0, colorBlack); } } const int xOptions = x + maxWidth + 10; // Display normal menu items Vec2i pos = Vec2iNew(x, yStart); for (int i = iStart; i < iEnd; i++) { const menu_t *subMenu = CArrayGet(&menu->u.normal.subMenus, i); char *nameBuf; CMALLOC(nameBuf, strlen(subMenu->name) + 3); if (subMenu->type == MENU_TYPE_NORMAL && subMenu->u.normal.isSubmenusAlt) { sprintf(nameBuf, "%s >", subMenu->name); } else { strcpy(nameBuf, subMenu->name); } switch (menu->u.normal.align) { case MENU_ALIGN_CENTER: pos.x = MS_CENTER_X(*ms, FontStrW(nameBuf)); break; case MENU_ALIGN_LEFT: // Do nothing break; default: assert(0 && "unknown alignment"); break; } const int yNext = DisplayMenuItem( pos, nameBuf, i == menu->u.normal.index, subMenu->isDisabled, subMenu->color).y + FontH(); // display option value if (subMenu->type == MENU_TYPE_SET_OPTION_TOGGLE || subMenu->type == MENU_TYPE_SET_OPTION_RANGE || subMenu->type == MENU_TYPE_SET_OPTION_SEED || subMenu->type == MENU_TYPE_SET_OPTION_UP_DOWN_VOID_FUNC_VOID || subMenu->type == MENU_TYPE_SET_OPTION_RANGE_GET_SET) { const int optionInt = MenuOptionGetIntValue(subMenu); const Vec2i value_pos = Vec2iNew(xOptions, pos.y); switch (subMenu->u.option.displayStyle) { case MENU_OPTION_DISPLAY_STYLE_INT: { char buf[32]; sprintf(buf, "%d", optionInt); FontStr(buf, value_pos); } break; case MENU_OPTION_DISPLAY_STYLE_YES_NO: FontStr(optionInt ? "Yes" : "No", value_pos); break; case MENU_OPTION_DISPLAY_STYLE_ON_OFF: FontStr(optionInt ? "On" : "Off", value_pos); break; case MENU_OPTION_DISPLAY_STYLE_STR_FUNC: FontStr(subMenu->u.option.uFunc.str(), value_pos); break; case MENU_OPTION_DISPLAY_STYLE_INT_TO_STR_FUNC: FontStr( subMenu->u.option.uFunc.intToStr(optionInt), value_pos); break; default: break; } } pos.y = yNext; } } break; case MENU_TYPE_KEYS: { int xKeys; x = MS_CENTER_X(*ms, (FontW('a') * 10)) / 2; xKeys = x * 3; yStart = (gGraphicsDevice.cachedConfig.Res.y / 2) - (FontH() * 10); for (int i = 0; i < (int)menu->u.normal.subMenus.size; i++) { int y = yStart + i * FontH(); int isSelected = i == menu->u.normal.index; const menu_t *subMenu = CArrayGet(&menu->u.normal.subMenus, i); const char *name = subMenu->name; if (isSelected && subMenu->type != MENU_TYPE_SET_OPTION_CHANGE_KEY) { FontStrMask(name, Vec2iNew(x, y), colorRed); } else { FontStr(name, Vec2iNew(x, y)); } if (subMenu->type == MENU_TYPE_SET_OPTION_CHANGE_KEY) { const char *keyName; if (menu->u.normal.changeKeyMenu == subMenu) { keyName = "Press a key"; } else if (subMenu->u.changeKey.code == KEY_CODE_MAP) { keyName = SDL_GetKeyName(gConfig.Input.PlayerKeys[0].Keys.map); } else { keyName = SDL_GetKeyName(InputGetKey( subMenu->u.changeKey.keys, subMenu->u.changeKey.code)); } DisplayMenuItem( Vec2iNew(xKeys, y), keyName, isSelected, 0, colorBlack); } } } break; default: // No submenus, don't display anything break; } }