Browse Source

Implement game hub

hubui
Anders Jenbo 3 years ago
parent
commit
bd0b446fdf
  1. 1
      CMake/Assets.cmake
  2. BIN
      Packaging/resources/assets/ui_art/bnconnbgw.clx
  3. 5
      Source/CMakeLists.txt
  4. 12
      Source/DiabloUI/button.cpp
  5. 16
      Source/DiabloUI/diabloui.cpp
  6. 1
      Source/DiabloUI/diabloui.h
  7. 4
      Source/DiabloUI/hero/selhero.cpp
  8. 141
      Source/DiabloUI/hub/chat.cpp
  9. 9
      Source/DiabloUI/hub/chat.h
  10. 51
      Source/DiabloUI/hub/create.cpp
  11. 8
      Source/DiabloUI/hub/create.h
  12. 22
      Source/DiabloUI/hub/friends.cpp
  13. 8
      Source/DiabloUI/hub/friends.h
  14. 221
      Source/DiabloUI/hub/hub.cpp
  15. 31
      Source/DiabloUI/hub/hub.h
  16. 135
      Source/DiabloUI/hub/join.cpp
  17. 8
      Source/DiabloUI/hub/join.h
  18. 10
      Source/DiabloUI/scrollbar.cpp
  19. 20
      Source/DiabloUI/scrollbar.h
  20. 1
      Source/DiabloUI/ui_flags.hpp
  21. 11
      Source/multi.cpp

1
CMake/Assets.cmake

