diff --git a/port/include/input.h b/port/include/input.h index 1921ed1ed..4eb357a6a 100644 --- a/port/include/input.h +++ b/port/include/input.h @@ -32,6 +32,7 @@ enum virtkey { VK_LEFTBRACKET = 47, VK_RIGHTBRACKET = 48, VK_SEMICOLON = 51, + VK_GRAVE = 53, VK_COMMA = 54, VK_PERIOD = 55, VK_DELETE = 76, @@ -214,6 +215,12 @@ void inputSetDefaultKeyBinds(s32 cidx, s32 n64mode); void inputClearLastKey(void); s32 inputGetLastKey(void); +void inputStartTextInput(void); +void inputStopTextInput(void); + +void inputClearLastTextChar(void); +char inputGetLastTextChar(void); + // get/set Input.MouseDefaultLocked s32 inputGetMouseDefaultLocked(void); void inputSetMouseDefaultLocked(s32 defaultLocked); diff --git a/port/include/net/net.h b/port/include/net/net.h index 5fda28c6f..b6a513d22 100644 --- a/port/include/net/net.h +++ b/port/include/net/net.h @@ -14,6 +14,9 @@ #define NET_DEFAULT_PORT 27100 +#define NET_NULL_CLIENT 0xFF +#define NET_NULL_PROP 0 + #define NETCHAN_DEFAULT 0 #define NETCHAN_COUNT 1 @@ -24,6 +27,7 @@ #define DISCONNECT_BANNED 4 #define DISCONNECT_TIMEOUT 5 #define DISCONNECT_FULL 6 +#define DISCONNECT_LATE 7 #define CLSTATE_DISCONNECTED 0 #define CLSTATE_CONNECTING 1 @@ -34,13 +38,16 @@ #define UCMD_FIRE (1 << 0) #define UCMD_ACTIVATE (1 << 1) #define UCMD_RELOAD (1 << 2) -#define UCMD_FIREMODE (1 << 3) -#define UCMD_AIMMODE (1 << 4) -#define UCMD_DUCK (1 << 5) -#define UCMD_SQUAT (1 << 6) -#define UCMD_ZOOMIN (1 << 7) -#define UCMD_SELECT (1 << 8) -#define UCMD_CHAT (1 << 19) +#define UCMD_AIMMODE (1 << 3) +#define UCMD_DUCK (1 << 4) +#define UCMD_SQUAT (1 << 5) +#define UCMD_ZOOMIN (1 << 6) +#define UCMD_SELECT (1 << 7) +#define UCMD_EYESSHUT (1 << 8) +#define UCMD_SECONDARY (1 << 9) +#define UCMD_RESPAWN (1 << 27) +#define UCMD_CHAT (1 << 28) +#define UCMD_IMPORTANT_MASK (UCMD_FIRE | UCMD_ACTIVATE | UCMD_RELOAD | UCMD_AIMMODE | UCMD_SELECT) #define UCMD_FL_FORCEPOS (1 << 29) #define UCMD_FL_FORCEANGLE (1 << 30) #define UCMD_FL_FORCEGROUND (1 << 31) @@ -52,10 +59,10 @@ struct netplayermove { f32 leanofs; // analog lean value (-1 .. 1; equal to player->swaytarget / 75.f) f32 crouchofs; // analog crouch value (-90 for SQUAT, 0 for STAND; player->crouchofs) f32 movespeed[2]; // move inputs, [0] is forward, [1] is sideways; used mostly for animation - f32 lookspeed[2]; // look inputs, [0] is theta, [1] is verta f32 angles[2]; // view angles, [0] is theta, [1] is verta + f32 crosspos[2]; // crosshair position in aiming mode + s8 weaponnum; // switch to this weapon if UCMD_SELECT is set struct coord pos; // player position at g_NetTick == tick - s16 rooms[2]; // cam_room, floorroom }; struct netclient { @@ -93,7 +100,8 @@ extern s32 g_NetMode; extern u32 g_NetTick; extern u32 g_NetInterpTicks; -extern u32 g_NetExterpTicks; + +extern u32 g_NetNextSyncId; extern s32 g_NetMaxClients; extern s32 g_NetNumClients; @@ -115,9 +123,12 @@ s32 netStartClient(const char *addr); u32 netSend(struct netclient *dstcl, struct netbuf *buf, const s32 reliable); +void netChat(const char *text); + void netServerStageStart(void); void netServerStageEnd(void); void netPlayersAllocate(void); +void netSyncIdsAllocate(void); #endif // _IN_NET_H diff --git a/port/include/net/netbuf.h b/port/include/net/netbuf.h index 01dfd523c..8461742d5 100644 --- a/port/include/net/netbuf.h +++ b/port/include/net/netbuf.h @@ -26,6 +26,8 @@ s64 netbufReadS64(struct netbuf *buf); f32 netbufReadF32(struct netbuf *buf); char *netbufReadStr(struct netbuf *buf); u32 netbufReadCoord(struct netbuf *buf, struct coord *out); +u32 netbufReadMtxf(struct netbuf *buf, Mtxf *out); +u32 netbufReadSkip(struct netbuf *buf, u32 count); void netbufStartWrite(struct netbuf *buf); s32 netbufWriteLeft(const struct netbuf *buf); @@ -41,6 +43,7 @@ u32 netbufWriteS64(struct netbuf *buf, const s64 v); u32 netbufWriteF32(struct netbuf *buf, const f32 v); u32 netbufWriteStr(struct netbuf *buf, const char *v); u32 netbufWriteCoord(struct netbuf *buf, const struct coord *v); +u32 netbufWriteMtxf(struct netbuf *buf, const Mtxf *in); void netbufReset(struct netbuf *buf); diff --git a/port/include/net/netmsg.h b/port/include/net/netmsg.h index a8fb41fc9..c822d9a36 100644 --- a/port/include/net/netmsg.h +++ b/port/include/net/netmsg.h @@ -5,32 +5,64 @@ #include "net/net.h" #include "net/netbuf.h" -#define SVC_BAD 0 // trash -#define SVC_NOP 1 // does nothing -#define SVC_AUTH 2 // auth response, sent in response to CLC_AUTH -#define SVC_CHAT 3 // chat message -#define SVC_STAGE_START 4 // start level -#define SVC_STAGE_END 5 // end level -#define SVC_PLAYER_MOVE 6 // player movement +#define SVC_BAD 0x00 // trash +#define SVC_NOP 0x01 // does nothing +#define SVC_AUTH 0x02 // auth response, sent in response to CLC_AUTH +#define SVC_CHAT 0x03 // chat message +#define SVC_STAGE_START 0x10 // start level +#define SVC_STAGE_END 0x11 // end level +#define SVC_PLAYER_MOVE 0x20 // player movement and inputs +#define SVC_PLAYER_GUNS 0x21 // player gun state +#define SVC_PLAYER_STATS 0x22 // player stats (health etc) +#define SVC_PROP_MOVE 0x30 // prop movement +#define SVC_PROP_SPAWN 0x31 // new prop spawned +#define SVC_PROP_DAMAGE 0x32 // prop was damaged +#define SVC_PROP_PICKUP 0x33 // prop was picked up +#define SVC_PROP_USE 0x34 // door/lift/etc was used +#define SVC_PROP_DOOR 0x35 // door state changed +#define SVC_CHR_DAMAGE 0x42 // chr was damaged +#define SVC_CHR_DISARM 0x43 // chr's weapons were dropped -#define CLC_BAD 0 // trash -#define CLC_NOP 1 // does nothing -#define CLC_AUTH 2 // auth request, sent immediately after connecting -#define CLC_CHAT 3 // chat message -#define CLC_MOVE 4 // player input +#define CLC_BAD 0x00 // trash +#define CLC_NOP 0x01 // does nothing +#define CLC_AUTH 0x02 // auth request, sent immediately after connecting +#define CLC_CHAT 0x03 // chat message +#define CLC_MOVE 0x04 // player input u32 netmsgClcAuthWrite(struct netbuf *dst); u32 netmsgClcAuthRead(struct netbuf *src, struct netclient *srccl); +u32 netmsgClcChatWrite(struct netbuf *dst, const char *str); +u32 netmsgClcChatRead(struct netbuf *src, struct netclient *srccl); u32 netmsgClcMoveWrite(struct netbuf *dst); u32 netmsgClcMoveRead(struct netbuf *src, struct netclient *srccl); u32 netmsgSvcAuthWrite(struct netbuf *dst, struct netclient *authcl); u32 netmsgSvcAuthRead(struct netbuf *src, struct netclient *srccl); +u32 netmsgSvcChatWrite(struct netbuf *dst, const char *str); +u32 netmsgSvcChatRead(struct netbuf *src, struct netclient *srccl); u32 netmsgSvcStageStartWrite(struct netbuf *dst); u32 netmsgSvcStageStartRead(struct netbuf *src, struct netclient *srccl); u32 netmsgSvcStageEndWrite(struct netbuf *dst); u32 netmsgSvcStageEndRead(struct netbuf *src, struct netclient *srccl); u32 netmsgSvcPlayerMoveWrite(struct netbuf *dst, struct netclient *movecl); u32 netmsgSvcPlayerMoveRead(struct netbuf *src, struct netclient *srccl); +u32 netmsgSvcPlayerStatsWrite(struct netbuf *dst, struct netclient *actcl); +u32 netmsgSvcPlayerStatsRead(struct netbuf *src, struct netclient *srccl); +u32 netmsgSvcPropSpawnWrite(struct netbuf *dst, struct prop *prop); +u32 netmsgSvcPropSpawnRead(struct netbuf *src, struct netclient *srccl); +u32 netmsgSvcPropMoveWrite(struct netbuf *dst, struct prop *prop, struct coord *initrot); +u32 netmsgSvcPropMoveRead(struct netbuf *src, struct netclient *srccl); +u32 netmsgSvcPropDamageWrite(struct netbuf *dst, struct prop *prop, f32 damage, struct coord *pos, s32 weaponnum, s32 playernum); +u32 netmsgSvcPropDamageRead(struct netbuf *src, struct netclient *srccl); +u32 netmsgSvcPropPickupWrite(struct netbuf *dst, struct netclient *actcl, struct prop *prop, const s32 tickop); +u32 netmsgSvcPropPickupRead(struct netbuf *src, struct netclient *srccl); +u32 netmsgSvcPropUseWrite(struct netbuf *dst, struct prop *prop, struct netclient *usercl, const s32 tickop); +u32 netmsgSvcPropUseRead(struct netbuf *src, struct netclient *srccl); +u32 netmsgSvcPropDoorWrite(struct netbuf *dst, struct prop *prop, struct netclient *usercl); +u32 netmsgSvcPropDoorRead(struct netbuf *src, struct netclient *srccl); +u32 netmsgSvcChrDamageWrite(struct netbuf *dst, struct chrdata *chr, f32 damage, struct coord *vector, struct gset *gset, struct prop *aprop, s32 hitpart, bool damageshield, struct prop *prop2, s32 side, s16 *arg11, bool explosion, struct coord *explosionpos); +u32 netmsgSvcChrDamageRead(struct netbuf *src, struct netclient *srccl); +u32 netmsgSvcChrDisarmWrite(struct netbuf *dst, struct chrdata *chr, struct prop *attacker, u8 weaponnum, f32 wpndamage, struct coord *wpnpos); +u32 netmsgSvcChrDisarmRead(struct netbuf *src, struct netclient *srccl); #endif diff --git a/port/include/system.h b/port/include/system.h index 005dbaf48..69e99366e 100644 --- a/port/include/system.h +++ b/port/include/system.h @@ -7,10 +7,13 @@ extern "C" { #include -enum LogLevel { - LOG_NOTE, - LOG_WARNING, - LOG_ERROR, +#define LOGFLAG_SHOWMSG (1 << 7) + +enum loglevel { + LOG_NOTE = 0, + LOG_CHAT = 0 | LOGFLAG_SHOWMSG, + LOG_WARNING = 1 | LOGFLAG_SHOWMSG, + LOG_ERROR = 2 | LOGFLAG_SHOWMSG, }; void sysInitArgs(s32 argc, const char **argv); @@ -21,6 +24,7 @@ const char *sysArgGetString(const char *arg); s32 sysArgGetInt(const char *arg, s32 defval); u64 sysGetMicroseconds(void); +f32 sysGetSeconds(void); void sysFatalError(const char *fmt, ...) __attribute__((noreturn)); diff --git a/port/include/utils.h b/port/include/utils.h index b47bd0941..18a3ee2a3 100644 --- a/port/include/utils.h +++ b/port/include/utils.h @@ -12,4 +12,18 @@ char *strParseToken(char *str, char *out, s32 *outCount); char *strFmt(const char *fmt, ...); char *strDuplicate(const char *str); +static inline f32 lerpf(const f32 a, const f32 b, const f32 t) { + return a + (b - a) * t; +} + +static inline f32 lerpanglef(const f32 a, const f32 b, const f32 t) { + f32 d = b - a; + if (d > 180.f) { + d -= 360.f; + } else if (d < -180.f) { + d += 360.f; + } + return a + d * t; +} + #endif diff --git a/port/src/input.c b/port/src/input.c index a4b14d0e5..c5c17e283 100644 --- a/port/src/input.c +++ b/port/src/input.c @@ -81,6 +81,8 @@ static f32 mouseSensX = 1.5f; static f32 mouseSensY = 1.5f; static s32 lastKey = 0; +static char lastChar = 0; +static s32 textInput = 0; static const char *ckNames[CK_TOTAL_COUNT] = { "R_CBUTTONS", @@ -512,6 +514,12 @@ static int inputEventFilter(void *data, SDL_Event *event) } break; + case SDL_TEXTINPUT: + if (!lastChar && event->text.text[0] && (u8)event->text.text[0] < 0x80) { + lastChar = event->text.text[0]; + } + break; + default: break; } @@ -719,6 +727,14 @@ s32 inputReadController(s32 idx, OSContPad *npad) npad->button = 0; + if (textInput) { + npad->stick_x = 0; + npad->stick_y = 0; + npad->rstick_x = 0; + npad->rstick_y = 0; + return 0; + } + for (u32 i = 0; i < CONT_NUM_BUTTONS; ++i) { if (inputBindPressed(idx, i)) { npad->button |= 1U << i; @@ -1247,6 +1263,30 @@ s32 inputGetLastKey(void) return lastKey; } +void inputStartTextInput(void) +{ + lastChar = 0; + lastKey = 0; + textInput = 1; + SDL_StartTextInput(); +} + +void inputClearLastTextChar(void) +{ + lastChar = 0; +} + +char inputGetLastTextChar(void) +{ + return lastChar; +} + +void inputStopTextInput(void) +{ + SDL_StopTextInput(); + textInput = 0; +} + PD_CONSTRUCTOR static void inputConfigInit(void) { configRegisterInt("Input.MouseEnabled", &mouseEnabled, 0, 1); diff --git a/port/src/net/net.c b/port/src/net/net.c index f94b728c3..94cf71fb2 100644 --- a/port/src/net/net.c +++ b/port/src/net/net.c @@ -11,11 +11,13 @@ #include "constants.h" #include "data.h" #include "bss.h" -#include "game/chraction.h" -#include "game/bot.h" -#include "game/botact.h" +#include "game/hudmsg.h" +#include "game/playermgr.h" +#include "game/bondgun.h" +#include "lib/main.h" #include "config.h" #include "system.h" +#include "console.h" #include "utils.h" s32 g_NetMode = NETMODE_NONE; @@ -23,9 +25,10 @@ s32 g_NetMode = NETMODE_NONE; u32 g_NetTick = 0; u32 g_NetInterpTicks = 6; -u32 g_NetExterpTicks = 6; -s32 g_NetSimPacketLoss = 20; +s32 g_NetSimPacketLoss = 0; + +u32 g_NetNextSyncId = 1; s32 g_NetMaxClients = NET_MAX_CLIENTS; s32 g_NetNumClients = 0; @@ -35,7 +38,7 @@ struct netclient *g_NetLocalClient = &g_NetClients[NET_MAX_CLIENTS]; static u8 g_NetMsgBuf[NET_BUFSIZE]; struct netbuf g_NetMsg = { .data = g_NetMsgBuf, .size = sizeof(g_NetMsgBuf) }; -static u8 g_NetMsgRelBuf[NET_BUFSIZE]; +static u8 g_NetMsgRelBuf[NET_BUFSIZE * 4]; // reliable buffer can be reliably fragmented struct netbuf g_NetMsgRel = { .data = g_NetMsgRelBuf, .size = sizeof(g_NetMsgRelBuf) }; static s32 g_NetInit = false; @@ -132,6 +135,10 @@ const char *netFormatClientAddr(const struct netclient *cl) static inline void netClientReset(struct netclient *cl) { + if (cl->state >= CLSTATE_GAME && cl->player) { + cl->player->client = NULL; + cl->player->isremote = false; + } memset(cl, 0, sizeof(*cl)); cl->out.data = cl->out_data; cl->out.size = sizeof(cl->out_data); @@ -159,13 +166,11 @@ static inline void netClientRecordMove(struct netclient *cl, const struct player move->leanofs = pl->swaytarget / 75.f; move->movespeed[0] = pl->speedforwards; move->movespeed[1] = pl->speedsideways; - move->lookspeed[0] = pl->speedtheta; - move->lookspeed[1] = pl->speedverta; move->angles[0] = pl->vv_theta; move->angles[1] = pl->vv_verta; move->pos = (pl->prop) ? pl->prop->pos : pl->cam_pos; - move->rooms[0] = pl->cam_room; - move->rooms[1] = pl->floorroom; + move->crosspos[0] = pl->crosspos[0]; + move->crosspos[1] = pl->crosspos[1]; move->ucmd = pl->ucmd; if (pl->crouchpos == CROUCHPOS_DUCK) { @@ -174,19 +179,53 @@ static inline void netClientRecordMove(struct netclient *cl, const struct player move->ucmd |= UCMD_SQUAT; } - if (pl->bondactivateorreload & JO_ACTION_ACTIVATE) { - move->ucmd |= UCMD_ACTIVATE; + if (pl->gunctrl.switchtoweaponnum >= 0) { + move->ucmd |= UCMD_SELECT; + move->weaponnum = pl->gunctrl.switchtoweaponnum; + } else { + move->weaponnum = -1; } - if (pl->bondactivateorreload & JO_ACTION_RELOAD) { - move->ucmd |= UCMD_RELOAD; + + const s32 oldnum = g_Vars.currentplayernum; + setCurrentPlayerNum(cl->playernum); + + if (bgunIsUsingSecondaryFunction()) { + move->ucmd |= UCMD_SECONDARY; } + setCurrentPlayerNum(oldnum); + if (cl != g_NetLocalClient && !cl->forcetick && (move->ucmd & UCMD_FL_FORCEMASK)) { cl->forcetick = move->tick; sysLogPrintf(LOG_NOTE, "NET: forcing client %u to move at tick %u", cl->id, cl->forcetick); } } +static inline s32 netClientNeedReliableMove(const struct netclient *cl) +{ + const struct netplayermove *move = &cl->outmove[0]; + const struct netplayermove *moveprev = &cl->outmove[1]; + return !moveprev->tick || (g_NetMode == NETMODE_SERVER && cl->forcetick) || + (moveprev->ucmd & UCMD_IMPORTANT_MASK) != (move->ucmd & UCMD_IMPORTANT_MASK); +} + +static inline void netFlushSendBuffers(void) +{ + if (g_NetMsgRel.wp) { + if (g_NetMsgRel.error) { + sysLogPrintf(LOG_WARNING, "NET: reliable out buffer overflow"); + } + netSend(NULL, &g_NetMsgRel, true); + } + + if (g_NetMsg.wp) { + if (g_NetMsg.error) { + sysLogPrintf(LOG_WARNING, "NET: unreliable out buffer overflow"); + } + netSend(NULL, &g_NetMsg, false); + } +} + void netInit(void) { if (enet_initialize() < 0) { @@ -219,10 +258,16 @@ s32 netStartServer(u16 port, s32 maxclients) g_NetLocalClient->settings.bodynum = g_PlayerConfigsArray[0].base.mpbodynum; g_NetLocalClient->settings.headnum = g_PlayerConfigsArray[0].base.mpheadnum; memcpy(g_NetLocalClient->settings.name, g_PlayerConfigsArray[0].base.name, sizeof(g_NetLocalClient->settings.name)); + // the \n will be readded in the playerconfig + char *newline = strrchr(g_NetLocalClient->settings.name, '\n'); + if (newline) { + *newline = '\0'; + } g_NetMode = NETMODE_SERVER; g_NetTick = 0; + g_NetNextSyncId = 1; sysLogPrintf(LOG_NOTE, "NET: created server on port %u", port); @@ -235,6 +280,11 @@ void netServerStageStart(void) return; } + if (g_StageNum == STAGE_TITLE || g_StageNum == STAGE_CITRAINING) { + g_NetLocalClient->state = CLSTATE_LOBBY; + return; + } + g_NetLocalClient->state = CLSTATE_GAME; netbufStartWrite(&g_NetMsgRel); @@ -249,6 +299,7 @@ void netServerStageEnd(void) } g_NetLocalClient->state = CLSTATE_LOBBY; + netbufStartWrite(&g_NetMsgRel); netmsgSvcStageEndWrite(&g_NetMsgRel); netSend(NULL, &g_NetMsgRel, true); @@ -292,10 +343,16 @@ s32 netStartClient(const char *addr) g_NetLocalClient->settings.bodynum = g_PlayerConfigsArray[0].base.mpbodynum; g_NetLocalClient->settings.headnum = g_PlayerConfigsArray[0].base.mpheadnum; memcpy(g_NetLocalClient->settings.name, g_PlayerConfigsArray[0].base.name, sizeof(g_NetLocalClient->settings.name)); + // the \n will be readded in the playerconfig + char *newline = strrchr(g_NetLocalClient->settings.name, '\n'); + if (newline) { + *newline = '\0'; + } g_NetMode = NETMODE_CLIENT; g_NetTick = 0; + g_NetNextSyncId = 1; sysLogPrintf(LOG_NOTE, "NET: waiting for response from %s...", addr); @@ -308,6 +365,8 @@ s32 netDisconnect(void) return -1; } + const bool wasingame = (g_NetLocalClient->state >= CLSTATE_GAME); + for (s32 i = 0; i < NET_MAX_CLIENTS + 1; ++i) { if (g_NetClients[i].peer) { enet_peer_disconnect_now(g_NetClients[i].peer, DISCONNECT_SHUTDOWN); @@ -330,6 +389,11 @@ s32 netDisconnect(void) sysLogPrintf(LOG_NOTE, "NET: disconnected"); + if (wasingame) { + mainEndStage(); + g_MpSetup.chrslots = 1; + } + return 0; } @@ -339,12 +403,20 @@ static void netServerEvConnect(ENetPeer *peer, const u32 data) sysLogPrintf(LOG_NOTE, "NET: connection attempt from %s", addrstr); + ++g_NetNumClients; + if (data != NET_PROTOCOL_VER) { sysLogPrintf(LOG_NOTE, "NET: %s rejected: protocol mismatch", addrstr); enet_peer_disconnect(peer, DISCONNECT_VERSION); return; } + if (g_NetLocalClient && g_NetLocalClient->state > CLSTATE_LOBBY) { + sysLogPrintf(LOG_NOTE, "NET: %s rejected: late joins not allowed", addrstr); + enet_peer_disconnect(peer, DISCONNECT_LATE); + return; + } + struct netclient *cl = NULL; // id 0 is the local client @@ -365,8 +437,6 @@ static void netServerEvConnect(ENetPeer *peer, const u32 data) cl->state = CLSTATE_AUTH; // skip CLSTATE_CONNECTING, since we already know it connected cl->peer = peer; enet_peer_set_data(peer, cl); - - ++g_NetNumClients; } static void netServerEvDisconnect(struct netclient *cl) @@ -377,6 +447,8 @@ static void netServerEvDisconnect(struct netclient *cl) enet_peer_reset(cl->peer); } + sysLogPrintf(LOG_CHAT, "NET: %s (client %u) disconnected", cl->settings.name, cl->id); + netClientReset(cl); --g_NetNumClients; @@ -392,7 +464,7 @@ static void netServerEvReceive(struct netclient *cl) switch (msgid) { case CLC_NOP: rc = 0; break; case CLC_AUTH: rc = netmsgClcAuthRead(&cl->in, cl); break; - case CLC_CHAT: rc = 0; break; + case CLC_CHAT: rc = netmsgClcChatRead(&cl->in, cl); break; case CLC_MOVE: rc = netmsgClcMoveRead(&cl->in, cl); break; default: rc = 1; @@ -421,6 +493,8 @@ static void netClientEvDisconnect(const u32 reason) { sysLogPrintf(LOG_NOTE, "NET: kicked from server, reason: %u", reason); netDisconnect(); + + sysLogPrintf(LOG_CHAT, "NET: disconnected from server"); } static void netClientEvReceive(struct netclient *cl) @@ -433,10 +507,19 @@ static void netClientEvReceive(struct netclient *cl) switch (msgid) { case SVC_NOP: rc = 0; break; case SVC_AUTH: rc = netmsgSvcAuthRead(&cl->in, cl); break; - case SVC_CHAT: rc = 0; break; + case SVC_CHAT: rc = netmsgSvcChatRead(&cl->in, cl); break; case SVC_STAGE_START: rc = netmsgSvcStageStartRead(&cl->in, cl); break; case SVC_STAGE_END: rc = netmsgSvcStageEndRead(&cl->in, cl); break; case SVC_PLAYER_MOVE: rc = netmsgSvcPlayerMoveRead(&cl->in, cl); break; + case SVC_PLAYER_STATS: rc = netmsgSvcPlayerStatsRead(&cl->in, cl); break; + case SVC_PROP_MOVE: rc = netmsgSvcPropMoveRead(&cl->in, cl); break; + case SVC_PROP_SPAWN: rc = netmsgSvcPropSpawnRead(&cl->in, cl); break; + case SVC_PROP_DAMAGE: rc = netmsgSvcPropDamageRead(&cl->in, cl); break; + case SVC_PROP_PICKUP: rc = netmsgSvcPropPickupRead(&cl->in, cl); break; + case SVC_PROP_USE: rc = netmsgSvcPropUseRead(&cl->in, cl); break; + case SVC_PROP_DOOR: rc = netmsgSvcPropDoorRead(&cl->in, cl); break; + case SVC_CHR_DAMAGE: rc = netmsgSvcChrDamageRead(&cl->in, cl); break; + case SVC_CHR_DISARM: rc = netmsgSvcChrDisarmRead(&cl->in, cl); break; default: rc = 1; break; @@ -522,34 +605,28 @@ void netEndFrame(void) return; } + // send whatever messages have accumulated so far + netFlushSendBuffers(); + if (g_NetLocalClient->state == CLSTATE_GAME && g_NetLocalClient->player && g_NetLocalClient->player->prop) { if (g_NetMode == NETMODE_CLIENT) { - netClientRecordMove(g_NetLocalClient, g_NetLocalClient->player); - netmsgClcMoveWrite(&g_NetMsg); + if (g_NetTick > 100) { + netClientRecordMove(g_NetLocalClient, g_NetLocalClient->player); + netmsgClcMoveWrite(netClientNeedReliableMove(g_NetLocalClient) ? &g_NetMsgRel : &g_NetMsg); + } } else { for (s32 i = 0; i < g_NetMaxClients; ++i) { struct netclient *cl = &g_NetClients[i]; if (cl->state >= CLSTATE_GAME && cl->player) { netClientRecordMove(cl, cl->player); - netmsgSvcPlayerMoveWrite(cl->forcetick ? &g_NetMsgRel : &g_NetMsg, cl); + netmsgSvcPlayerMoveWrite(netClientNeedReliableMove(g_NetLocalClient) ? &g_NetMsgRel : &g_NetMsg, cl); } } } } - if (g_NetMsgRel.wp) { - if (g_NetMsgRel.error) { - sysLogPrintf(LOG_WARNING, "NET: reliable out buffer overflow"); - } - netSend(NULL, &g_NetMsgRel, true); - } - - if (g_NetMsg.wp) { - if (g_NetMsg.error) { - sysLogPrintf(LOG_WARNING, "NET: unreliable out buffer overflow"); - } - netSend(NULL, &g_NetMsg, false); - } + // send position updates + netFlushSendBuffers(); enet_host_flush(g_NetHost); } @@ -610,7 +687,7 @@ void netPlayersAllocate(void) cfg->controlmode = CONTROLMODE_NA; cfg->base.mpbodynum = cl->settings.bodynum; cfg->base.mpheadnum = cl->settings.headnum; - memcpy(cfg->base.name, cl->settings.name, MAX_PLAYERNAME); + snprintf(cfg->base.name, sizeof(cfg->base.name), "%s\n", cl->settings.name); } cl->player = g_Vars.players[cl->playernum]; @@ -620,3 +697,66 @@ void netPlayersAllocate(void) cl->player->isremote = (cl != g_NetLocalClient); } } + +void netSyncIdsAllocate(void) +{ + // allocate sync ids sequentially for all active or paused props + g_NetNextSyncId = 1; + + // don't allocate anything else if we're in lobby + if (g_StageNum == STAGE_TITLE || g_StageNum == STAGE_CITRAINING) { + return; + } + + // iterate active props first + struct prop *prop = g_Vars.activeprops; + while (prop && prop != g_Vars.pausedprops) { + prop->syncid = prop - g_Vars.props + 1; + if (prop->syncid > g_NetNextSyncId) { + g_NetNextSyncId = prop->syncid; + } + prop = prop->next; + } + + // then the paused props + prop = g_Vars.pausedprops; + while (prop) { + prop->syncid = prop - g_Vars.props + 1; + if (prop->syncid > g_NetNextSyncId) { + g_NetNextSyncId = prop->syncid; + } + prop = prop->next; + } + + // HACK: when we're a client, we'll need to swap our player and server player's props + // because of what we do in netPlayersAllocate + if (g_NetMode == NETMODE_CLIENT) { + const u16 sid = g_Vars.players[g_NetLocalClient->id]->prop->syncid; + g_Vars.players[g_NetLocalClient->id]->prop->syncid = g_Vars.players[0]->prop->syncid; + g_Vars.players[0]->prop->syncid = sid; + } + + sysLogPrintf(LOG_NOTE, "NET: last initial syncid: %u", g_NetNextSyncId); +} + +void netChat(const char *text) +{ + char tmp[1024]; + + if (!g_NetMode || !g_NetLocalClient || g_NetLocalClient->state < CLSTATE_LOBBY) { + return; + } + + snprintf(tmp, sizeof(tmp), "%s: %s", g_NetLocalClient->settings.name, text); + char *nl = strchr(tmp, '\n'); + if (nl) { + *nl = ' '; + } + + if (g_NetMode == NETMODE_SERVER) { + sysLogPrintf(LOG_CHAT, tmp); + netmsgSvcChatWrite(&g_NetMsgRel, tmp); + } else { + netmsgClcChatWrite(&g_NetMsgRel, tmp); + } +} diff --git a/port/src/net/netbuf.c b/port/src/net/netbuf.c index de9836973..ceb48443e 100644 --- a/port/src/net/netbuf.c +++ b/port/src/net/netbuf.c @@ -132,6 +132,28 @@ u32 netbufReadCoord(struct netbuf *buf, struct coord *out) return 0; } +u32 netbufReadMtxf(struct netbuf *buf, Mtxf *out) +{ + if (netbufCanRead(buf, sizeof(*out))) { + for (s32 i = 0; i < 4; ++i) { + for (s32 j = 0; j < 4; ++j) { + out->m[i][j] = netbufReadF32(buf); + } + } + return sizeof(*out); + } + return 0; +} + +u32 netbufReadSkip(struct netbuf *buf, u32 count) +{ + if (netbufCanRead(buf, count)) { + buf->rp += count; + return count; + } + return 0; +} + /* write */ void netbufStartWrite(struct netbuf *buf) @@ -264,6 +286,19 @@ u32 netbufWriteCoord(struct netbuf *buf, const struct coord *v) return 0; } +u32 netbufWriteMtxf(struct netbuf *buf, const Mtxf *in) +{ + if (netbufCanWrite(buf, sizeof(*in))) { + for (s32 i = 0; i < 4; ++i) { + for (s32 j = 0; j < 4; ++j) { + netbufWriteF32(buf, in->m[i][j]); + } + } + return sizeof(*in); + } + return 0; +} + void netbufReset(struct netbuf *buf) { memset(buf, 0, sizeof(*buf)); diff --git a/port/src/net/netmenu.c b/port/src/net/netmenu.c index 7384a16a4..2ad41666c 100644 --- a/port/src/net/netmenu.c +++ b/port/src/net/netmenu.c @@ -15,6 +15,7 @@ #include "net/net.h" extern MenuItemHandlerResult menuhandlerMainMenuCombatSimulator(s32 operation, struct menuitem *item, union handlerdata *data); +extern MenuItemHandlerResult menuhandlerMpAdvancedSetup(s32 operation, struct menuitem *item, union handlerdata *data); static s32 g_NetMenuMaxPlayers = NET_MAX_CLIENTS; static s32 g_NetMenuPort = NET_DEFAULT_PORT; @@ -44,6 +45,7 @@ static MenuItemHandlerResult menuhandlerHostStart(s32 operation, struct menuitem if (operation == MENUOP_SET) { if (netStartServer(g_NetMenuPort, g_NetMenuMaxPlayers) == 0) { menuhandlerMainMenuCombatSimulator(MENUOP_SET, NULL, NULL); + menuhandlerMpAdvancedSetup(MENUOP_SET, NULL, NULL); } } diff --git a/port/src/net/netmsg.c b/port/src/net/netmsg.c index 6ef1cd013..b8ab0d05e 100644 --- a/port/src/net/netmsg.c +++ b/port/src/net/netmsg.c @@ -1,36 +1,71 @@ #include +#include #include "types.h" #include "data.h" #include "bss.h" #include "lib/main.h" +#include "lib/mtx.h" +#include "lib/model.h" #include "game/mplayer/mplayer.h" #include "game/chr.h" #include "game/chraction.h" +#include "game/prop.h" +#include "game/propobj.h" +#include "game/player.h" +#include "game/playermgr.h" +#include "game/bondgun.h" +#include "game/game_0b0fd0.h" +#include "game/inv.h" +#include "game/menu.h" +#include "game/setup.h" +#include "game/setuputils.h" +#include "game/modelmgr.h" +#include "game/propsnd.h" #include "system.h" +#include "console.h" #include "net/net.h" #include "net/netbuf.h" #include "net/netmsg.h" -extern s32 g_StageNum; extern u64 g_RngSeed; extern u64 g_Rng2Seed; /* utils */ -static inline u32 netbufWriteRooms(struct netbuf *buf, const s16 *rooms, const s32 num) { +static inline u32 netbufWriteRooms(struct netbuf *buf, const s16 *rooms, const s32 num) +{ for (s32 i = 0; i < num; ++i) { netbufWriteS16(buf, rooms[i]); + if (rooms[i] < 0) { + break; + } } return buf->error; } -static inline u32 netbufReadRooms(struct netbuf *buf, s16 *rooms, const s32 num) { +static inline u32 netbufReadRooms(struct netbuf *buf, s16 *rooms, const s32 num) +{ for (s32 i = 0; i < num; ++i) { rooms[i] = netbufReadS16(buf); + if (rooms[i] < 0) { + break; + } } return buf->error; } +static inline u32 netbufWriteGset(struct netbuf *buf, const struct gset *gset) +{ + netbufWriteData(buf, gset, sizeof(*gset)); + return buf->error; +} + +static inline u32 netbufReadGset(struct netbuf *buf, struct gset *gset) +{ + netbufReadData(buf, gset, sizeof(*gset)); + return buf->error; +} + static inline u32 netbufWritePlayerMove(struct netbuf *buf, const struct netplayermove *in) { netbufWriteU32(buf, in->tick); @@ -39,12 +74,12 @@ static inline u32 netbufWritePlayerMove(struct netbuf *buf, const struct netplay netbufWriteF32(buf, in->crouchofs); netbufWriteF32(buf, in->movespeed[0]); netbufWriteF32(buf, in->movespeed[1]); - netbufWriteF32(buf, in->lookspeed[0]); - netbufWriteF32(buf, in->lookspeed[1]); netbufWriteF32(buf, in->angles[0]); netbufWriteF32(buf, in->angles[1]); + netbufWriteF32(buf, in->crosspos[0]); + netbufWriteF32(buf, in->crosspos[1]); + netbufWriteS8(buf, in->weaponnum); netbufWriteCoord(buf, &in->pos); - netbufWriteRooms(buf, in->rooms, ARRAYCOUNT(in->rooms)); return buf->error; } @@ -56,24 +91,61 @@ static inline u32 netbufReadPlayerMove(struct netbuf *buf, struct netplayermove in->crouchofs = netbufReadF32(buf); in->movespeed[0] = netbufReadF32(buf); in->movespeed[1] = netbufReadF32(buf); - in->lookspeed[0] = netbufReadF32(buf); - in->lookspeed[1] = netbufReadF32(buf); in->angles[0] = netbufReadF32(buf); in->angles[1] = netbufReadF32(buf); + in->crosspos[0] = netbufReadF32(buf); + in->crosspos[1] = netbufReadF32(buf); + in->weaponnum = netbufReadS8(buf); netbufReadCoord(buf, &in->pos); - netbufReadRooms(buf, in->rooms, ARRAYCOUNT(in->rooms)); return buf->error; } +static inline u32 netbufWritePropPtr(struct netbuf *buf, const struct prop *prop) +{ + netbufWriteU32(buf, prop ? prop->syncid : 0); + return buf->error; +} + +static inline struct prop *netbufReadPropPtr(struct netbuf *buf) +{ + const u32 syncid = netbufReadU32(buf); + if (syncid == 0) { + return NULL; + } + + // TODO: make a map or something + for (s32 i = 0; i < g_Vars.maxprops; ++i) { + if (g_Vars.props[i].syncid == syncid) { + return &g_Vars.props[i]; + } + } + + sysLogPrintf(LOG_WARNING, "NET: prop with syncid %u does not exist", syncid); + return NULL; +} + +static inline s32 propRoomsEqual(const RoomNum *ra, const RoomNum *rb) +{ + for (s32 i = 0; i < 8; ++i) { + if (ra[i] != rb[i]) { + return 0; + } + if (ra[i] == -1) { + break; + } + } + return 1; +} + /* client -> server */ u32 netmsgClcAuthWrite(struct netbuf *dst) { netbufWriteU8(dst, CLC_AUTH); netbufWriteU8(dst, 1); // TODO: number of local players - netbufWriteU8(dst, g_PlayerConfigsArray[0].base.mpbodynum); - netbufWriteU8(dst, g_PlayerConfigsArray[0].base.mpheadnum); - netbufWriteStr(dst, g_PlayerConfigsArray[0].base.name); + netbufWriteU8(dst, g_NetLocalClient->settings.bodynum); + netbufWriteU8(dst, g_NetLocalClient->settings.headnum); + netbufWriteStr(dst, g_NetLocalClient->settings.name); return dst->error; } @@ -104,9 +176,31 @@ u32 netmsgClcAuthRead(struct netbuf *src, struct netclient *srccl) netmsgSvcAuthWrite(&srccl->out, srccl); netSend(srccl, NULL, true); + sysLogPrintf(LOG_CHAT, "NET: %s (client %u) joined", srccl->settings.name, srccl->id); + return 0; } +u32 netmsgClcChatWrite(struct netbuf *dst, const char *str) +{ + netbufWriteU8(dst, CLC_CHAT); + netbufWriteStr(dst, str); + return dst->error; +} + +u32 netmsgClcChatRead(struct netbuf *src, struct netclient *srccl) +{ + char tmp[1024]; + const char *msg = netbufReadStr(src); + if (msg && !src->error) { + sysLogPrintf(LOG_CHAT, msg); + netbufStartWrite(&g_NetMsgRel); + netmsgSvcChatWrite(&g_NetMsgRel, msg); + netSend(NULL, &g_NetMsgRel, true); + } + return src->error; +} + u32 netmsgClcMoveWrite(struct netbuf *dst) { netbufWriteU8(dst, CLC_MOVE); @@ -117,14 +211,16 @@ u32 netmsgClcMoveWrite(struct netbuf *dst) u32 netmsgClcMoveRead(struct netbuf *src, struct netclient *srccl) { + struct netplayermove newmove; + const u32 outmoveack = netbufReadU32(src); + netbufReadPlayerMove(src, &newmove); + if (srccl->state != CLSTATE_GAME) { - sysLogPrintf(LOG_WARNING, "NET: CLC_MOVE from %s, who is not in CLSTATE_GAME", netFormatClientAddr(srccl)); + // silently ignore return src->error; } - struct netplayermove newmove; - srccl->outmoveack = netbufReadU32(src); - netbufReadPlayerMove(src, &newmove); + srccl->outmoveack = outmoveack; if (!src->error) { // enforce teleports and such @@ -175,7 +271,7 @@ u32 netmsgSvcAuthRead(struct netbuf *src, struct netclient *srccl) const u8 id = netbufReadU8(src); const u8 maxclients = netbufReadU8(src); g_NetTick = netbufReadU32(src); - if (g_NetLocalClient->in.error || id == 0xFF || id == 0 || maxclients == 0) { + if (g_NetLocalClient->in.error || id == NET_NULL_CLIENT || id == 0 || maxclients == 0) { sysLogPrintf(LOG_WARNING, "NET: malformed SVC_AUTH from server"); return 1; } @@ -205,6 +301,23 @@ u32 netmsgSvcAuthRead(struct netbuf *src, struct netclient *srccl) return src->error; } +u32 netmsgSvcChatWrite(struct netbuf *dst, const char *str) +{ + netbufWriteU8(dst, SVC_CHAT); + netbufWriteStr(dst, str); + return dst->error; +} + +u32 netmsgSvcChatRead(struct netbuf *src, struct netclient *srccl) +{ + char tmp[1024]; + const char *msg = netbufReadStr(src); + if (msg && !src->error) { + sysLogPrintf(LOG_CHAT, msg); + } + return src->error; +} + u32 netmsgSvcStageStartWrite(struct netbuf *dst) { netbufWriteU8(dst, SVC_STAGE_START); @@ -216,6 +329,11 @@ u32 netmsgSvcStageStartWrite(struct netbuf *dst) netbufWriteU8(dst, g_StageNum); + if (g_StageNum == STAGE_TITLE || g_StageNum == STAGE_CITRAINING) { + // going back to lobby, don't need anything else + return dst->error; + } + // game settings netbufWriteU8(dst, 0); // 0 for combat sim TODO: coop/anti netbufWriteU8(dst, g_MpSetup.scenario); @@ -235,6 +353,10 @@ u32 netmsgSvcStageStartWrite(struct netbuf *dst) netbufWriteU8(dst, ncl->settings.bodynum); netbufWriteU8(dst, ncl->settings.headnum); netbufWriteStr(dst, ncl->settings.name); + memset(ncl->inmove, 0, sizeof(ncl->inmove)); + memset(ncl->outmove, 0, sizeof(ncl->outmove)); + ncl->lerpticks = 0; + ncl->outmoveack = 0; ncl->state = CLSTATE_GAME; } } @@ -255,8 +377,13 @@ u32 netmsgSvcStageStartRead(struct netbuf *src, struct netclient *srccl) g_Rng2Seed = netbufReadU64(src); const u8 stagenum = netbufReadU8(src); - const u8 mode = netbufReadU8(src); // TODO: coop, anti + if (stagenum == STAGE_TITLE || stagenum == STAGE_CITRAINING) { + // server went back to lobby, we don't really care + return src->error; + } + + const u8 mode = netbufReadU8(src); // TODO: coop, anti g_MpSetup.stagenum = stagenum; g_MpSetup.scenario = netbufReadU8(src); g_MpSetup.scorelimit = netbufReadU8(src); @@ -325,8 +452,16 @@ u32 netmsgSvcStageEndWrite(struct netbuf *dst) struct netclient *ncl = &g_NetClients[i]; if (ncl->state) { ncl->state = CLSTATE_LOBBY; - ncl->player = NULL; - ncl->config = NULL; + ncl->playernum = 0; + if (ncl->player) { + ncl->player->client = NULL; + ncl->player->isremote = false; + ncl->player = NULL; + } + if (ncl->config) { + ncl->config->client = NULL; + ncl->config = NULL; + } } } @@ -359,6 +494,9 @@ u32 netmsgSvcPlayerMoveWrite(struct netbuf *dst, struct netclient *movecl) netbufWriteU8(dst, movecl->id); netbufWriteU32(dst, movecl->inmove[0].tick); netbufWritePlayerMove(dst, &movecl->outmove[0]); + if (movecl->outmove[0].ucmd & UCMD_FL_FORCEMASK) { + netbufWriteRooms(dst, movecl->player->prop->rooms, ARRAYCOUNT(movecl->player->prop->rooms)); + } return dst->error; } @@ -373,10 +511,14 @@ u32 netmsgSvcPlayerMoveRead(struct netbuf *src, struct netclient *srccl) u8 id = 0; u32 outmoveack = 0; struct netplayermove newmove; + RoomNum newrooms[8] = { -1 }; id = netbufReadU8(src); outmoveack = netbufReadU32(src); netbufReadPlayerMove(src, &newmove); + if (newmove.ucmd & UCMD_FL_FORCEMASK) { + netbufReadRooms(src, newrooms, ARRAYCOUNT(newrooms)); + } if (src->error) { return src->error; @@ -393,11 +535,813 @@ u32 netmsgSvcPlayerMoveRead(struct netbuf *src, struct netclient *srccl) if (movecl == g_NetLocalClient && (newmove.ucmd & UCMD_FL_FORCEMASK)) { // server wants to teleport us if (movecl->player && movecl->player->prop) { - RoomNum newrooms[8] = { newmove.rooms[0], -1 }; chrSetPos(movecl->player->prop->chr, &newmove.pos, newrooms, newmove.angles[0], (newmove.ucmd & UCMD_FL_FORCEGROUND) != 0); - sysLogPrintf(LOG_NOTE, "NET: server is forcing us to move"); } } return src->error; } + +u32 netmsgSvcPlayerStatsWrite(struct netbuf *dst, struct netclient *actcl) +{ + if (actcl->state < CLSTATE_GAME || !actcl->player || !actcl->player->prop) { + return dst->error; + } + const struct player *pl = actcl->player; + const u8 flags = (pl->isdead != 0) | (pl->gunctrl.dualwielding << 1) | + (pl->hands[0].inuse << 2) | (pl->hands[1].inuse << 3); + netbufWriteU8(dst, SVC_PLAYER_STATS); + netbufWriteU8(dst, actcl->id); + netbufWriteU8(dst, flags); + netbufWriteS8(dst, pl->gunctrl.weaponnum); + netbufWriteF32(dst, pl->prop->chr->damage); + netbufWriteF32(dst, pl->bondhealth); + netbufWriteF32(dst, pl->prop->chr->cshield); + netbufWriteCoord(dst, &pl->bondshotspeed); + + for (s32 i = 0; i < 2; ++i) { + if (pl->hands[i].inuse) { + netbufWriteS16(dst, pl->hands[i].loadedammo[0]); + netbufWriteS16(dst, pl->hands[i].loadedammo[1]); + } + } + + // assemble a bitmask of all non-zero ammo entries below 32 + u32 mask = 0; + for (u32 i = 0; i < 32; ++i) { + if (pl->ammoheldarr[i]) { + mask |= (1 << i); + } + } + + netbufWriteU32(dst, mask); + + // write all entries that are non-zero or have index above 32 + for (s32 i = 0; i < ARRAYCOUNT(pl->ammoheldarr); ++i) { + if (pl->ammoheldarr[i] || i >= 32) { + netbufWriteS16(dst, pl->ammoheldarr[i]); + } + } + + return dst->error; +} + +u32 netmsgSvcPlayerStatsRead(struct netbuf *src, struct netclient *srccl) +{ + const u8 clid = netbufReadU8(src); + const u8 flags = netbufReadU8(src); + const s8 newweaponnum = netbufReadS8(src); + const f32 newdamage = netbufReadF32(src); + const f32 newhealth = netbufReadF32(src); + const f32 newshield = netbufReadF32(src); + struct coord newshotspeed; netbufReadCoord(src, &newshotspeed); + const bool handused[2] = { (flags & (1 << 2)) != 0, (flags & (1 << 3)) != 0 }; + + if (src->error) { + return src->error; + } + + struct netclient *actcl = g_NetClients + clid; + struct player *pl = actcl->player; + if (!pl || !pl->prop) { + return src->error; + } + + pl->prop->chr->damage = newdamage; + pl->prop->chr->cshield = newshield; + pl->bondhealth = newhealth; + pl->bondshotspeed = newshotspeed; + + for (s32 i = 0; i < 2; ++i) { + if (handused[i]) { + pl->hands[i].loadedammo[0] = netbufReadS16(src); + pl->hands[i].loadedammo[1] = netbufReadS16(src); + } + } + + const u32 ammomask = netbufReadU32(src); + for (s32 i = 0; i < ARRAYCOUNT(pl->ammoheldarr); ++i) { + if (i >= 32 || (ammomask & (1 << i))) { + pl->ammoheldarr[i] = netbufReadS16(src); + } else { + pl->ammoheldarr[i] = 0; + } + } + + const s32 prevplayernum = g_Vars.currentplayernum; + setCurrentPlayerNum(actcl->playernum); + + const bool newisdead = (flags & (1 << 0)) != 0; + if (!pl->isdead && newisdead) { + s16 shooter; + if (pl->prop->chr->lastshooter >= 0 && pl->prop->chr->timeshooter > 0) { + shooter = pl->prop->chr->lastshooter; + } else { + shooter = g_Vars.currentplayernum; + } + playerDieByShooter(shooter, true); + } else if (pl->isdead && !newisdead) { + playerStartNewLife(); + } + + const bool dualwielding = (flags & (1 << 1)) != 0; + if (!pl->isdead && (newweaponnum != pl->gunctrl.weaponnum || dualwielding != pl->gunctrl.dualwielding)) { + bgunEquipWeapon(newweaponnum); + pl->gunctrl.dualwielding = dualwielding; + } + + setCurrentPlayerNum(prevplayernum); + + return src->error; +} + +u32 netmsgSvcPropMoveWrite(struct netbuf *dst, struct prop *prop, struct coord *initrot) +{ + u8 flags = (prop->obj != NULL); + + struct projectile *projectile = NULL; + if (prop->obj) { + if (prop->obj->hidden & OBJHFLAG_EMBEDDED) { + projectile = prop->obj->embedment->projectile; + } else if (prop->obj->hidden & OBJHFLAG_PROJECTILE) { + projectile = prop->obj->projectile; + } + if (projectile) { + flags |= (1 << 1); + if (initrot) { + flags |= (1 << 2); + } + if (prop->obj->type == OBJTYPE_HOVERPROP || prop->obj->type == OBJTYPE_HOVERBIKE) { + flags |= (1 << 3); + } + } + } + + netbufWriteU8(dst, SVC_PROP_MOVE); + netbufWriteU8(dst, flags); + netbufWritePropPtr(dst, prop); + netbufWriteCoord(dst, &prop->pos); + netbufWriteRooms(dst, prop->rooms, ARRAYCOUNT(prop->rooms)); + if (projectile) { + netbufWriteCoord(dst, &projectile->speed); + netbufWriteF32(dst, projectile->unk0dc); + netbufWriteU32(dst, projectile->flags); + netbufWriteS8(dst, projectile->bouncecount); + netbufWritePropPtr(dst, projectile->ownerprop); + if (initrot) { + netbufWriteCoord(dst, initrot); + } + if (prop->obj->type == OBJTYPE_HOVERPROP || prop->obj->type == OBJTYPE_HOVERBIKE) { + netbufWriteF32(dst, projectile->unk08c); + netbufWriteF32(dst, projectile->unk098); + netbufWriteF32(dst, projectile->unk0e0); + netbufWriteF32(dst, projectile->unk0e4); + netbufWriteF32(dst, projectile->unk0ec); + netbufWriteF32(dst, projectile->unk0f0); + } + } + + return dst->error; +} + +u32 netmsgSvcPropMoveRead(struct netbuf *src, struct netclient *srccl) +{ + const u8 flags = netbufReadU8(src); + struct prop *prop = netbufReadPropPtr(src); + struct coord pos; netbufReadCoord(src, &pos); + RoomNum rooms[8] = { -1 }; netbufReadRooms(src, rooms, ARRAYCOUNT(rooms)); + + if (src->error || !prop) { + return src->error; + } + + prop->pos = pos; + + if (!propRoomsEqual(rooms, prop->rooms)) { + if (prop->active) { + propDeregisterRooms(prop); + } + roomsCopy(rooms, prop->rooms); + if (prop->active) { + propRegisterRooms(prop); + } + } + + if (!(flags & (1 << 0))) { + return src->error; + } + + if (!prop->obj) { + sysLogPrintf(LOG_WARNING, "NET: SVC_PROP_MOVE: prop %u should have an obj, but doesn't", prop->syncid); + return 1; + } + + if (!(flags & (1 << 1))) { + return src->error; + } + + // create a projectile for this prop if it isn't already there + func0f0685e4(prop); + + struct projectile *projectile = NULL; + if (prop->obj->hidden & OBJHFLAG_EMBEDDED) { + projectile = prop->obj->embedment->projectile; + } else if (prop->obj->hidden & OBJHFLAG_PROJECTILE) { + projectile = prop->obj->projectile; + } + + if (!projectile) { + sysLogPrintf(LOG_WARNING, "NET: SVC_PROP_MOVE: prop %u should have a projectile, but doesn't", prop->syncid); + return 1; + } + + netbufReadCoord(src, &projectile->speed); + projectile->unk0dc = netbufReadF32(src); + projectile->flags = netbufReadU32(src); + projectile->bouncecount = netbufReadS8(src); + projectile->ownerprop = netbufReadPropPtr(src); + + if (flags & (1 << 2)) { + struct coord initrot; netbufReadCoord(src, &initrot); + mtx4LoadRotation(&initrot, &projectile->mtx); + } + + if (flags & (1 << 3)) { + projectile->unk08c = netbufReadF32(src); + projectile->unk098 = netbufReadF32(src); + projectile->unk0e0 = netbufReadF32(src); + projectile->unk0e4 = netbufReadF32(src); + projectile->unk0ec = netbufReadF32(src); + projectile->unk0f0 = netbufReadF32(src); + } + + return src->error; +} + +u32 netmsgSvcPropSpawnWrite(struct netbuf *dst, struct prop *prop) +{ + const u8 msgflags = (prop->active != 0) | ((prop->obj != NULL) << 1) | ((prop->forcetick != 0) << 2); + const u8 objtype = prop->obj ? prop->obj->type : 0; + + netbufWriteU8(dst, SVC_PROP_SPAWN); + netbufWriteU8(dst, msgflags); + netbufWriteU32(dst, prop->syncid); + netbufWriteCoord(dst, &prop->pos); + netbufWriteRooms(dst, prop->rooms, ARRAYCOUNT(prop->rooms)); + netbufWritePropPtr(dst, prop->parent); + netbufWriteU8(dst, prop->type); + netbufWriteU8(dst, objtype); + netbufWriteU8(dst, prop->flags); + + switch (prop->type) { + case PROPTYPE_WEAPON: + // dropped gun or projectile + netbufWriteS16(dst, prop->weapon->base.modelnum); + netbufWriteU8(dst, prop->weapon->weaponnum); + netbufWriteS8(dst, prop->weapon->unk5d); + netbufWriteS8(dst, prop->weapon->unk5e); + netbufWriteU8(dst, prop->weapon->gunfunc); + netbufWriteS16(dst, prop->weapon->timer240); + break; + case PROPTYPE_OBJ: + // we already send most of the important obj stuff below, so + netbufWriteS16(dst, prop->obj->modelnum); + if (objtype == OBJTYPE_AUTOGUN) { + // thrown laptop probably + struct autogunobj *autogun = (struct autogunobj *)prop->obj; + const u8 ownerplayernum = (prop->obj->hidden & 0xf0000000) >> 28; + netbufWriteU8(dst, autogun->ammoquantity); + netbufWriteU8(dst, autogun->firecount); + netbufWriteU8(dst, g_Vars.players[ownerplayernum]->client->id); + } + break; + default: + break; + } + + if (prop->obj) { + netbufWriteU32(dst, prop->obj->flags); + netbufWriteU32(dst, prop->obj->flags2); + netbufWriteU32(dst, prop->obj->flags3); + netbufWriteU32(dst, prop->obj->hidden); + netbufWriteU8(dst, prop->obj->hidden2); + netbufWriteU16(dst, prop->obj->extrascale); + for (s32 i = 0; i < 3; ++i) { + for (s32 j = 0; j < 3; ++j) { + netbufWriteF32(dst, prop->obj->realrot[i][j]); + } + } + if ((prop->obj->hidden & OBJHFLAG_PROJECTILE) && prop->obj->projectile) { + netbufWritePropPtr(dst, prop->obj->projectile->ownerprop); + netbufWriteU32(dst, prop->obj->projectile->flags); + netbufWriteS32(dst, prop->obj->projectile->pickuptimer240); + netbufWriteF32(dst, prop->obj->projectile->unk08c); + netbufWriteS16(dst, prop->obj->projectile->droptype); + netbufWriteMtxf(dst, &prop->obj->projectile->mtx); + } + } + + return dst->error; +} + +u32 netmsgSvcPropSpawnRead(struct netbuf *src, struct netclient *srccl) +{ + const u8 msgflags = netbufReadU8(src); + const u32 syncid = netbufReadU32(src); + struct coord pos; netbufReadCoord(src, &pos); + RoomNum rooms[8] = { -1 }; netbufReadRooms(src, rooms, ARRAYCOUNT(rooms)); + struct prop *parent = netbufReadPropPtr(src); + const u8 type = netbufReadU8(src); + const u8 objtype = netbufReadU8(src); + const u8 propflags = netbufReadU8(src); + + if (src->error) { + return src->error; + } + + struct prop *prop = (type == PROPTYPE_OBJ && objtype == OBJTYPE_AUTOGUN) ? NULL : propAllocate(); + + if (type == PROPTYPE_WEAPON) { + const s16 modelnum = netbufReadS16(src); + const u8 weaponnum = netbufReadU8(src); + const s8 unk5d = netbufReadS8(src); + const s8 unk5e = netbufReadS8(src); + const u8 gunfunc = netbufReadU8(src); + const s16 timer240 = netbufReadS16(src); + setupLoadModeldef(modelnum); + struct modeldef *modeldef = g_ModelStates[modelnum].modeldef; + struct model *model = modelmgrInstantiateModelWithoutAnim(modeldef); + struct weaponobj *weapon = weaponCreate(prop == NULL, model == NULL, modeldef); + struct weaponobj tmp = { + 256, // extrascale + 0, // hidden2 + OBJTYPE_WEAPON, // type + 0, // modelnum + -1, // pad + OBJFLAG_FALL, // flags + 0, // flags2 + 0, // flags3 + NULL, // prop + NULL, // model + 1, 0, 0, // realrot + 0, 1, 0, + 0, 0, 1, + 0, // hidden + NULL, // geo + NULL, // projectile + 0, // damage + 1000, // maxdamage + 0xff, 0xff, 0xff, 0x00, // shadecol + 0xff, 0xff, 0xff, 0x00, // nextcol + 0x0fff, // floorcol + 0, // tiles + 0, // weaponnum + 0, // unk5d + 0, // unk5e + 0, // gunfunc + 0, // fadeouttimer60 + -1, // dualweaponnum + -1, // timer240 + NULL, // dualweapon + }; + *weapon = tmp; + weapon->base.modelnum = modelnum; + weapon->weaponnum = weaponnum; + weapon->unk5d = unk5d; + weapon->unk5e = unk5e; + weapon->gunfunc = gunfunc; + weapon->timer240 = timer240; + prop = func0f08adc8(weapon, modeldef, prop, model); + } else if (type == PROPTYPE_OBJ) { + const s16 modelnum = netbufReadS16(src); + if (objtype == OBJTYPE_AUTOGUN) { + // thrown laptop? + const u8 ammocount = netbufReadU8(src); + const u8 firecount = netbufReadU8(src); + const u8 clid = netbufReadU8(src); + struct chrdata *ownerchr = g_NetClients[clid].player->prop->chr; + struct autogunobj *obj = laptopDeploy(modelnum, NULL, ownerchr); + prop = obj->base.prop; + } + } + + if (prop) { + prop->type = type; + prop->syncid = syncid; + prop->pos = pos; + prop->forcetick = (msgflags & (1 << 2)) != 0; + // prop->flags = propflags; + roomsCopy(rooms, prop->rooms); + propRegisterRooms(prop); + if (msgflags & (1 << 0)) { + propActivate(prop); + } else { + propPause(prop); + } + if (propflags & PROPFLAG_ENABLED) { + propEnable(prop); + } else { + propDisable(prop); + } + if (parent) { + propReparent(prop, parent); + } + } else { + sysLogPrintf(LOG_WARNING, "NET: no prop allocated when spawning prop %u (%u)", syncid, type); + return src->error; + } + + if (msgflags & (1 << 1)) { + const u32 flags = netbufReadU32(src); + const u32 flags2 = netbufReadU32(src); + const u32 flags3 = netbufReadU32(src); + u32 hidden = netbufReadU32(src); + const u8 hidden2 = netbufReadU8(src); + const u16 extrascale = netbufReadU16(src); + for (s32 i = 0; i < 3; ++i) { + for (s32 j = 0; j < 3; ++j) { + prop->obj->realrot[i][j] = netbufReadF32(src); + } + } + if (prop->obj) { + if (hidden & OBJHFLAG_PROJECTILE) { + func0f0685e4(prop); + prop->obj->projectile->ownerprop = netbufReadPropPtr(src); + prop->obj->projectile->flags = netbufReadU32(src); + prop->obj->projectile->pickuptimer240 = netbufReadS32(src); + prop->obj->projectile->unk08c = netbufReadF32(src); + prop->obj->projectile->droptype = netbufReadS16(src); + netbufReadMtxf(src, &prop->obj->projectile->mtx); + if (prop->obj->projectile->flags & PROJECTILEFLAG_00000002) { + // this is a thrown projectile, play throw sound + psCreate(NULL, prop, SFX_THROW, -1, -1, 0, 0, PSTYPE_NONE, NULL, -1, NULL, -1, -1, -1, -1); + } + } + // swap owner player numbers to match server + u8 ownerplayernum = (hidden & 0xf0000000) >> 28; + ownerplayernum = g_NetClients[ownerplayernum].playernum; + hidden = (hidden & 0x0fffffff) | (ownerplayernum << 28); + prop->obj->flags = flags; + prop->obj->flags2 = flags2; + prop->obj->flags3 = flags3; + prop->obj->hidden = hidden; + prop->obj->hidden2 = hidden2; + prop->obj->extrascale = extrascale; + if (prop->obj->model) { + modelSetScale(prop->obj->model, prop->obj->model->scale * ((f32)extrascale / 256.f)); + } + } + } + + return src->error; +} + +u32 netmsgSvcPropDamageWrite(struct netbuf *dst, struct prop *prop, f32 damage, struct coord *pos, s32 weaponnum, s32 playernum) +{ + if (!prop || !prop->obj) { + return dst->error; + } + netbufWriteU8(dst, SVC_PROP_DAMAGE); + netbufWritePropPtr(dst, prop); + netbufWriteCoord(dst, pos); + netbufWriteF32(dst, prop->obj->damage); + netbufWriteF32(dst, damage); + netbufWriteS8(dst, weaponnum); + netbufWriteS8(dst, playernum); + netbufWriteU32(dst, prop->obj->hidden); + return dst->error; +} + +u32 netmsgSvcPropDamageRead(struct netbuf *src, struct netclient *srccl) +{ + struct prop *prop = netbufReadPropPtr(src); + struct coord pos; netbufReadCoord(src, &pos); + const f32 damagepre = netbufReadF32(src); + const f32 damage = netbufReadF32(src); + const s8 weaponnum = netbufReadS8(src); + const s8 playernum = netbufReadS8(src); + const u32 hidden = netbufReadU32(src); + if (prop && prop->obj && prop->type != PROPTYPE_PLAYER && prop->type != PROPTYPE_CHR && !src->error) { + prop->obj->damage = damagepre; + prop->obj->hidden = hidden; + objDamage(prop->obj, -damage, &pos, weaponnum, playernum); + } + return src->error; +} + +u32 netmsgSvcPropPickupWrite(struct netbuf *dst, struct netclient *actcl, struct prop *prop, const s32 tickop) +{ + netbufWriteU8(dst, SVC_PROP_PICKUP); + netbufWriteU8(dst, actcl->id); + netbufWriteS8(dst, tickop); + netbufWritePropPtr(dst, prop); + return dst->error; +} + +u32 netmsgSvcPropPickupRead(struct netbuf *src, struct netclient *srccl) +{ + const u8 clid = netbufReadU8(src); + const s8 tickop = netbufReadS8(src); + struct prop *prop = netbufReadPropPtr(src); + if (src->error || !prop) { + return 1; + } + + struct netclient *actcl = g_NetClients + clid; + + const s32 prevplayernum = g_Vars.currentplayernum; + setCurrentPlayerNum(actcl->playernum); + + propPickupByPlayer(prop, true); + if (tickop != TICKOP_NONE) { + propExecuteTickOperation(prop, tickop); + } + + setCurrentPlayerNum(prevplayernum); + + return src->error; +} + +u32 netmsgSvcPropUseWrite(struct netbuf *dst, struct prop *prop, struct netclient *usercl, const s32 tickop) +{ + netbufWriteU8(dst, SVC_PROP_USE); + netbufWritePropPtr(dst, prop); + netbufWriteU8(dst, usercl->id); + netbufWriteS8(dst, tickop); + return dst->error; +} + +u32 netmsgSvcPropUseRead(struct netbuf *src, struct netclient *srccl) +{ + struct prop *prop = netbufReadPropPtr(src); + const u8 clid = netbufReadU8(src); + const s8 tickop = netbufReadS8(src); + + if (!prop) { + return src->error; + } + + struct netclient *actcl = &g_NetClients[clid]; + + const s32 prevplayernum = g_Vars.currentplayernum; + setCurrentPlayerNum(actcl->playernum); + + s32 ownop; + switch (prop->type) { + case PROPTYPE_OBJ: + case PROPTYPE_WEAPON: + ownop = propobjInteract(prop); + break; + default: + // NOTE: doors are handled with SVC_PROP_DOOR + // TODO: eventually remove this message completely + ownop = TICKOP_NONE; + break; + } + + propExecuteTickOperation(prop, tickop); + + setCurrentPlayerNum(prevplayernum); + + return src->error; +} + +u32 netmsgSvcPropDoorWrite(struct netbuf *dst, struct prop *prop, struct netclient *usercl) +{ + if (prop->type != PROPTYPE_DOOR || !prop->door) { + return dst->error; + } + + struct doorobj *door = prop->door; + + netbufWriteU8(dst, SVC_PROP_DOOR); + netbufWritePropPtr(dst, prop); + netbufWriteU8(dst, usercl ? usercl->id : NET_NULL_CLIENT); + netbufWriteS8(dst, door->mode); + netbufWriteU32(dst, door->base.flags); + netbufWriteU32(dst, door->base.hidden); + + return dst->error; +} + +u32 netmsgSvcPropDoorRead(struct netbuf *src, struct netclient *srccl) +{ + struct prop *prop = netbufReadPropPtr(src); + const u8 clid = netbufReadU8(src); + const s8 doormode = netbufReadS8(src); + const u32 flags = netbufReadU32(src); + const u32 hidden = netbufReadU32(src); + + struct netclient *actcl = (clid == NET_NULL_CLIENT) ? NULL : &g_NetClients[clid]; + + if (!prop) { + return src->error; + } + + if (!prop->door || prop->type != PROPTYPE_DOOR) { + sysLogPrintf(LOG_WARNING, "NET: SVC_PROP_DOOR: prop %u should be a door, but isn't", prop->syncid); + return src->error; + } + + const s32 prevplayernum = g_Vars.currentplayernum; + if (actcl) { + setCurrentPlayerNum(actcl->playernum); + } + + doorSetMode(prop->door, doormode); + prop->door->base.hidden = hidden; + prop->door->base.flags = flags; + + if (actcl) { + setCurrentPlayerNum(prevplayernum); + } + + return src->error; +} + +u32 netmsgSvcChrDamageWrite(struct netbuf *dst, struct chrdata *chr, f32 damage, struct coord *vector, struct gset *gset, + struct prop *aprop, s32 hitpart, bool damageshield, struct prop *prop2, s32 side, s16 *arg11, bool explosion, struct coord *explosionpos) +{ + const u8 flags = damageshield | (explosion << 1) | ((gset != NULL) << 2) | + ((aprop != NULL) << 3) | ((prop2 != NULL) << 4) | ((arg11 != NULL) << 5) | ((explosionpos != NULL) << 6); + + netbufWriteU8(dst, SVC_CHR_DAMAGE); + netbufWriteU8(dst, flags); + netbufWritePropPtr(dst, chr->prop); + netbufWriteF32(dst, damage); + netbufWriteCoord(dst, vector); + netbufWriteS16(dst, hitpart); + netbufWriteS16(dst, side); + if (gset) { + netbufWriteGset(dst, gset); + } + if (aprop) { + netbufWritePropPtr(dst, aprop); + } + if (prop2) { + netbufWritePropPtr(dst, prop2); + } + if (arg11) { + netbufWriteS16(dst, arg11[0]); + netbufWriteS16(dst, arg11[1]); + netbufWriteS16(dst, arg11[2]); + } + if (explosionpos) { + netbufWriteCoord(dst, explosionpos); + } + + return dst->error; +} + +u32 netmsgSvcChrDamageRead(struct netbuf *src, struct netclient *srccl) +{ + const u8 flags = netbufReadU8(src); + struct prop *chrprop = netbufReadPropPtr(src); + const f32 damage = netbufReadF32(src); + struct coord vector; netbufReadCoord(src, &vector); + const s16 hitpart = netbufReadS16(src); + const s16 side = netbufReadS16(src); + + struct gset gsetvalue; + struct gset *gset = NULL; + if (flags & (1 << 2)) { + netbufReadGset(src, &gsetvalue); + gset = &gsetvalue; + } + + struct prop *aprop = (flags & (1 << 3)) ? netbufReadPropPtr(src) : NULL; + struct prop *prop2 = (flags & (1 << 4)) ? netbufReadPropPtr(src) : NULL; + + s16 arg11[3], *arg11ptr = NULL; + if (flags & (1 << 5)) { + arg11[0] = netbufReadS16(src); + arg11[1] = netbufReadS16(src); + arg11[2] = netbufReadS16(src); + arg11ptr = arg11; + } + + struct coord explosionpos, *explosionposptr = NULL; + if (flags & (1 << 6)) { + netbufReadCoord(src, &explosionpos); + explosionposptr = &explosionpos; + } + + if (src->error) { + return src->error; + } + + const bool damageshield = (flags & (1 << 0)) != 0; + const bool explosion = (flags & (1 << 1)) != 0; + + const s32 prevplayernum = g_Vars.currentplayernum; + if (chrprop->type == PROPTYPE_PLAYER) { + setCurrentPlayerNum(playermgrGetPlayerNumByProp(chrprop)); + } + + chrDamage(chrprop->chr, damage, &vector, gset, aprop, hitpart, damageshield, prop2, NULL, NULL, side, arg11ptr, explosion, explosionposptr); + + if (chrprop->type == PROPTYPE_PLAYER) { + setCurrentPlayerNum(prevplayernum); + } + + return src->error; +} + +u32 netmsgSvcChrDisarmWrite(struct netbuf *dst, struct chrdata *chr, struct prop *aprop, u8 weaponnum, f32 wpndamage, struct coord *wpnpos) +{ + netbufWriteU8(dst, SVC_CHR_DISARM); + netbufWritePropPtr(dst, chr->prop); + netbufWritePropPtr(dst, aprop); + netbufWriteU8(dst, weaponnum); + netbufWriteF32(dst, wpndamage); + if (wpndamage > 0.f && wpnpos) { + netbufWriteCoord(dst, wpnpos); + } + return dst->error; +} + +u32 netmsgSvcChrDisarmRead(struct netbuf *src, struct netclient *srccl) +{ + struct prop *chrprop = netbufReadPropPtr(src); + struct prop *aprop = netbufReadPropPtr(src); + const u8 weaponnum = netbufReadU8(src); + const f32 weapondmg = netbufReadF32(src); + struct coord pos = { 0.f, 0.f, 0.f }; + + if (src->error) { + return src->error; + } + + if (!chrprop || !chrprop->chr) { + return 1; + } + + struct chrdata *chr = chrprop->chr; + + if (chrprop->type == PROPTYPE_CHR) { + return src->error; + } + + if (weapondmg > 0.f) { + // someone shot a grenade the chr is holding, explode that shit + netbufReadCoord(src, &pos); + struct weaponobj *weapon = NULL; + if (chr->weapons_held[0] && chr->weapons_held[0]->weapon) { + weapon = chr->weapons_held[0]->weapon; + } else if (chr->weapons_held[1] && chr->weapons_held[1]->weapon) { + weapon = chr->weapons_held[1]->weapon; + } else { + sysLogPrintf(LOG_WARNING, "NET: trying to explode chr %u's gun, but there's no gun", chrprop->syncid); + return src->error; + } + objSetDropped(chrprop, DROPTYPE_DEFAULT); + chr->hidden |= CHRHFLAG_DROPPINGITEM; + objDamage(&weapon->base, -weapondmg, &pos, weaponnum, g_Vars.currentplayernum); + return src->error; + } + + const s32 prevplayernum = g_Vars.currentplayernum; + setCurrentPlayerNum(playermgrGetPlayerNumByProp(chrprop)); + + struct player *player = g_Vars.currentplayer; + + if (weaponHasFlag(weaponnum, WEAPONFLAG_UNDROPPABLE) || weaponnum > WEAPON_RCP45 || weaponnum <= WEAPON_UNARMED) { + setCurrentPlayerNum(prevplayernum); + return src->error; + } + + if (weaponnum == WEAPON_RCP120) { + player->devicesactive &= ~DEVICE_CLOAKRCP120; + } + + if (weaponnum == WEAPON_CLOAKINGDEVICE) { + player->devicesactive &= ~DEVICE_CLOAKDEVICE; + } + + weaponDeleteFromChr(chr, HAND_RIGHT); + weaponDeleteFromChr(chr, HAND_LEFT); + + invRemoveItemByNum(weaponnum); + + player->hands[1].state = HANDSTATE_IDLE; + player->hands[1].ejectstate = EJECTSTATE_INIT; + player->hands[1].ejecttype = EJECTTYPE_GUN; + player->hands[0].ejectstate = EJECTSTATE_INIT; + player->hands[0].ejecttype = EJECTTYPE_GUN; + player->hands[0].state = HANDSTATE_IDLE; + + if (player->visionmode == VISIONMODE_SLAYERROCKET) { + struct weaponobj *rocket = g_Vars.currentplayer->slayerrocket; + if (rocket && rocket->base.prop) { + rocket->timer240 = 0; + } + player->visionmode = VISIONMODE_NORMAL; + } + + bgunEquipWeapon2(HAND_RIGHT, WEAPON_UNARMED); + bgunEquipWeapon2(HAND_LEFT, WEAPON_NONE); + + setCurrentPlayerNum(prevplayernum); + + return src->error; +} diff --git a/src/game/bondgun.c b/src/game/bondgun.c index 3fc0fb140..d3c50f626 100644 --- a/src/game/bondgun.c +++ b/src/game/bondgun.c @@ -56,6 +56,9 @@ #ifndef PLATFORM_N64 #include "game/stagetable.h" #include "video.h" +#include "net/net.h" +#include "net/netmsg.h" +#include "system.h" #endif #define GUNLOADSTATE_FLUX 0 @@ -4472,7 +4475,7 @@ struct defaultobj *bgunCreateThrownProjectile2(struct chrdata *chr, struct gset * 2 = fumbling grenade from right hand (due to nbomb) * 3 = fumbling grenade from left hand (actually not possible) */ -void bgunCreateThrownProjectile(s32 handnum, struct gset *gset) +struct defaultobj *bgunCreateThrownProjectile(s32 handnum, struct gset *gset) { struct coord velocity = {0, 0, 0}; Mtxf sp1f4; @@ -4502,6 +4505,13 @@ void bgunCreateThrownProjectile(s32 handnum, struct gset *gset) struct trainingdata *data; u32 stack; +#ifndef PLATFORM_N64 + // don't do anything if we're not the authority + if (g_NetMode == NETMODE_CLIENT) { + return NULL; + } +#endif + if (handnum >= 2) { droppinggrenade = true; handnum -= 2; @@ -4667,7 +4677,16 @@ void bgunCreateThrownProjectile(s32 handnum, struct gset *gset) weapon->base.hidden |= OBJHFLAG_THROWNKNIFE; } } + +#ifndef PLATFORM_N64 + if (obj && g_NetMode == NETMODE_SERVER) { + netmsgSvcPropSpawnWrite(&g_NetMsgRel, obj->prop); + netmsgSvcPropMoveWrite(&g_NetMsgRel, obj->prop, NULL); + } +#endif } + + return obj; } void bgunUpdateHeldRocket(s32 handnum) @@ -4771,6 +4790,12 @@ void bgunCreateFiredProjectile(s32 handnum) f32 spd4[4]; f32 spc4[4]; +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_CLIENT) { + return; + } +#endif + hand = g_Vars.currentplayer->hands + handnum; playerprop = g_Vars.currentplayer->prop; @@ -4983,6 +5008,13 @@ void bgunCreateFiredProjectile(s32 handnum) weapon->base.prop = NULL; weapon->base.model = NULL; } + +#ifndef PLATFORM_N64 + if (!failed && g_NetMode == NETMODE_SERVER) { + netmsgSvcPropSpawnWrite(&g_NetMsgRel, weapon->base.prop); + netmsgSvcPropMoveWrite(&g_NetMsgRel, weapon->base.prop, NULL); + } +#endif #else // NTSC beta doesn't have any of the failure checks Mtxf sp78; @@ -6313,6 +6345,13 @@ void bgunDisarm(struct prop *attackerprop) s32 modelnum; s32 i; bool drop; + struct defaultobj *obj; +#ifndef PLATFORM_N64 + // don't do anything if we're not the authority + if (g_NetMode == NETMODE_CLIENT) { + return; + } +#endif if (!weaponHasFlag(weaponnum, WEAPONFLAG_UNDROPPABLE) && weaponnum <= WEAPON_RCP45) { #if VERSION >= VERSION_NTSC_1_0 @@ -6330,6 +6369,12 @@ void bgunDisarm(struct prop *attackerprop) return; } +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER) { + netmsgSvcChrDisarmWrite(&g_NetMsgRel, player->prop->chr, attackerprop, weaponnum, 0.f, NULL); + } +#endif + chr = player->prop->chr; drop = true; @@ -6358,7 +6403,7 @@ void bgunDisarm(struct prop *attackerprop) && player->hands[i].stateminor == HANDSTATEMINOR_ATTACK_THROW_0) { #endif drop = false; - bgunCreateThrownProjectile(i + 2, &player->hands[i].gset); + obj = bgunCreateThrownProjectile(i + 2, &player->hands[i].gset); } } } @@ -6382,6 +6427,13 @@ void bgunDisarm(struct prop *attackerprop) } objDrop(prop2, true); + +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER) { + netmsgSvcPropSpawnWrite(&g_NetMsgRel, prop2); + netmsgSvcPropMoveWrite(&g_NetMsgRel, prop2, NULL); + } +#endif } } @@ -11734,7 +11786,7 @@ void bgunSetTriggerOn(s32 handnum, bool on) s32 bgunConsiderToggleGunFunction(s32 usedowntime, bool trigpressed, bool fromactivemenu, bool fromdedicatedbutton) { #ifndef PLATFORM_N64 - const bool extcontrols = PLAYER_EXTCFG().extcontrols; + const bool extcontrols = PLAYER_EXTCFG().extcontrols || g_Vars.currentplayer->isremote; #endif switch (bgunGetWeaponNum(HAND_RIGHT)) { case WEAPON_SNIPERRIFLE: @@ -11795,6 +11847,8 @@ s32 bgunConsiderToggleGunFunction(s32 usedowntime, bool trigpressed, bool fromac g_Vars.currentplayer->hands[HAND_RIGHT].activatesecondary = true; } + sysLogPrintf(LOG_NOTE, "switchan %d -> %d", bgunIsUsingSecondaryFunction(), fromdedicatedbutton); + return USETIMER_STOP; case WEAPON_MAULER: case WEAPON_CMP150: @@ -11841,7 +11895,7 @@ void bgun0f0a8c50(void) case WEAPON_LAPTOPGUN: case WEAPON_DRAGON: case WEAPON_REMOTEMINE: - if (PLAYER_EXTCFG().extcontrols) { + if (PLAYER_EXTCFG().extcontrols || g_Vars.currentplayer->isremote) { return; } break; diff --git a/src/game/bondmove.c b/src/game/bondmove.c index 7a5314979..1f3fb8169 100644 --- a/src/game/bondmove.c +++ b/src/game/bondmove.c @@ -43,7 +43,9 @@ #include "input.h" #include "video.h" #include "system.h" +#include "utils.h" #include "net/net.h" +#include "net/netmsg.h" static void bgunProcessQuickDetonate(struct movedata *data, u32 c1buttons, u32 c1buttonsthisframe, u32 buttons1, u32 buttons2) { if ((((c1buttons & (buttons1)) && (c1buttonsthisframe & (buttons2))) @@ -107,10 +109,23 @@ static void bgunProcessInputAltButton(struct movedata *data, s8 contpad, s32 i) } } -static inline void bmoveProcessRemoteInput(void) +static inline void bmoveProcessRemoteInput(const bool allowc1buttons) { struct player *pl = g_Vars.currentplayer; struct netplayermove *inmove = &pl->client->inmove[0]; + struct netplayermove *inmoveprev = &pl->client->inmove[1]; + s32 moveticks = inmove->tick - inmoveprev->tick; + if (moveticks > g_NetInterpTicks) { + moveticks = g_NetInterpTicks; + } + + if (!inmove->tick) { + // no input + inmove->pos = inmoveprev->pos = pl->prop->pos; + inmove->angles[0] = inmoveprev->angles[0] = pl->vv_theta; + inmove->angles[1] = inmoveprev->angles[1] = pl->vv_verta; + inmove->ucmd = 0; + } if (inmove->ucmd & UCMD_DUCK) { pl->crouchpos = CROUCHPOS_DUCK; @@ -122,19 +137,47 @@ static inline void bmoveProcessRemoteInput(void) pl->bondactivateorreload = 0; + pl->eyesshut = (inmove->ucmd & UCMD_EYESSHUT) != 0; + + pl->speedtheta = 0.f; // TODO: figure out if anglespeed is even required + pl->speedverta = 0.f; + pl->crouchoffset = inmove->crouchofs; + + pl->oldcrosspos[0] = pl->crosspos[0]; + pl->oldcrosspos[1] = pl->crosspos[1]; + pl->crosspos[0] = inmove->crosspos[0]; + pl->crosspos[1] = inmove->crosspos[1]; + + if (inmove->ucmd & UCMD_SELECT) { + bgunEquipWeapon(inmove->weaponnum); + } + + const bool fireguns = (inmove->ucmd & UCMD_FIRE) && + !g_Vars.currentplayer->waitforzrelease && allowc1buttons; + + if ((inmove->ucmd & UCMD_SECONDARY) && !bgunIsUsingSecondaryFunction()) { + bgunConsiderToggleGunFunction(0, false, false, true); + } else if (!(inmove->ucmd & UCMD_SECONDARY) && bgunIsUsingSecondaryFunction()) { + // do not switch back to primary if in the process of throwing laptop + if (!(bgunGetWeaponNum(HAND_RIGHT) == WEAPON_LAPTOPGUN && pl->hands[HAND_RIGHT].state == HANDSTATE_ATTACK)) { + bgunConsiderToggleGunFunction(0, false, false, true); + } + } + + bgunTickGameplay(fireguns); + + if (fireguns && g_NetMode == NETMODE_SERVER) { + netmsgSvcPlayerStatsWrite(&g_NetMsgRel, pl->client); + } + + if (inmove->ucmd & UCMD_RELOAD) { + pl->bondactivateorreload |= JO_ACTION_RELOAD; + } + if (g_NetMode == NETMODE_SERVER) { if (inmove->ucmd & UCMD_ACTIVATE) { pl->bondactivateorreload |= JO_ACTION_ACTIVATE; } - if (inmove->ucmd & UCMD_RELOAD) { - pl->bondactivateorreload |= JO_ACTION_RELOAD; - } - - if (pl->bondactivateorreload) { - bmoveHandleActivate(); - } - - bgunTickGameplay((inmove->ucmd & UCMD_FIRE) != 0); if (g_Vars.bondvisible && (bgunIsFiring(HAND_RIGHT) || bgunIsFiring(HAND_LEFT))) { f32 noiseradius = 0.f; @@ -150,16 +193,18 @@ static inline void bmoveProcessRemoteInput(void) bgunSetSightVisible(GUNSIGHTREASON_NOTAIMING, (inmove->ucmd & UCMD_AIMMODE) != 0); - pl->speedgo = inmove->movespeed[0]; - pl->speedforwards = inmove->movespeed[0]; - pl->speedstrafe = inmove->movespeed[1]; - pl->speedsideways = inmove->movespeed[1]; - pl->speedtheta = inmove->lookspeed[0]; - pl->speedverta = inmove->lookspeed[1]; - pl->vv_theta = inmove->angles[0]; - pl->vv_verta = inmove->angles[1]; + const bool forcepos = !inmoveprev->tick || !moveticks || (inmove->ucmd & UCMD_FL_FORCEANGLE); - pl->crouchoffset = inmove->crouchofs; + // lerp towards the current speeds and angles + const f32 dt = (forcepos ? 1.f : (1.f / (f32)moveticks)); + f32 t = (forcepos ? 1.f : ((f32)pl->client->lerpticks / (f32)moveticks)); + if (t > 1.f) { + t = 1.f; + } + pl->speedgo = pl->speedforwards = lerpf(inmoveprev->movespeed[0], inmove->movespeed[0], t); + pl->speedstrafe = pl->speedsideways = lerpf(inmoveprev->movespeed[1], inmove->movespeed[1], t); + pl->vv_theta = lerpanglef(pl->vv_theta, inmove->angles[0], dt); + pl->vv_verta = lerpanglef(pl->vv_verta, inmove->angles[1], dt); if (pl->bondmovemode == MOVEMODE_GRAB) { bgrabUpdateSpeedTheta(); @@ -177,22 +222,6 @@ static inline void bmoveProcessRemoteInput(void) } else { pl->speedmaxtime60 = 0; } - - if (pl->speedforwards > 1) { - pl->speedforwards = 1; - } - if (pl->speedforwards < -1) { - pl->speedforwards = -1; - } - if (pl->speedsideways > 1) { - pl->speedsideways = 1; - } - if (pl->speedsideways < -1) { - pl->speedsideways = -1; - } - - pl->speedforwards *= 1.08f; - pl->speedforwards *= pl->speedboost; } #endif // PLATFORM_N64 @@ -403,10 +432,37 @@ void bmoveApplyMoveData(struct movedata *data) } else if (g_Vars.currentplayer->bondmovemode == MOVEMODE_WALK) { bwalkApplyMoveData(data); } + #ifndef PLATFORM_N64 // record some inputs if this is a local player if (g_NetMode && !g_Vars.currentplayer->isremote) { g_Vars.currentplayer->ucmd = 0; + if (g_Vars.currentplayer->isdead) { + if (g_NetMode == NETMODE_CLIENT) { + if (joyGetButtons(optionsGetContpadNum1(g_Vars.currentplayerstats->mpindex), 0xb000)) { + g_Vars.currentplayer->ucmd |= UCMD_RESPAWN; + } + } + } else { + if (data->triggeron) { + g_Vars.currentplayer->ucmd |= UCMD_FIRE; + } + if (data->aiming) { + g_Vars.currentplayer->ucmd |= UCMD_AIMMODE; + } + if (g_Vars.currentplayer->bondactivateorreload & JO_ACTION_ACTIVATE) { + g_Vars.currentplayer->ucmd |= UCMD_ACTIVATE; + } + if (g_Vars.currentplayer->bondactivateorreload & JO_ACTION_RELOAD) { + g_Vars.currentplayer->ucmd |= UCMD_RELOAD; + } + if (data->eyesshut) { + g_Vars.currentplayer->ucmd |= UCMD_EYESSHUT; + } + if (data->weaponbackoffset || data->weaponforwardoffset) { + g_Vars.currentplayer->ucmd |= UCMD_SELECT; + } + } } #endif } @@ -813,7 +869,7 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i bool allowmcross = false; if (g_Vars.currentplayer->isremote) { - bmoveProcessRemoteInput(); + bmoveProcessRemoteInput(allowc1buttons); return; } #endif diff --git a/src/game/bondwalk.c b/src/game/bondwalk.c index bc3929429..8bd907e2e 100644 --- a/src/game/bondwalk.c +++ b/src/game/bondwalk.c @@ -29,25 +29,26 @@ #ifndef PLATFORM_N64 #include "net/net.h" #include "system.h" +#include "utils.h" + +static inline void lerpcoord(struct coord *c, const struct coord *a, const struct coord *b, const f32 t) { + c->x = lerpf(a->x, b->x, t); + c->y = lerpf(a->y, b->y, t); + c->z = lerpf(a->z, b->z, t); +} static void bwalkUpdateRemote(void) { struct player *pl = g_Vars.currentplayer; const struct netplayermove *inmove = &pl->client->inmove[0]; const struct netplayermove *inmoveprev = &pl->client->inmove[1]; - struct coord delta, target; - f32 spc8, spc4; + struct coord delta; + u16 cdtype = CDTYPE_ALL; s32 moveticks = inmove->tick - inmoveprev->tick; if (moveticks > g_NetInterpTicks) { moveticks = g_NetInterpTicks; } - const bool forcepos = !inmoveprev->tick || !moveticks || - (inmove->ucmd & UCMD_FL_FORCEPOS) || - (fabsf(pl->prop->pos.x - inmove->pos.x) > 256.f) || - (fabsf(pl->prop->pos.y - inmove->pos.y) > 256.f) || - (fabsf(pl->prop->pos.z - inmove->pos.z) > 256.f); - pl->client->inmovetick = inmove->tick; pl->bondshotspeed.x = 0.f; @@ -55,38 +56,39 @@ static void bwalkUpdateRemote(void) pl->bondshotspeed.z = 0.f; pl->bondbreathing = 0.f; - if (forcepos) { - // no interpolation, set position and lock the lad in place - delta.x = 0.f; - delta.y = 0.f; - delta.z = 0.f; - pl->prop->pos = inmove->pos; - pl->client->lerpticks = g_NetInterpTicks + g_NetExterpTicks; - } else if (pl->client->lerpticks <= moveticks) { - // lerp towards the new position - const f32 t = (f32)pl->client->lerpticks / (f32)moveticks; - target.x = inmoveprev->pos.x + (inmove->pos.x - inmoveprev->pos.x) * t; - target.y = inmoveprev->pos.y + (inmove->pos.y - inmoveprev->pos.y) * t; - target.z = inmoveprev->pos.z + (inmove->pos.z - inmoveprev->pos.z) * t; - delta.x = target.x - pl->prop->pos.x; - delta.y = target.y - pl->prop->pos.y; - delta.z = target.z - pl->prop->pos.z; - pl->client->lerpticks += g_Vars.lvupdate60; - } else if (pl->client->lerpticks <= moveticks + g_NetExterpTicks) { - // try extrapolating based on inputs for a while - pl->client->lerpticks += g_Vars.lvupdate60; - bwalk0f0c69b8(); - return; - } else { - // we're too far gone, lock position - delta.x = 0.f; - delta.y = 0.f; - delta.z = 0.f; - pl->prop->pos = inmove->pos; - } + const bool forcepos = !inmoveprev->tick || + (inmove->ucmd & UCMD_FL_FORCEPOS) || + (fabsf(pl->prop->pos.x - inmove->pos.x) > 512.f) || + (fabsf(pl->prop->pos.y - inmove->pos.y) > 512.f) || + (fabsf(pl->prop->pos.z - inmove->pos.z) > 512.f); - bwalkUpdateCrouchOffset(); - bwalk0f0c63bc(&delta, g_Vars.currentplayer->swaytarget == 0.0f, CDTYPE_ALL); + delta.x = 0.f; + delta.y = 0.f; + delta.z = 0.f; + + // extrapolate + bwalk0f0c69b8(); + + if (forcepos) { + // no interpolation, set position and lock the lad in place + pl->prop->pos = inmove->pos; + pl->client->lerpticks = g_NetInterpTicks + 1; + cdtype = CDTYPE_PLAYERS; // don't get stuck in the local client + } else if (!moveticks) { + // duplicate move, reposition to correct coords + delta.x = inmove->pos.x - pl->prop->pos.x; + delta.x = inmove->pos.y - pl->prop->pos.y; + delta.x = inmove->pos.z - pl->prop->pos.z; + bwalk0f0c63bc(&delta, pl->swaytarget == 0.0f, cdtype); + } else if (pl->client->lerpticks <= moveticks) { + // lerp towards the correct position + const f32 dt = (f32)1.f / (f32)moveticks; + delta.x = g_Vars.lvupdate60freal * (inmove->pos.x - pl->prop->pos.x) * dt; + delta.y = g_Vars.lvupdate60freal * (inmove->pos.y - pl->prop->pos.y) * dt; + delta.z = g_Vars.lvupdate60freal * (inmove->pos.z - pl->prop->pos.z) * dt; + bwalk0f0c63bc(&delta, pl->swaytarget == 0.0f, cdtype); + pl->client->lerpticks += g_Vars.lvupdate60; + } } #endif diff --git a/src/game/chr.c b/src/game/chr.c index c037e9a3a..08817916a 100644 --- a/src/game/chr.c +++ b/src/game/chr.c @@ -49,6 +49,8 @@ #include "types.h" #ifndef PLATFORM_N64 #include "video.h" +#include "net/net.h" +#include "net/netmsg.h" #endif void rng2SetSeed(u32 seed); @@ -4691,9 +4693,17 @@ void chrHit(struct shotdata *shotdata, struct hit *hit) || weapon->weaponnum == WEAPON_TIMEDMINE || weapon->weaponnum == WEAPON_REMOTEMINE || weapon->weaponnum == WEAPON_PROXIMITYMINE) { - objSetDropped(hit->prop, DROPTYPE_DEFAULT); - chr->hidden |= CHRHFLAG_DROPPINGITEM; - objDamage(&weapon->base, gsetGetDamage(&shotdata->gset), &sp98, shotdata->gset.weaponnum, g_Vars.currentplayernum); +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER) { + netmsgSvcChrDisarmWrite(&g_NetMsgRel, chr, g_Vars.currentplayer->prop, weapon->weaponnum, gsetGetDamage(&shotdata->gset), &sp98); + } + if (g_NetMode != NETMODE_CLIENT) +#endif + { + objSetDropped(hit->prop, DROPTYPE_DEFAULT); + chr->hidden |= CHRHFLAG_DROPPINGITEM; + objDamage(&weapon->base, gsetGetDamage(&shotdata->gset), &sp98, shotdata->gset.weaponnum, g_Vars.currentplayernum); + } return; } diff --git a/src/game/chraction.c b/src/game/chraction.c index beeaf1b28..926531424 100644 --- a/src/game/chraction.c +++ b/src/game/chraction.c @@ -4105,6 +4105,12 @@ bool func0f034080(struct chrdata *chr, struct modelnode *node, struct prop *prop */ void chrDamageByMisc(struct chrdata *chr, f32 damage, struct coord *vector, struct gset *gset, struct prop *prop) { +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_CLIENT) { + // don't do anything if we're not the authority + return; + } +#endif chrDamage(chr, damage, vector, gset, prop, HITPART_GENERAL, false, // damageshield NULL, // prop2 @@ -4118,6 +4124,12 @@ void chrDamageByMisc(struct chrdata *chr, f32 damage, struct coord *vector, stru void chrDamageByLaser(struct chrdata *chr, f32 damage, struct coord *vector, struct gset *gset, struct prop *prop) { +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_CLIENT) { + // don't do anything if we're not the authority + return; + } +#endif chrDamage(chr, damage, vector, gset, prop, HITPART_GENERAL, true, // damageshield chr->prop, // prop2 @@ -4131,6 +4143,12 @@ void chrDamageByLaser(struct chrdata *chr, f32 damage, struct coord *vector, str void func0f0341dc(struct chrdata *chr, f32 damage, struct coord *vector, struct gset *gset, struct prop *prop, s32 hitpart, struct prop *prop2, struct modelnode *node, struct model *model, s32 side, s16 *arg10) { +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_CLIENT) { + // don't do anything if we're not the authority + return; + } +#endif chrDamage(chr, damage, vector, gset, prop, hitpart, true, // damageshield prop2, // prop2 @@ -4152,6 +4170,13 @@ void func0f034248(struct chrdata *chr, f32 damage, struct coord *vector, struct s32 side = 0; s32 hitpart = HITPART_GENERAL; +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_CLIENT) { + // don't do anything if we're not the authority + return; + } +#endif + if (chrGetShield(chr) >= 0 && chr->model) { chrCalculateShieldHit(chr, &chr->prop->pos, vector, &node, &hitpart, &model, &side); } @@ -4176,6 +4201,13 @@ void chrDamageByImpact(struct chrdata *chr, f32 damage, struct coord *vector, st struct model *model = NULL; s32 side = 0; +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_CLIENT) { + // don't do anything if we're not the authority + return; + } +#endif + if (chrGetShield(chr) >= 0 && chr->model) { chrCalculateShieldHit(chr, &chr->prop->pos, vector, &node, &hitpart, &model, &side); } @@ -4193,6 +4225,12 @@ void chrDamageByImpact(struct chrdata *chr, f32 damage, struct coord *vector, st void chrDamageByExplosion(struct chrdata *chr, f32 damage, struct coord *vector, struct prop *prop, struct coord *explosionpos) { +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_CLIENT) { + // don't do anything if we're not the authority + return; + } +#endif chrDamage(chr, damage, vector, NULL, prop, HITPART_GENERAL, true, // damageshield chr->prop, // prop2 @@ -4277,9 +4315,9 @@ void chrDamage(struct chrdata *chr, f32 damage, struct coord *vector, struct gse s32 choketype = CHOKETYPE_NONE; #ifndef PLATFORM_N64 - if (g_NetMode == NETMODE_CLIENT) { - // don't do anything if we're not the authority - return; + if (g_NetMode == NETMODE_SERVER) { + netmsgSvcChrDamageWrite(&g_NetMsgRel, chr, damage, vector, gset, aprop, hitpart, + damageshield, prop2, side, arg11, explosion, explosionpos); } #endif @@ -4853,6 +4891,12 @@ void chrDamage(struct chrdata *chr, f32 damage, struct coord *vector, struct gse } } +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER && g_Vars.currentplayer->client) { + netmsgSvcPlayerStatsWrite(&g_NetMsgRel, g_Vars.currentplayer->client); + } +#endif + setCurrentPlayerNum(prevplayernum); return; } diff --git a/src/game/game_1531a0.c b/src/game/game_1531a0.c index 649888117..c92c6fe55 100644 --- a/src/game/game_1531a0.c +++ b/src/game/game_1531a0.c @@ -2183,7 +2183,7 @@ Gfx *text0f156a24(Gfx *gdl, s32 x, s32 y, struct fontchar *char1, s32 arg4, s32 if (y + char1->baseline >= arg5) { if (arg5 + arg7 >= y + char1->baseline + char1->height + 2) { if (g_TextRotated90) { - gSPTextureRectangleFlip(gdl++, + gSPTextureRectangleFlipEXT(gdl++, ((y - char1->baseline) - ((char1->height + 2) * var8007fad0)) * 4, x * 4, (y - char1->baseline) * 4, @@ -2194,7 +2194,7 @@ Gfx *text0f156a24(Gfx *gdl, s32 x, s32 y, struct fontchar *char1, s32 arg4, s32 1024 / var8007fad0, -1024); } else { - gSPTextureRectangle(gdl++, + gSPTextureRectangleEXT(gdl++, x * 4, (y + char1->baseline) * 4, (x + char1->width * var8007fad0 + 2) * 4, @@ -2207,7 +2207,7 @@ Gfx *text0f156a24(Gfx *gdl, s32 x, s32 y, struct fontchar *char1, s32 arg4, s32 } } else { if (arg5 + arg7 >= y + char1->baseline) { - gSPTextureRectangle(gdl++, + gSPTextureRectangleEXT(gdl++, x * 4, (y + char1->baseline) * 4, (x + char1->width * var8007fad0 + 2) * 4, @@ -2221,7 +2221,7 @@ Gfx *text0f156a24(Gfx *gdl, s32 x, s32 y, struct fontchar *char1, s32 arg4, s32 } } else { if (y + char1->baseline + char1->height + 2 >= arg5) { - gSPTextureRectangle(gdl++, + gSPTextureRectangleEXT(gdl++, x * 4, arg5 * 4, (x + char1->width * var8007fad0 + 2) * 4, diff --git a/src/game/lv.c b/src/game/lv.c index 0b63f8142..192223358 100644 --- a/src/game/lv.c +++ b/src/game/lv.c @@ -323,6 +323,11 @@ void lvReset(s32 stagenum) s32 i; s32 j; +#ifndef PLATFORM_N64 + // if we're a server, signal to clients that the level is changing + netServerStageStart(); +#endif + tilesReset(); bgReset(g_Vars.stagenum); bgBuildTables(g_Vars.stagenum); @@ -364,11 +369,6 @@ void lvReset(s32 stagenum) g_Vars.playerstats[i].kills[j] = 0; } } - -#ifndef PLATFORM_N64 - // if we're a server, signal to clients that the level is changing - netServerStageStart(); -#endif } mpSetDefaultNamesIfEmpty(); @@ -469,6 +469,12 @@ void lvReset(s32 stagenum) pheadReset(); } +#ifndef PLATFORM_N64 + if (g_NetMode) { + netSyncIdsAllocate(); + } +#endif + modelmgrSetLvResetting(false); var80084018 = 1; schedResetArtifacts(); @@ -1094,7 +1100,8 @@ Gfx *lvRender(Gfx *gdl) #ifndef PLATFORM_N64 if (g_NetMode) { - forcesingleplayer = true; + // tick all players, we'll skip the rendering + forcesingleplayer = false; } #endif @@ -1678,12 +1685,21 @@ Gfx *lvRender(Gfx *gdl) } if (g_Vars.currentplayer->dostartnewlife) { - playerStartNewLife(); +#ifndef PLATFORM_N64 + if (g_NetMode != NETMODE_CLIENT) +#endif + playerStartNewLife(); } } artifactsTick(); +#ifndef PLATFORM_N64 + if (g_NetMode && i) { + gdl = savedgdl; + } +#endif + if ((g_Vars.coopplayernum >= 0 || g_Vars.antiplayernum >= 0) #if VERSION >= VERSION_NTSC_1_0 && playerHasSharedViewport() diff --git a/src/game/menu.c b/src/game/menu.c index 60ede6647..6bc210525 100644 --- a/src/game/menu.c +++ b/src/game/menu.c @@ -5384,7 +5384,7 @@ Gfx *menuRender(Gfx *gdl) sprintf(text, "Client: ID %u", g_NetLocalClient->id); } x = viewleft + 2; - y = viewtop + 2; + y = viewbottom - 9; colour = 0x5070ffff; gdl = textRenderProjected(gdl, &x, &y, text, g_CharsHandelGothicSm, g_FontHandelGothicSm, colour, viGetWidth(), viGetHeight(), 0, 0); } else diff --git a/src/game/menutick.c b/src/game/menutick.c index ee4c8b5e9..74c2e1de9 100644 --- a/src/game/menutick.c +++ b/src/game/menutick.c @@ -221,6 +221,12 @@ void menuTick(void) viBlack(false); g_MpNumJoined = 0; +#ifndef PLATFORM_N64 + if (g_NetMode) { + g_Vars.mpsetupmenu = MPSETUPMENU_ADVSETUP; + g_MpSetup.chrslots = 1; + } else +#endif if (g_Vars.usingadvsetup) { g_Vars.mpsetupmenu = MPSETUPMENU_ADVSETUP; } else { @@ -236,6 +242,13 @@ void menuTick(void) if (g_Vars.mpsetupmenu == MPSETUPMENU_ADVSETUP) { g_MpNumJoined++; func0f17fcb0(true); +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_CLIENT) { + // autodump client into waiting screen while host is changing settings + extern struct menudialogdef g_NetJoiningDialog; + menuPushDialog(&g_NetJoiningDialog); + } +#endif } else if (g_MpNumJoined == 0) { g_MpNumJoined++; @@ -536,7 +549,12 @@ void menuTick(void) func0f0fd548(4); } +#ifdef PLATFORM_N64 for (i = 0; i < MAX_PLAYERS; i++) { +#else + const s32 maxplayers = g_NetMode ? 1 : MAX_PLAYERS; + for (i = 0; i < maxplayers; i++) { +#endif if (g_MpSetup.chrslots & (1 << i)) { if (g_Vars.coopplayernum >= 0) { if (g_Vars.stagenum == STAGE_DEEPSEA) { diff --git a/src/game/mplayer/ingame.c b/src/game/mplayer/ingame.c index ef96daa54..2d3de7215 100644 --- a/src/game/mplayer/ingame.c +++ b/src/game/mplayer/ingame.c @@ -19,6 +19,9 @@ #include "lib/main.h" #include "data.h" #include "types.h" +#ifndef PLATFORM_N64 +#include "net/net.h" +#endif struct menudialogdef g_MpEndscreenChallengeCompletedMenuDialog; struct menudialogdef g_MpEndscreenIndGameOverMenuDialog; @@ -102,6 +105,11 @@ MenuItemHandlerResult menuhandlerMpEndGame(s32 operation, struct menuitem *item, { if (operation == MENUOP_SET) { g_Vars.currentplayer->aborted = true; +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_CLIENT) { + netDisconnect(); + } else +#endif mainEndStage(); } @@ -831,6 +839,9 @@ void mpPushEndscreenDialog(u32 arg0, u32 playernum) && g_PlayerConfigsArray[g_MpPlayerNum].fileguid.fileid == 0 && g_PlayerConfigsArray[g_MpPlayerNum].fileguid.deviceserial == 0) { g_PlayerConfigsArray[g_MpPlayerNum].options |= OPTION_ASKEDSAVEPLAYER; +#ifndef PLATFORM_N64 + if (!g_NetMode) +#endif menuPushDialog(&g_MpEndscreenSavePlayerMenuDialog); } } diff --git a/src/game/player.c b/src/game/player.c index 5ae586ffa..ecfcab38e 100644 --- a/src/game/player.c +++ b/src/game/player.c @@ -73,6 +73,8 @@ #ifndef PLATFORM_N64 #include "video.h" #include "input.h" +#include "net/net.h" +#include "net/netmsg.h" #endif s32 g_DefaultWeapons[2]; @@ -1116,6 +1118,12 @@ void playerSpawn(void) } playerUpdatePerimInfo(); + +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER && g_Vars.currentplayer->client) { + netmsgSvcPlayerStatsWrite(&g_NetMsgRel, g_Vars.currentplayer->client); + } +#endif } void playerResetBond(struct playerbond *pb, struct coord *pos) @@ -4677,6 +4685,13 @@ Gfx *playerRenderHud(Gfx *gdl) if (g_Vars.antiplayernum >= 0 && g_Vars.currentplayer == g_Vars.anti) { // Anti +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER && g_Vars.currentplayer->isremote) { + if (g_Vars.currentplayer->client->inmove[0].ucmd & UCMD_RESPAWN) { + g_Vars.currentplayer->dostartnewlife = true; + } + } else if (g_NetMode != NETMODE_CLIENT) +#endif if (joyGetButtons(optionsGetContpadNum1(g_Vars.currentplayerstats->mpindex), 0xb000) && !mpIsPaused()) { g_Vars.currentplayer->dostartnewlife = true; } @@ -4690,8 +4705,12 @@ Gfx *playerRenderHud(Gfx *gdl) f32 stealhealth; f32 shield; - canrestart = joyGetButtons(optionsGetContpadNum1(g_Vars.currentplayerstats->mpindex), 0xb000) - && !mpIsPaused(); +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER && g_Vars.currentplayer->isremote) { + canrestart = (g_Vars.currentplayer->client->inmove[0].ucmd & UCMD_RESPAWN) != 0; + } else if (g_NetMode != NETMODE_CLIENT) +#endif + canrestart = joyGetButtons(optionsGetContpadNum1(g_Vars.currentplayerstats->mpindex), 0xb000) && !mpIsPaused(); // Get ready to respawn. // The other player's health will be halved. @@ -4797,6 +4816,13 @@ Gfx *playerRenderHud(Gfx *gdl) } } +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER && g_Vars.currentplayer->isremote) { + if (!mpIsPaused() && g_NumReasonsToEndMpMatch == 0 && (g_Vars.currentplayer->client->inmove[0].ucmd & UCMD_RESPAWN)) { + canrestart = true; + } + } else if (g_NetMode != NETMODE_CLIENT) +#endif if (joyGetButtons(optionsGetContpadNum1(g_Vars.currentplayerstats->mpindex), 0xb000) && !mpIsPaused() && g_NumReasonsToEndMpMatch == 0) { @@ -4880,6 +4906,12 @@ void playerDie(bool force) struct chrdata *chr = g_Vars.currentplayer->prop->chr; s32 shooter; +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_CLIENT) { + return; + } +#endif + if (chr->lastshooter >= 0 && chr->timeshooter > 0) { shooter = chr->lastshooter; } else { diff --git a/src/game/playermgr.c b/src/game/playermgr.c index ab1f006b7..609ecaadb 100644 --- a/src/game/playermgr.c +++ b/src/game/playermgr.c @@ -64,7 +64,7 @@ void playermgrAllocatePlayers(s32 count) } #ifndef PLATFORM_N64 - if (g_NetMode) { + if (g_NetMode && g_StageNum != STAGE_TITLE && g_StageNum != STAGE_CITRAINING) { netPlayersAllocate(); } #endif diff --git a/src/game/prop.c b/src/game/prop.c index d775f5ac8..e9d544e06 100644 --- a/src/game/prop.c +++ b/src/game/prop.c @@ -41,6 +41,7 @@ #include "types.h" #ifndef PLATFORM_N64 #include "net/net.h" +#include "net/netmsg.h" #endif s16 *g_RoomPropListChunkIndexes; @@ -170,6 +171,10 @@ struct prop *propAllocate(void) prop->propupdate60err = 2; prop->opawallhits = NULL; prop->xluwallhits = NULL; +#ifndef PLATFORM_N64 + // NOTE: this will be automatically overwritten at the start of the stage for the setup props + prop->syncid = (g_NetMode == NETMODE_SERVER) ? g_NetNextSyncId++ : 0; +#endif g_Vars.propstates[prop->propstateindex].propcount++; g_Vars.allocstateindex++; @@ -202,6 +207,9 @@ void propFree(struct prop *prop) prop->prev = NULL; prop->chr = NULL; prop->rooms[0] = -1; +#ifndef PLATFORM_N64 + prop->syncid = 0; +#endif g_Vars.freeprops = prop; } @@ -1528,6 +1536,12 @@ bool currentPlayerInteract(bool eyespy) propExecuteTickOperation(prop, op); +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER) { + netmsgSvcPropUseWrite(&g_NetMsgRel, prop, g_Vars.currentplayer->client, op); + } +#endif + return false; } diff --git a/src/game/propobj.c b/src/game/propobj.c index c8385a858..b52eb9def 100644 --- a/src/game/propobj.c +++ b/src/game/propobj.c @@ -79,6 +79,7 @@ #include "string.h" #ifndef PLATFORM_N64 #include "net/net.h" +#include "net/netmsg.h" #endif void rng2SetSeed(u32 seed); @@ -8120,6 +8121,13 @@ void liftTick(struct prop *prop) stop = (stop + 1) % 4; } while (lift->pads[stop] < 0); +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_CLIENT) { + // don't start if we're not the authority + return; + } +#endif + liftGoToStop(lift, stop); } } @@ -14211,6 +14219,12 @@ void objBounce(struct defaultobj *obj, struct coord *gundir2d) projectile->speed.z += 3.3333333f * dir.z; projectile->ownerprop = g_Vars.currentplayer->prop; projectile->bouncecount = 1; + +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER) { + netmsgSvcPropMoveWrite(&g_NetMsgRel, obj->prop, &rot); + } +#endif } } @@ -14288,6 +14302,11 @@ void objApplyMomentum(struct defaultobj *obj, struct coord *speed, f32 rotation, projectile->unk0ec = 0.07852732f; projectile->unk0f0 = 6.6666665f; } +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER) { + netmsgSvcPropMoveWrite(&g_NetMsgRel, obj->prop, NULL); + } +#endif return; } @@ -14318,6 +14337,12 @@ void objApplyMomentum(struct defaultobj *obj, struct coord *speed, f32 rotation, projectile->unk0ec = 0.07852732f; projectile->unk0f0 = 1.6666666f; } + +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER) { + netmsgSvcPropMoveWrite(&g_NetMsgRel, obj->prop, NULL); + } +#endif } } @@ -15358,9 +15383,14 @@ void objTakeGunfire(struct defaultobj *obj, f32 damage, struct coord *pos, s32 w void objDamage(struct defaultobj *obj, f32 damage, struct coord *pos, s32 weaponnum, s32 playernum) { #ifndef PLATFORM_N64 - // if we aren't the authority, don't do anything + // if we aren't the authority, don't do anything, unless called from netmsg handler if (g_NetMode == NETMODE_CLIENT) { - return; + if (damage < 0.f) { + // HACK: negative damage means we were called from netmsg.c + damage = -damage; + } else { + return; + } } #endif @@ -15951,6 +15981,10 @@ void objHit(struct shotdata *shotdata, struct hit *hit) pushdir.y = shotdata->gundir3d.y; pushdir.z = shotdata->gundir3d.z; +#ifndef PLATFORM_N64 + // don't push anything if we're not the authority + if (g_NetMode != NETMODE_CLIENT) +#endif func0f082e84(obj, &spa4, &pushdir, &spb0, true); } else { bool bounce = false; @@ -15973,6 +16007,13 @@ void objHit(struct shotdata *shotdata, struct hit *hit) bounce = false; } +#ifndef PLATFORM_N64 + // don't bounce anything if we're not the authority + if (g_NetMode == NETMODE_CLIENT) { + bounce = false; + } +#endif + if (bounce) { objBounce(obj, &shotdata->gundir2d); } @@ -17281,6 +17322,11 @@ s32 propPickupByPlayer(struct prop *prop, bool showhudmsg) if (sp64) { weaponPlayPickupSound(weapon->weaponnum); +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER && g_Vars.currentplayer->client) { + netmsgSvcPropPickupWrite(&g_NetMsgRel, g_Vars.currentplayer->client, prop, sp64); + } +#endif } return sp64; @@ -17291,6 +17337,11 @@ s32 propPickupByPlayer(struct prop *prop, bool showhudmsg) if (sp64) { weaponPlayPickupSound(weapon->weaponnum); +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER && g_Vars.currentplayer->client) { + netmsgSvcPropPickupWrite(&g_NetMsgRel, g_Vars.currentplayer->client, prop, sp64); + } +#endif } return sp64; @@ -17458,6 +17509,13 @@ s32 propPickupByPlayer(struct prop *prop, bool showhudmsg) break; } +#ifndef PLATFORM_N64 + if (result != TICKOP_NONE && g_NetMode == NETMODE_SERVER && g_Vars.currentplayer->client) { + netmsgSvcPropPickupWrite(&g_NetMsgRel, g_Vars.currentplayer->client, prop, result); + netmsgSvcPlayerStatsWrite(&g_NetMsgRel, g_Vars.currentplayer->client); + } +#endif + if (result == TICKOP_FREE && (obj->hidden & OBJHFLAG_TAGGED) == 0) { objFree(obj, false, obj->hidden2 & OBJH2FLAG_CANREGEN); return TICKOP_FREE; @@ -19074,6 +19132,13 @@ void doorsCheckAutomatic(void) s16 *propnumptr; s16 propnums[256]; +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_CLIENT) { + // don't do anything if we're not the authority + return; + } +#endif + roomGetProps(g_Vars.currentplayer->prop->rooms, propnums, 256); propnumptr = propnums; @@ -19850,6 +19915,11 @@ void doorSetMode(struct doorobj *door, s32 newmode) } else { door->mode = newmode; } +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER) { + netmsgSvcPropDoorWrite(&g_NetMsgRel, door->base.prop, g_Vars.currentplayer->client); + } +#endif } /** @@ -21215,6 +21285,13 @@ void weaponCreateForPlayerDrop(s32 weaponnum) struct chrdata *chr; u32 stack2; +#ifndef PLATFORM_N64 + // don't do anything if we're not the authority + if (g_NetMode == NETMODE_CLIENT) { + return; + } +#endif + chr = g_Vars.currentplayer->prop->chr; prop = weaponCreateForChr(chr, playermgrGetModelOfWeapon(weaponnum), weaponnum, OBJFLAG_WEAPON_AICANNOTUSE, NULL, NULL); @@ -21225,6 +21302,13 @@ void weaponCreateForPlayerDrop(s32 weaponnum) if (weaponnum == WEAPON_BRIEFCASE2) { scenarioHandleDroppedToken(chr, prop); } + +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER) { + netmsgSvcPropSpawnWrite(&g_NetMsgRel, prop); + netmsgSvcPropMoveWrite(&g_NetMsgRel, prop, NULL); + } +#endif } } diff --git a/src/include/data.h b/src/include/data.h index c2bf3a87d..7797182ca 100644 --- a/src/include/data.h +++ b/src/include/data.h @@ -15,6 +15,7 @@ #define bool s32 #define ubool u32 +extern s32 g_StageNum; extern u8 *g_StackLeftAddrs[NUM_THREADS]; extern u8 *g_StackRightAddrs[NUM_THREADS]; extern u8 *g_StackAllocatedPos; diff --git a/src/include/game/bondgun.h b/src/include/game/bondgun.h index 5bc113d38..e4dad3663 100644 --- a/src/include/game/bondgun.h +++ b/src/include/game/bondgun.h @@ -76,7 +76,7 @@ struct modeldef *bgunGetCartModeldef(void); void bgun0f09ebcc(struct defaultobj *obj, struct coord *coord, RoomNum *rooms, Mtxf *matrix1, struct coord *velocity, Mtxf *matrix2, struct prop *prop, struct coord *pos); void bgun0f09ed2c(struct defaultobj *obj, struct coord *coord, Mtxf *arg2, struct coord *velocity, Mtxf *arg4); struct defaultobj *bgunCreateThrownProjectile2(struct chrdata *chr, struct gset *gset, struct coord *pos, RoomNum *rooms, Mtxf *arg4, struct coord *velocity); -void bgunCreateThrownProjectile(s32 handnum, struct gset *gset); +struct defaultobj *bgunCreateThrownProjectile(s32 handnum, struct gset *gset); void bgunUpdateHeldRocket(s32 handnum); void bgunCreateHeldRocket(s32 handnum, struct weaponfunc_shootprojectile *func); void bgunFreeHeldRocket(s32 handnum); diff --git a/src/include/types.h b/src/include/types.h index 58c735156..f8d24b7d7 100644 --- a/src/include/types.h +++ b/src/include/types.h @@ -324,6 +324,9 @@ struct prop { /*0x3f*/ u8 active : 1; /*0x40*/ struct wallhit *opawallhits; // opaque /*0x44*/ struct wallhit *xluwallhits; // translucent +#ifndef PLATFORM_N64 + /*0x48*/ u32 syncid; +#endif }; struct packedpad {