static int DisplayPage( const char *title, int idx, struct Entry *e, int highlights[MAX_LOCAL_PLAYERS]) { int x = 80; int y = 5 + FontH(); FontStr(title, Vec2iNew(5, 5)); while (idx < MAX_ENTRY && e[idx].score > 0 && x < 300) { bool isHighlighted = false; for (int i = 0; i < MAX_LOCAL_PLAYERS; i++) { if (idx == highlights[i]) { isHighlighted = true; break; } } y += DisplayEntry(x, y, idx, &e[idx], isHighlighted); if (y > 198 - FontH()) { y = 20; x += 100; } idx++; } return idx; }
static void DrawNameMenu( menu_t *menu, GraphicsDevice *g, Vec2i pos, Vec2i size, void *data) { PlayerSelectMenuData *d = data; #define ENTRY_COLS 8 #define ENTRY_SPACING 7 int x = pos.x; int y = CENTER_Y( pos, size, FontH() * ((strlen(letters) - 1) / ENTRY_COLS)); UNUSED(menu); UNUSED(g); int i; for (i = 0; i < (int)strlen(letters); i++) { Vec2i menuPos = Vec2iNew( x + (i % ENTRY_COLS) * ENTRY_SPACING, y + (i / ENTRY_COLS) * FontH()); FontChMask( letters[i], menuPos, i == d->nameMenuSelection ? colorRed : colorWhite); } DisplayMenuItem( Vec2iNew( x + (i % ENTRY_COLS) * ENTRY_SPACING, y + (i / ENTRY_COLS) * FontH()), "(End)", i == d->nameMenuSelection, 0, colorBlack); }
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; }
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, 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 DisplayEquippedWeapons( const menu_t *menu, GraphicsDevice *g, const Vec2i pos, const Vec2i size, const void *data) { UNUSED(g); const WeaponMenuData *d = data; Vec2i weaponsPos; Vec2i maxTextSize = FontStrSize("LongestWeaponName"); UNUSED(menu); Vec2i dPos = pos; dPos.x -= size.x; // move to left half of screen weaponsPos = Vec2iNew( dPos.x + size.x * 3 / 4 - maxTextSize.x / 2, CENTER_Y(dPos, size, 0) + 14); const PlayerData *p = PlayerDataGetByUID(d->display.PlayerUID); if (p->weaponCount == 0) { FontStr("None selected...", weaponsPos); } else { for (int i = 0; i < p->weaponCount; i++) { FontStr( p->weapons[i]->name, Vec2iAdd(weaponsPos, Vec2iNew(0, i * FontH()))); } } }
bool ScreenMissionBriefing(const struct MissionOptions *m) { const int w = gGraphicsDevice.cachedConfig.Res.x; const int h = gGraphicsDevice.cachedConfig.Res.y; const int y = h / 4; MissionBriefingData mData; memset(&mData, 0, sizeof mData); mData.IsOK = true; // Title CMALLOC(mData.Title, strlen(m->missionData->Title) + 32); sprintf(mData.Title, "Mission %d: %s", m->index + 1, m->missionData->Title); mData.TitleOpts = FontOptsNew(); mData.TitleOpts.HAlign = ALIGN_CENTER; mData.TitleOpts.Area = gGraphicsDevice.cachedConfig.Res; mData.TitleOpts.Pad.y = y - 25; // Password if (m->index > 0) { sprintf( mData.Password, "Password: %s", gAutosave.LastMission.Password); mData.PasswordOpts = FontOptsNew(); mData.PasswordOpts.HAlign = ALIGN_CENTER; mData.PasswordOpts.Area = gGraphicsDevice.cachedConfig.Res; mData.PasswordOpts.Pad.y = y - 15; } // Split the description, and prepare it for typewriter effect mData.TypewriterCount = 0; // allow some slack for newlines CMALLOC(mData.Description, strlen(m->missionData->Description) * 2 + 1); CCALLOC(mData.TypewriterBuf, strlen(m->missionData->Description) * 2 + 1); // Pad about 1/6th of the screen width total (1/12th left and right) FontSplitLines(m->missionData->Description, mData.Description, w * 5 / 6); mData.DescriptionPos = Vec2iNew(w / 12, y); // Objectives mData.ObjectiveDescPos = Vec2iNew(w / 6, y + FontStrH(mData.Description) + h / 10); mData.ObjectiveInfoPos = Vec2iNew(w - (w / 6), mData.ObjectiveDescPos.y + FontH()); mData.ObjectiveHeight = h / 12; mData.MissionOptions = m; GameLoopData gData = GameLoopDataNew( &mData, MissionBriefingUpdate, &mData, MissionBriefingDraw); GameLoop(&gData); if (mData.IsOK) { SoundPlay(&gSoundDevice, StrSound("mg")); } CFREE(mData.Title); CFREE(mData.Description); CFREE(mData.TypewriterBuf); return mData.IsOK; }
UIObject *CreateAddItemObjs( Vec2i pos, EditorBrush *brush, CampaignOptions *co) { const int th = FontH(); UIObject *o2; UIObject *c = UIObjectCreate(UITYPE_CONTEXT_MENU, 0, pos, Vec2iZero()); UIObject *o = UIObjectCreate( UITYPE_LABEL, 0, Vec2iZero(), Vec2iNew(65, th)); o->Data = brush; pos = Vec2iZero(); o2 = UIObjectCopy(o); o2->Label = "Player start"; o2->ChangeFunc = BrushSetBrushTypeSetPlayerStart; o2->Pos = pos; CSTRDUP(o2->Tooltip, "Location where players start"); o2->OnFocusFunc = ActivateBrush; o2->OnUnfocusFunc = DeactivateBrush; o2->Data = brush; UIObjectAddChild(c, o2); pos.y += th; o2 = UIObjectCopy(o); o2->Label = "Map item >"; o2->Pos = pos; UIObjectAddChild(o2, CreateAddMapItemObjs( o2->Size, AddMapItemBrushObjFunc, brush, sizeof(IndexedEditorBrush), true)); UIObjectAddChild(c, o2); pos.y += th; o2 = UIObjectCopy(o); o2->Label = "Pickup spawner >"; o2->Pos = pos; UIObjectAddChild(o2, CreateAddPickupSpawnerObjs( o2->Size, AddPickupSpawnerBrushObjFunc, brush, sizeof(IndexedEditorBrush))); UIObjectAddChild(c, o2); pos.y += th; o2 = UIObjectCopy(o); o2->Label = "Character >"; o2->Pos = pos; UIObjectAddChild(o2, CreateAddCharacterObjs(o2->Size, brush, co)); UIObjectAddChild(c, o2); pos.y += th; o2 = UIObjectCopy(o); o2->Label = "Objective >"; o2->Pos = pos; UIObjectAddChild(o2, CreateAddObjectiveObjs(o2->Size, brush, co)); UIObjectAddChild(c, o2); pos.y += th; o2 = UIObjectCopy(o); o2->Label = "Key >"; o2->Pos = pos; UIObjectAddChild(o2, CreateAddKeyObjs(o2->Size, brush)); UIObjectAddChild(c, o2); UIObjectDestroy(o); return c; }
static void DrawHealthUpdate(const HUDNumUpdate *u, const int flags) { const PlayerData *p = PlayerDataGetByUID(u->u.PlayerUID); if (!IsPlayerAlive(p)) return; const int rowHeight = 1 + FontH(); const int y = 5 + rowHeight * 2; const TActor *a = ActorGetByUID(p->ActorUID); DrawNumUpdate(u, "%d", a->health, Vec2iNew(5, y), flags); }
void WallClockDraw(WallClock *wc) { char s[50]; sprintf(s, "%02d:%02d", wc->hours, wc->minutes); FontOpts opts = FontOptsNew(); opts.VAlign = ALIGN_END; opts.Area = gGraphicsDevice.cachedConfig.Res; opts.Pad = Vec2iNew(10, 5 + FontH()); FontStrOpt(s, Vec2iZero(), opts); }
static void DrawWeaponStatus( HUD *hud, const TActor *actor, Vec2i pos, const FontAlign hAlign, const FontAlign vAlign) { const Weapon *weapon = ActorGetGun(actor); // Draw gun icon, and allocate padding to draw the gun icon const GunDescription *g = ActorGetGun(actor)->Gun; const Vec2i iconPos = Vec2iAligned( Vec2iNew(pos.x - 2, pos.y - 2), g->Icon->size, hAlign, vAlign, gGraphicsDevice.cachedConfig.Res); Blit(&gGraphicsDevice, g->Icon, iconPos); // don't draw gauge if not reloading if (weapon->lock > 0) { const Vec2i gaugePos = Vec2iAdd(pos, Vec2iNew(-1 + GUN_ICON_PAD, -1)); const Vec2i size = Vec2iNew(GAUGE_WIDTH - GUN_ICON_PAD, FontH() + 2); const color_t barColor = { 0, 0, 255, 255 }; const int maxLock = weapon->Gun->Lock; int innerWidth; color_t backColor = { 128, 128, 128, 255 }; if (maxLock == 0) { innerWidth = 0; } else { innerWidth = MAX(1, size.x * (maxLock - weapon->lock) / maxLock); } DrawGauge( hud->device, gaugePos, size, innerWidth, barColor, backColor, hAlign, vAlign); } FontOpts opts = FontOptsNew(); opts.HAlign = hAlign; opts.VAlign = vAlign; opts.Area = gGraphicsDevice.cachedConfig.Res; opts.Pad = Vec2iNew(pos.x + GUN_ICON_PAD, pos.y); char buf[128]; if (ConfigGetBool(&gConfig, "Game.Ammo") && weapon->Gun->AmmoId >= 0) { // Include ammo counter sprintf(buf, "%s %d/%d", weapon->Gun->name, ActorGunGetAmmo(actor, weapon), AmmoGetById(&gAmmo, weapon->Gun->AmmoId)->Max); } else { strcpy(buf, weapon->Gun->name); } FontStrOpt(buf, Vec2iZero(), opts); }
static void DrawScoreUpdate(const HUDNumUpdate *u, const int flags) { if (!IsScoreNeeded(gCampaign.Entry.Mode)) { return; } const PlayerData *p = PlayerDataGetByUID(u->u.PlayerUID); if (!IsPlayerAlive(p)) return; const int rowHeight = 1 + FontH(); const int y = 5 + rowHeight; DrawNumUpdate(u, "Score: %d", p->score, Vec2iNew(5, y), flags); }
static void DrawAmmoUpdate(const HUDNumUpdate *u, const int flags) { const PlayerData *p = PlayerDataGetByUID(u->u.PlayerUID); if (!IsPlayerAlive(p)) return; const int rowHeight = 1 + FontH(); const int y = 5 + rowHeight * 4 + LIVES_ROW_EXTRA_Y; const TActor *a = ActorGetByUID(p->ActorUID); const Weapon *w = ActorGetGun(a); char gunNameBuf[256]; sprintf(gunNameBuf, "%s %%d", w->Gun->name); const int ammo = ActorGunGetAmmo(a, w); DrawNumUpdate(u, gunNameBuf, ammo, Vec2iNew(5 + GUN_ICON_PAD, y), flags); }
static void DisplayGunIcon( const menu_t *menu, GraphicsDevice *g, const Vec2i pos, const Vec2i size, const void *data) { UNUSED(data); if (menu->u.normal.index >= (int)gMission.Weapons.size) { return; } // Display a gun icon next to the currently selected weapon const GunDescription **gun = CArrayGet(&gMission.Weapons, menu->u.normal.index); const int menuItems = MIN( menu->u.normal.maxItems, (int)menu->u.normal.subMenus.size); const int textScroll = -menuItems * FontH() / 2 + (menu->u.normal.index - menu->u.normal.scroll) * FontH(); const Vec2i iconPos = Vec2iNew( pos.x - (*gun)->Icon->size.x - 4, pos.y + size.y / 2 + textScroll + (FontH() - (*gun)->Icon->size.y) / 2); Blit(g, (*gun)->Icon, iconPos); }
UIObject *CreateCampaignSeedObj(const struct vec2i pos, CampaignOptions *co) { const int th = FontH(); UIObject *o = UIObjectCreate( UITYPE_LABEL, 0, svec2i_zero(), svec2i(50, th)); o->ChangesData = true; o->u.LabelFunc = CampaignGetSeedStr; o->Data = co; o->ChangeFunc = CampaignChangeSeed; CSTRDUP(o->Tooltip, "Preview with different random seed"); o->Pos = pos; return o; }
void FPSCounterDraw(FPSCounter *counter) { char s[50]; counter->framesDrawn++; sprintf(s, "FPS: %d", counter->fps); FontOpts opts = FontOptsNew(); opts.HAlign = ALIGN_END; opts.VAlign = ALIGN_END; opts.Area = gGraphicsDevice.cachedConfig.Res; opts.Pad = Vec2iNew(10, 5 + FontH()); FontStrOpt(s, Vec2iZero(), opts); }
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; } }
UIObject *CreateAddMapItemObjs( const struct vec2i pos, bool (*objFunc)(UIObject *, MapObject *, void *), void *data, const size_t dataSize, const bool expandDown) { CreateAddMapItemObjsImplData d; d.ObjFunc = objFunc; d.Data = data; d.DataSize = dataSize; d.GridItemSize = svec2i(TILE_WIDTH + 4, TILE_HEIGHT * 2 + 4); d.GridSize = svec2i(8, 6); if (expandDown) { d.PageOffset = svec2i(-5, 5); } else { d.PageOffset = svec2i(-5, -d.GridItemSize.y * d.GridSize.y - FontH() - 5); } return CreateAddMapItemObjsImpl(pos, d); }
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 DrawHealth( GraphicsDevice *device, const TActor *actor, const Vec2i pos, const FontAlign hAlign, const FontAlign vAlign) { char s[50]; Vec2i gaugePos = Vec2iAdd(pos, Vec2iNew(-1, -1)); Vec2i size = Vec2iNew(GAUGE_WIDTH, FontH() + 2); HSV hsv = { 0.0, 1.0, 1.0 }; color_t barColor; int health = actor->health; const int maxHealth = ActorGetCharacter(actor)->maxHealth; int innerWidth; color_t backColor = { 50, 0, 0, 255 }; innerWidth = MAX(1, size.x * health / maxHealth); if (actor->poisoned) { hsv.h = 120.0; hsv.v = 0.5; } else { double maxHealthHue = 50.0; double minHealthHue = 0.0; hsv.h = ((maxHealthHue - minHealthHue) * health / maxHealth + minHealthHue); } barColor = ColorTint(colorWhite, hsv); DrawGauge( device, gaugePos, size, innerWidth, barColor, backColor, hAlign, vAlign); sprintf(s, "%d", health); FontOpts opts = FontOptsNew(); opts.HAlign = hAlign; opts.VAlign = vAlign; opts.Area = gGraphicsDevice.cachedConfig.Res; opts.Pad = pos; FontStrOpt(s, Vec2iZero(), opts); }
static void CreateAddMapItemSubObjs(UIObject *c, void *vData) { const CreateAddMapItemObjsImplData *data = vData; const int pageSize = data->GridSize.x * data->GridSize.y; // Check if we need to recreate the objs // TODO: this is a very heavyweight way to do it int count = 0; bool allChildrenSame = true; for (int i = 0; i < MapObjectsCount(&gMapObjects); i++) { MapObject *mo = IndexMapObject(i); UIObject *o2 = UIObjectCreate(UITYPE_CUSTOM, 0, svec2i_zero(), data->GridItemSize); o2->IsDynamicData = true; CCALLOC(o2->Data, data->DataSize); if (!data->ObjFunc(o2, mo, data->Data)) { UIObjectDestroy(o2); continue; } const int pageIdx = count / pageSize; if (pageIdx >= (int)c->Children.size) { allChildrenSame = false; break; } const UIObject **op = CArrayGet(&c->Children, pageIdx); const UIObject **octx = CArrayGet(&(*op)->Children, 0); const int idx = count % pageSize; if (idx >= (int)(*octx)->Children.size) { allChildrenSame = false; break; } const UIObject **oc = CArrayGet(&(*octx)->Children, idx); if (memcmp(o2->Data, (*oc)->Data, data->DataSize) != 0) { allChildrenSame = false; UIObjectDestroy(o2); break; } count++; UIObjectDestroy(o2); } int cCount = CountAddMapItemSubObjs(c); if (cCount == count && allChildrenSame) { return; } // Recreate the child UI objects c->Highlighted = NULL; UIObject **objs = c->Children.data; for (int i = 0; i < (int)c->Children.size; i++, objs++) { UIObjectDestroy(*objs); } CArrayClear(&c->Children); // Create pagination int pageNum = 1; UIObject *pageLabel = NULL; UIObject *page = NULL; UIObject *o = UIObjectCreate(UITYPE_CUSTOM, 0, svec2i_zero(), data->GridItemSize); o->ChangesData = true; const struct vec2i gridStart = svec2i_zero(); struct vec2i pos = svec2i_zero(); count = 0; for (int i = 0; i < MapObjectsCount(&gMapObjects); i++) { // Only add normal map objects MapObject *mo = IndexMapObject(i); UIObject *o2 = UIObjectCopy(o); o2->IsDynamicData = true; CCALLOC(o2->Data, data->DataSize); if (!data->ObjFunc(o2, mo, data->Data)) { UIObjectDestroy(o2); continue; } o2->Pos = pos; if (count == 0) { pageLabel = UIObjectCreate( UITYPE_LABEL, 0, svec2i((pageNum - 1) * 10, 0), svec2i(10, FontH())); char buf[32]; sprintf(buf, "%d", pageNum); UIObjectSetDynamicLabel(pageLabel, buf); page = UIObjectCreate( UITYPE_CONTEXT_MENU, 0, svec2i_add(data->PageOffset, pageLabel->Size), svec2i_zero()); UIObjectAddChild(pageLabel, page); pageNum++; } UIObjectAddChild(page, o2); pos.x += o->Size.x; if (((count + 1) % data->GridSize.x) == 0) { pos.x = gridStart.x; pos.y += o->Size.y; } count++; if (count == pageSize) { count = 0; pos = gridStart; UIObjectAddChild(c, pageLabel); } } if (pageLabel != NULL) { UIObjectAddChild(c, pageLabel); } UIObjectDestroy(o); }
UIObject *CreateCaveMapObjs(struct vec2i pos, CampaignOptions *co) { const int th = FontH(); UIObject *c = UIObjectCreate(UITYPE_NONE, 0, svec2i_zero(), svec2i_zero()); UIObject *o = UIObjectCreate( UITYPE_LABEL, 0, svec2i_zero(), svec2i(50, th)); const int x = pos.x; o->ChangesData = true; // Check whether the map type matches, and set visibility c->CheckVisible = MissionCheckTypeFunc; c->Data = co; UIObject *o2 = CreateCampaignSeedObj(pos, co); UIObjectAddChild(c, o2); pos.x += o2->Size.x; o2 = UIObjectCopy(o); o2->u.LabelFunc = MissionGetFillPercentStr; o2->Data = co; o2->ChangeFunc = MissionChangeFillPercent; o2->Pos = pos; UIObjectAddChild(c, o2); pos.x += o2->Size.x; o2 = UIObjectCopy(o); o2->u.LabelFunc = MissionGetRepeatStr; o2->Data = co; o2->ChangeFunc = MissionChangeRepeat; o2->Pos = pos; UIObjectAddChild(c, o2); pos.x = x; pos.y += th; o2 = UIObjectCopy(o); o2->u.LabelFunc = MissionGetR1Str; o2->Data = co; o2->ChangeFunc = MissionChangeR1; o2->Pos = pos; UIObjectAddChild(c, o2); pos.x += o2->Size.x; o2 = UIObjectCopy(o); o2->u.LabelFunc = MissionGetR2Str; o2->Data = co; o2->ChangeFunc = MissionChangeR2; o2->Pos = pos; UIObjectAddChild(c, o2); pos.x += o2->Size.x; o2 = UIObjectCopy(o); o2->u.LabelFunc = MissionGetCorridorWidthStr; o2->Data = co; o2->ChangeFunc = MissionChangeCorridorWidth; o2->Pos = pos; o2->Size.x = 60; UIObjectAddChild(c, o2); pos.x = x; pos.y += th; o2 = UIObjectCopy(o); o2->u.LabelFunc = MissionGetRoomCountStr; o2->Data = co; o2->ChangeFunc = MissionChangeRoomCount; o2->Pos = pos; UIObjectAddChild(c, o2); pos.x += o2->Size.x; o2 = UIObjectCopy(o); o2->u.LabelFunc = MissionGetRoomMinStr; o2->Data = co; o2->ChangeFunc = MissionChangeRoomMin; o2->Pos = pos; UIObjectAddChild(c, o2); pos.x += o2->Size.x; o2 = UIObjectCopy(o); o2->u.LabelFunc = MissionGetRoomMaxStr; o2->Data = co; o2->ChangeFunc = MissionChangeRoomMax; o2->Pos = pos; UIObjectAddChild(c, o2); pos.x += o2->Size.x; o2 = UIObjectCreate(UITYPE_CUSTOM, 0, pos, svec2i(60, th)); o2->ChangesData = true; o2->u.CustomDrawFunc = MissionDrawRoomsOverlap; o2->Data = co; o2->ChangeFunc = MissionChangeRoomsOverlap; UIObjectAddChild(c, o2); pos.x = x; pos.y += th; o2 = UIObjectCopy(o); o2->u.LabelFunc = MissionGetRoomWallCountStr; o2->Data = co; o2->ChangeFunc = MissionChangeRoomWallCount; o2->Pos = pos; UIObjectAddChild(c, o2); pos.x += o2->Size.x; o2 = UIObjectCopy(o); o2->u.LabelFunc = MissionGetRoomWallLenStr; o2->Data = co; o2->ChangeFunc = MissionChangeRoomWallLen; o2->Pos = pos; o2->Size.x = 60; UIObjectAddChild(c, o2); pos.x += o2->Size.x; o2 = UIObjectCopy(o); o2->u.LabelFunc = MissionGetRoomWallPadStr; o2->Data = co; o2->ChangeFunc = MissionChangeRoomWallPad; o2->Pos = pos; o2->Size.x = 60; UIObjectAddChild(c, o2); pos.x = x; pos.y += th; o2 = UIObjectCopy(o); o2->u.LabelFunc = MissionGetSquareCountStr; o2->Data = co; o2->ChangeFunc = MissionChangeSquareCount; o2->Pos = pos; UIObjectAddChild(c, o2); pos.x += o2->Size.x; o2 = UIObjectCreate(UITYPE_CUSTOM, 0, pos, o->Size); o2->u.CustomDrawFunc = MissionDrawDoorEnabled; o2->Data = co; o2->ChangeFunc = MissionChangeDoorEnabled; o2->ChangesData = true; o2->Pos = pos; UIObjectAddChild(c, o2); UIObjectDestroy(o); return c; }
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); } }
// Draw player's score, health etc. static void DrawPlayerStatus( HUD *hud, const PlayerData *data, const TActor *p, const int flags, const Rect2i r) { if (p != NULL) { DrawObjectiveCompass( hud->device, Vec2iFull2Real(p->Pos), r, hud->showExit); } Vec2i pos = Vec2iNew(5, 5); 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; FontStrOpt(data->name, Vec2iZero(), opts); const int rowHeight = 1 + FontH(); pos.y += rowHeight; char s[50]; if (IsScoreNeeded(gCampaign.Entry.Mode)) { if (ConfigGetBool(&gConfig, "Game.Ammo")) { // Display money instead of ammo sprintf(s, "Cash: $%d", data->score); } else { sprintf(s, "Score: %d", data->score); } } else { s[0] = 0; } if (p) { // Score/money opts.Pad = pos; FontStrOpt(s, Vec2iZero(), opts); // Health pos.y += rowHeight; DrawHealth(hud->device, p, pos, opts.HAlign, opts.VAlign); // Lives pos.y += rowHeight; DrawLives(hud->device, data, pos, opts.HAlign, opts.VAlign); // Weapon pos.y += rowHeight + LIVES_ROW_EXTRA_Y; DrawWeaponStatus(hud, p, pos, opts.HAlign, opts.VAlign); } else { opts.Pad = pos; FontStrOpt(s, Vec2iZero(), opts); } if (ConfigGetBool(&gConfig, "Interface.ShowHUDMap") && !(flags & HUDFLAGS_SHARE_SCREEN) && IsAutoMapEnabled(gCampaign.Entry.Mode)) { DrawRadar(hud->device, p, RADAR_SCALE, flags, hud->showExit); } }
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 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; } }
static void MissionSummaryDraw(void *data) { // This will only draw once const struct MissionOptions *m = data; GraphicsBlitBkg(&gGraphicsDevice); const int w = gGraphicsDevice.cachedConfig.Res.x; const int h = gGraphicsDevice.cachedConfig.Res.y; // Display password if (strlen(gAutosave.LastMission.Password) > 0) { char s[64]; sprintf(s, "Last password: %s", gAutosave.LastMission.Password); FontOpts opts = FontOptsNew(); opts.HAlign = ALIGN_CENTER; opts.VAlign = ALIGN_END; opts.Area = gGraphicsDevice.cachedConfig.Res; opts.Pad.y = opts.Area.y / 12; FontStrOpt(s, Vec2iZero(), opts); } // Display objectives and bonuses Vec2i pos = Vec2iNew(w / 6, h / 2 + h / 10); int idx = 1; for (int i = 0; i < (int)m->missionData->Objectives.size; i++) { const struct Objective *o = CArrayGet(&m->Objectives, i); const MissionObjective *mo = CArrayGet(&m->missionData->Objectives, i); // Do not mention optional objectives with none completed if (o->done == 0 && mo->Required == 0) { continue; } // Objective icon DrawObjectiveInfo(m, i, Vec2iAdd(pos, Vec2iNew(-26, FontH()))); // Objective completion text char s[100]; sprintf(s, "Objective %d: %d of %d, %d required", idx, o->done, mo->Count, mo->Required); FontOpts opts = FontOptsNew(); opts.Area = gGraphicsDevice.cachedConfig.Res; opts.Pad = pos; if (mo->Required == 0) { // Show optional objectives in purple opts.Mask = colorPurple; } FontStrOpt(s, Vec2iZero(), opts); // Objective status text opts = FontOptsNew(); opts.HAlign = ALIGN_END; opts.Area = gGraphicsDevice.cachedConfig.Res; opts.Pad = pos; if (o->done < mo->Required) { opts.Mask = colorRed; FontStrOpt("Failed", Vec2iZero(), opts); } else if ( o->done == mo->Count && o->done > mo->Required && AreAnySurvived()) { opts.Mask = colorGreen; char buf[16]; sprintf(buf, "Perfect: %d", PERFECT_BONUS); FontStrOpt(buf, Vec2iZero(), opts); } else if (mo->Required > 0) { FontStrOpt("Done", Vec2iZero(), opts); } else { FontStrOpt("Bonus!", Vec2iZero(), opts); } pos.y += 15; idx++; } // Draw other bonuses if (AreAnySurvived()) { char s[64]; sprintf(s, "Access bonus: %d", GetAccessBonus(m)); FontStr(s, pos); pos.y += FontH() + 1; int seconds; const int timeBonus = GetTimeBonus(m, &seconds); sprintf(s, "Time bonus: %d secs x 25 = %d", seconds, timeBonus); FontStr(s, pos); } // Draw per-player summaries Vec2i size; switch (gPlayerDatas.size) { case 1: size = Vec2iNew(w, h / 2); DrawPlayerSummary(Vec2iZero(), size, CArrayGet(&gPlayerDatas, 0)); break; case 2: // side by side size = Vec2iNew(w / 2, h / 2); DrawPlayerSummary(Vec2iZero(), size, CArrayGet(&gPlayerDatas, 0)); DrawPlayerSummary( Vec2iNew(w / 2, 0), size, CArrayGet(&gPlayerDatas, 1)); break; case 3: // fallthrough case 4: // 2x2 size = Vec2iNew(w / 2, h / 4); DrawPlayerSummary(Vec2iZero(), size, CArrayGet(&gPlayerDatas, 0)); DrawPlayerSummary( Vec2iNew(w / 2, 0), size, CArrayGet(&gPlayerDatas, 1)); DrawPlayerSummary( Vec2iNew(0, h / 4), size, CArrayGet(&gPlayerDatas, 2)); if (gPlayerDatas.size == 4) { DrawPlayerSummary( Vec2iNew(w / 2, h / 4), size, CArrayGet(&gPlayerDatas, 3)); } break; default: CASSERT(false, "not implemented"); break; } }
static void MissionSummaryDraw( const menu_t *menu, GraphicsDevice *g, const Vec2i p, const Vec2i size, const void *data) { UNUSED(menu); UNUSED(p); UNUSED(size); const struct MissionOptions *m = data; const int w = gGraphicsDevice.cachedConfig.Res.x; const int h = gGraphicsDevice.cachedConfig.Res.y; // Display password if (strlen(gAutosave.LastMission.Password) > 0) { char s[64]; sprintf(s, "Last password: %s", gAutosave.LastMission.Password); FontOpts opts = FontOptsNew(); opts.HAlign = ALIGN_CENTER; opts.VAlign = ALIGN_END; opts.Area = g->cachedConfig.Res; opts.Pad.y = opts.Area.y / 12; FontStrOpt(s, Vec2iZero(), opts); } // Display objectives and bonuses Vec2i pos = Vec2iNew(w / 6, h / 2 + h / 10); int idx = 1; CA_FOREACH(const Objective, o, m->missionData->Objectives) // Do not mention optional objectives with none completed if (o->done == 0 && !ObjectiveIsRequired(o)) { continue; } // Objective icon DrawObjectiveInfo(o, Vec2iAdd(pos, Vec2iNew(-26, FontH()))); // Objective completion text char s[100]; sprintf(s, "Objective %d: %d of %d, %d required", idx, o->done, o->Count, o->Required); FontOpts opts = FontOptsNew(); opts.Area = gGraphicsDevice.cachedConfig.Res; opts.Pad = pos; if (!ObjectiveIsRequired(o)) { // Show optional objectives in purple opts.Mask = colorPurple; } FontStrOpt(s, Vec2iZero(), opts); // Objective status text opts = FontOptsNew(); opts.HAlign = ALIGN_END; opts.Area = gGraphicsDevice.cachedConfig.Res; opts.Pad = pos; if (!ObjectiveIsComplete(o)) { opts.Mask = colorRed; FontStrOpt("Failed", Vec2iZero(), opts); } else if (ObjectiveIsPerfect(o) && AreAnySurvived()) { opts.Mask = colorGreen; char buf[16]; sprintf(buf, "Perfect: %d", PERFECT_BONUS); FontStrOpt(buf, Vec2iZero(), opts); } else if (ObjectiveIsRequired(o)) { FontStrOpt("Done", Vec2iZero(), opts); } else { FontStrOpt("Bonus!", Vec2iZero(), opts); } pos.y += 15; idx++; CA_FOREACH_END() // Draw other bonuses if (AreAnySurvived()) { char s[64]; sprintf(s, "Access bonus: %d", GetAccessBonus(m)); FontStr(s, pos); pos.y += FontH() + 1; int seconds; const int timeBonus = GetTimeBonus(m, &seconds); sprintf(s, "Time bonus: %d secs x 25 = %d", seconds, timeBonus); FontStr(s, pos); } // Draw per-player summaries PlayerData *pds[MAX_LOCAL_PLAYERS]; idx = 0; CA_FOREACH(PlayerData, pd, gPlayerDatas) if (!pd->IsLocal) { continue; } pds[idx] = pd; idx++; CA_FOREACH_END() Vec2i playerSize; switch (idx) { case 1: playerSize = Vec2iNew(w, h / 2); DrawPlayerSummary(Vec2iZero(), playerSize, pds[0]); break; case 2: // side by side playerSize = Vec2iNew(w / 2, h / 2); DrawPlayerSummary(Vec2iZero(), playerSize, pds[0]); DrawPlayerSummary(Vec2iNew(w / 2, 0), playerSize, pds[1]); break; case 3: // fallthrough case 4: // 2x2 playerSize = Vec2iNew(w / 2, h / 4); DrawPlayerSummary(Vec2iZero(), playerSize, pds[0]); DrawPlayerSummary(Vec2iNew(w / 2, 0), playerSize, pds[1]); DrawPlayerSummary(Vec2iNew(0, h / 4), playerSize, pds[2]); if (idx == 4) { DrawPlayerSummary(Vec2iNew(w / 2, h / 4), playerSize, pds[3]); } break; default: CASSERT(false, "not implemented"); break; } }
// Display compact player summary, with player on left half and score summaries // on right half static void DrawPlayerSummary( const Vec2i pos, const Vec2i size, const PlayerData *data) { char s[50]; const int totalTextHeight = FontH() * 7; // display text on right half Vec2i textPos = Vec2iNew( pos.x + size.x / 2, CENTER_Y(pos, size, totalTextHeight)); DisplayCharacterAndName( Vec2iAdd(pos, Vec2iNew(size.x / 4, size.y / 2)), &data->Char, data->name); if (data->survived) { FontStr("Completed mission", textPos); } else { FontStrMask("Failed mission", textPos, colorRed); } textPos.y += 2 * FontH(); sprintf(s, "Score: %d", data->score); FontStr(s, textPos); textPos.y += FontH(); sprintf(s, "Total: %d", data->totalScore); FontStr(s, textPos); textPos.y += FontH(); sprintf(s, "Missions: %d", data->missions + (data->survived ? 1 : 0)); FontStr(s, textPos); textPos.y += FontH(); // Display bonuses if the player has survived if (data->survived) { const int healthBonus = GetHealthBonus(data); if (healthBonus != 0) { sprintf(s, "Health bonus: %d", healthBonus); } const int resurrectionFee = GetResurrectionFee(data); if (resurrectionFee != 0) { sprintf(s, "Resurrection fee: %d", resurrectionFee); } if (healthBonus != 0 || resurrectionFee != 0) { FontStr(s, textPos); textPos.y += FontH(); } const int butcherPenalty = GetButcherPenalty(data); if (butcherPenalty != 0) { sprintf(s, "Butcher penalty: %d", butcherPenalty); } const int ninjaBonus = GetNinjaBonus(data); if (ninjaBonus != 0) { sprintf(s, "Ninja bonus: %d", ninjaBonus); } const int friendlyBonus = GetFriendlyBonus(data); if (friendlyBonus != 0) { sprintf(s, "Friendly bonus: %d", friendlyBonus); } if (butcherPenalty != 0 || ninjaBonus != 0 || friendlyBonus != 0) { FontStr(s, textPos); } } }
bool ScreenMissionSummary( CampaignOptions *c, struct MissionOptions *m, const bool completed) { if (completed) { // Save password MissionSave ms; MissionSaveInit(&ms); ms.Campaign = c->Entry; // Don't make password for next level if there is none int passwordIndex = m->index + 1; if (passwordIndex == c->Entry.NumMissions) { passwordIndex--; } strcpy(ms.Password, MakePassword(passwordIndex, 0)); ms.MissionsCompleted = m->index + 1; AutosaveAddMission(&gAutosave, &ms); AutosaveSave(&gAutosave, GetConfigFilePath(AUTOSAVE_FILE)); } // Calculate bonus scores // Bonuses only apply if at least one player has lived if (AreAnySurvived()) { int bonus = 0; // Objective bonuses CA_FOREACH(const Objective, o, m->missionData->Objectives) if (ObjectiveIsPerfect(o)) { bonus += PERFECT_BONUS; } CA_FOREACH_END() bonus += GetAccessBonus(m); bonus += GetTimeBonus(m, NULL); CA_FOREACH(PlayerData, p, gPlayerDatas) ApplyBonuses(p, bonus); CA_FOREACH_END() } MenuSystem ms; const int h = FontH() * 10; MenuSystemInit( &ms, &gEventHandlers, &gGraphicsDevice, Vec2iNew(0, gGraphicsDevice.cachedConfig.Res.y - h), Vec2iNew(gGraphicsDevice.cachedConfig.Res.x, h)); ms.current = ms.root = MenuCreateNormal("", "", MENU_TYPE_NORMAL, 0); // Use return code 0 for whether to continue the game if (completed) { MenuAddSubmenu(ms.root, MenuCreateReturn("Continue", 0)); } else { MenuAddSubmenu(ms.root, MenuCreateReturn("Replay mission", 0)); MenuAddSubmenu(ms.root, MenuCreateReturn("Back to menu", 1)); } ms.allowAborts = true; MenuAddExitType(&ms, MENU_TYPE_RETURN); MenuSystemAddCustomDisplay(&ms, MissionSummaryDraw, m); MenuLoop(&ms); return ms.current->u.returnCode == 0; }