@ -173,6 +173,7 @@ set(devilutionx_assets
nlevels/l5data/uberroom.dun
ui_art/diablo.pal
ui_art/hellfire.pal
ui_art/bnconnbgw.clx
ui_art/creditsw.clx
ui_art/dvl_but_sml.clx
ui_art/dvl_lrpopup.clx

BIN
Packaging/resources/assets/ui_art/bnconnbgw.clx

Binary file not shown.

5
Source/CMakeLists.txt

@ -73,6 +73,11 @@ set(libdevilutionx_SRCS
DiabloUI/diabloui.cpp
DiabloUI/dialogs.cpp
DiabloUI/hero/selhero.cpp
DiabloUI/hub/chat.cpp
DiabloUI/hub/create.cpp
DiabloUI/hub/friends.cpp
DiabloUI/hub/hub.cpp
DiabloUI/hub/join.cpp
DiabloUI/mainmenu.cpp
DiabloUI/multi/selconn.cpp
DiabloUI/multi/selgame.cpp

12
Source/DiabloUI/button.cpp

@ -13,6 +13,8 @@ namespace devilution {
namespace {
OptionalOwnedClxSpriteList ButtonSprites;
OptionalOwnedClxSpriteList ButtonXsSprites;
OptionalOwnedClxSpriteList ButtonBnSprites;
} // namespace
@ -22,11 +24,21 @@ void LoadDialogButtonGraphics()
if (!ButtonSprites) {
ButtonSprites = LoadPcxSpriteList("ui_art\\but_sml", 15);
}
ButtonXsSprites = LoadOptionalClx("ui_art\\but_xsm.clx");
if (!ButtonXsSprites) {
ButtonXsSprites = LoadPcxSpriteList("ui_art\\but_xsm", -35);
}
ButtonBnSprites = LoadOptionalClx("ui_art\\bnbuttns.clx");
if (!ButtonBnSprites) {
ButtonBnSprites = LoadPcxSpriteList("ui_art\\bnbuttns", -71);
}
}
void FreeDialogButtonGraphics()
{
ButtonSprites = std::nullopt;
ButtonXsSprites = std::nullopt;
ButtonBnSprites = std::nullopt;
}
ClxSprite ButtonSprite(bool pressed)

16
Source/DiabloUI/diabloui.cpp

@ -818,7 +818,11 @@ void Render(const UiImageClx &uiImage)
if (uiImage.isCentered()) {
x += GetCenterOffset(sprite.width(), uiImage.m_rect.w);
}
RenderClxSprite(Surface(DiabloUiSurface()), sprite, { x, uiImage.m_rect.y });
const Surface &out = Surface(DiabloUiSurface());
if (uiImage.m_rect.w != 0)
RenderClxSprite(out.subregion(x, 0, uiImage.m_rect.w, out.h()), sprite, { 0, uiImage.m_rect.y });
else
RenderClxSprite(out, sprite, { x, uiImage.m_rect.y });
}
void Render(const UiImageAnimatedClx &uiImage)
@ -863,7 +867,7 @@ void Render(const UiScrollbar &uiSb)
{
const int bgY = uiSb.m_rect.y + uiSb.m_arrow[0].height();
const int bgH = DownArrowRect(uiSb).y - bgY;
const Surface backgroundOut = out.subregion(uiSb.m_rect.x, bgY, ScrollBarBgWidth, bgH);
const Surface backgroundOut = out.subregion(uiSb.m_rect.x, bgY, ScrollBarWidth, bgH);
int y = 0;
while (y < bgH) {
RenderClxSprite(backgroundOut, uiSb.m_bg, { 0, y });
@ -874,13 +878,13 @@ void Render(const UiScrollbar &uiSb)
// Arrows:
{
const SDL_Rect rect = UpArrowRect(uiSb);
const auto frame = static_cast<uint16_t>(scrollBarState.upArrowPressed ? ScrollBarArrowFrame_UP_ACTIVE : ScrollBarArrowFrame_UP);
RenderClxSprite(out.subregion(rect.x, 0, ScrollBarArrowWidth, out.h()), uiSb.m_arrow[frame], { 0, rect.y });
const uint16_t frame = scrollBarState.upArrowPressed ? ScrollBarArrowFrame_UP_ACTIVE : ScrollBarArrowFrame_UP;
RenderClxSprite(out.subregion(rect.x, 0, ScrollBarWidth, out.h()), uiSb.m_arrow[frame], { 0, rect.y });
}
{
const SDL_Rect rect = DownArrowRect(uiSb);
const auto frame = static_cast<uint16_t>(scrollBarState.downArrowPressed ? ScrollBarArrowFrame_DOWN_ACTIVE : ScrollBarArrowFrame_DOWN);
RenderClxSprite(out.subregion(rect.x, 0, ScrollBarArrowWidth, out.h()), uiSb.m_arrow[frame], { 0, rect.y });
const uint16_t frame = scrollBarState.downArrowPressed ? ScrollBarArrowFrame_DOWN_ACTIVE : ScrollBarArrowFrame_DOWN;
RenderClxSprite(out.subregion(rect.x, 0, ScrollBarWidth, out.h()), uiSb.m_arrow[frame], { 0, rect.y });
}
// Thumb:

1
Source/DiabloUI/diabloui.h

@ -89,6 +89,7 @@ bool UiCreditsDialog();
bool UiSupportDialog();
bool UiMainMenuDialog(const char *name, _mainmenu_selections *pdwResult, int attractTimeOut);
bool UiProgressDialog(int (*fnfunc)());
bool UiHubMain();
bool UiSelectGame(GameData *gameData, int *playerId);
bool UiSelectProvider(GameData *gameData);
void UiFadeIn();

4
Source/DiabloUI/hero/selhero.cpp

@ -8,6 +8,7 @@
#include "DiabloUI/diabloui.h"
#include "DiabloUI/dialogs.h"
#include "DiabloUI/hub/hub.h"
#include "DiabloUI/multi/selgame.h"
#include "DiabloUI/scrollbar.h"
#include "DiabloUI/selok.h"
@ -336,6 +337,7 @@ void SelheroLoadSelect(int value)
}
if (!selhero_isMultiPlayer) {
// TODO fix this :(
// This is part of a dangerous hack to enable difficulty selection in single-player.
// FIXME: Dialogs should not refer to each other's variables.
@ -350,7 +352,7 @@ void SelheroLoadSelect(int value)
selhero_isSavegame = false;
SelheroFree();
LoadBackgroundArt("ui_art\\selgame");
LoadBackgroundArt("ui_art\\bnconnbg");
selgame_GameSelection_Select(0);
}

141
Source/DiabloUI/hub/chat.cpp

@ -0,0 +1,141 @@
#include "DiabloUI/hub/chat.h"
#include "DiabloUI/hub/hub.h"
#include "DiabloUI/scrollbar.h"
#include "engine/load_pcx.hpp"
namespace devilution {
namespace {
char ChatMessage[129];
OptionalOwnedClxSpriteList MainButton;
std::vector<PlayerInfo> PlayerList;
struct ColoredText {
std::string text;
UiFlags color;
};
struct MultiColoredText {
std::string text;
std::vector<ColoredText> colors;
int offset = 0;
};
std::vector<MultiColoredText> ChatLogLines;
void BuildFixtures()
{
PlayerList.emplace_back(PlayerInfo { "KPhoenix", HeroClass::Warrior, 35, 2, GameIdDiabloFull, 50 });
PlayerList.emplace_back(PlayerInfo { "AJenbo", HeroClass::Rogue, 23, 1, GameIdHellfireFull, 320 });
PlayerList.emplace_back(PlayerInfo { "FireIceTalon", HeroClass::Sorcerer, 50, 3, GameIdDiabloFull, 150 });
PlayerList.emplace_back(PlayerInfo { "glebm", HeroClass::Monk, 12, 0, GameIdHellfireFull, 1400 });
PlayerList.emplace_back(PlayerInfo { "qndel", HeroClass::Warrior, 1, 0, GameIdDiabloSpawn, 450 });
ChatLogLines.emplace_back(MultiColoredText { "{0}", { { "KPhoenix has appeared on the network", UiFlags::ColorUiGreen } } });
ChatLogLines.emplace_back(MultiColoredText { "{0} {1}", { { "<KPhoenix>", UiFlags::ColorYellow }, { "Hello", UiFlags::ColorDialogWhite } } });
ChatLogLines.emplace_back(MultiColoredText { "{0} {1}", { { "<AJenbo>", UiFlags::ColorBlue }, { "Ready?", UiFlags::ColorUiSilverDark } } });
ChatLogLines.emplace_back(MultiColoredText { "{0}", { { "FireIceTalon started a new game", UiFlags::ColorRed } } });
}
void hubmain_Free()
{
PlayerList.clear();
ChatLogLines.clear();
}
void DialogActionOK()
{
}
} // namespace
void hubmain_Init()
{
Layout = LoadPcx("ui_art\\chat_bkg", /*transparentColor=*/0);
MainButton = LoadPcxSpriteList("ui_art\\bnbuttns", -71);
BuildFixtures();
}
void UiHubInitPlayerList()
{
const Point uiPosition = GetUIRectangle().position + Displacement { 460, 200 };
int yOffset = 0;
for (const PlayerInfo &player : PlayerList) {
UiHubPlacePlayerIcon({ uiPosition.x, uiPosition.y + yOffset }, player.gameMode, player);
const SDL_Rect rect3 = MakeSdlRect(uiPosition.x + 30, uiPosition.y + yOffset - 6, 89, 20);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(player.name.data(), rect3, UiFlags::FontSizeDialog | UiFlags::ColorYellow, -1));
UiHubPlaceLatencyMeter(player.latency, { uiPosition.x + 121, uiPosition.y + yOffset + 2 });
yOffset += 16;
}
}
void hubmain_GameSelection_Init()
{
const Point uiPosition = GetUIRectangle().position;
const SDL_Rect rect2 = MakeSdlRect(uiPosition.x + 445, uiPosition.y + 148, 179, 28);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("Global Chat").data(), rect2, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite | UiFlags::AlignCenter | UiFlags::VerticalCenter, -1));
vecHubMainDialog.push_back(std::make_unique<UiImageClx>((*MainButton)[0], MakeSdlRect(uiPosition.x + 10, uiPosition.y + 140, 85, 71)));
const SDL_Rect rect3 = MakeSdlRect(uiPosition.x + 10, uiPosition.y + 140 + 43, 85, 71);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("Friends").data(), rect3, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite | UiFlags::AlignCenter, -1));
vecHubMainDialog.push_back(std::make_unique<UiImageClx>((*MainButton)[5], MakeSdlRect(uiPosition.x + 10, uiPosition.y + 227, 85, 71)));
const SDL_Rect rect4 = MakeSdlRect(uiPosition.x + 10, uiPosition.y + 227 + 43, 85, 71);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("Create").data(), rect4, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite | UiFlags::AlignCenter, -1));
vecHubMainDialog.push_back(std::make_unique<UiImageClx>((*MainButton)[10], MakeSdlRect(uiPosition.x + 10, uiPosition.y + 315, 85, 71)));
const SDL_Rect rect5 = MakeSdlRect(uiPosition.x + 10, uiPosition.y + 315 + 43, 85, 71);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("Join").data(), rect5, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite | UiFlags::AlignCenter, -1));
vecHubMainDialog.push_back(std::make_unique<UiImageClx>((*MainButton)[15], MakeSdlRect(uiPosition.x + 10, uiPosition.y + 402, 85, 71)));
const SDL_Rect rect6 = MakeSdlRect(uiPosition.x + 10, uiPosition.y + 402 + 43, 85, 71);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("Quit").data(), rect6, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite | UiFlags::AlignCenter, -1));
UiHubInitPlayerList();
const SDL_Rect rectScrollbar1 = MakeSdlRect(uiPosition.x + 402, uiPosition.y + 158, 17, 251);
vecHubMainDialog.push_back(std::make_unique<UiScrollbar>((*ArtScrollBarBackground)[0], (*ArtScrollBarThumb)[0], *ArtScrollBarArrow, rectScrollbar1));
const SDL_Rect rectScrollbar2 = MakeSdlRect(uiPosition.x + 602, uiPosition.y + 199, 17, 209);
vecHubMainDialog.push_back(std::make_unique<UiScrollbar>((*ArtScrollBarBackground)[0], (*ArtScrollBarThumb)[0], *ArtScrollBarArrow, rectScrollbar2));
const SDL_Rect rect7 = MakeSdlRect(uiPosition.x + 264 + 188, uiPosition.y + 335 + 103, 85, 35);
vecHubMainDialog.push_back(std::make_unique<UiButton>(_("Send"), &DialogActionOK, rect7));
const SDL_Rect rect8 = MakeSdlRect(uiPosition.x + 264 + 283, uiPosition.y + 335 + 103, 85, 35);
vecHubMainDialog.push_back(std::make_unique<UiButton>(_("Whisper"), &DialogActionOK, rect8));
const SDL_Rect rect9 = MakeSdlRect(uiPosition.x + 117, uiPosition.y + 433, 306, 30);
vecHubMainDialog.push_back(std::make_unique<UiEdit>(_("Enter chat message"), ChatMessage, 128, false, rect9, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite));
}
void DrawChat()
{
const Point uiPosition = GetUIRectangle().position + Displacement { 116, 161 };
const Surface &out = Surface(DiabloUiSurface());
int displayLines = ChatLogLines.size();
displayLines = std::min(100, displayLines);
int SkipLines = 0;
int lineHeight = 22;
for (int i = 0; i < displayLines; i++) {
if (i + SkipLines >= ChatLogLines.size())
break;
MultiColoredText &text = ChatLogLines[ChatLogLines.size() - (i + SkipLines + 1)];
const string_view line = text.text;
std::vector<DrawStringFormatArg> args;
for (auto &x : text.colors) {
args.emplace_back(DrawStringFormatArg { x.text, x.color });
}
DrawStringWithColors(out, line, args, { { (uiPosition.x + text.offset), uiPosition.y + i * lineHeight }, { 280 - text.offset * 2, lineHeight } }, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite, /*spacing=*/1, lineHeight);
}
}
} // namespace devilution

