Files
Justin Marshall 7806bb8ef8 Added uisrc.
2026-04-27 10:37:37 -07:00

1097 lines
30 KiB
C++

//****************************************************************************
//
// connect.CPP
// Diablo UI
//
// By Michael Morhaime
//
//****************************************************************************
#include "pch.h"
#include "stdio.h"
#include "..\battle\bnetart.h"
#include "uisnd.h"
#include <time.h>
#define MAX_SHORT_STRING_LEN 128
#define MAX_STRING_LEN 256
#define DIABLO_RETAIL_ID 'DRET'
#define DIABLO_SHARE_ID 'DSHR'
#define PROGRAMID 'HRTL'
#define MIN_SPACING 2
#define NUM_PORTS (3*4)
#define PORTRAIT_HEIGHT 14
#define HERO_PORT_X 0
#define HERO_LEVEL_X1 14
#define HERO_LEVEL_X2 20
#define HERO_LEVEL_Y 5
#define COLOR_BG_HILITE (RGB(0x80,0x80,0x80))
BOOL Txt2HeroInfo(char *szDesc, TUIHEROINFO *pHeroInfo);
void HeroInfo2Txt(TUIHEROINFO *pHeroInfo, char *szDesc);
DWORD GetPlayerCategory(const char *szDesc); //### MM Diablo Patch #2 3/21/97
extern int UiOkCancelDialog(HWND parent, LPCSTR okcanceltext, BOOL bErrFlag);
extern int UiBigOkCancelDialog(HWND parent, LPCSTR okcanceltext, LPCSTR title, BOOL bErrFlag);
extern int UiBigOkDialog(HWND parent, LPCSTR oktext, LPCSTR title, BOOL errorflag);
extern int UiOkDialog(HWND parent, LPCSTR okcanceltext, BOOL bErrFlag);
static LPBYTE gBmpHeroPort = NULL;
static LPBYTE gBmpSpawnedPort = NULL;
static LPBYTE gBmpSpecial = NULL;
static LPBYTE gBmpScratchPort = NULL;
static HTRANS sgTransNum[10] = { 0 };
static SIZE gSizeHeroPort;
static SIZE gSizeHeroNum;
static SIZE gSizeSpecial;
static int gHeroNumHgt; // Height of individual numbers/portraits
static int gHeroPortHgt;
static int gSpecialHgt;
static int gnPortBytesSize; // Precalulated bytes per Portrait and Digit image (for easy calculations)
static int gnDigitBytesSize;
static DWORD sgMyProgramId = 0; // This is saved during call to CreatePlayerDesc()
enum _special_icons {
eSPECIAL_BLIZZARD,
eSPECIAL_SYSOP_BATTLENET,
eSPECIAL_MODERATOR,
eSPECIAL_SPEAKER,
eSPECIAL_SYSOP,
eSPECIAL_SQUELCHED,
NUM_SPECIAL_ICONS
};
// Game Description Fields
// "%d\r%s\r%s", bDiff, szPlayerName, szPlayerDesc
//
// Player Description Fields
// "%c%c%c%c %d %d %d %d %d %d %d %d", PROGRAMID, wLevel, bHeroClass, bStrength, bMagic, bDexterity, bVitality, dwGold, bSpawned
enum _description_fields {
eGAMEDESC_DIFFICULTY,
eGAMEDESC_CREATOR_NAME,
// These are from the Player Description
eGAMEDESC_PROGRAMID,
eGAMEDESC_CREATOR_LEVEL,
eGAMEDESC_CREATOR_CLASS,
eGAMEDESC_CREATOR_STRENGTH,
eGAMEDESC_CREATOR_MAGIC,
eGAMEDESC_CREATOR_DEXTERITY,
eGAMEDESC_CREATOR_VITALITY,
eGAMEDESC_CREATOR_GOLD,
eGAMEDESC_CREATOR_SPAWNED
};
//===========================================================================
//===========================================================================
// DATA Used in GetDataCallback
// speed to play diablo logo
#define SML_LOGO_DELAY 54
SNET_DATA_SYSCOLORTABLE sgSysColorTbl[] = {
{ COLOR_HIGHLIGHT, RGB(0x80, 0x80, 0x80) },
{ COLOR_HIGHLIGHTTEXT, RGB(0xff, 0xff, 0xff) },
};
//===========================================================================
//===========================================================================
void FreeGlobalHeroArt(void) {
if (gBmpHeroPort) {
FREE(gBmpHeroPort);
gBmpHeroPort = NULL;
}
if (gBmpSpawnedPort) {
FREE(gBmpSpawnedPort);
gBmpSpawnedPort = NULL;
}
for (int i=0; i<10; i++) {
if (sgTransNum[i]) {
STransDelete(sgTransNum[i]);
sgTransNum[i] = NULL;
}
}
if (gBmpSpecial) {
FREE(gBmpSpecial);
gBmpSpecial = NULL;
}
if (gBmpScratchPort) {
FREE(gBmpScratchPort);
gBmpScratchPort = NULL;
}
}
//===========================================================================
BOOL LoadGlobalHeroArt(void) {
LPBYTE bmpHeroNum = NULL;
RECT rect;
// Make sure this doesn't get loaded twice
if (gBmpHeroPort || sgTransNum[0]) {
return 1;
}
UiLoadBmpFile(
TEXT("ui_art\\heroport.pcx"),
&gBmpHeroPort,
&gSizeHeroPort
);
UiLoadBmpFile(
TEXT("ui_art\\spwnport.pcx"),
&gBmpSpawnedPort,
NULL
);
UiLoadBmpFile(
TEXT("ui_art\\heronum.pcx"),
&bmpHeroNum,
&gSizeHeroNum
);
UiLoadBmpFile(
TEXT("ui_art\\special.pcx"),
&gBmpSpecial,
&gSizeSpecial // Special 'chat' icons
);
gHeroNumHgt = gSizeHeroNum.cy/10;
gHeroPortHgt = PORTRAIT_HEIGHT;
gSpecialHgt = gSizeSpecial.cy/NUM_SPECIAL_ICONS;
gnDigitBytesSize = gSizeHeroNum.cx * gHeroNumHgt;
gnPortBytesSize = gSizeHeroPort.cx * gHeroPortHgt;
// Allocate space offscreen to build portraits
gBmpScratchPort = (LPBYTE)ALLOC(gHeroPortHgt * gSizeHeroPort.cx);
if (!gBmpScratchPort) {
return 0;
}
// Build Transparencies for numbers
memset(sgTransNum, 0, sizeof(sgTransNum));
if (bmpHeroNum) {
// create an HTRANS for each number
for (int index = 0; index < 10; index++) {
rect.left = 0;
rect.right = gSizeHeroNum.cx - 1;
rect.top = index * gHeroNumHgt;
rect.bottom = rect.top + gHeroNumHgt - 1;
STransCreate(
bmpHeroNum,
gSizeHeroNum.cx,
gHeroNumHgt,
8,
&rect,
PALETTEINDEX(239),
&sgTransNum[index]
);
}
FREE(bmpHeroNum);
}
return 1;
}
//****************************************************************************
//****************************************************************************
BOOL CALLBACK UiArtCallback (DWORD providerid,
DWORD artid,
LPPALETTEENTRY pe,
LPBYTE buffer,
DWORD buffersize,
int *width,
int *height,
int *bitdepth) {
char filename[MAX_PATH] = "";
if (providerid == 'BNET') {
// Check for battlenet specific art
switch (artid) {
case SNET_ART_BACKGROUND: strcpy(filename,"ui_art\\bn_bkg.pcx"); break;
case SNET_ART_JOINBACKGROUND: strcpy(filename, "ui_art\\bnjoinbg.pcx"); break;
case SNET_ART_HELPBACKGROUND: strcpy(filename, "ui_art\\hpopup.pcx"); break;
case SNET_ART_BATTLE_LOGIN_BKG: strcpy(filename, "ui_art\\epopup.pcx"); break;
case SNET_ART_BATTLE_CONNECT_BKG: strcpy(filename, "ui_art\\bnconnbg.pcx"); break;
case SNET_ART_BATTLE_CHAT_BKG: strcpy(filename, "ui_art\\chat_bkg.pcx"); break;
case SNET_ART_BATTLE_BTNS: strcpy(filename, "ui_art\\bnbuttns.pcx"); break;
case SNET_ART_BATTLE_SELECT_CHNL_BKG: strcpy(filename, "ui_art\\bnselchn.pcx"); break;
case SNET_ART_BATTLE_REDLAG: strcpy(filename, "ui_art\\redlag.pcx"); break;
case SNET_ART_BATTLE_YELLOWLAG: strcpy(filename, "ui_art\\yellolag.pcx"); break;
case SNET_ART_BATTLE_GREENLAG: strcpy(filename, "ui_art\\greenlag.pcx"); break;
case SNET_ART_BUTTON_XSML: strcpy(filename,"ui_art\\but_xsm.pcx"); break;
case SNET_ART_BUTTON_SML: strcpy(filename,"ui_art\\but_sml.pcx"); break;
case SNET_ART_BUTTON_MED: strcpy(filename,"ui_art\\but_med.pcx"); break;
case SNET_ART_BUTTON_LRG: strcpy(filename,"ui_art\\but_lrg.pcx"); break;
case SNET_ART_BATTLE_BADCONNECTION: strcpy(filename,"ui_art\\badconn.pcx"); break;
case SNET_ART_BATTLE_WELCOME_AD: strcpy(filename,"ui_art\\welcome.pcx"); break;
case SNET_ART_BATTLE_LRG_EDIT_POPUP_BKG: strcpy(filename,"ui_art\\lepopup.pcx"); break;
}
}
else if (providerid == 'IPXN') {
switch (artid) {
case SNET_ART_BACKGROUND: strcpy(filename, "ui_art\\ipx_bkg.pcx"); break;
}
}
else if (providerid == 0) {
switch (artid) {
case SNET_ART_BACKGROUND: strcpy(filename, "ui_art\\connect.pcx"); break;
}
}
if (!filename[0]) {
// Check for generic artwork
switch (artid) {
case SNET_ART_JOINBACKGROUND: // fall through
case SNET_ART_BACKGROUND: strcpy(filename,"ui_art\\menu.pcx"); break;
case SNET_ART_BUTTONTEXTURE: strcpy(filename,"ui_art\\but_lrg.pcx"); break;
case SNET_ART_POPUPBACKGROUND: strcpy(filename,"ui_art\\lpopup.pcx"); break;
case SNET_ART_HELPBACKGROUND: strcpy(filename,"ui_art\\lpopup.pcx"); break;
case SNET_ART_APP_LOGO_SML: strcpy(filename, "ui_art\\xsmlogo.pcx"); break;
case SNET_ART_BUTTON_XSML: strcpy(filename,"ui_art\\but_xsm.pcx"); break;
case SNET_ART_BUTTON_SML: strcpy(filename,"ui_art\\but_sml.pcx"); break;
case SNET_ART_BUTTON_MED: strcpy(filename,"ui_art\\but_med.pcx"); break;
case SNET_ART_BUTTON_LRG: strcpy(filename,"ui_art\\but_lrg.pcx"); break;
case SNET_ART_PROGRESS_BACKGROUND: strcpy(filename, "ui_art\\prog_bg.pcx"); break;
case SNET_ART_PROGRESS_FILLER: strcpy(filename, "ui_art\\prog_fil.pcx"); break;
case SNET_ART_POPUPBACKGROUND_SML: strcpy(filename,"ui_art\\spopup.pcx"); break;
case SNET_ART_SCROLLBARARROWS: strcpy(filename, "ui_art\\scrlarrw.pcx"); break;
case SNET_ART_SCROLLTHUMB: strcpy(filename, "ui_art\\scrlthmb.pcx"); break;
case SNET_ART_SCROLLBAR: strcpy(filename, "ui_art\\scrlbar.pcx"); break;
}
}
if (filename[0])
return SBmpLoadImage(filename,pe,buffer,buffersize,width,height,bitdepth);
return 0;
}
//****************************************************************************
//****************************************************************************
BOOL CALLBACK UiGetDataCallback(DWORD providerid, DWORD dataid, LPVOID buffer, DWORD buffersize, DWORD *bytesused) {
DWORD dwSize = 0;
if (providerid == 'BNET') {
switch (dataid) {
case SNET_DATA_BATTLE_LOGODELAY:
dwSize = sizeof(DWORD);
if (buffer) {
DWORD *pdword;
if (buffersize >= dwSize) {
pdword = (DWORD *)buffer;
*pdword = SML_LOGO_DELAY;
}
else return 0;
}
break;
}
}
if (!dwSize) { // Have we filled the data in yet?
switch (dataid) { // No... so move on to snp nonspecific constants
case SNET_DATA_SYSCOLORS:
dwSize = sizeof(sgSysColorTbl);
if (buffer) {
if (buffersize >= dwSize)
memcpy((LPBYTE)buffer, (LPBYTE)sgSysColorTbl, dwSize);
else return 0;
}
break;
case SNET_DATA_CURSORARROW:
dwSize = sizeof(HCURSOR);
if (buffer) {
if (buffersize >= dwSize) {
HCURSOR *phCursor;
phCursor = (HCURSOR *)buffer;
*phCursor = LoadCursor(global_hinstance, "DIABLO_ARROWCURSOR");
if (*phCursor == NULL)
return 0;
}
else return 0;
}
break;
case SNET_DATA_CURSORIBEAM:
dwSize = sizeof(HCURSOR);
if (buffer) {
if (buffersize >= dwSize) {
HCURSOR *phCursor;
phCursor = (HCURSOR *)buffer;
*phCursor = LoadCursor(global_hinstance, "DIABLOIBEAM");
if (*phCursor == NULL)
return 0;
}
else return 0;
}
break;
case SNET_DATA_CURSORLINK:
dwSize = sizeof(HCURSOR);
if (buffer) {
if (buffersize >= dwSize) {
HCURSOR *phCursor;
phCursor = (HCURSOR *)buffer;
*phCursor = LoadCursor(global_hinstance, "DIABLO_LINKCURSOR");
if (*phCursor == NULL)
return 0;
}
else return 0;
}
break;
}
}
if (bytesused)
*bytesused = dwSize;
if (dwSize)
return 1;
return 0;
}
//****************************************************************************
//****************************************************************************
BOOL CALLBACK UiSoundCallback(DWORD providerid, DWORD soundid, DWORD flags) {
switch (soundid) {
case SNET_SND_SELECTITEM:
UiSndPlayEnter();
break;
case SNET_SND_CHANGEFOCUS:
UiSndPlayClick();
break;
}
return 0;
}
typedef BOOL (CALLBACK *SNETCHECKAUTHPROC )(DWORD,LPCSTR,LPCSTR,DWORD,LPCSTR,LPSTR,DWORD);
//****************************************************************************
//****************************************************************************
BOOL CALLBACK UiAuthCallback(DWORD dwItemType, LPCSTR szName, LPCSTR szDesc, DWORD dwUserFlags, LPCSTR szItem, LPSTR szErrorBuf, DWORD dwErrorBufSize) {
TUIHEROINFO heroInfo;
char szString[256];
char szFmt[256];
BOOL bAuth = FALSE; // we are not authorized unless we find a reason to be
char *p;
char szDescCopy[SNETSPI_MAXSTRINGLENGTH*2]; // trashable copy of description string
char szItemCopy[SNETSPI_MAXSTRINGLENGTH*2]; // trashable copy of item string
int nDescLen;
int nItemLen;
// Default to NULL error string
if (dwErrorBufSize)
szErrorBuf[0] = 0;
// First make trashable copies of description and item strings
nDescLen = strlen(szDesc) + 1;
nItemLen = strlen(szItem) + 1;
if (nDescLen > sizeof(szDescCopy) || nItemLen > sizeof(szItemCopy)) {
if (szErrorBuf)
LoadString(global_hinstance, IDS_AUTH_STRTOOLONG, szErrorBuf, dwErrorBufSize);
return 0;
}
memcpy(szDescCopy, szDesc, nDescLen);
memcpy(szItemCopy, szItem, nItemLen);
// Put data into our heroinfo struct so we can understand it.
if (!Txt2HeroInfo(szDescCopy, &heroInfo)) {
if (szErrorBuf)
LoadString(global_hinstance, IDS_AUTH_BADHERODESC, szErrorBuf, dwErrorBufSize);
return 0;
}
if (dwItemType == SNET_AUTHTYPE_CHANNEL) {
// Blizzard employees and Sysop can go anywhere
if ((dwUserFlags & SNET_DDPF_BLIZZARD) || (dwUserFlags & SNET_DDPF_SYSOP))
return 1;
for (int i=0; i<UI_NUM_CLASSES; i++) {
LoadString(global_hinstance, IDS_WARRIOR+i, szString, sizeof(szString));
if (strstr(szItemCopy, szString)) {
// Found a reserved class word, make sure we are of the correct class
if (heroInfo.heroclass != i)
goto CHANNEL_UNAUTHORIZED;
bAuth = TRUE;
break;
}
}
// Check if there is a level requirement
LoadString(global_hinstance, IDS_LEVEL_AUTHORIZATION, szString, sizeof(szString));
p = strstr(szItemCopy, szString);
if (p != NULL) {
// There is a level requirement. Let's see if we pass
p += strlen(szString);
int nMinLevel = atoi(p);
if (heroInfo.level >= nMinLevel)
return TRUE;
}
// ok, bAuth contains our answer at this point
if (bAuth)
return TRUE;
// fall through to Unauthorized
CHANNEL_UNAUTHORIZED:
if (szErrorBuf) {
int nLen;
// Generate error string
LoadString(global_hinstance, IDS_AUTH_CHANNELRESTRICTED, szFmt, sizeof(szFmt));
nLen = 1+sprintf(szString, szFmt, szItemCopy);
if ((int)dwErrorBufSize < nLen) {
memcpy(szErrorBuf, szString, dwErrorBufSize);
szErrorBuf[dwErrorBufSize-1] = 0;
}
else
memcpy(szErrorBuf, szString, nLen);
}
return 0;
}
else { // SNET_AUTHTYPE_GAME
TGAMEDATA GameData;
// Sysop can join any game
if (dwUserFlags & SNET_DDPF_SYSOP)
return 1;
Txt2GameData((char *)szItemCopy, &GameData, NULL, NULL);
if (GameData.bDiff == DIFF_NIGHTMARE && heroInfo.level < 20) {
if (szErrorBuf)
LoadString(global_hinstance, IDS_NIGHTMARE_MINREQ, szErrorBuf, dwErrorBufSize);
return 0;
}
if (GameData.bDiff == DIFF_HELL && heroInfo.level < 30) {
if (szErrorBuf)
LoadString(global_hinstance, IDS_HELL_MINREQ, szErrorBuf, dwErrorBufSize);
return 0;
}
return 1;
}
}
//****************************************************************************
//****************************************************************************
BOOL CALLBACK UiDrawDescCallback (DWORD providerid,
DWORD itemtype,
LPCSTR itemname,
LPCSTR itemdescription,
DWORD itemflags,
DWORD drawflags,
DWORD time,
LPDRAWITEMSTRUCT lpdis) {
char szItemDescCopy[SNETSPI_MAXSTRINGLENGTH*2];
char szDiff[MAX_SHORT_STRING_LEN] = "";
BOOL bSelected = lpdis->itemState & ODS_SELECTED;
COLORREF oldTextColor, oldBkColor;
static BOOL sbDrawingGameDesc = FALSE;
int nDescLen;
// Make a trashable copy of the item description
nDescLen = strlen(itemdescription)+1;
if (nDescLen > sizeof(szItemDescCopy))
return 0;
memcpy(szItemDescCopy, itemdescription, nDescLen);
// First check if this is a game or player description
if (itemtype == SNET_DRAWTYPE_GAME) {
char szString[128];
char szFmt[32];
TGAMEDATA gameData;
LPSTR pszPlayerName = NULL; // Used to describe player who created the games
LPSTR pszPlayerDesc = NULL;
// Put data into our gamedata struct so we can understand it.
if (!Txt2GameData(szItemDescCopy, &gameData, &pszPlayerName, &pszPlayerDesc))
return 0;
// Now display whatever we need to display
oldTextColor = SetTextColor(lpdis->hDC, RGB(0xff, 0xff, 0xff)); // white text
oldBkColor = SetBkColor(lpdis->hDC, (bSelected) ? COLOR_BG_HILITE : RGB(0, 0, 0));
// get format string and create text description
if (drawflags & SNET_DDF_INCLUDENAME) {
// Just print game name only
ExtTextOut(
lpdis->hDC,
lpdis->rcItem.left,
lpdis->rcItem.top,
ETO_CLIPPED | ETO_OPAQUE,
&lpdis->rcItem,
itemname,
strlen(itemname),
NULL);
}
else {
BOOL bHaveCreatorInfo;
bHaveCreatorInfo = ((pszPlayerName != NULL) && (pszPlayerDesc != NULL));
// Build string for display
if ((unsigned char)gameData.bDiff < NUM_DIFFICULTIES)
LoadString(global_hinstance, IDS_DIFF_NORMAL+gameData.bDiff, szDiff, sizeof(szDiff));
if ((drawflags & SNET_DDF_MULTILINE) && bHaveCreatorInfo) {
DRAWITEMSTRUCT disCopy;
POINT oldPos;
int nSaveAlignMode;
int nLineHgt;
TEXTMETRIC tm;
// Get font height information
GetTextMetrics(lpdis->hDC, &tm);
nLineHgt = tm.tmHeight + tm.tmExternalLeading;
// Print game description on first line
MoveToEx(lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.top, &oldPos);
nSaveAlignMode = SetTextAlign(lpdis->hDC, TA_UPDATECP);
TextOut(lpdis->hDC, 0, 0, szDiff, strlen(szDiff));
LoadString(global_hinstance, IDS_CREATOR, szString, sizeof(szString));
MoveToEx(lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.top+nLineHgt, NULL);
TextOut(lpdis->hDC, 0, 0, szString, strlen(szString));
// make a copy of the draw item struct so we can modify the draw rect location
disCopy = *lpdis;
// Use Current Position as top-left of draw rect for player desc.
GetCurrentPositionEx(lpdis->hDC, (POINT *)&disCopy.rcItem.left);
SetTextAlign(lpdis->hDC, TA_NOUPDATECP);
// Draw creator description on first second line
sbDrawingGameDesc = TRUE;
UiDrawDescCallback(
providerid,
SNET_DRAWTYPE_PLAYER,
pszPlayerName,
pszPlayerDesc,
0, // No player flags
SNET_DDF_INCLUDENAME,
0, // No time stamp
&disCopy);
sbDrawingGameDesc = FALSE;
if (time) {
struct tm *newtime;
char szTime[32];
char szAmPm[10];
BOOL bAfternoon;
SetTextAlign(lpdis->hDC, TA_UPDATECP);
// Display Creation Time info
LoadString(global_hinstance, IDS_CREATIONTIME_FMT, szFmt, sizeof(szFmt));
LoadString(global_hinstance, IDS_TIME, szTime, sizeof(szTime));
newtime = localtime((time_t *)&time ); /* Convert to local time. */
if (newtime) { //### BUG FIX MM 1/13/97 Protect against time out of range
bAfternoon = (newtime->tm_hour > 12);
LoadString(global_hinstance, IDS_AM + bAfternoon, szAmPm, sizeof(szAmPm));
if( newtime->tm_hour > 12 ) /* Convert from 24-hour */
newtime->tm_hour -= 12; /* to 12-hour clock. */
if( newtime->tm_hour == 0 ) /* Set hour to 12 if midnight. */
newtime->tm_hour = 12;
sprintf(szString, szFmt, szTime, newtime->tm_hour, newtime->tm_min, szAmPm);
MoveToEx(lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.top+2*nLineHgt, NULL);
TextOut(lpdis->hDC, 0, 0, szString, strlen(szString));
}
}
MoveToEx(lpdis->hDC, oldPos.x, oldPos.y, NULL);
SetTextAlign(lpdis->hDC, nSaveAlignMode);
}
else {
// For single line, don't worry about creator info
TextOut(lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.top, szDiff, strlen(szDiff));
}
}
// Restore colors to hdc
SetTextColor(lpdis->hDC, oldTextColor);
SetBkColor(lpdis->hDC, oldBkColor);
return 1;
}
else { // if (itemtype == SNET_DRAWTYPE_PLAYER) {
TUIHEROINFO heroInfo;
RECT srcRect;
int digit;
int nLineHgt;
int nPortOffset;
TEXTMETRIC tm;
// Get font height information for centering of portrait on line
GetTextMetrics(lpdis->hDC, &tm);
nLineHgt = tm.tmHeight; // don't include tm.tmExternalLeading for centering purposes
nPortOffset = (nLineHgt - gHeroPortHgt)/2 + 1;
if (!sbDrawingGameDesc) {
oldTextColor = SetTextColor(lpdis->hDC, RGB(0xff, 0xff, 0)); // yellow text
oldBkColor = SetBkColor(lpdis->hDC, (bSelected) ? COLOR_BG_HILITE : RGB(0, 0, 0));
}
if (drawflags & SNET_DDF_INCLUDENAME) {
// Draw username
ExtTextOut(
lpdis->hDC,
lpdis->rcItem.left + HERO_PORT_X + gSizeHeroPort.cx + MIN_SPACING,
lpdis->rcItem.top,
ETO_CLIPPED | ETO_OPAQUE,
&lpdis->rcItem,
itemname,
strlen(itemname),
NULL);
}
// Handle special user types, if necessary
int nIcon = -1;
if (itemflags) {
// User is special, so display a special icon instead of a character class
if (itemflags & SNET_DDPF_SQUELCHED) {
nIcon = eSPECIAL_SQUELCHED;
}
else if (itemflags & SNET_DDPF_BLIZZARD) {
nIcon = eSPECIAL_BLIZZARD;
}
else if (itemflags & SNET_DDPF_MODERATOR) {
nIcon = eSPECIAL_MODERATOR;
}
else if (itemflags & SNET_DDPF_SPEAKER) {
nIcon = eSPECIAL_SPEAKER;
}
else if (itemflags & SNET_DDPF_SYSOP) {
if (providerid == 'BNET')
nIcon = eSPECIAL_SYSOP_BATTLENET;
else
nIcon = eSPECIAL_SYSOP;
}
}
if (nIcon >= 0 && gBmpSpecial) {
SetRect(&srcRect, 0, 0, gSizeSpecial.cx - 1, gSpecialHgt - 1);
OffsetRect(&srcRect, 0, gSpecialHgt * nIcon);
// Now draw icon in place of the hero icon
SDlgBltToWindow(
lpdis->hwndItem,
NULL,
lpdis->rcItem.left + HERO_PORT_X,
lpdis->rcItem.top + nPortOffset,
gBmpSpecial,
&srcRect,
&gSizeSpecial);
}
else {
// Player is not a special player type.
// Make sure that he/she is playing diablo before drawing
// hero information.
if (Txt2HeroInfo(szItemDescCopy, &heroInfo)) {
// compose portrait in offscreen portrait buffer.
// That way we don't have to clip on each blit.
// Check for spawned here...
if (heroInfo.spawned) {
if (gBmpScratchPort && gBmpSpawnedPort)
SBltROP3(
gBmpScratchPort,
gBmpSpawnedPort, // Only one class in spawned version
gSizeHeroPort.cx,
gHeroPortHgt,
gSizeHeroPort.cx,
gSizeHeroPort.cx,
NULL,
SRCCOPY);
}
else {
if (gBmpScratchPort && gBmpHeroPort)
SBltROP3(
gBmpScratchPort,
gBmpHeroPort + (gnPortBytesSize*(heroInfo.heroclass + (heroInfo.herorank*UI_NUM_CLASSES))),
gSizeHeroPort.cx,
gHeroPortHgt,
gSizeHeroPort.cx,
gSizeHeroPort.cx,
NULL,
SRCCOPY);
}
// Draw level number
// Draw 10's digit
digit = heroInfo.level/10;
if (digit && gBmpScratchPort) {
STransBlt(
gBmpScratchPort + (gSizeHeroPort.cx*HERO_LEVEL_Y) + HERO_LEVEL_X1,
0, 0,
gSizeHeroPort.cx,
sgTransNum[digit]);
}
// Draw 1's digit
digit = heroInfo.level % 10;
if (gBmpScratchPort)
STransBlt(
gBmpScratchPort + (gSizeHeroPort.cx*HERO_LEVEL_Y) + HERO_LEVEL_X2,
0, 0,
gSizeHeroPort.cx,
sgTransNum[digit]);
// Now copy portrait to the screen
// init source rect
SetRect(&srcRect, 0, 0, gSizeHeroPort.cx - 1, gHeroPortHgt -1);
if (gBmpScratchPort)
SDlgBltToWindow(
lpdis->hwndItem,
NULL,
lpdis->rcItem.left + HERO_PORT_X,
lpdis->rcItem.top + nPortOffset,
gBmpScratchPort,
&srcRect,
&gSizeHeroPort);
}
}
// Restore colors to hdc
if (!sbDrawingGameDesc) {
SetTextColor(lpdis->hDC, oldTextColor);
SetBkColor(lpdis->hDC, oldBkColor);
}
return 1;
}
}
//### MM Diablo Patch #2 3/21/97 BEGIN
#define DIABLO_CATEGORY_MASK 0xffff // Directs Battle.net to match on all category bits.
//===========================================================================
// UiSelectCategory()
//
//===========================================================================
BOOL CALLBACK UiCategoryCallback (
BOOL userinitiated,
SNETPROGRAMDATAPTR programdata,
SNETPLAYERDATAPTR playerdata,
SNETUIDATAPTR interfacedata,
SNETVERSIONDATAPTR versiondata,
DWORD * categorybits,
DWORD * categorymask) {
*categorymask = DIABLO_CATEGORY_MASK;
*categorybits = GetPlayerCategory(playerdata->playerdescription);
return TRUE;
}
//===========================================================================
//
//===========================================================================
DWORD GetPlayerCategory(const char *szDesc) {
TUIHEROINFO HeroInfo;
TCHAR szDescCopy[512];
DWORD dwCategory = 0;
int nLevel;
// First make a trashable copy of the player description string
strcpy(szDescCopy, szDesc);
if (!Txt2HeroInfo(szDescCopy, &HeroInfo))
return 0;
nLevel = HeroInfo.level;
if (nLevel == 1) // Level 1
return dwCategory;
dwCategory++;
if (nLevel < 4) // Level 2-3
return dwCategory;
dwCategory++;
if (nLevel < 6) // Level 4-5
return dwCategory;
dwCategory++;
if (nLevel < 8) // Level 6-7
return dwCategory;
dwCategory++;
if (nLevel < 10) // Level 8-9
return dwCategory;
dwCategory++;
if (nLevel < 13) // Level 10-12
return dwCategory;
dwCategory++;
if (nLevel < 17) // Level 13-16
return dwCategory;
dwCategory++;
if (nLevel < 20) // Level 17-19
return dwCategory;
dwCategory++;
if (nLevel < 25) // Level 20-24
return dwCategory;
dwCategory++;
if (nLevel < 30) // Level 25-29
return dwCategory;
dwCategory++;
if (nLevel < 35) // Level 30-34
return dwCategory;
dwCategory++;
if (nLevel < 40) // Level 35-39
return dwCategory;
dwCategory++;
if (nLevel < 48) // Level 40-47
return dwCategory;
dwCategory++; // Level 48+
return dwCategory;
}
//### MM Diablo Patch #2 3/21/97 END
//===========================================================================
// These routines will translate to and from the game description string that storm uses.
//
// The game description format is as follows:
// "%d\r%s\r%s", bDiff, szPlayerName, szPlayerDesc
//
// NOTE: if this format changes, update CreateGameCriteria(), and enum _description_fields
//===========================================================================
BOOL Txt2GameData(char *szDesc, TGAMEDATA *pGameData, LPSTR *ppszPlayerName, LPSTR *ppszPlayerDesc) {
LPSTR p;
if (szDesc[0] == 0)
return 0;
pGameData->bDiff = atoi(szDesc);
if ((unsigned int)pGameData->bDiff >= NUM_DIFFICULTIES)
return 0;
// Find player name
p = strchr(szDesc, '\r');
if (p == NULL)
return 1;
*p++ = 0;
if (ppszPlayerName)
*ppszPlayerName = p;
// Find player description
p = strchr(p, '\r');
if (p == NULL)
return 1;
*p++ = 0;
if (ppszPlayerDesc)
*ppszPlayerDesc = p;
return 1;
}
//===========================================================================
void GameData2Txt(TGAMEDATA *pGameData, LPCSTR szPlayerName, LPCSTR szPlayerDesc, LPSTR szDesc, int nMaxLength) {
// fill string with game info text, including creator's player info if it will fit.
if (nMaxLength > (int)(strlen(szPlayerName)+strlen(szPlayerDesc)+5))
sprintf(szDesc, "%d\r%s\r%s", pGameData->bDiff, szPlayerName, szPlayerDesc);
else
itoa(pGameData->bDiff, szDesc, 10);
}
//===========================================================================
// These routines will translate to and from the hero description string that storm uses.
//
// The game description format is as follows:
// "%d %d %d %d %d %d %d", wLevel, bHeroClass, bStrength, bMagic, bDexterity, bVitality, dwGold
//
// NOTE: if this format changes, update CreatGameCriteria(), and enum _description_fields
//===========================================================================
BOOL Txt2HeroInfo(char *szDesc, TUIHEROINFO *pHeroInfo) {
int nLevel, nHeroClass, nHeroRank, nStrength, nMagic, nDexterity, nVitality, nGold, nSpawned;
DWORD *pdwProgramId = (DWORD *)szDesc;
// Reset everything, in case this routine fails
ZeroMemory(pHeroInfo, sizeof(TUIHEROINFO));
// Only parse the hero info string if it was built by a user game with a programid we recognize.
if (szDesc[0] == 0 || *pdwProgramId != PROGRAMID)
return 0;
// If item count changes, change if constant
if (9 != sscanf(&szDesc[4], "%d %d %d %d %d %d %d %d %d",
&nLevel,
&nHeroClass,
&nHeroRank,
&nStrength,
&nMagic,
&nDexterity,
&nVitality,
&nGold,
&nSpawned)) {
return 0;
}
// Validate some of the data
if (*pdwProgramId == PROGRAMID && nSpawned)
return 0;
//if ((*pdwProgramId == 'DSHR') && !nSpawned)
// return 0;
//### BUG FIX MM 2/3/97 Character Level 0 is invalid also.
if (nLevel == 0)
return 0;
//### END
if ((unsigned long)nLevel > 99)
return 0;
if ((unsigned long)nHeroClass >= UI_NUM_CLASSES)
return 0;
if ((unsigned long)nHeroRank > NUM_DIFFICULTIES)
return 0;
if (nStrength < 0 || nMagic < 0 || nDexterity < 0 || nVitality < 0)
return 0;
// Now write values into hero info struct
pHeroInfo->level = (WORD) nLevel;
pHeroInfo->heroclass = (BYTE) nHeroClass;
pHeroInfo->herorank = (BYTE) nHeroRank;
pHeroInfo->strength = (BYTE) nStrength;
pHeroInfo->magic = (BYTE) nMagic;
pHeroInfo->dexterity = (BYTE) nDexterity;
pHeroInfo->vitality = (BYTE) nVitality;
pHeroInfo->gold = (DWORD) nGold;
pHeroInfo->spawned = (BOOL) nSpawned;
return 1;
}
//===========================================================================
void HeroInfo2Txt(TUIHEROINFO *pHeroInfo, char *szDesc) {
DWORD *pdword;
// fill string with hero info text
pdword = (DWORD *)szDesc;
*pdword = sgMyProgramId;
sprintf(&szDesc[4], " %d %d %d %d %d %d %d %d %d",
pHeroInfo->level,
pHeroInfo->heroclass,
pHeroInfo->herorank,
pHeroInfo->strength,
pHeroInfo->magic,
pHeroInfo->dexterity,
pHeroInfo->vitality,
pHeroInfo->gold,
pHeroInfo->spawned);
}
//===========================================================================
// UiCreateGameCriteria()
//
// This routine directs the Battle.net server how to prioritize games in the join game list.
// If the game description string changes, this must change as well.
//===========================================================================
int APIENTRY UiCreateGameCriteria(TPUIHEROINFO pHeroInfo, char *szCriteria) {
return sprintf(szCriteria, "#%d?%d", eGAMEDESC_CREATOR_LEVEL, pHeroInfo->level);
}
//***************************************************************************
//***************************************************************************
BOOL APIENTRY UiCreatePlayerDescription(TPUIHEROINFO pHeroInfo, DWORD dwProgramId, LPSTR pPlayerDesc) {
sgMyProgramId = dwProgramId;
HeroInfo2Txt(pHeroInfo, pPlayerDesc);
return 1;
}