Files
Justin Marshall 101266ab72 Initial commit.
2026-04-27 10:35:40 -07:00

1453 lines
41 KiB
C++

/*-----------------------------------------------------------------------**
** Diablo
**
** Multi file
**
** (C)1995 Condor, Inc. All rights reserved.
**
**-----------------------------------------------------------------------**
** $Header: /Diablo/MULTI.CPP 9 4/01/97 5:09p Pwyatt $
**
** File Routines
**-----------------------------------------------------------------------*/
#include "diablo.h"
#pragma hdrstop
#include "sound.h"
#include "storm/h/storm.h"
#include "msg.h"
#include "multi.h"
#include "gendung.h"
#include "items.h"
#include "player.h"
#include "monster.h"
#include "spells.h"
#include "packplr.h"
#include "engine.h"
#include "diabloui.h"
#include "portal.h"
//******************************************************************
// extern
//******************************************************************
extern char gszHero[];
extern BOOL gbRunGame;
extern char gszVersionNumber[];
extern SNETVERSIONDATA gVersion;
extern int gnDifficulty;
void tmsg_init();
void tmsg_free();
void dthread_init();
void dthread_free();
void delta_init();
void dthread_remove_player(int pnum);
void SetupLocalPlayer();
void FixPlrWalkTags(int pnum);
void StartStand(int,int);
BOOL wait_delta_info();
void plrmsg_init();
void __cdecl sysmsg_add(const char * pszFmt,...);
void sysmsg_add_string(const char * pszMsg);
void game_2_ui_player(const PlayerStruct * p,TPUIHEROINFO heroinfo,BOOL bHasSaveFile);
void nthread_check_snet_error(const char * pszFcn);
DWORD tmsg_get(BYTE * pbMsg,DWORD dwMaxLen);
void tmsg_add(const BYTE * pbMsg,BYTE bLen);
void delta_close_portal(int pnum);
void NewPlrAnim(int pnum, BYTE *pAnim, int numFrames, int Delay, long width);
//******************************************************************
// public
//******************************************************************
BYTE gbMaxPlayers;
BYTE gbActivePlayers;
BYTE gbGameDestroyed;
BYTE gbSelectProvider;
BYTE gbDeltaSender;
BYTE gbSomebodyWonGameKludge;
char gszGameName[128];
char gszGamePass[128];
//******************************************************************
// private -- packets
//******************************************************************
// network initialized
static BOOL sgbNetInited = FALSE;
// did we send an async message in this messaging cycle?
static BOOL sgbSentThisCycle;
// number of sync messages/game loops -- will be synchronized
// across all systems as new players asychronously join the game
static DWORD sgdwSyncMsgs;
static DWORD sgdwGameLoops;
// buffers for player information which
// is too big to fit into one packet
// PATCH3.JMM
WORD sgwPackPlrOffsetTbl[MAX_PLRS];
// ENDPATCH3.JMM
static PkPlayerStruct sgPackPlr[MAX_PLRS];
static BYTE sgbPlayerLeftGameTbl[MAX_PLRS];
static DWORD sgdwPlayerLeftReasonTbl[MAX_PLRS];
static BYTE sgbSendDeltaTbl[MAX_PLRS];
static TGAMEDATA sgGameInitInfo;
// pjw.patch2.start
static BYTE sgbGameJoiner[MAX_PLRS];
// pjw.patch2.end
static BYTE sgbTimeout;
static long sglTimeoutStart;
// time to hang out in timeout state before dropping nonresponsive players
#define DROP_NOTRESPONDING_DELAY (10*1000) // milliseconds
// time to hang out in timeout state if we're not the master
#define LEAVE_GAME_DELAY (20*1000) // milliseconds
//******************************************************************
// private -- internal buffering
//******************************************************************
#define MAX_BUF_DATA 4096
typedef struct TBuffer {
DWORD dwNextWriteOffset;
BYTE bData[MAX_BUF_DATA];
} TBuffer;
static TBuffer sgLoPriBuf;
static TBuffer sgHiPriBuf;
//******************************************************************
//******************************************************************
#if TRACEOUT
static DWORD sgdwTraceStartTime;
void __cdecl TraceOut(const char * pszFmt, ...) {
static FILE * f = NULL;
if (! f) f = fopen("c:\\dumphist.txt","wb");
if (! f) return;
va_list args;
va_start(args,pszFmt);
DWORD dwCurrTime = GetTickCount() - sgdwTraceStartTime;
fprintf(f,"%4u.%02u ",dwCurrTime / 1000, (dwCurrTime % 1000) / 10);
vfprintf(f,pszFmt,args);
fprintf(
f,"\r\n %x%c %x%c %x%c %x%c\r\n",
gdwMsgStatTbl[0] >> 16,plr[0].plractive ? 'A' : ' ',
gdwMsgStatTbl[1] >> 16,plr[1].plractive ? 'A' : ' ',
gdwMsgStatTbl[2] >> 16,plr[2].plractive ? 'A' : ' ',
gdwMsgStatTbl[3] >> 16,plr[3].plractive ? 'A' : ' '
);
va_end(args);
fflush(f);
}
#endif
//******************************************************************
//******************************************************************
static void buffer_init(TBuffer * pBuf) {
pBuf->dwNextWriteOffset = 0;
pBuf->bData[0] = 0;
}
//******************************************************************
//******************************************************************
static BOOL buffer_empty(TBuffer * pBuf) {
return pBuf->dwNextWriteOffset == 0;
}
//******************************************************************
//******************************************************************
static void buffer_add(TBuffer * pBuf,const BYTE * pbMsg,BYTE bLen) {
// is there enough space in the buffer for length byte + data
// plus one more byte for terminating NULL?
if (pBuf->dwNextWriteOffset + bLen + 2 > MAX_BUF_DATA) {
#ifndef NDEBUG
app_fatal("msg buffer failure");
#endif
return;
}
// get ptr to curr location, and setup next location
BYTE * pbData = &pBuf->bData[pBuf->dwNextWriteOffset];
pBuf->dwNextWriteOffset += bLen + 1;
// write length, data, terminating NULL
*pbData++ = bLen;
CopyMemory(pbData,pbMsg,bLen);
pbData += bLen;
*pbData = 0;
}
//******************************************************************
//******************************************************************
static BYTE * buffer_get(TBuffer * pBuf,BYTE * pbMsg,DWORD * pdwMaxLen) {
// is there any data in the buffer?
if (! pBuf->dwNextWriteOffset)
return pbMsg;
BYTE * pbData = pBuf->bData;
while (1) {
// is there enough space to copy the data from the buffer
BYTE bLen = *pbData;
if (! bLen) break;
if (bLen > *pdwMaxLen) break;
// skip over the length byte, and copy the data
CopyMemory(pbMsg,++pbData,bLen);
pbData += bLen;
pbMsg += bLen;
*pdwMaxLen -= bLen;
}
// fixup buffer
MoveMemory(
pBuf->bData, // start of buffer
pbData, // curr position in buffer
pBuf->dwNextWriteOffset - (pbData - pBuf->bData) + 1
);
// fixup buffer write offset
pBuf->dwNextWriteOffset -= (pbData - pBuf->bData);
return pbMsg;
}
//******************************************************************
//******************************************************************
static void build_pkt_hdr(TPkt * pkt) {
app_assert(pkt);
pkt->hdr.wCheck = 0x6970;
pkt->hdr.px = plr[myplr]._px;
pkt->hdr.py = plr[myplr]._py;
pkt->hdr.targx = plr[myplr]._ptargx;
pkt->hdr.targy = plr[myplr]._ptargy;
pkt->hdr.php = plr[myplr]._pHitPoints;
pkt->hdr.pmhp = plr[myplr]._pMaxHP;
pkt->hdr.bstr = plr[myplr]._pBaseStr;
pkt->hdr.bmag = plr[myplr]._pBaseMag;
pkt->hdr.bdex = plr[myplr]._pBaseDex;
}
//******************************************************************
//******************************************************************
void NetSendMyselfPri(const BYTE * pbMsg,BYTE bLen) {
app_assert(sgbNetInited);
if (! pbMsg) return;
if (! bLen) return;
app_assert(bLen <= gdwNormalMsgSize - sizeof(TPktHdr));
tmsg_add(pbMsg,bLen);
}
//******************************************************************
//******************************************************************
static void NetSendLocal(const BYTE * pbMsg,BYTE bLen) {
app_assert(sgbNetInited);
app_assert(pbMsg);
app_assert(bLen);
app_assert(bLen <= gdwNormalMsgSize - sizeof(TPktHdr));
// build message
TPkt pkt;
build_pkt_hdr(&pkt);
pkt.hdr.wLen = sizeof(pkt.hdr) + bLen;
CopyMemory(pkt.body,pbMsg,bLen);
// send it
TRACE_FCN("SNetSendMessage0");
if (! SNetSendMessage(myplr,&pkt,pkt.hdr.wLen))
nthread_check_snet_error("SNetSendMessage0");
TRACE_FCN(NULL);
#ifndef NDEBUG
extern DWORD gdwAsyncSendTbl[MAX_PLRS];
gdwAsyncSendTbl[myplr]++;
#endif
}
//******************************************************************
//******************************************************************
void NetSendLoPri(const BYTE * pbMsg,BYTE bLen) {
app_assert(sgbNetInited);
if (! pbMsg) return;
if (! bLen) return;
app_assert(bLen <= gdwNormalMsgSize - sizeof(TPktHdr));
// message not sent out of deference to higher priority
// commands which might come later in this message cycle
buffer_add(&sgLoPriBuf,pbMsg,bLen);
NetSendLocal(pbMsg,bLen);
}
//******************************************************************
//******************************************************************
void NetSendHiPri(const BYTE * pbMsg,BYTE bLen) {
app_assert(sgbNetInited);
if (pbMsg && bLen) {
app_assert(bLen <= gdwNormalMsgSize - sizeof(TPktHdr));
buffer_add(&sgHiPriBuf,pbMsg,bLen);
NetSendLocal(pbMsg,bLen);
}
// if we have already sent a message this cycle, then
// we cannot send another until the next cycle.
if (sgbSentThisCycle)
return;
sgbSentThisCycle = TRUE;
app_assert((DWORD) myplr < MAX_PLRS);
// setup packet header
TPkt pkt;
build_pkt_hdr(&pkt);
// fill in packet body
BYTE * pbBody = pkt.body;
DWORD dwBytesLeft = gdwNormalMsgSize - sizeof(pkt.hdr);
pbBody = buffer_get(&sgHiPriBuf,pbBody,&dwBytesLeft);
pbBody = buffer_get(&sgLoPriBuf,pbBody,&dwBytesLeft);
dwBytesLeft = sync_get(pbBody,dwBytesLeft);
DWORD dwSendBytes = gdwNormalMsgSize - dwBytesLeft;
pkt.hdr.wLen = (WORD) dwSendBytes;
// send it
TRACE_FCN("SNetSendMessage");
if (! SNetSendMessage(SNET_BROADCASTNONLOCALPLAYERID,&pkt,dwSendBytes))
nthread_check_snet_error("SNetSendMessage");
TRACE_FCN(NULL);
#ifndef NDEBUG
extern DWORD gdwAsyncSendTbl[MAX_PLRS];
if (myplr != 0) gdwAsyncSendTbl[0]++;
if (myplr != 1) gdwAsyncSendTbl[1]++;
if (myplr != 2) gdwAsyncSendTbl[2]++;
if (myplr != 3) gdwAsyncSendTbl[3]++;
#endif
}
//******************************************************************
//******************************************************************
void NetSendMask(DWORD dwSendMask,const BYTE * pbMsg,BYTE bLen) {
app_assert(sgbNetInited);
app_assert(pbMsg);
app_assert(bLen);
// setup packet header
TPkt pkt;
build_pkt_hdr(&pkt);
// fill in packet body
DWORD dwSendBytes = sizeof(pkt.hdr) + bLen;
app_assert(dwSendBytes < gdwNormalMsgSize);
pkt.hdr.wLen = (WORD) dwSendBytes;
CopyMemory(pkt.body,pbMsg,bLen);
// send to each player listed in send mask
DWORD dwMaskFlag = 0x1;
for (DWORD dwID = 0; dwID < MAX_PLRS; dwID++,dwMaskFlag <<= 1) {
if (! (dwMaskFlag & dwSendMask)) continue;
TRACE_FCN("SNetSendMessage");
BOOL bSent = SNetSendMessage(dwID,&pkt,dwSendBytes);
TRACE_FCN(NULL);
if (bSent) {
#ifndef NDEBUG
extern DWORD gdwAsyncSendTbl[MAX_PLRS];
gdwAsyncSendTbl[dwID]++;
#endif
continue;
}
// we don't keep track of players, so we may have sent a
// message to somebody who isn't there anymore.
if (GetLastError() == SNET_ERROR_INVALID_PLAYER) continue;
nthread_check_snet_error("SNetSendMessage");
break;
}
}
//******************************************************************
//******************************************************************
static void NetResync() {
// resync all monster seeds based on current value of game loops
sgdwGameLoops++;
// reinitialize monster AI seed so that
// all systems can try to resynchronize
DWORD dwSeed = _rotr(sgdwGameLoops,8);
for (int i = 0; i < MAXMONSTERS; i++)
monster[i]._mAISeed = i + dwSeed;
#if 0
if (! lpDDSPrimary) return;
if (sgdwGameLoops % 10) return;
#define ACCUM_ENTRIES 4
static DWORD sgdwTime[ACCUM_ENTRIES];
static DWORD sgdwCount[ACCUM_ENTRIES];
MoveMemory(&sgdwTime[0],&sgdwTime[1],sizeof(DWORD) * (ACCUM_ENTRIES-1));
MoveMemory(&sgdwCount[0],&sgdwCount[1],sizeof(DWORD) * (ACCUM_ENTRIES-1));
sgdwTime[ACCUM_ENTRIES - 1] = GetTickCount();
sgdwCount[ACCUM_ENTRIES - 1] = sgdwGameLoops;
DWORD dwRate = sgdwTime[ACCUM_ENTRIES - 1] - sgdwTime[0];
if (! dwRate) return;
dwRate = (sgdwCount[ACCUM_ENTRIES - 1] - sgdwCount[0]) * 1000 / dwRate;
HDC hDC;
char szRate[16];
sprintf(szRate,"%d",dwRate);
HRESULT ddr = lpDDSPrimary->GetDC(&hDC);
if (ddr != DD_OK) return;
SetTextColor(hDC,0x00ff0000);
TextOut(hDC,0,420,szRate,strlen(szRate));
lpDDSPrimary->ReleaseDC(hDC);
#endif
}
//******************************************************************
//******************************************************************
static void check_DeltaSendAllLevels(int nReceiver) {
// the lowest numbered active player is
// responsible for sending the level delta
// information to the new player
for (int nSender = 0; nSender < MAX_PLRS; nSender++) {
// inactive non-player cannot send info
// pjw.patch2.start
if (! (gdwMsgStatTbl[nSender] & SNET_PSF_ACTIVE)) continue;
// pjw.patch2.end
// player requesting info cannot send info
if (nSender == nReceiver) continue;
// we found a player who is responsible
break;
}
if (myplr == nSender)
sgbSendDeltaTbl[nReceiver] = TRUE;
else if (myplr == nReceiver)
gbDeltaSender = (BYTE) nSender;
}
//******************************************************************
//******************************************************************
static void check_sync(int pnum,DWORD dwSyncMsgs) {
if (dwSyncMsgs & TURN_REQUEST_DELTA_FLAG) {
// player has just joined and needs delta information
check_DeltaSendAllLevels(pnum);
}
// remove flag bits
dwSyncMsgs &= TURN_COUNTER_MASK;
// if the other player has sent more sync msgs than we have,
// then recompute the number of game loops which have occurred
// using his msg count to keep both systems synchronized
if (sgdwSyncMsgs < dwSyncMsgs + gdwTurnsInTransit) {
if (dwSyncMsgs >= TURN_COUNTER_MASK)
dwSyncMsgs &= TURN_COUNTER_RESET_MASK;
sgdwSyncMsgs = dwSyncMsgs + gdwTurnsInTransit;
dwSyncMsgs *= ASYNC_CYCLES_PER_SYNC;
dwSyncMsgs *= gbGameLoopsPerPacket;
sgdwGameLoops = dwSyncMsgs;
}
}
//******************************************************************
//******************************************************************
// pjw.patch2.start
void process_turn() {
for (int i = 0; i < MAX_PLRS; i++) {
if (gdwMsgStatTbl[i] & SNET_PSF_TURNAVAILABLE) {
// make sure we got a valid message
app_assert(glpMsgTbl[i]);
// odds are we will eventually get a bad packet
if (gdwMsgLenTbl[i] != sizeof(DWORD)) continue;
// process turn
check_sync(i,*(DWORD *)glpMsgTbl[i]);
}
}
}
// pjw.patch2.end
//******************************************************************
//******************************************************************
static void remove_active_player(int i,BOOL bMsg) {
if (! plr[i].plractive) return;
#if TRACEOUT
TraceOut("(%d) player %d --> inactive",myplr,i);
#endif
// remove player from map
void RemovePlrFromMap(int pnum);
RemovePlrFromMap(i);
RemovePortalMissile(i);
DeactivatePortal(i);
delta_close_portal(i);
void RemovePlrMissiles(int pnum);
RemovePlrMissiles(i);
if (bMsg) {
const char * pszMsg_s = "Player '%s' just left the game";
switch (sgdwPlayerLeftReasonTbl[i]) {
case SNET_EXIT_NOTRESPONDING:
pszMsg_s = "Player '%s' dropped due to timeout";
break;
case SNET_EXIT_PLAYERWON:
pszMsg_s = "Player '%s' killed Diablo and left the game!";
gbSomebodyWonGameKludge = TRUE;
break;
}
sysmsg_add(pszMsg_s,plr[i]._pName);
}
plr[i].plractive = 0;
plr[i]._pName[0] = 0;
gbActivePlayers--;
}
//******************************************************************
//******************************************************************
static void update_players() {
for (int i = 0; i < MAX_PLRS; i++) {
// did the player leave the game
if (! sgbPlayerLeftGameTbl[i]) continue;
if (gbBufferMsgs == BUFFER_ON) {
#if TRACEOUT
TraceOut("(%d) buffering -- player %d left game due to 0x%x",myplr,i,sgdwPlayerLeftReasonTbl[i]);
#endif
void buffer_drop_player(int pnum,DWORD dwReason);
buffer_drop_player(i,sgdwPlayerLeftReasonTbl[i]);
}
else {
#if TRACEOUT
TraceOut("(%d) player %d left game due to 0x%x",myplr,i,sgdwPlayerLeftReasonTbl[i]);
#endif
remove_active_player(i,TRUE);
}
sgbPlayerLeftGameTbl[i] = FALSE;
sgdwPlayerLeftReasonTbl[i] = 0;
}
}
//******************************************************************
//******************************************************************
void unbuffer_remove_player(int pnum,DWORD dwReason) {
sgbPlayerLeftGameTbl[pnum] = TRUE;
sgdwPlayerLeftReasonTbl[pnum] = dwReason;
update_players();
}
//******************************************************************
//******************************************************************
void NetStartTimeout() {
sgbTimeout = TRUE;
sglTimeoutStart = (long) GetTickCount();
}
//******************************************************************
//******************************************************************
// pjw.patch2.start
static void drop_players() {
// kill other players
for (int i = 0; i < MAX_PLRS; i++) {
if (gdwMsgStatTbl[i] & SNET_PSF_RESPONDING) continue;
if (! (gdwMsgStatTbl[i] & SNET_PSF_ACTIVE)) continue;
#if TRACEOUT
TraceOut("(%d) dropping player %d state 0x%x",myplr,i,gdwMsgStatTbl[i]);
#endif
// snet drop player will perform a callback to
// our event function to indicate that the player "left"
TRACE_FCN("SNetDropPlayer");
SNetDropPlayer(i,SNET_EXIT_NOTRESPONDING);
TRACE_FCN(NULL);
}
}
// pjw.patch2.end
//******************************************************************
//******************************************************************
// pjw.patch2.start
static void drop_inactive_players() {
// if we're not in a timeout state, don't drop players
if (! sgbTimeout) return;
// debug mode
#if CHEATS
extern BOOL gbNoDropInactive;
if (gbNoDropInactive) return;
#endif
// check delay since timeout started
long lCurrTime = (long) GetTickCount();
lCurrTime -= sglTimeoutStart;
// if we've been hanging out forever, just exit game
if (lCurrTime > LEAVE_GAME_DELAY) {
gbRunGame = FALSE;
return;
}
// time to kill other players?
if (lCurrTime < DROP_NOTRESPONDING_DELAY) return;
// if there are only two players in the game, then the high
// player bails out, and the low player keeps playing. If there
// are three or more players in the game, then the group that has
// the most players keeps playing and the other player(s) bail.
int nLowestActive = -1;
int nLowestPlayer = -1;
BYTE bGroupPlayers = 0;
BYTE bNonGroupPlayers = 0;
for (int i = 0; i < MAX_PLRS; i++) {
if (! (gdwMsgStatTbl[i] & SNET_PSF_ACTIVE)) continue;
if (nLowestPlayer == -1) nLowestPlayer = i;
// get player state
if (gdwMsgStatTbl[i] & SNET_PSF_RESPONDING) {
// one more player in our group
bGroupPlayers++;
if (nLowestActive == -1) nLowestActive = i;
}
else {
// one more player not in our group
bNonGroupPlayers++;
}
}
app_assert(bGroupPlayers);
app_assert(nLowestActive != -1);
app_assert(nLowestPlayer != -1);
#if 0
TraceOut(
"(%d) grp:%d ngrp:%d lowp:%d lowa:%d",
myplr,
bGroupPlayers,
bNonGroupPlayers,
nLowestPlayer,
nLowestActive
);
#endif
if (bGroupPlayers < bNonGroupPlayers) {
// we're not part of the big group, give up
gbGameDestroyed = TRUE;
}
else if (bGroupPlayers == bNonGroupPlayers) {
if (nLowestPlayer != nLowestActive)
gbGameDestroyed = TRUE;
else if (nLowestActive == myplr)
drop_players();
}
else if (nLowestActive == myplr) {
drop_players();
}
}
// pjw.patch2.end
//******************************************************************
//******************************************************************
BOOL NetEndSendCycle() {
app_assert(sgbNetInited);
if (gbGameDestroyed) {
gbRunGame = FALSE;
return FALSE;
}
// send delta information to other players
for (int i = 0; i < MAX_PLRS; i++) {
// are we supposed to send somebody delta information?
if (! sgbSendDeltaTbl[i]) continue;
sgbSendDeltaTbl[i] = FALSE;
DeltaSendAllLevels(i);
}
// fill up the outgoing message queue
// send the number of sync messages we think have been
// sent to other players (and increment by 1 each msg)
BOOL bSendAsync;
sgdwSyncMsgs = nthread_fill_sync_queue(sgdwSyncMsgs,1);
if (! nthread_msg_check(&bSendAsync)) {
drop_inactive_players();
return FALSE;
}
// we got some data -- reset timeout mode
sgbTimeout = FALSE;
if (! bSendAsync) {
// it's not time to send an async message yet
}
else if (! sgbSentThisCycle) {
// we haven't sent any async messages this cycle so
// send a blank message and reset for next cycle
NetSendHiPri(NULL,0);
sgbSentThisCycle = FALSE;
}
else {
// we already sent an async message this cycle
// if there is any high priority stuff
// still in the queue, send the next cycle's
// message immediately
sgbSentThisCycle = FALSE;
if (! buffer_empty(&sgHiPriBuf))
NetSendHiPri(NULL,0);
}
// update the monster random number seeds
NetResync();
return TRUE;
}
//******************************************************************
//******************************************************************
static void process_msg_body(DWORD dwID,const BYTE * pbCmd,DWORD dwBytes) {
while (dwBytes) {
DWORD dwCmdLen = ParseCmd(dwID,(const TCmd *) pbCmd);
// patch3.jmm
// zero is an error condition:
// skip the rest of the packet
if( dwCmdLen == 0 ) {
return;
}
// endpatch3.jmm
pbCmd += dwCmdLen;
dwBytes -= dwCmdLen;
}
}
//******************************************************************
//******************************************************************
static void handle_local_msgs() {
DWORD dwLen;
BYTE msg[MAX_MSG_SIZE];
while (0 != (dwLen = tmsg_get(msg,sizeof(msg))))
process_msg_body(myplr,msg,dwLen);
}
//******************************************************************
//******************************************************************
// patch1.jmm
extern DWORD dwRecCount;
// endpatch1.jmm
void NetReceivePackets() {
DWORD dwID;
LPVOID lpMsg;
DWORD dwMsgSize;
// make sure all players are dropped ASAP
update_players();
handle_local_msgs();
app_assert(sgbNetInited);
TRACE_FCN("SNetReceiveMessage");
while (SNetReceiveMessage(&dwID,&lpMsg,&dwMsgSize)) {
TRACE_FCN(NULL);
// patch1.jmm
dwRecCount++;
// endpatch1.jmm
// do not dispatch any messages until
// we've dropped players who left the game
update_players();
// odds are we will eventually get a bad packet so ignore bad ones
const TPkt * pkt = (const TPkt *) lpMsg;
if (dwMsgSize < sizeof(TPktHdr)) continue;
if (dwID >= MAX_PLRS) continue;
if (pkt->hdr.wCheck != 0x6970) continue;
if (pkt->hdr.wLen != dwMsgSize) continue;
plr[dwID]._pownerx = pkt->hdr.px;
plr[dwID]._pownery = pkt->hdr.py;
#ifndef NDEBUG
extern DWORD gdwAsyncRecvTbl[MAX_PLRS];
gdwAsyncRecvTbl[dwID]++;
#endif
// the header contains the last known location of the
// player at the time the message was sent. Try to get
// the player to walk to his correct location. Any other
// commands in the message will be executed later and so
// can overwrite the results of this MakePlrPath.
if (dwID != (DWORD) myplr) {
app_assert(gbBufferMsgs != BUFFER_PROCESS);
plr[dwID]._pHitPoints = pkt->hdr.php;
plr[dwID]._pMaxHP = pkt->hdr.pmhp;
plr[dwID]._pBaseStr = pkt->hdr.bstr;
plr[dwID]._pBaseMag = pkt->hdr.bmag;
plr[dwID]._pBaseDex = pkt->hdr.bdex;
if (gbBufferMsgs == BUFFER_ON) {
// do nothing
}
else if (! plr[dwID].plractive) {
// do nothing
}
else if (plr[dwID]._pHitPoints == 0) {
// do nothing
}
else if ((currlevel == plr[dwID].plrlevel) && (!plr[dwID]._pLvlChanging)) {
int dx = abs(plr[dwID]._px - pkt->hdr.px);
int dy = abs(plr[dwID]._py - pkt->hdr.py);
// Is player in "ok delta" dist of where he should be?
if (((dx > 3) || (dy > 3)) && (!dPlayer[pkt->hdr.px][pkt->hdr.py])) {
// Warp player to location
FixPlrWalkTags(dwID);
plr[dwID]._poldx = plr[dwID]._px;
plr[dwID]._poldy = plr[dwID]._py;
FixPlrWalkTags(dwID);
plr[dwID]._px = pkt->hdr.px;
plr[dwID]._py = pkt->hdr.py;
plr[dwID]._pfutx = pkt->hdr.px;
plr[dwID]._pfuty = pkt->hdr.py;
dPlayer[plr[dwID]._px][plr[dwID]._py] = 1 + (char)dwID;
}
dx = abs(plr[dwID]._pfutx - plr[dwID]._px);
dy = abs(plr[dwID]._pfuty - plr[dwID]._py);
if ((dx > 1) || (dy > 1)) {
plr[dwID]._pfutx = plr[dwID]._px;
plr[dwID]._pfuty = plr[dwID]._py;
}
MakePlrPath(dwID, pkt->hdr.targx, pkt->hdr.targy, TRUE);
// do not override destAction
}
else {
plr[dwID]._px = pkt->hdr.px;
plr[dwID]._py = pkt->hdr.py;
plr[dwID]._pfutx = pkt->hdr.px;
plr[dwID]._pfuty = pkt->hdr.py;
plr[dwID]._ptargx = pkt->hdr.targx;
plr[dwID]._ptargy = pkt->hdr.targy;
// do not override destAction
}
}
process_msg_body(dwID,pkt->body,dwMsgSize - sizeof(TPktHdr));
}
TRACE_FCN(NULL);
// if (GetLastError() != SNET_ERROR_NO_MESSAGES_WAITING)
// nthread_check_snet_error("SNetReceiveMsg");
}
//******************************************************************
// NOTE: this routine is called from multiple threads
// don't do anything that would be thread unsafe
//******************************************************************
void SendPlayerInfoChunk(int pnum,BYTE bCmd,const BYTE * pbSrc,DWORD dwLen) {
app_assert(pnum != myplr);
// write info piece by piece
app_assert(pbSrc);
app_assert(dwLen <= 0x0ffff);
DWORD dwOffset = 0;
while (dwLen) {
// setup packet header
TPkt pkt;
pkt.hdr.wCheck = 0x6970;
pkt.hdr.px = 0;
pkt.hdr.py = 0;
pkt.hdr.targx = 0;
pkt.hdr.targy = 0;
pkt.hdr.php = 0;
pkt.hdr.pmhp = 0;
pkt.hdr.bstr = 0;
pkt.hdr.bmag = 0;
pkt.hdr.bdex = 0;
// setup cmd header
TCmdPlrInfoHdr * p = (TCmdPlrInfoHdr *) &pkt.body[0];
p->bCmd = bCmd;
p->wOffset = (WORD) dwOffset;
// calculate how much data we can send
DWORD dwBody = gdwLargestMsgSize - sizeof(pkt.hdr) - sizeof(TCmdPlrInfoHdr);
dwBody = min(dwLen,dwBody);
app_assert(dwBody <= 0x0ffff);
p->wBytes = (WORD) dwBody;
// copy cmd data
CopyMemory(&pkt.body[sizeof TCmdPlrInfoHdr],pbSrc,p->wBytes);
// send message to target
DWORD dwSendBytes = sizeof(pkt.hdr);
dwSendBytes += sizeof(TCmdPlrInfoHdr);
dwSendBytes += p->wBytes;
pkt.hdr.wLen = (WORD) dwSendBytes;
TRACE_FCN("SnetSendMessage");
if (! SNetSendMessage(pnum,&pkt,dwSendBytes)) {
TRACE_FCN(NULL);
nthread_check_snet_error("SNetSendMessage2");
break;
}
TRACE_FCN(NULL);
#ifndef NDEBUG
extern DWORD gdwAsyncSendTbl[MAX_PLRS];
if ((DWORD) pnum < MAX_PLRS) {
gdwAsyncSendTbl[pnum]++;
}
else {
if (myplr != 0) gdwAsyncSendTbl[0]++;
if (myplr != 1) gdwAsyncSendTbl[1]++;
if (myplr != 2) gdwAsyncSendTbl[2]++;
if (myplr != 3) gdwAsyncSendTbl[3]++;
}
#endif
// next data section
pbSrc += p->wBytes;
dwLen -= p->wBytes;
dwOffset += p->wBytes;
}
}
//******************************************************************
//******************************************************************
// pjw.patch2.start -- for patch, changed SendPlayerInfoChunk to
// dthread_SendPlayerInfoChunk to more closely attempt to match the
// available bandwidth. A player with a slow modem joining
// a game with 3 players would have to broadcast ~3k of playerdata,
// clogging the modem and preventing turns from getting out.
void dthread_SendPlayerInfoChunk(int pnum,BYTE bCmd,const BYTE * pbSrc,DWORD dwLen);
static void SendLocalPlayerInfo(int pnum,BYTE bCmd) {
// get local player information and send it to the specified net addr
PkPlayerStruct pack;
PackPlayer(&pack,myplr);
dthread_SendPlayerInfoChunk(pnum,bCmd,(const BYTE *) &pack,sizeof(pack));
}
// pjw.patch2.end
//******************************************************************
//******************************************************************
static int InitLevelType(int l) {
if (l == 0) return(0);
if ((l >= 1) && (l <= 4)) return(1);
if ((l >= 5) && (l <= 8)) return(2);
if ((l >= 9) && (l <= 12)) return(3);
//return(4); JKE 7/29
if ((l >=13) && (l <= 16)) return(4); // Original level types JKE
if ((l >= CRYPTSTART) && (l <= CRYPTEND)) return(1); // change this to crypt type later JKE 7/29
if ((l >= HIVESTART) && (l <= HIVEEND)) return(3); // use this for hives JKE
return(1);
}
//******************************************************************
//******************************************************************
static void SetupLocalCoords() {
// set the current level to start at the town
if (! leveldebug || gbMaxPlayers > 1) {
currlevel = 0;
leveltype = 0;
setlevel = FALSE;
}
// put character near house on town level
int x = STARTX;
int y = STARTY;
#if CHEATS
if (cheatflag || davedebug) {
//x = 25; // Near church
//y = 31;
x = 49; // Near maus
y = 23;
}
#endif
// adjust character position for character number
// so everyone doesn't start on the same location
extern int plrxoff[9];
extern int plryoff[9];
x += plrxoff[myplr];
y += plryoff[myplr];
plr[myplr]._px = x;
plr[myplr]._py = y;
plr[myplr]._pfutx = x;
plr[myplr]._pfuty = y;
plr[myplr]._ptargx = x;
plr[myplr]._ptargy = y;
plr[myplr].plrlevel = currlevel;
plr[myplr]._pLvlChanging = TRUE;
plr[myplr].pLvlLoad = 0;
plr[myplr]._pmode = PM_NEWLVL;
plr[myplr].destAction = PCMD_NOTHING;
}
//******************************************************************
//******************************************************************
static BOOL handle_upgrade(BOOL * pfExitProgram) {
DWORD dwStatus;
SNetPerformUpgrade(&dwStatus);
switch (dwStatus) {
case SNET_UPGRADE_FAILED:
app_warning("Network upgrade failed");
break;
case SNET_UPGRADE_NOT_NEEDED:
// continue game
return TRUE;
case SNET_UPGRADE_SUCCEEDED:
// continue game
return TRUE;
case SNET_UPGRADING_TERMINATE:
*pfExitProgram = TRUE;
break;
}
return FALSE;
}
//******************************************************************
//******************************************************************
static void CALLBACK net_callback(SNETEVENTPTR pEvt) {
DWORD dwReason;
TGAMEDATA * pGameData;
// NOTE: this function must be thread-safe since
// it can be called from either the main thread, or during
// the progress screen, called from nthread_*()
switch (pEvt->eventid) {
case SNET_EVENT_INITDATA:
app_assert(pEvt->data);
app_assert(pEvt->databytes >= sizeof(DWORD));
pGameData = (TGAMEDATA *) pEvt->data;
sgGameInitInfo.dwSeed = pGameData->dwSeed;
sgGameInitInfo.bDiff = pGameData->bDiff;
// if we get a callback containing init data
// then we cannot be the one who started the game
// pjw.patch2.start
sgbGameJoiner[pEvt->playerid] = TRUE;
//pjw.patch2.end
break;
case SNET_EVENT_PLAYERLEAVE:
app_assert(pEvt->playerid >= 0 && pEvt->playerid < MAX_PLRS);
sgbPlayerLeftGameTbl[pEvt->playerid] = TRUE;
sgbGameJoiner[pEvt->playerid] = FALSE;
dwReason = 0;
if (pEvt->data && pEvt->databytes >= sizeof(DWORD))
dwReason = * (DWORD *) pEvt->data;
sgdwPlayerLeftReasonTbl[pEvt->playerid] = dwReason;
if (dwReason == SNET_EXIT_PLAYERWON)
gbSomebodyWonGameKludge = TRUE;
// protected by critical section -- OK
sgbSendDeltaTbl[pEvt->playerid] = 0;
dthread_remove_player(pEvt->playerid);
// was this the guy who was supposed to send us delta info?
if (gbDeltaSender == pEvt->playerid)
gbDeltaSender = MAX_PLRS;
#if TRACEOUT
TraceOut("(%d) callback: player %d left game due to 0x%x",myplr,pEvt->playerid,dwReason);
#endif
break;
case SNET_EVENT_SERVERMESSAGE:
app_assert(pEvt->data);
sysmsg_add_string((const char *) pEvt->data);
break;
}
}
//******************************************************************
//******************************************************************
static void RegisterEventHandler(BOOL bRegister) {
static const DWORD sdwEventTbl[] = {
SNET_EVENT_PLAYERLEAVE,
SNET_EVENT_INITDATA,
SNET_EVENT_SERVERMESSAGE,
};
BOOL (__stdcall * fnReg)(DWORD,SNETEVENTPROC) =
bRegister ? SNetRegisterEventHandler : SNetUnregisterEventHandler;
for (int i = 0; i < sizeof(sdwEventTbl) / sizeof(sdwEventTbl[0]); i++) {
if (! fnReg(sdwEventTbl[i],net_callback) && bRegister)
app_fatal("SNetRegisterEventHandler:\n%s",strGetLastError());
}
}
//******************************************************************
//******************************************************************
void NetClose() {
if (sgbNetInited) {
sgbNetInited = FALSE;
nthread_free();
dthread_free();
tmsg_free();
RegisterEventHandler(FALSE);
TRACE_FCN("SNetLeaveGame");
SNetLeaveGame(SNET_EXIT_AUTO_SHUTDOWN);
TRACE_FCN(NULL);
#if TRACEOUT
TraceOut("(%d) NetClose",myplr);
#endif
// give storm enough time to send leave pkt
if (gbMaxPlayers > 1) Sleep(2000);
}
}
//******************************************************************
//******************************************************************
// pjw.patch2.start
BOOL NetInit(BOOL bSinglePlayer,BOOL * pfExitProgram) {
top_of_routine:
app_assert(pfExitProgram);
*pfExitProgram = FALSE;
// get player description
char szPlayerDescript[UI_DESC_MAXLENGTH];
ZeroMemory(szPlayerDescript,sizeof szPlayerDescript);
if (! bSinglePlayer) {
TUIHEROINFO heroinfo;
myplr = 0;
SetupLocalPlayer();
game_2_ui_player(&plr[0],&heroinfo,gbValidSaveFile);
if (! UiCreatePlayerDescription(&heroinfo,PROGRAMID,szPlayerDescript))
return FALSE;
}
// initialize random seed for game
SetRndSeed(0);
sgGameInitInfo.dwSeed = (DWORD) time(NULL);
sgGameInitInfo.bDiff = (BYTE) gnDifficulty;
// BUILD A PROGRAM DATA RECORD
SNETPROGRAMDATA progdata;
ZeroMemory(&progdata,sizeof(progdata));
progdata.size = sizeof(progdata);
#if IS_VERSION(RETAIL)
//progdata.programname = TEXT("Diablo Retail");
progdata.programname = TEXT("Hellfire Retail");
#elif IS_VERSION(BETA)
progdata.programname = TEXT("Diablo");
#elif IS_VERSION(SHAREWARE)
progdata.programname = TEXT("Diablo Shareware");
#else
#error VERSION NOT DEFINED
#endif
progdata.programdescription = gszVersionNumber;
progdata.programid = PROGRAMID;
progdata.versionid = VERSIONID;
progdata.maxplayers = MAX_PLRS;
progdata.initdata = &sgGameInitInfo.dwSeed;
progdata.initdatabytes = sizeof(sgGameInitInfo);
progdata.optcategorybits = 0x0f;
// BUILD A PLAYER DATA RECORD
SNETPLAYERDATA plrdata;
ZeroMemory(&plrdata,sizeof(plrdata));
plrdata.size = sizeof(plrdata);
plrdata.playername = gszHero;
plrdata.playerdescription = szPlayerDescript;
// BUILD AN INTERFACE DATA RECORD
SNETUIDATA uidata;
ZeroMemory(&uidata,sizeof(SNETUIDATA));
uidata.size = sizeof(SNETUIDATA);
uidata.parentwindow = SDrawGetFrameWindow();
uidata.artcallback = UiArtCallback;
uidata.createcallback = UiCreateGameCallback;
uidata.drawdesccallback = UiDrawDescCallback;
uidata.messageboxcallback = UiMessageBoxCallback;
uidata.soundcallback = UiSoundCallback;
uidata.authcallback = UiAuthCallback;
uidata.getdatacallback = UiGetDataCallback;
uidata.categorycallback = UiCategoryCallback;
// pjw.patch2.start
ZeroMemory(sgbGameJoiner,sizeof(sgbGameJoiner));
// pjw.patch2.end
gbGameDestroyed = FALSE;
ZeroMemory(sgbPlayerLeftGameTbl,sizeof(sgbPlayerLeftGameTbl));
ZeroMemory(sgdwPlayerLeftReasonTbl,sizeof(sgdwPlayerLeftReasonTbl));
ZeroMemory(sgbSendDeltaTbl,sizeof(sgbSendDeltaTbl));
// pjw.patch1.start
ZeroMemory(plr,sizeof(PlayerStruct) * MAX_PLRS);
// pjw.patch1.end
ZeroMemory(sgwPackPlrOffsetTbl,sizeof(sgwPackPlrOffsetTbl));
SNetSetBasePlayer(0);
if (bSinglePlayer) {
if (! SNetInitializeProvider(NULL,&progdata,&plrdata,&uidata,&gVersion))
app_fatal("SNetInitializeProvider:\n%s",strGetLastError());
DWORD dwID = 0;
if (! SNetCreateGame (
"local", // gamename
"local", // password
"local", // gamedescription
0, // game category bits
&sgGameInitInfo, // initdata
sizeof(sgGameInitInfo), // initdatabytes
1, // maxplayers
"local", // playername
"local", // playerdescription
&dwID // playerid
)) app_fatal("SNetCreateGame1:\n%s",strGetLastError());
app_assert(dwID == 0);
myplr = 0;
gbMaxPlayers = 1;
}
else {
if (gbSelectProvider) {
BOOL bTryAgain = TRUE;
while (1) {
DWORD dwProviderID;
if (UiSelectProvider(NULL,&progdata,&plrdata,&uidata,&gVersion,&dwProviderID))
break;
if (! bTryAgain)
return FALSE;
if (GetLastError() != SNET_ERROR_REQUIRES_UPGRADE)
return FALSE;
if (! handle_upgrade(pfExitProgram))
return FALSE;
bTryAgain = FALSE;
}
}
RegisterEventHandler(TRUE);
DWORD dwID;
if (! UiSelectGame(SNET_SF_ALLOWCREATE,&progdata,&plrdata,&uidata,&gVersion,&dwID)) {
app_assert(! *pfExitProgram);
return FALSE;
}
// this happened once, so handle it...
if (dwID >= MAX_PLRS) return FALSE;
myplr = dwID;
gbMaxPlayers = MAX_PLRS;
}
#if TRACEOUT
sgdwTraceStartTime = GetTickCount();
TraceOut("(%d) new game started",myplr);
#endif
// do various network initializations
sgbNetInited = TRUE;
sgbTimeout = FALSE;
delta_init();
plrmsg_init();
buffer_init(&sgHiPriBuf);
buffer_init(&sgLoPriBuf);
sgbSentThisCycle = FALSE;
sync_init();
// pjw.patch2.start
nthread_init(sgbGameJoiner[myplr]);
// pjw.patch2.end
dthread_init();
tmsg_init();
sgdwGameLoops = 0;
sgdwSyncMsgs = 0;
gbDeltaSender = (BYTE) myplr;
gbSomebodyWonGameKludge = FALSE;
// request everyone's player info
SetupLocalPlayer();
// pjw.patch2.start -- added this code so that the first thing
// out of the modem is the turn, not tons of playerdata. If we send
// only playerdata, the modem queue gets filled up, and we don't respond
// fast enough to other players - sometimes causing a timeout
nthread_fill_sync_queue(0,0);
// pjw.patch2.end
// broadcast our player info to everyone
SetupLocalCoords();
SendLocalPlayerInfo(SNET_BROADCASTNONLOCALPLAYERID,CMD_SEND_PLRINFO);
// i'm always active on my system
plr[myplr].plractive = 1;
gbActivePlayers = 1;
// if we started the game, then we can proceed immediately.
// if we joined the game, we must wait until we receive
// all the level delta information before proceeding
// pjw.patch2.start
if (sgbGameJoiner[myplr] && !wait_delta_info()) {
NetClose();
gbSelectProvider = FALSE;
goto top_of_routine; // sorry :(
}
// pjw.patch2.end
// initialize map random number seeds
gnDifficulty = sgGameInitInfo.bDiff;
SetRndSeed(sgGameInitInfo.dwSeed);
for (int i = 0; i < NUMLEVELS; i++) {
glSeedTbl[i] = GetRndSeed();
gnLevelTypeTbl[i] = InitLevelType(i);
}
DWORD dwTemp;
if (! SNetGetGameInfo(SNET_INFO_GAMENAME,gszGameName,SNET_MAXNAMELENGTH,&dwTemp))
nthread_check_snet_error("SNetGetGameInfo1");
if (! SNetGetGameInfo(SNET_INFO_GAMEPASSWORD,gszGamePass,SNET_MAXNAMELENGTH,&dwTemp))
nthread_check_snet_error("SNetGetGameInfo2");
return TRUE;
}
// pjw.patch2.end
//******************************************************************
//******************************************************************
void recv_plrinfo(int pnum,const TCmdPlrInfoHdr * p,BOOL bAck) {
// local player is always properly set up
// and doesn't need to be unpacked
if (myplr == pnum)
return;
app_assert((DWORD)pnum < MAX_PLRS);
// check for out of order packet
if (sgwPackPlrOffsetTbl[pnum] != p->wOffset) {
sgwPackPlrOffsetTbl[pnum] = 0;
if (p->wOffset != 0) return;
}
// if this packet came to us unrequested
// and we haven't responded before,
// respond by sending back our plrinfo
if (! bAck && ! sgwPackPlrOffsetTbl[pnum])
SendLocalPlayerInfo(pnum,CMD_ACK_PLRINFO);
// copy the message information into a global structure
// until we have received the entire packed player
CopyMemory(
((BYTE *)&sgPackPlr[pnum]) + p->wOffset,
((BYTE *)p) + sizeof(TCmdPlrInfoHdr),
p->wBytes
);
sgwPackPlrOffsetTbl[pnum] += p->wBytes;
// did we get the entire pack structure yet?
if (sgwPackPlrOffsetTbl[pnum] != sizeof(PkPlayerStruct))
return;
sgwPackPlrOffsetTbl[pnum] = 0;
remove_active_player(pnum,FALSE);
plr[pnum]._pGFXLoad = 0;
UnPackPlayer(&sgPackPlr[pnum],pnum, TRUE);
// if this packet was an acknowledgement to our request
// for character information, then this character must
// already be in the game, so activate him
// Otherwise, this packet was being broadcast by a newbie
// joining the game. Wait until he sends a joinlevel command
// before activating his character
if (bAck) {
plr[pnum].plractive = 1;
gbActivePlayers++;
// pjw.patch2.start
sysmsg_add(
sgbGameJoiner[pnum] ?
"Player '%s' (level %d) just joined the game" :
"Player '%s' (level %d) is already in the game",
plr[pnum]._pName,plr[pnum]._pLevel
);
// pjw.patch2.end
LoadPlrGFX(pnum,PGL_STAND);
SyncInitPlr(pnum);
if (plr[pnum].plrlevel == currlevel) {
if ((plr[pnum]._pHitPoints >> HP_SHIFT) > 0) {
StartStand(pnum,0);
} else {
plr[pnum]._pgfxnum = PGFX_NGUY;
LoadPlrGFX(pnum, PGL_DEAD);
plr[pnum]._pmode = PM_DEATH;
NewPlrAnim(pnum, plr[pnum]._pDAnim[DIR_D], plr[pnum]._pDFrames, 1, plr[pnum]._pDWidth);
plr[pnum]._pAnimFrame = plr[pnum]._pAnimLen - 1;
plr[pnum]._pVar8 = plr[pnum]._pAnimLen << 1;
dFlags[plr[pnum]._px][plr[pnum]._py] |= BFLAG_DEADPLR;
}
}
#if TRACEOUT
TraceOut("(%d) making %d active -- recv_plrinfo",myplr,pnum);
#endif
}
else {
#if TRACEOUT
TraceOut("(%d) received all %d plrinfo",myplr,pnum);
#endif
}
}