9
Source/DiabloUI/hub/chat.h

@ -0,0 +1,9 @@
#pragma once
namespace devilution {
void hubmain_Init();
void hubmain_GameSelection_Init();
void DrawChat();
} // namespace devilution

51
Source/DiabloUI/hub/create.cpp

@ -0,0 +1,51 @@
#include "DiabloUI/hub/create.h"
#include "DiabloUI/hub/hub.h"
#include "engine/load_pcx.hpp"
namespace devilution {
namespace {
OptionalOwnedClxSpriteList CreateButton;
void DialogActionOK()
{
}
} // namespace
void HubLoadCreate()
{
Layout = LoadPcx("ui_art\\creat_bg", /*transparentColor=*/0);
CreateButton = LoadPcxSpriteList("ui_art\\diffbtns", -68);
}
void HubInitCreate()
{
const Point uiPosition = GetUIRectangle().position;
const SDL_Rect rect0 = MakeSdlRect(uiPosition.x + 17, uiPosition.y + 154, 274, 26);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("Difficulty").data(), rect0, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite | UiFlags::AlignCenter, -1));
vecHubMainDialog.push_back(std::make_unique<UiImageClx>((*CreateButton)[1], MakeSdlRect(uiPosition.x + 115, uiPosition.y + 187, 85, 68)));
const SDL_Rect rect1 = MakeSdlRect(uiPosition.x + 115, uiPosition.y + 227, 85, 26);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("Normal").data(), rect1, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite | UiFlags::AlignCenter, -2));
vecHubMainDialog.push_back(std::make_unique<UiImageClx>((*CreateButton)[2], MakeSdlRect(uiPosition.x + 62, uiPosition.y + 263, 85, 68)));
const SDL_Rect rect2 = MakeSdlRect(uiPosition.x + 62, uiPosition.y + 303, 85, 26);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("Nightmare").data(), rect2, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite | UiFlags::AlignCenter, -2));
vecHubMainDialog.push_back(std::make_unique<UiImageClx>((*CreateButton)[4], MakeSdlRect(uiPosition.x + 167, uiPosition.y + 263, 85, 68)));
const SDL_Rect rect3 = MakeSdlRect(uiPosition.x + 167, uiPosition.y + 303, 85, 26);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("Hell").data(), rect3, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite | UiFlags::AlignCenter, -2));
const SDL_Rect rect4 = MakeSdlRect(uiPosition.x + 26, uiPosition.y + 340, 262, 110);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("Normal Difficulty\nThis is where a starting\ncharacter should begin the quest\nto defeat Diablo.").data(), rect4, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite, -1, 19));
const SDL_Rect rect7 = MakeSdlRect(uiPosition.x + 264 + 188, uiPosition.y + 335 + 103, 85, 35);
vecHubMainDialog.push_back(std::make_unique<UiButton>(_("OK"), &DialogActionOK, rect7));
const SDL_Rect rect8 = MakeSdlRect(uiPosition.x + 264 + 283, uiPosition.y + 335 + 103, 85, 35);
vecHubMainDialog.push_back(std::make_unique<UiButton>(_("Cancel"), &DialogActionOK, rect8));
}
} // namespace devilution

