You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
882 lines
21 KiB
882 lines
21 KiB
/** |
|
* @file multi.cpp |
|
* |
|
* Implementation of functions for keeping multiplaye games in sync. |
|
*/ |
|
|
|
#include <SDL.h> |
|
#include <config.h> |
|
|
|
#include "DiabloUI/diabloui.h" |
|
#include "diablo.h" |
|
#include "dthread.h" |
|
#include "mainmenu.h" |
|
#include "nthread.h" |
|
#include "options.h" |
|
#include "pfile.h" |
|
#include "plrmsg.h" |
|
#include "storm/storm.h" |
|
#include "sync.h" |
|
#include "tmsg.h" |
|
#include "utils/language.h" |
|
|
|
namespace devilution { |
|
|
|
bool gbSomebodyWonGameKludge; |
|
TBuffer sgHiPriBuf; |
|
char szPlayerDescript[128]; |
|
uint16_t sgwPackPlrOffsetTbl[MAX_PLRS]; |
|
PkPlayerStruct netplr[MAX_PLRS]; |
|
bool sgbPlayerTurnBitTbl[MAX_PLRS]; |
|
bool sgbPlayerLeftGameTbl[MAX_PLRS]; |
|
DWORD sgbSentThisCycle; |
|
bool gbShouldValidatePackage; |
|
BYTE gbActivePlayers; |
|
bool gbGameDestroyed; |
|
bool sgbSendDeltaTbl[MAX_PLRS]; |
|
GameData sgGameInitInfo; |
|
bool gbSelectProvider; |
|
int sglTimeoutStart; |
|
int sgdwPlayerLeftReasonTbl[MAX_PLRS]; |
|
TBuffer sgLoPriBuf; |
|
DWORD sgdwGameLoops; |
|
/** |
|
* Specifies the maximum number of players in a game, where 1 |
|
* represents a single player game and 4 represents a multi player game. |
|
*/ |
|
bool gbIsMultiplayer; |
|
bool sgbTimeout; |
|
char szPlayerName[128]; |
|
BYTE gbDeltaSender; |
|
bool sgbNetInited; |
|
uint32_t player_state[MAX_PLRS]; |
|
|
|
/** |
|
* Contains the set of supported event types supported by the multiplayer |
|
* event handler. |
|
*/ |
|
const event_type event_types[3] = { |
|
EVENT_TYPE_PLAYER_LEAVE_GAME, |
|
EVENT_TYPE_PLAYER_CREATE_GAME, |
|
EVENT_TYPE_PLAYER_MESSAGE |
|
}; |
|
|
|
static void buffer_init(TBuffer *pBuf) |
|
{ |
|
pBuf->dwNextWriteOffset = 0; |
|
pBuf->bData[0] = byte { 0 }; |
|
} |
|
|
|
// Microsoft VisualC 2-11/net runtime |
|
static int multi_check_pkt_valid(TBuffer *pBuf) |
|
{ |
|
return pBuf->dwNextWriteOffset == 0; |
|
} |
|
|
|
static void multi_copy_packet(TBuffer *buf, byte *packet, uint8_t size) |
|
{ |
|
if (buf->dwNextWriteOffset + size + 2 > 0x1000) { |
|
return; |
|
} |
|
|
|
byte *p = &buf->bData[buf->dwNextWriteOffset]; |
|
buf->dwNextWriteOffset += size + 1; |
|
*p = static_cast<byte>(size); |
|
p++; |
|
memcpy(p, packet, size); |
|
p[size] = byte { 0 }; |
|
} |
|
|
|
static byte *multi_recv_packet(TBuffer *pBuf, byte *body, DWORD *size) |
|
{ |
|
if (pBuf->dwNextWriteOffset != 0) { |
|
byte *src_ptr = pBuf->bData; |
|
while (true) { |
|
auto chunk_size = static_cast<uint8_t>(*src_ptr); |
|
if (chunk_size == 0) |
|
break; |
|
if (chunk_size > *size) |
|
break; |
|
src_ptr++; |
|
memcpy(body, src_ptr, chunk_size); |
|
body += chunk_size; |
|
src_ptr += chunk_size; |
|
*size -= chunk_size; |
|
} |
|
memcpy(pBuf->bData, src_ptr, (pBuf->bData - src_ptr) + pBuf->dwNextWriteOffset + 1); |
|
pBuf->dwNextWriteOffset += (pBuf->bData - src_ptr); |
|
return body; |
|
} |
|
return body; |
|
} |
|
|
|
static void NetRecvPlrData(TPkt *pkt) |
|
{ |
|
const Point target = plr[myplr].GetTargetPosition(); |
|
|
|
pkt->hdr.wCheck = LoadBE32("\0\0ip"); |
|
pkt->hdr.px = plr[myplr].position.tile.x; |
|
pkt->hdr.py = plr[myplr].position.tile.y; |
|
pkt->hdr.targx = target.x; |
|
pkt->hdr.targy = target.y; |
|
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 multi_msg_add(byte *pbMsg, BYTE bLen) |
|
{ |
|
if (pbMsg && bLen) { |
|
tmsg_add(pbMsg, bLen); |
|
} |
|
} |
|
|
|
static void multi_send_packet(int playerId, void *packet, BYTE dwSize) |
|
{ |
|
TPkt pkt; |
|
|
|
NetRecvPlrData(&pkt); |
|
pkt.hdr.wLen = dwSize + sizeof(pkt.hdr); |
|
memcpy(pkt.body, packet, dwSize); |
|
if (!SNetSendMessage(playerId, &pkt.hdr, pkt.hdr.wLen)) |
|
nthread_terminate_game("SNetSendMessage0"); |
|
} |
|
|
|
void NetSendLoPri(int playerId, byte *pbMsg, BYTE bLen) |
|
{ |
|
if (pbMsg && bLen) { |
|
multi_copy_packet(&sgLoPriBuf, pbMsg, bLen); |
|
multi_send_packet(playerId, pbMsg, bLen); |
|
} |
|
} |
|
|
|
void NetSendHiPri(int playerId, byte *pbMsg, BYTE bLen) |
|
{ |
|
DWORD size, len; |
|
TPkt pkt; |
|
|
|
if (pbMsg && bLen) { |
|
multi_copy_packet(&sgHiPriBuf, pbMsg, bLen); |
|
multi_send_packet(playerId, pbMsg, bLen); |
|
} |
|
if (!gbShouldValidatePackage) { |
|
gbShouldValidatePackage = true; |
|
NetRecvPlrData(&pkt); |
|
size = gdwNormalMsgSize - sizeof(TPktHdr); |
|
byte *hipri_body = multi_recv_packet(&sgHiPriBuf, pkt.body, &size); |
|
byte *lowpri_body = multi_recv_packet(&sgLoPriBuf, hipri_body, &size); |
|
size = sync_all_monsters(lowpri_body, size); |
|
len = gdwNormalMsgSize - size; |
|
pkt.hdr.wLen = len; |
|
if (!SNetSendMessage(-2, &pkt.hdr, len)) |
|
nthread_terminate_game("SNetSendMessage"); |
|
} |
|
} |
|
|
|
void multi_send_msg_packet(uint32_t pmask, byte *src, BYTE len) |
|
{ |
|
DWORD v, p, t; |
|
TPkt pkt; |
|
|
|
NetRecvPlrData(&pkt); |
|
t = len + sizeof(pkt.hdr); |
|
pkt.hdr.wLen = t; |
|
memcpy(pkt.body, src, len); |
|
for (v = 1, p = 0; p < MAX_PLRS; p++, v <<= 1) { |
|
if ((v & pmask) != 0) { |
|
if (!SNetSendMessage(p, &pkt.hdr, t) && SErrGetLastError() != STORM_ERROR_INVALID_PLAYER) { |
|
nthread_terminate_game("SNetSendMessage"); |
|
return; |
|
} |
|
} |
|
} |
|
} |
|
|
|
static void multi_mon_seeds() |
|
{ |
|
int i; |
|
DWORD l; |
|
|
|
sgdwGameLoops++; |
|
l = (sgdwGameLoops >> 8) | (sgdwGameLoops << 24); // _rotr(sgdwGameLoops, 8) |
|
for (i = 0; i < MAXMONSTERS; i++) |
|
monster[i]._mAISeed = l + i; |
|
} |
|
|
|
static void multi_handle_turn_upper_bit(int pnum) |
|
{ |
|
int i; |
|
|
|
for (i = 0; i < MAX_PLRS; i++) { |
|
if ((player_state[i] & PS_CONNECTED) != 0 && i != pnum) |
|
break; |
|
} |
|
|
|
if (myplr == i) { |
|
sgbSendDeltaTbl[pnum] = true; |
|
} else if (myplr == pnum) { |
|
gbDeltaSender = i; |
|
} |
|
} |
|
|
|
static void multi_parse_turn(int pnum, int turn) |
|
{ |
|
DWORD absTurns; |
|
|
|
if (turn >> 31) |
|
multi_handle_turn_upper_bit(pnum); |
|
absTurns = turn & 0x7FFFFFFF; |
|
if (sgbSentThisCycle < gdwTurnsInTransit + absTurns) { |
|
if (absTurns >= 0x7FFFFFFF) |
|
absTurns &= 0xFFFF; |
|
sgbSentThisCycle = absTurns + gdwTurnsInTransit; |
|
sgdwGameLoops = 4 * absTurns * sgbNetUpdateRate; |
|
} |
|
} |
|
|
|
void multi_msg_countdown() |
|
{ |
|
int i; |
|
|
|
for (i = 0; i < MAX_PLRS; i++) { |
|
if ((player_state[i] & PS_TURN_ARRIVED) != 0) { |
|
if (gdwMsgLenTbl[i] == 4) |
|
multi_parse_turn(i, *(DWORD *)glpMsgTbl[i]); |
|
} |
|
} |
|
} |
|
|
|
static void multi_player_left_msg(int pnum, bool left) |
|
{ |
|
const char *pszFmt; |
|
|
|
if (plr[pnum].plractive) { |
|
RemovePlrFromMap(pnum); |
|
RemovePortalMissile(pnum); |
|
DeactivatePortal(pnum); |
|
delta_close_portal(pnum); |
|
RemovePlrMissiles(pnum); |
|
if (left) { |
|
pszFmt = _("Player '%s' just left the game"); |
|
switch (sgdwPlayerLeftReasonTbl[pnum]) { |
|
case LEAVE_ENDING: |
|
pszFmt = _("Player '%s' killed Diablo and left the game!"); |
|
gbSomebodyWonGameKludge = true; |
|
break; |
|
case LEAVE_DROP: |
|
pszFmt = _("Player '%s' dropped due to timeout"); |
|
break; |
|
} |
|
EventPlrMsg(pszFmt, plr[pnum]._pName); |
|
} |
|
plr[pnum].plractive = false; |
|
plr[pnum]._pName[0] = '\0'; |
|
FreePlayerGFX(plr[pnum]); |
|
gbActivePlayers--; |
|
} |
|
} |
|
|
|
static void multi_clear_left_tbl() |
|
{ |
|
int i; |
|
|
|
for (i = 0; i < MAX_PLRS; i++) { |
|
if (sgbPlayerLeftGameTbl[i]) { |
|
if (gbBufferMsgs == 1) |
|
msg_send_drop_pkt(i, sgdwPlayerLeftReasonTbl[i]); |
|
else |
|
multi_player_left_msg(i, true); |
|
|
|
sgbPlayerLeftGameTbl[i] = false; |
|
sgdwPlayerLeftReasonTbl[i] = 0; |
|
} |
|
} |
|
} |
|
|
|
void multi_player_left(int pnum, int reason) |
|
{ |
|
sgbPlayerLeftGameTbl[pnum] = true; |
|
sgdwPlayerLeftReasonTbl[pnum] = reason; |
|
multi_clear_left_tbl(); |
|
} |
|
|
|
void multi_net_ping() |
|
{ |
|
sgbTimeout = true; |
|
sglTimeoutStart = SDL_GetTicks(); |
|
} |
|
|
|
static void multi_check_drop_player() |
|
{ |
|
int i; |
|
|
|
for (i = 0; i < MAX_PLRS; i++) { |
|
if (!(player_state[i] & PS_ACTIVE) && player_state[i] & PS_CONNECTED) { |
|
SNetDropPlayer(i, LEAVE_DROP); |
|
} |
|
} |
|
} |
|
|
|
static void multi_begin_timeout() |
|
{ |
|
int i, nTicks, nLowestActive, nLowestPlayer; |
|
BYTE bGroupPlayers, bGroupCount; |
|
|
|
if (!sgbTimeout) { |
|
return; |
|
} |
|
#ifdef _DEBUG |
|
if (debug_mode_key_i) { |
|
return; |
|
} |
|
#endif |
|
|
|
nTicks = SDL_GetTicks() - sglTimeoutStart; |
|
if (nTicks > 20000) { |
|
gbRunGame = false; |
|
return; |
|
} |
|
if (nTicks < 10000) { |
|
return; |
|
} |
|
|
|
nLowestActive = -1; |
|
nLowestPlayer = -1; |
|
bGroupPlayers = 0; |
|
bGroupCount = 0; |
|
for (i = 0; i < MAX_PLRS; i++) { |
|
uint32_t nState = player_state[i]; |
|
if ((nState & PS_CONNECTED) != 0) { |
|
if (nLowestPlayer == -1) { |
|
nLowestPlayer = i; |
|
} |
|
if ((nState & PS_ACTIVE) != 0) { |
|
bGroupPlayers++; |
|
if (nLowestActive == -1) { |
|
nLowestActive = i; |
|
} |
|
} else { |
|
bGroupCount++; |
|
} |
|
} |
|
} |
|
|
|
assert(bGroupPlayers); |
|
assert(nLowestActive != -1); |
|
assert(nLowestPlayer != -1); |
|
|
|
if (bGroupPlayers < bGroupCount) { |
|
gbGameDestroyed = true; |
|
} else if (bGroupPlayers == bGroupCount) { |
|
if (nLowestPlayer != nLowestActive) { |
|
gbGameDestroyed = true; |
|
} else if (nLowestActive == myplr) { |
|
multi_check_drop_player(); |
|
} |
|
} else if (nLowestActive == myplr) { |
|
multi_check_drop_player(); |
|
} |
|
} |
|
|
|
/** |
|
* @return Always true for singleplayer |
|
*/ |
|
bool multi_handle_delta() |
|
{ |
|
int i; |
|
bool received; |
|
|
|
if (gbGameDestroyed) { |
|
gbRunGame = false; |
|
return false; |
|
} |
|
|
|
for (i = 0; i < MAX_PLRS; i++) { |
|
if (sgbSendDeltaTbl[i]) { |
|
sgbSendDeltaTbl[i] = false; |
|
DeltaExportData(i); |
|
} |
|
} |
|
|
|
sgbSentThisCycle = nthread_send_and_recv_turn(sgbSentThisCycle, 1); |
|
if (!nthread_recv_turns(&received)) { |
|
multi_begin_timeout(); |
|
return false; |
|
} |
|
|
|
sgbTimeout = false; |
|
if (received) { |
|
if (!gbShouldValidatePackage) { |
|
NetSendHiPri(myplr, nullptr, 0); |
|
gbShouldValidatePackage = false; |
|
} else { |
|
gbShouldValidatePackage = false; |
|
if (!multi_check_pkt_valid(&sgHiPriBuf)) |
|
NetSendHiPri(myplr, nullptr, 0); |
|
} |
|
} |
|
multi_mon_seeds(); |
|
|
|
return true; |
|
} |
|
|
|
static void multi_handle_all_packets(int pnum, byte *pData, int nSize) |
|
{ |
|
int nLen; |
|
|
|
while (nSize != 0) { |
|
nLen = ParseCmd(pnum, (TCmd *)pData); |
|
if (nLen == 0) { |
|
break; |
|
} |
|
pData += nLen; |
|
nSize -= nLen; |
|
} |
|
} |
|
|
|
static void multi_process_tmsgs() |
|
{ |
|
int cnt; |
|
TPkt pkt; |
|
|
|
while ((cnt = tmsg_get((byte *)&pkt)) != 0) { |
|
multi_handle_all_packets(myplr, (byte *)&pkt, cnt); |
|
} |
|
} |
|
|
|
void multi_process_network_packets() |
|
{ |
|
int dx, dy; |
|
TPktHdr *pkt; |
|
DWORD dwMsgSize; |
|
int dwID; |
|
bool cond; |
|
char *data; |
|
|
|
multi_clear_left_tbl(); |
|
multi_process_tmsgs(); |
|
while (SNetReceiveMessage(&dwID, &data, (int *)&dwMsgSize)) { |
|
dwRecCount++; |
|
multi_clear_left_tbl(); |
|
pkt = (TPktHdr *)data; |
|
if (dwMsgSize < sizeof(TPktHdr)) |
|
continue; |
|
if (dwID < 0 || dwID >= MAX_PLRS) |
|
continue; |
|
if (pkt->wCheck != LoadBE32("\0\0ip")) |
|
continue; |
|
if (pkt->wLen != dwMsgSize) |
|
continue; |
|
plr[dwID].position.last = { pkt->px, pkt->py }; |
|
if (dwID != myplr) { |
|
assert(gbBufferMsgs != 2); |
|
plr[dwID]._pHitPoints = pkt->php; |
|
plr[dwID]._pMaxHP = pkt->pmhp; |
|
cond = gbBufferMsgs == 1; |
|
plr[dwID]._pBaseStr = pkt->bstr; |
|
plr[dwID]._pBaseMag = pkt->bmag; |
|
plr[dwID]._pBaseDex = pkt->bdex; |
|
if (!cond && plr[dwID].plractive && plr[dwID]._pHitPoints != 0) { |
|
if (currlevel == plr[dwID].plrlevel && !plr[dwID]._pLvlChanging) { |
|
dx = abs(plr[dwID].position.tile.x - pkt->px); |
|
dy = abs(plr[dwID].position.tile.y - pkt->py); |
|
if ((dx > 3 || dy > 3) && dPlayer[pkt->px][pkt->py] == 0) { |
|
FixPlrWalkTags(dwID); |
|
plr[dwID].position.old = plr[dwID].position.tile; |
|
FixPlrWalkTags(dwID); |
|
plr[dwID].position.tile = { pkt->px, pkt->py }; |
|
plr[dwID].position.future = { pkt->px, pkt->py }; |
|
dPlayer[plr[dwID].position.tile.x][plr[dwID].position.tile.y] = dwID + 1; |
|
} |
|
dx = abs(plr[dwID].position.future.x - plr[dwID].position.tile.x); |
|
dy = abs(plr[dwID].position.future.y - plr[dwID].position.tile.y); |
|
if (dx > 1 || dy > 1) { |
|
plr[dwID].position.future = plr[dwID].position.tile; |
|
} |
|
MakePlrPath(dwID, pkt->targx, pkt->targy, true); |
|
} else { |
|
plr[dwID].position.tile = { pkt->px, pkt->py }; |
|
plr[dwID].position.future = { pkt->px, pkt->py }; |
|
} |
|
} |
|
} |
|
multi_handle_all_packets(dwID, (byte *)(pkt + 1), dwMsgSize - sizeof(TPktHdr)); |
|
} |
|
if (SErrGetLastError() != STORM_ERROR_NO_MESSAGES_WAITING) |
|
nthread_terminate_game("SNetReceiveMsg"); |
|
} |
|
|
|
void multi_send_zero_packet(int pnum, _cmd_id bCmd, byte *pbSrc, DWORD dwLen) |
|
{ |
|
DWORD dwOffset, dwBody, dwMsg; |
|
TPkt pkt; |
|
TCmdPlrInfoHdr *p; |
|
|
|
assert(pnum != myplr); |
|
assert(pbSrc); |
|
assert(dwLen <= 0x0ffff); |
|
|
|
dwOffset = 0; |
|
|
|
while (dwLen != 0) { |
|
pkt.hdr.wCheck = LoadBE32("\0\0ip"); |
|
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; |
|
p = (TCmdPlrInfoHdr *)pkt.body; |
|
p->bCmd = bCmd; |
|
p->wOffset = dwOffset; |
|
dwBody = gdwLargestMsgSize - sizeof(pkt.hdr) - sizeof(*p); |
|
if (dwLen < dwBody) { |
|
dwBody = dwLen; |
|
} |
|
assert(dwBody <= 0x0ffff); |
|
p->wBytes = dwBody; |
|
memcpy(&pkt.body[sizeof(*p)], pbSrc, p->wBytes); |
|
dwMsg = sizeof(pkt.hdr); |
|
dwMsg += sizeof(*p); |
|
dwMsg += p->wBytes; |
|
pkt.hdr.wLen = dwMsg; |
|
if (!SNetSendMessage(pnum, &pkt, dwMsg)) { |
|
nthread_terminate_game("SNetSendMessage2"); |
|
return; |
|
} |
|
#if 0 |
|
if((DWORD)pnum >= MAX_PLRS) { |
|
if(myplr != 0) { |
|
debug_plr_tbl[0]++; |
|
} |
|
if(myplr != 1) { |
|
debug_plr_tbl[1]++; |
|
} |
|
if(myplr != 2) { |
|
debug_plr_tbl[2]++; |
|
} |
|
if(myplr != 3) { |
|
debug_plr_tbl[3]++; |
|
} |
|
} else { |
|
debug_plr_tbl[pnum]++; |
|
} |
|
#endif |
|
pbSrc += p->wBytes; |
|
dwLen -= p->wBytes; |
|
dwOffset += p->wBytes; |
|
} |
|
} |
|
|
|
static void multi_send_pinfo(int pnum, _cmd_id cmd) |
|
{ |
|
PkPlayerStruct pkplr; |
|
|
|
PackPlayer(&pkplr, plr[myplr], true); |
|
dthread_send_delta(pnum, cmd, (byte *)&pkplr, sizeof(pkplr)); |
|
} |
|
|
|
static dungeon_type InitLevelType(int l) |
|
{ |
|
if (l == 0) |
|
return DTYPE_TOWN; |
|
if (l >= 1 && l <= 4) |
|
return DTYPE_CATHEDRAL; |
|
if (l >= 5 && l <= 8) |
|
return DTYPE_CATACOMBS; |
|
if (l >= 9 && l <= 12) |
|
return DTYPE_CAVES; |
|
if (l >= 13 && l <= 16) |
|
return DTYPE_HELL; |
|
if (l >= 21 && l <= 24) |
|
return DTYPE_CATHEDRAL; // Crypt |
|
if (l >= 17 && l <= 20) |
|
return DTYPE_CAVES; // Hive |
|
|
|
return DTYPE_CATHEDRAL; |
|
} |
|
|
|
static void SetupLocalCoords() |
|
{ |
|
int x, y; |
|
|
|
if (!leveldebug || gbIsMultiplayer) { |
|
currlevel = 0; |
|
leveltype = DTYPE_TOWN; |
|
setlevel = false; |
|
} |
|
x = 75; |
|
y = 68; |
|
#ifdef _DEBUG |
|
if (debug_mode_key_inverted_v) { |
|
x = 49; |
|
y = 23; |
|
} |
|
#endif |
|
x += plrxoff[myplr]; |
|
y += plryoff[myplr]; |
|
plr[myplr].position.tile = { x, y }; |
|
plr[myplr].position.future = { x, y }; |
|
plr[myplr].plrlevel = currlevel; |
|
plr[myplr]._pLvlChanging = true; |
|
plr[myplr].pLvlLoad = 0; |
|
plr[myplr]._pmode = PM_NEWLVL; |
|
plr[myplr].destAction = ACTION_NONE; |
|
} |
|
|
|
static void multi_handle_events(_SNETEVENT *pEvt) |
|
{ |
|
DWORD LeftReason; |
|
|
|
switch (pEvt->eventid) { |
|
case EVENT_TYPE_PLAYER_CREATE_GAME: { |
|
auto *gameData = (GameData *)pEvt->data; |
|
if (gameData->size != sizeof(GameData)) |
|
app_fatal("Invalid size of game data: %i", gameData->size); |
|
sgGameInitInfo = *gameData; |
|
sgbPlayerTurnBitTbl[pEvt->playerid] = true; |
|
break; |
|
} |
|
case EVENT_TYPE_PLAYER_LEAVE_GAME: |
|
sgbPlayerLeftGameTbl[pEvt->playerid] = true; |
|
sgbPlayerTurnBitTbl[pEvt->playerid] = false; |
|
|
|
LeftReason = 0; |
|
if (pEvt->data && pEvt->databytes >= sizeof(DWORD)) |
|
LeftReason = *(DWORD *)pEvt->data; |
|
sgdwPlayerLeftReasonTbl[pEvt->playerid] = LeftReason; |
|
if (LeftReason == LEAVE_ENDING) |
|
gbSomebodyWonGameKludge = true; |
|
|
|
sgbSendDeltaTbl[pEvt->playerid] = false; |
|
dthread_remove_player(pEvt->playerid); |
|
|
|
if (gbDeltaSender == pEvt->playerid) |
|
gbDeltaSender = MAX_PLRS; |
|
break; |
|
case EVENT_TYPE_PLAYER_MESSAGE: |
|
ErrorPlrMsg((char *)pEvt->data); |
|
break; |
|
} |
|
} |
|
|
|
static void multi_event_handler(bool add) |
|
{ |
|
DWORD i; |
|
bool (*fn)(event_type, SEVTHANDLER); |
|
|
|
if (add) |
|
fn = SNetRegisterEventHandler; |
|
else |
|
fn = SNetUnregisterEventHandler; |
|
|
|
for (i = 0; i < 3; i++) { |
|
if (!fn(event_types[i], multi_handle_events) && add) { |
|
app_fatal("SNetRegisterEventHandler:\n%s", SDL_GetError()); |
|
} |
|
} |
|
} |
|
|
|
void NetClose() |
|
{ |
|
if (!sgbNetInited) { |
|
return; |
|
} |
|
|
|
sgbNetInited = false; |
|
nthread_cleanup(); |
|
dthread_cleanup(); |
|
tmsg_cleanup(); |
|
multi_event_handler(false); |
|
SNetLeaveGame(3); |
|
if (gbIsMultiplayer) |
|
SDL_Delay(2000); |
|
} |
|
|
|
bool NetInit(bool bSinglePlayer) |
|
{ |
|
while (true) { |
|
SetRndSeed(0); |
|
sgGameInitInfo.size = sizeof(sgGameInitInfo); |
|
sgGameInitInfo.dwSeed = time(nullptr); |
|
sgGameInitInfo.programid = GAME_ID; |
|
sgGameInitInfo.versionMajor = PROJECT_VERSION_MAJOR; |
|
sgGameInitInfo.versionMinor = PROJECT_VERSION_MINOR; |
|
sgGameInitInfo.versionPatch = PROJECT_VERSION_PATCH; |
|
sgGameInitInfo.nTickRate = sgOptions.Gameplay.nTickRate; |
|
sgGameInitInfo.bRunInTown = sgOptions.Gameplay.bRunInTown; |
|
sgGameInitInfo.bTheoQuest = sgOptions.Gameplay.bTheoQuest; |
|
sgGameInitInfo.bCowQuest = sgOptions.Gameplay.bCowQuest; |
|
sgGameInitInfo.bFriendlyFire = sgOptions.Gameplay.bFriendlyFire; |
|
memset(sgbPlayerTurnBitTbl, 0, sizeof(sgbPlayerTurnBitTbl)); |
|
gbGameDestroyed = false; |
|
memset(sgbPlayerLeftGameTbl, 0, sizeof(sgbPlayerLeftGameTbl)); |
|
memset(sgdwPlayerLeftReasonTbl, 0, sizeof(sgdwPlayerLeftReasonTbl)); |
|
memset(sgbSendDeltaTbl, 0, sizeof(sgbSendDeltaTbl)); |
|
memset(plr, 0, sizeof(plr)); |
|
memset(sgwPackPlrOffsetTbl, 0, sizeof(sgwPackPlrOffsetTbl)); |
|
SNetSetBasePlayer(0); |
|
if (bSinglePlayer) { |
|
if (!multi_init_single(&sgGameInitInfo)) |
|
return false; |
|
} else { |
|
if (!multi_init_multi(&sgGameInitInfo)) |
|
return false; |
|
} |
|
sgbNetInited = true; |
|
sgbTimeout = false; |
|
delta_init(); |
|
InitPlrMsg(); |
|
buffer_init(&sgHiPriBuf); |
|
buffer_init(&sgLoPriBuf); |
|
gbShouldValidatePackage = false; |
|
sync_init(); |
|
nthread_start(sgbPlayerTurnBitTbl[myplr]); |
|
dthread_start(); |
|
tmsg_start(); |
|
sgdwGameLoops = 0; |
|
sgbSentThisCycle = 0; |
|
gbDeltaSender = myplr; |
|
gbSomebodyWonGameKludge = false; |
|
nthread_send_and_recv_turn(0, 0); |
|
SetupLocalCoords(); |
|
multi_send_pinfo(-2, CMD_SEND_PLRINFO); |
|
|
|
InitPlrGFXMem(plr[myplr]); |
|
plr[myplr].plractive = true; |
|
gbActivePlayers = 1; |
|
|
|
if (!sgbPlayerTurnBitTbl[myplr] || msg_wait_resync()) |
|
break; |
|
NetClose(); |
|
gbSelectProvider = false; |
|
} |
|
SetRndSeed(sgGameInitInfo.dwSeed); |
|
gnTickDelay = 1000 / sgGameInitInfo.nTickRate; |
|
|
|
for (int i = 0; i < NUMLEVELS; i++) { |
|
glSeedTbl[i] = AdvanceRndSeed(); |
|
gnLevelTypeTbl[i] = InitLevelType(i); |
|
} |
|
if (!SNetGetGameInfo(GAMEINFO_NAME, szPlayerName, 128)) |
|
nthread_terminate_game("SNetGetGameInfo1"); |
|
if (!SNetGetGameInfo(GAMEINFO_PASSWORD, szPlayerDescript, 128)) |
|
nthread_terminate_game("SNetGetGameInfo2"); |
|
|
|
return true; |
|
} |
|
|
|
bool multi_init_single(GameData *gameData) |
|
{ |
|
int unused; |
|
|
|
if (!SNetInitializeProvider(SELCONN_LOOPBACK, gameData)) { |
|
SErrGetLastError(); |
|
return false; |
|
} |
|
|
|
unused = 0; |
|
if (!SNetCreateGame("local", "local", (char *)&sgGameInitInfo, sizeof(sgGameInitInfo), &unused)) { |
|
app_fatal("SNetCreateGame1:\n%s", SDL_GetError()); |
|
} |
|
|
|
myplr = 0; |
|
gbIsMultiplayer = false; |
|
|
|
return true; |
|
} |
|
|
|
bool multi_init_multi(GameData *gameData) |
|
{ |
|
int playerId; |
|
|
|
while (true) { |
|
if (gbSelectProvider && !UiSelectProvider(gameData)) { |
|
return false; |
|
} |
|
|
|
multi_event_handler(true); |
|
if (UiSelectGame(gameData, &playerId)) |
|
break; |
|
|
|
gbSelectProvider = true; |
|
} |
|
|
|
if ((DWORD)playerId >= MAX_PLRS) { |
|
return false; |
|
} |
|
myplr = playerId; |
|
gbIsMultiplayer = true; |
|
|
|
pfile_read_player_from_save(gszHero, myplr); |
|
|
|
return true; |
|
} |
|
|
|
void recv_plrinfo(int pnum, TCmdPlrInfoHdr *p, bool recv) |
|
{ |
|
const char *szEvent; |
|
|
|
if (myplr == pnum) { |
|
return; |
|
} |
|
assert((DWORD)pnum < MAX_PLRS); |
|
|
|
if (sgwPackPlrOffsetTbl[pnum] != p->wOffset) { |
|
sgwPackPlrOffsetTbl[pnum] = 0; |
|
if (p->wOffset != 0) { |
|
return; |
|
} |
|
} |
|
if (!recv && sgwPackPlrOffsetTbl[pnum] == 0) { |
|
multi_send_pinfo(pnum, CMD_ACK_PLRINFO); |
|
} |
|
|
|
memcpy((char *)&netplr[pnum] + p->wOffset, &p[1], p->wBytes); /* todo: cast? */ |
|
sgwPackPlrOffsetTbl[pnum] += p->wBytes; |
|
if (sgwPackPlrOffsetTbl[pnum] != sizeof(*netplr)) { |
|
return; |
|
} |
|
|
|
sgwPackPlrOffsetTbl[pnum] = 0; |
|
multi_player_left_msg(pnum, false); |
|
UnPackPlayer(&netplr[pnum], pnum, true); |
|
|
|
if (!recv) { |
|
return; |
|
} |
|
|
|
InitPlrGFXMem(plr[pnum]); |
|
plr[pnum].plractive = true; |
|
gbActivePlayers++; |
|
|
|
if (sgbPlayerTurnBitTbl[pnum]) { |
|
szEvent = _("Player '%s' (level %i) just joined the game"); |
|
} else { |
|
szEvent = _("Player '%s' (level %i) is already in the game"); |
|
} |
|
EventPlrMsg(szEvent, plr[pnum]._pName, plr[pnum]._pLevel); |
|
|
|
LoadPlrGFX(pnum, PFILE_STAND); |
|
SyncInitPlr(pnum); |
|
|
|
if (plr[pnum].plrlevel == currlevel) { |
|
if (plr[pnum]._pHitPoints >> 6 > 0) { |
|
StartStand(pnum, DIR_S); |
|
} else { |
|
plr[pnum]._pgfxnum = 0; |
|
LoadPlrGFX(pnum, PFILE_DEATH); |
|
plr[pnum]._pmode = PM_DEATH; |
|
NewPlrAnim(plr[pnum], plr[pnum]._pDAnim[DIR_S], plr[pnum]._pDFrames, 1, plr[pnum]._pDWidth); |
|
plr[pnum].AnimInfo.CurrentFrame = plr[pnum].AnimInfo.NumberOfFrames - 1; |
|
dFlags[plr[pnum].position.tile.x][plr[pnum].position.tile.y] |= BFLAG_DEAD_PLAYER; |
|
} |
|
} |
|
} |
|
|
|
} // namespace devilution
|
|
|