mirror of
https://github.com/jmarshall23/Hellfire.git
synced 2026-08-11 23:40:51 +02:00
1453 lines
41 KiB
C++
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
|
|
}
|
|
}
|