8
Source/DiabloUI/hub/create.h

@ -0,0 +1,8 @@
#pragma once
namespace devilution {
void HubLoadCreate();
void HubInitCreate();
} // namespace devilution

22
Source/DiabloUI/hub/friends.cpp

@ -0,0 +1,22 @@
#include "DiabloUI/hub/friends.h"
#include "DiabloUI/hub/hub.h"
#include "engine/load_pcx.hpp"
namespace devilution {
namespace {
} // namespace
void HubLoadFriends()
{
Layout = LoadPcx("ui_art\\bnselchn", /*transparentColor=*/0);
}
void HubInitFriends()
{
const Point uiPosition = GetUIRectangle().position;
}
} // namespace devilution

8
Source/DiabloUI/hub/friends.h

@ -0,0 +1,8 @@
#pragma once
namespace devilution {
void HubLoadFriends();
void HubInitFriends();
} // namespace devilution

221
Source/DiabloUI/hub/hub.cpp

@ -0,0 +1,221 @@
#include "DiabloUI/hub/hub.h"
#include <fmt/format.h>
#include "DiabloUI/button.h"
#include "DiabloUI/diabloui.h"
#include "DiabloUI/dialogs.h"
#include "DiabloUI/hero/selhero.h"
#include "DiabloUI/hub/chat.h"
#include "DiabloUI/hub/create.h"
#include "DiabloUI/hub/friends.h"
#include "DiabloUI/hub/join.h"
#include "DiabloUI/scrollbar.h"
#include "DiabloUI/selok.h"
#include "config.h"
#include "control.h"
#include "engine/assets.hpp"
#include "engine/load_clx.hpp"
#include "menu.h"
#include "options.h"
#include "storm/storm_net.hpp"
#include "utils/language.h"
#include "utils/str_cat.hpp"
#include "utils/utf8.hpp"
namespace devilution {
bool hubmain_endMenu;
std::vector<std::unique_ptr<UiItemBase>> vecHubMainDialog;
OptionalOwnedClxSpriteList Layout;
namespace {
enum class HubPanels : uint8_t {
Chat,
Friends,
Create,
Join,
};
HubPanels HubPanel = HubPanels::Join;
OptionalOwnedClxSpriteList PlayerIcons;
OptionalOwnedClxSpriteList PlayerSpawnIcon;
OptionalOwnedClxSpriteList PlayerLevelFont;
OptionalOwnedClxSpriteList LagGreen;
OptionalOwnedClxSpriteList LagYellow;
OptionalOwnedClxSpriteList LagRed;
void LoadHubPlayerGraphics()
{
PlayerIcons = LoadPcxSpriteList("ui_art\\heroport", -14);
PlayerSpawnIcon = LoadPcx("ui_art\\spwnport");
PlayerLevelFont = LoadPcxSpriteList("ui_art\\heronum", 10);
LagGreen = LoadPcx("ui_art\\greenlag");
LagYellow = LoadPcx("ui_art\\yellolag");
LagRed = LoadPcx("ui_art\\redlag");
}
std::vector<std::unique_ptr<UiItemBase>> vecHubBackground;
void hub_Init()
{
LoadDialogButtonGraphics();
LoadHubScrollBar();
LoadBackgroundArt("ui_art\\bnconnbg");
ArtBackgroundWidescreen = LoadOptionalClx("ui_art\\bnconnbgw.clx");
uint8_t transparentColor = 250;
AssetRef ref = FindAsset("ui_art\\xsmlogo");
if (ref.ok() && ref.size() == 167723)
transparentColor = 32;
ArtLogo = LoadPcxSpriteList("ui_art\\xsmlogo", /*numFrames=*/15, transparentColor);
switch (HubPanel) {
case HubPanels::Chat:
hubmain_Init();
break;
case HubPanels::Friends:
break;
case HubPanels::Create:
HubLoadCreate();
break;
case HubPanels::Join:
HubLoadJoin();
break;
}
LoadHubPlayerGraphics();
}
void hubmain_Free()
{
FreeDialogButtonGraphics();
ArtBackground = std::nullopt;
ArtBackgroundWidescreen = std::nullopt;
Layout = std::nullopt;
vecHubMainDialog.clear();
}
bool IsKnownHeroType(uint32_t gameMode, HeroClass heroClass)
{
if (gameMode != GameIdDiabloFull && gameMode != GameIdHellfireFull)
return false;
return heroClass == HeroClass::Warrior
|| heroClass == HeroClass::Rogue
|| heroClass == HeroClass::Sorcerer;
}
} // namespace
void LoadHubScrollBar()
{
ScrollBarWidth = 17;
ScrollBarArrowFrame_UP_ACTIVE = 2;
ScrollBarArrowFrame_UP = 0;
ScrollBarArrowFrame_DOWN_ACTIVE = 3;
ScrollBarArrowFrame_DOWN = 1;
ArtScrollBarArrow = LoadPcxSpriteList("ui_art\\scrlarrw", 4);
ArtScrollBarBackground = LoadPcx("ui_art\\scrlbar");
ArtScrollBarThumb = LoadPcx("ui_art\\scrlthmb");
}
void UiHubPlacePlayerIcon(Point position, uint32_t gameMode, const PlayerInfo &player)
{
ClxSprite sprite = (*PlayerSpawnIcon)[0];
if (IsKnownHeroType(gameMode, player.heroClass)) {
int level = player.diabloKillLevel * 3;
sprite = (*PlayerIcons)[level + static_cast<uint8_t>(player.heroClass)];
}
const SDL_Rect rect0 = MakeSdlRect(position.x, position.y, 0, 0);
vecHubMainDialog.push_back(std::make_unique<UiImageClx>(sprite, rect0));
const SDL_Rect rect1 = MakeSdlRect(position.x + 20, position.y + 5, 0, 0);
vecHubMainDialog.push_back(std::make_unique<UiImageClx>((*PlayerLevelFont)[player.level % 10], rect1));
if (player.level > 9) {
const SDL_Rect rect2 = MakeSdlRect(position.x + 14, position.y + 5, 0, 0);
vecHubMainDialog.push_back(std::make_unique<UiImageClx>((*PlayerLevelFont)[player.level / 10], rect2));
}
}
void UiHubPlaceLatencyMeter(int latency, Point position)
{
int bars = latency / 50 + 1;
bars = std::min(bars, 6);
ClxSprite lagSprite = (*LagRed)[0];
if (bars <= 2)
lagSprite = (*LagGreen)[0];
else if (bars <= 4)
lagSprite = (*LagYellow)[0];
const SDL_Rect rect4 = MakeSdlRect(position.x, position.y, 3 * bars, 11);
vecHubMainDialog.push_back(std::make_unique<UiImageClx>(lagSprite, rect4));
}
void hubmain_GameSelection_Focus(int value)
{
}
void hubmain_Diff_Select(int value)
{
}
void hubmain_GameSelection_Esc()
{
UiInitList_clear();
hubmain_endMenu = true;
}
void hub_GameSelection_Init()
{
const Point uiPosition = GetUIRectangle().position;
const SDL_Rect rect0 = MakeSdlRect(0, uiPosition.y, 0, 0);
vecHubBackground.push_back(std::make_unique<UiImageClx>((*ArtBackground)[0], rect0, UiFlags::AlignCenter));
vecHubBackground.push_back(std::make_unique<UiImageClx>((*ArtBackgroundWidescreen)[0], rect0, UiFlags::AlignCenter));
vecHubBackground.push_back(std::make_unique<UiImageAnimatedClx>(*ArtLogo, MakeSdlRect(uiPosition.x, uiPosition.y, 0, 0)));
const SDL_Rect rect1 = MakeSdlRect(uiPosition.x, uiPosition.y + (*ArtBackground)[0].height() - (*Layout)[0].height(), (*Layout)[0].width(), (*Layout)[0].height());
vecHubBackground.push_back(std::make_unique<UiImageClx>((*Layout)[0], rect1, UiFlags::AlignCenter));
switch (HubPanel) {
case HubPanels::Chat:
hubmain_GameSelection_Init();
break;
case HubPanels::Friends:
break;
case HubPanels::Create:
HubInitCreate();
break;
case HubPanels::Join:
HubInitJoin();
break;
}
UiInitList(hubmain_GameSelection_Focus, hubmain_Diff_Select, hubmain_GameSelection_Esc, vecHubMainDialog, true);
}
bool UiHubMain()
{
hub_Init();
hub_GameSelection_Init();
hubmain_endMenu = false;
while (!hubmain_endMenu) {
UiClearScreen();
UiRenderItems(vecHubBackground);
if (HubPanel == HubPanels::Chat)
DrawChat();
UiPollAndRender();
}
hubmain_Free();
return true;
}
} // namespace devilution

31
Source/DiabloUI/hub/hub.h

@ -0,0 +1,31 @@
#pragma once
#include <memory>
#include <vector>
#include "DiabloUI/diabloui.h"
#include "engine/point.hpp"
namespace devilution {
struct PlayerInfo {
std::string name;
HeroClass heroClass;
uint8_t level;
uint8_t diabloKillLevel;
uint32_t gameMode;
int latency;
};
extern std::vector<std::unique_ptr<UiItemBase>> vecHubMainDialog;
extern OptionalOwnedClxSpriteList Layout;
void selgame_GameSelection_Init();
void selgame_GameSelection_Focus(int value);
void selgame_GameSelection_Esc();
void selgame_Diff_Select(int value);
void LoadHubScrollBar();
void UiHubPlacePlayerIcon(Point position, uint32_t gameMode, const PlayerInfo &player);
void UiHubPlaceLatencyMeter(int latency, Point position);
} // namespace devilution

135
Source/DiabloUI/hub/join.cpp

@ -0,0 +1,135 @@
#include "DiabloUI/hub/join.h"
#include "DiabloUI/hub/hub.h"
#include "DiabloUI/scrollbar.h"
#include "engine/load_pcx.hpp"
namespace devilution {
namespace {
void DialogActionOK()
{
}
std::vector<PlayerInfo> PlayerList;
std::string relativeTime;
std::string difficultyString;
std::string GetDifficultyString(int difficulty)
{
constexpr std::array<const char *, 3> DifficultyStrs = { N_("Normal"), N_("Nightmare"), N_("Hell") };
const string_view difficultyStr = _(DifficultyStrs[difficulty]);
return fmt::format(fmt::runtime(_(/* TRANSLATORS: "Nightmare Difficulty" */ "{:s} Difficulty")), difficultyStr);
}
char GameNameInput[32];
char GamePasswordInput[32];
} // namespace
void HubLoadJoin()
{
Layout = LoadPcx("ui_art\\bnjoinbg", /*transparentColor=*/0);
}
void HubInitJoin()
{
const Point uiPosition = GetUIRectangle().position;
const SDL_Rect rect0 = MakeSdlRect(uiPosition.x + 17, uiPosition.y + 150, 274, 26);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("Matching Public Games").data(), rect0, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite | UiFlags::AlignCenter, -1));
const SDL_Rect rect1 = MakeSdlRect(uiPosition.x + 312, uiPosition.y + 150, 311, 26);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("Join Game").data(), rect1, UiFlags::FontSizeDialog | UiFlags::ColorYellow | UiFlags::AlignCenter, -1));
const SDL_Rect rect2 = MakeSdlRect(uiPosition.x + 326, uiPosition.y + 185, 274, 26 * 2);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("To Join a game, enter the game\ninfomration below.").data(), rect2, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite, -1));
const SDL_Rect rect3 = MakeSdlRect(uiPosition.x + 326, uiPosition.y + 249, 86, 26);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("Name:").data(), rect3, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite | UiFlags::AlignRight, -1));
const SDL_Rect rect4 = MakeSdlRect(uiPosition.x + 421, uiPosition.y + 250, 192, 29);
vecHubMainDialog.push_back(std::make_unique<UiEdit>(_("Enter game name"), GameNameInput, 32, false, rect4, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite));
const SDL_Rect rect5 = MakeSdlRect(uiPosition.x + 326, uiPosition.y + 294, 86, 26);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("Password:").data(), rect5, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite | UiFlags::AlignRight, -1));
const SDL_Rect rect6 = MakeSdlRect(uiPosition.x + 421, uiPosition.y + 295, 192, 29);
vecHubMainDialog.push_back(std::make_unique<UiEdit>(_("Enter game password"), GamePasswordInput, 32, false, rect6, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite));
const SDL_Rect rectScrollbar = MakeSdlRect(uiPosition.x + 269, uiPosition.y + 189, 17, 274);
vecHubMainDialog.push_back(std::make_unique<UiScrollbar>((*ArtScrollBarBackground)[0], (*ArtScrollBarThumb)[0], *ArtScrollBarArrow, rectScrollbar));
UiHubPlaceLatencyMeter(75, { uiPosition.x + 245, uiPosition.y + 195 });
const SDL_Rect rect7 = MakeSdlRect(uiPosition.x + 27, uiPosition.y + 186, 218, 26);
vecHubMainDialog.push_back(std::make_unique<UiArtText>("CWEJZ", rect7, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite, -1));
const Point detailsPosition = uiPosition + Displacement { 326, 342 };
int yOffset = 0;
difficultyString = GetDifficultyString(1);
const SDL_Rect rect8 = MakeSdlRect(detailsPosition.x, detailsPosition.y + yOffset - 6, 149, 18);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(difficultyString.data(), rect8, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite, -1));
yOffset += 16;
int seconds = 76 * 60;
int hours = seconds / 3600;
int minutes = (seconds % 3600) / 60;
relativeTime = fmt::format(fmt::runtime(ngettext("{:d} minute", "{:d} minutes", minutes)), minutes);
if (hours > 0) {
if (minutes > 0) {
relativeTime = fmt::format(fmt::runtime(ngettext("{:d} minute", "{:d} minutes", minutes)), minutes);
relativeTime = fmt::format(fmt::runtime(ngettext(
/* TRANSLATORS: {:s} the translated minuts (3 minuts).*/
"Time: {:d} hour and {:s}",
"Time: {:d} hours and {:s}",
hours)),
hours, relativeTime);
} else {
relativeTime = fmt::format(fmt::runtime(ngettext("Time: {:d} hour", "Time: {:d} hours", hours)), hours);
}
} else {
relativeTime = fmt::format(fmt::runtime(_("Time: {:s}")), relativeTime);
}
const SDL_Rect rect9 = MakeSdlRect(detailsPosition.x, detailsPosition.y + yOffset - 6, 149, 18);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(relativeTime.data(), rect9, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite, -1));
yOffset += 16;
const SDL_Rect rect10 = MakeSdlRect(detailsPosition.x, detailsPosition.y + yOffset - 6, 51, 18);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("Players:").data(), rect10, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite, -1));
PlayerList.emplace_back(PlayerInfo { "KPhoenix", HeroClass::Warrior, 35, 2, GameIdDiabloFull, 0 });
PlayerList.emplace_back(PlayerInfo { "AJenbo", HeroClass::Rogue, 23, 1, GameIdDiabloFull, 0 });
PlayerList.emplace_back(PlayerInfo { "glebm", HeroClass::Sorcerer, 19, 0, GameIdDiabloFull, 0 });
for (auto &player : PlayerList) {
UiHubPlacePlayerIcon({ detailsPosition.x + 52, detailsPosition.y + yOffset }, GameIdDiabloFull, player);
const SDL_Rect rect = MakeSdlRect(detailsPosition.x + 52 + 30, detailsPosition.y + yOffset - 6, 149 - 30, 18);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(player.name.data(), rect, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite, -1));
yOffset += 16;
}
const Point detailsPosition2 = detailsPosition + Displacement { 184, 0 };
yOffset = 0;
const SDL_Rect rect11 = MakeSdlRect(detailsPosition2.x, detailsPosition2.y + yOffset - 6, 149, 18);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("Run in Town").data(), rect11, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite, -1));
yOffset += 16;
const SDL_Rect rect12 = MakeSdlRect(detailsPosition2.x, detailsPosition2.y + yOffset - 6, 149, 18);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("Theo Quest").data(), rect12, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite, -1));
yOffset += 16;
const SDL_Rect rect13 = MakeSdlRect(detailsPosition2.x, detailsPosition2.y + yOffset - 6, 149, 18);
vecHubMainDialog.push_back(std::make_unique<UiArtText>(_("Friendly Fire").data(), rect13, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite, -1));
yOffset += 16;
const SDL_Rect rect14 = MakeSdlRect(uiPosition.x + 264 + 188, uiPosition.y + 335 + 103, 85, 35);
vecHubMainDialog.push_back(std::make_unique<UiButton>(_("OK"), &DialogActionOK, rect14));
const SDL_Rect rect15 = MakeSdlRect(uiPosition.x + 264 + 283, uiPosition.y + 335 + 103, 85, 35);
vecHubMainDialog.push_back(std::make_unique<UiButton>(_("Cancel"), &DialogActionOK, rect15));
}
} // namespace devilution

8
Source/DiabloUI/hub/join.h

@ -0,0 +1,8 @@
#pragma once
namespace devilution {
void HubLoadJoin();
void HubInitJoin();
} // namespace devilution

10
Source/DiabloUI/scrollbar.cpp

@ -7,9 +7,19 @@ namespace devilution {
OptionalOwnedClxSpriteList ArtScrollBarBackground;
OptionalOwnedClxSpriteList ArtScrollBarThumb;
OptionalOwnedClxSpriteList ArtScrollBarArrow;
Uint16 ScrollBarWidth;
uint16_t ScrollBarArrowFrame_UP_ACTIVE;
uint16_t ScrollBarArrowFrame_UP;
uint16_t ScrollBarArrowFrame_DOWN_ACTIVE;
uint16_t ScrollBarArrowFrame_DOWN;
void LoadScrollBar()
{
ScrollBarWidth = 25;
ScrollBarArrowFrame_UP_ACTIVE = 0;
ScrollBarArrowFrame_UP = 1;
ScrollBarArrowFrame_DOWN_ACTIVE = 2;
ScrollBarArrowFrame_DOWN = 3;
ArtScrollBarBackground = LoadPcx("ui_art\\sb_bg");
ArtScrollBarThumb = LoadPcx("ui_art\\sb_thumb");
ArtScrollBarArrow = LoadPcxSpriteList("ui_art\\sb_arrow", 4);

20
Source/DiabloUI/scrollbar.h

@ -11,23 +11,19 @@ namespace devilution {
extern OptionalOwnedClxSpriteList ArtScrollBarBackground;
extern OptionalOwnedClxSpriteList ArtScrollBarThumb;
extern OptionalOwnedClxSpriteList ArtScrollBarArrow;
constexpr Uint16 ScrollBarBgWidth = 25;
extern Uint16 ScrollBarWidth;
enum ScrollBarArrowFrame : uint8_t {
ScrollBarArrowFrame_UP_ACTIVE,
ScrollBarArrowFrame_UP,
ScrollBarArrowFrame_DOWN_ACTIVE,
ScrollBarArrowFrame_DOWN,
};
constexpr Uint16 ScrollBarArrowWidth = 25;
extern uint16_t ScrollBarArrowFrame_UP_ACTIVE;
extern uint16_t ScrollBarArrowFrame_UP;
extern uint16_t ScrollBarArrowFrame_DOWN_ACTIVE;
extern uint16_t ScrollBarArrowFrame_DOWN;
inline SDL_Rect UpArrowRect(const UiScrollbar &bar)
{
return MakeSdlRect(
bar.m_rect.x,
bar.m_rect.y,
ScrollBarArrowWidth,
ScrollBarWidth,
bar.m_arrow[0].height());
}
@ -36,7 +32,7 @@ inline SDL_Rect DownArrowRect(const UiScrollbar &bar)
return MakeSdlRect(
bar.m_rect.x,
bar.m_rect.y + bar.m_rect.h - bar.m_arrow[0].height(),
ScrollBarArrowWidth,
ScrollBarWidth,
bar.m_arrow[0].height());
}
@ -50,7 +46,7 @@ inline SDL_Rect BarRect(const UiScrollbar &bar)
return MakeSdlRect(
bar.m_rect.x,
bar.m_rect.y + bar.m_arrow[0].height(),
ScrollBarArrowWidth,
ScrollBarWidth,
BarHeight(bar));
}

1
Source/DiabloUI/ui_flags.hpp

@ -21,6 +21,7 @@ enum class UiFlags : uint32_t {
ColorUiSilver = 1 << 7,
ColorUiGoldDark = 1 << 8,
ColorUiSilverDark = 1 << 9,
ColorUiGreen = 1 << 6,
ColorDialogWhite = 1 << 10,
ColorYellow = 1 << 11,
ColorGold = 1 << 12,

11
Source/multi.cpp

@ -440,6 +440,8 @@ bool InitSingle(GameData *gameData)
return true;
}
extern int provider;
bool InitMulti(GameData *gameData)
{
Players.resize(MAX_PLRS);
@ -452,8 +454,13 @@ bool InitMulti(GameData *gameData)
}
RegisterNetEventHandlers();
if (UiSelectGame(gameData, &playerId))
break;
if (true) {
if (UiHubMain())
break;
} else {
if (UiSelectGame(gameData, &playerId))
break;
}
gbSelectProvider = true;
}

Loading…
Cancel
Save