diff --git a/docs/rh-log.txt b/docs/rh-log.txt index 5e9116f79..46053a434 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -33,6 +33,7 @@ March 25, 2008 the 2D panning area. March 25, 2008 (Changes by Graf Zahl) +- Added Karate Chris's submission for multiplayer Strife conversations. - Increased the limit for 'imp/active' to 6. This sound definitely benefits from a higher limit. - Fixed: $limit should not apply to sounds played from the menu. diff --git a/src/d_net.cpp b/src/d_net.cpp index f58c6a2e9..83db8f21d 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -53,6 +53,7 @@ #include "a_sharedglobal.h" #include "st_start.h" #include "teaminfo.h" +#include "p_conversation.h" int P_StartScript (AActor *who, line_t *where, int script, char *map, bool backSide, int arg0, int arg1, int arg2, int always, bool wantResultCode, bool net); @@ -2361,6 +2362,10 @@ void Net_DoCommand (int type, BYTE **stream, int player) } break; + case DEM_CONVERSATION: + P_ConversationCommand (player, stream); + break; + default: I_Error ("Unknown net command: %d", type); break; @@ -2451,6 +2456,31 @@ void Net_SkipCommand (int type, BYTE **stream) skip = 3 + *(*stream + 2) * 4; break; + case DEM_CONVERSATION: + { + t = **stream; + skip = 1; + + switch (t) + { + case CONV_ANIMATE: + skip += 1; + break; + + case CONV_GIVEINVENTORY: + skip += strlen ((char *)(*stream + skip)) + 1; + break; + + case CONV_TAKEINVENTORY: + skip += strlen ((char *)(*stream + skip)) + 3; + break; + + default: + break; + } + } + break; + default: return; } diff --git a/src/d_player.h b/src/d_player.h index 934adbf9c..a22677929 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -320,6 +320,11 @@ public: fixed_t crouchoffset; fixed_t crouchviewdelta; + // [CW] I moved these here for multiplayer conversation support. + AActor *ConversationNPC, *ConversationPC; + angle_t ConversationNPCAngle; + bool ConversationFaceTalker; + fixed_t GetDeltaViewHeight() const { return (mo->ViewHeight + crouchviewdelta - viewheight) >> 3; diff --git a/src/d_protocol.h b/src/d_protocol.h index e28e1d3d3..eedf353c2 100644 --- a/src/d_protocol.h +++ b/src/d_protocol.h @@ -150,6 +150,7 @@ enum EDemoCommand DEM_ADDCONTROLLER, // 48 Player to add to the controller list. DEM_DELCONTROLLER, // 49 Player to remove from the controller list. DEM_KILLCLASSCHEAT, // 50 String: Class to kill. + DEM_CONVERSATION, // 51 Make conversations work. }; // The following are implemented by cht_DoCheat in m_cheat.cpp diff --git a/src/g_strife/a_strifeglobal.h b/src/g_strife/a_strifeglobal.h index 3474bcd5a..bc485614a 100644 --- a/src/g_strife/a_strifeglobal.h +++ b/src/g_strife/a_strifeglobal.h @@ -81,6 +81,13 @@ public: bool TryPickup (AActor *toucher); }; +class ASlideshowStarter : public ADummyStrifeItem +{ + DECLARE_STATELESS_ACTOR (ASlideshowStarter, ADummyStrifeItem) +public: + bool TryPickup (AActor *toucher); +}; + class AStrifeWeapon : public AWeapon { DECLARE_STATELESS_ACTOR (AStrifeWeapon, AWeapon) diff --git a/src/g_strife/a_strifeitems.cpp b/src/g_strife/a_strifeitems.cpp index 6335b823f..4014ed032 100644 --- a/src/g_strife/a_strifeitems.cpp +++ b/src/g_strife/a_strifeitems.cpp @@ -514,13 +514,6 @@ bool AUpgradeAccuracy::TryPickup (AActor *toucher) // Start a slideshow -------------------------------------------------------- -class ASlideshowStarter : public ADummyStrifeItem -{ - DECLARE_STATELESS_ACTOR (ASlideshowStarter, ADummyStrifeItem) -public: - bool TryPickup (AActor *toucher); -}; - IMPLEMENT_STATELESS_ACTOR (ASlideshowStarter, Strife, -1, 0) PROP_StrifeType (343) END_DEFAULTS diff --git a/src/p_conversation.cpp b/src/p_conversation.cpp index da2a5f02d..e7aaedd2a 100644 --- a/src/p_conversation.cpp +++ b/src/p_conversation.cpp @@ -117,9 +117,8 @@ static void ConversationMenuEscaped (); static FStrifeDialogueNode *CurNode, *PrevNode; static FBrokenLines *DialogueLines; -static AActor *ConversationNPC, *ConversationPC; -static angle_t ConversationNPCAngle; -static bool ConversationFaceTalker; + +static bool Conversation_TakeStuff; #define NUM_RANDOM_LINES 10 #define NUM_RANDOM_GOODBYES 3 @@ -571,14 +570,14 @@ static int FindNode (const FStrifeDialogueNode *node) // //============================================================================ -static bool CheckStrifeItem (const PClass *itemtype, int amount=-1) +static bool CheckStrifeItem (player_t *player, const PClass *itemtype, int amount=-1) { AInventory *item; if (itemtype == NULL || amount == 0) return true; - item = ConversationPC->FindInventory (itemtype); + item = player->ConversationPC->FindInventory (itemtype); if (item == NULL) return false; @@ -594,7 +593,7 @@ static bool CheckStrifeItem (const PClass *itemtype, int amount=-1) // //============================================================================ -static void TakeStrifeItem (const PClass *itemtype, int amount) +static void TakeStrifeItem (player_t *player, const PClass *itemtype, int amount) { if (itemtype == NULL || amount == 0) return; @@ -611,15 +610,10 @@ static void TakeStrifeItem (const PClass *itemtype, int amount) if (itemtype == RUNTIME_CLASS(ASigil)) return; - AInventory *item = ConversationPC->FindInventory (itemtype); - if (item != NULL) - { - item->Amount -= amount; - if (item->Amount <= 0) - { - item->Destroy (); - } - } + Net_WriteByte (DEM_CONVERSATION); + Net_WriteByte (CONV_TAKEINVENTORY); + Net_WriteString (itemtype->TypeName.GetChars ()); + Net_WriteWord (amount); } CUSTOM_CVAR(Float, dlg_musicvolume, 1.0f, CVAR_ARCHIVE) @@ -633,7 +627,6 @@ CUSTOM_CVAR(Float, dlg_musicvolume, 1.0f, CVAR_ARCHIVE) // P_StartConversation // // Begins a conversation between a PC and NPC. -// FIXME: Make this work in multiplayer. // //============================================================================ @@ -645,14 +638,22 @@ void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveang const char *toSay; int i, j; + // [CW] If an NPC is talking to a PC already, then don't let + // anyone else talk to NPC. + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || pc->player == &players[i]) + continue; + + if (npc == players[i].ConversationNPC) + return; + } + pc->momx = pc->momy = 0; // Stop moving pc->player->momx = pc->player->momy = 0; - if (pc->player - players != consoleplayer) - return; - - ConversationPC = pc; - ConversationNPC = npc; + pc->player->ConversationPC = pc; + pc->player->ConversationNPC = npc; CurNode = npc->Conversation; @@ -662,10 +663,10 @@ void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveang } npc->reactiontime = 2; - ConversationFaceTalker = facetalker; + pc->player->ConversationFaceTalker = facetalker; if (saveangle) { - ConversationNPCAngle = npc->angle; + pc->player->ConversationNPCAngle = npc->angle; } oldtarget = npc->target; npc->target = pc; @@ -682,11 +683,11 @@ void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveang // Check if we should jump to another node while (CurNode->ItemCheck[0] != NULL) { - if (CheckStrifeItem (CurNode->ItemCheck[0]) && - CheckStrifeItem (CurNode->ItemCheck[1]) && - CheckStrifeItem (CurNode->ItemCheck[2])) + if (CheckStrifeItem (pc->player, CurNode->ItemCheck[0]) && + CheckStrifeItem (pc->player, CurNode->ItemCheck[1]) && + CheckStrifeItem (pc->player, CurNode->ItemCheck[2])) { - int root = FindNode (ConversationNPC->GetDefault()->Conversation); + int root = FindNode (pc->player->ConversationNPC->GetDefault()->Conversation); CurNode = StrifeDialogues[root + CurNode->ItemCheckNode - 1]; } else @@ -697,10 +698,13 @@ void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveang if (CurNode->SpeakerVoice != 0) { - I_SetMusicVolume(dlg_musicvolume); + I_SetMusicVolume (dlg_musicvolume); S_SoundID (npc, CHAN_VOICE|CHAN_NOPAUSE, CurNode->SpeakerVoice, 1, ATTN_NORM); } + if (pc->player != &players[consoleplayer]) + return; + // Set up the menu ConversationMenu.PreDraw = DrawConversationMenu; ConversationMenu.EscapeHandler = ConversationMenuEscaped; @@ -786,9 +790,17 @@ void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveang void P_ResumeConversation () { - if (ConversationPC != NULL && ConversationNPC != NULL) + for (int i = 0; i < MAXPLAYERS; i++) { - P_StartConversation (ConversationNPC, ConversationPC, ConversationFaceTalker, false); + if (!playeringame[i]) + continue; + + player_t *p = &players[i]; + + if (p->ConversationPC != NULL && p->ConversationNPC != NULL) + { + P_StartConversation (p->ConversationNPC, p->ConversationPC, p->ConversationFaceTalker, false); + } } } @@ -803,6 +815,8 @@ static void DrawConversationMenu () const char *speakerName; int i, x, y, linesize; + player_t *cp = &players[consoleplayer]; + assert (DialogueLines != NULL); assert (CurNode != NULL); @@ -812,7 +826,8 @@ static void DrawConversationMenu () return; } - if (ConversationPauseTic < gametic) + // [CW] Pausing the game in a multiplayer game is a bad idea. + if (ConversationPauseTic < gametic && !multiplayer) { menuactive = MENU_On; } @@ -832,7 +847,7 @@ static void DrawConversationMenu () } else { - speakerName = ConversationNPC->GetClass()->Meta.GetMetaString (AMETA_StrifeName); + speakerName = cp->ConversationNPC->GetClass()->Meta.GetMetaString (AMETA_StrifeName); if (speakerName == NULL) { speakerName = "Person"; @@ -873,7 +888,7 @@ static void DrawConversationMenu () if (ShowGold) { - AInventory *coin = ConversationPC->FindInventory (RUNTIME_CLASS(ACoin)); + AInventory *coin = cp->ConversationPC->FindInventory (RUNTIME_CLASS(ACoin)); char goldstr[32]; sprintf (goldstr, "%d", coin != NULL ? coin->Amount : 0); @@ -892,94 +907,89 @@ static void DrawConversationMenu () // // PickConversationReply // -// FIXME: Make this work in multiplayer -// //============================================================================ static void PickConversationReply () { const char *replyText = NULL; FStrifeDialogueReply *reply = (FStrifeDialogueReply *)ConversationItems[ConversationMenu.lastOn].c.extra; - bool takestuff; int i; + player_t *cp = &players[consoleplayer]; + + Conversation_TakeStuff = false; M_ClearMenus (); CleanupConversationMenu (); if (reply == NULL) { - ConversationNPC->angle = ConversationNPCAngle; + Net_WriteByte (DEM_CONVERSATION); + Net_WriteByte (CONV_NPCANGLE); return; } // Check if you have the requisite items for this choice for (i = 0; i < 3; ++i) { - if (!CheckStrifeItem (reply->ItemCheck[i], reply->ItemCheckAmount[i])) + if (!CheckStrifeItem (cp, reply->ItemCheck[i], reply->ItemCheckAmount[i])) { // No, you don't. Say so and let the NPC animate negatively. if (reply->QuickNo) { Printf ("%s\n", reply->QuickNo); } - ConversationNPC->ConversationAnimation (2); - ConversationNPC->angle = ConversationNPCAngle; + Net_WriteByte (DEM_CONVERSATION); + Net_WriteByte (CONV_ANIMATE); + Net_WriteByte (2); + + Net_WriteByte (DEM_CONVERSATION); + Net_WriteByte (CONV_NPCANGLE); return; } } // Yay, you do! Let the NPC animate affirmatively. - ConversationNPC->ConversationAnimation (1); + Net_WriteByte (DEM_CONVERSATION); + Net_WriteByte (CONV_ANIMATE); + Net_WriteByte (1); // If this reply gives you something, then try to receive it. - takestuff = true; + Conversation_TakeStuff = true; if (reply->GiveType != NULL) { if (reply->GiveType->IsDescendantOf(RUNTIME_CLASS(AInventory))) { if (reply->GiveType->IsDescendantOf(RUNTIME_CLASS(AWeapon))) { - if (players[consoleplayer].mo->FindInventory(reply->GiveType) != NULL) + if (cp->mo->FindInventory(reply->GiveType) != NULL) { - takestuff = false; + Conversation_TakeStuff = false; } } - if (takestuff) + if (Conversation_TakeStuff) { - AInventory *item = static_cast (Spawn (reply->GiveType, 0, 0, 0, NO_REPLACE)); - // Items given here should not count as items! - if (item->flags & MF_COUNTITEM) - { - level.total_items--; - item->flags &= ~MF_COUNTITEM; - } - if (item->IsA(RUNTIME_CLASS(AFlameThrower))) - { - // The flame thrower gives less ammo when given in a dialog - static_cast(item)->AmmoGive1 = 40; - } - item->flags |= MF_DROPPED; - if (!item->TryPickup (players[consoleplayer].mo)) - { - item->Destroy (); - takestuff = false; - } + Net_WriteByte (DEM_CONVERSATION); + Net_WriteByte (CONV_GIVEINVENTORY); + Net_WriteString (reply->GiveType->TypeName.GetChars ()); } + + if (reply->GiveType->IsDescendantOf (RUNTIME_CLASS (ASlideshowStarter))) + gameaction = ga_slideshow; } else { // Trying to give a non-inventory item. - takestuff = false; + Conversation_TakeStuff = false; Printf("Attempting to give non-inventory item %s\n", reply->GiveType->TypeName.GetChars()); } } // Take away required items if the give was successful or none was needed. - if (takestuff) + if (Conversation_TakeStuff) { for (i = 0; i < 3; ++i) { - TakeStrifeItem (reply->ItemCheck[i], reply->ItemCheckAmount[i]); + TakeStrifeItem (&players[consoleplayer], reply->ItemCheck[i], reply->ItemCheckAmount[i]); } replyText = reply->QuickYes; } @@ -989,9 +999,9 @@ static void PickConversationReply () } // Update the quest log, if needed. - if (reply->LogNumber != 0) + if (reply->LogNumber != 0) { - players[consoleplayer].SetLogNumber (reply->LogNumber); + cp->SetLogNumber (reply->LogNumber); } if (replyText != NULL) @@ -1000,32 +1010,43 @@ static void PickConversationReply () } // Does this reply alter the speaker's conversation node? If NextNode is positive, - // the next time they talk, the will show the new node. If it is negative, then they + // the next time they talk, they will show the new node. If it is negative, then they // will show the new node right away without terminating the dialogue. if (reply->NextNode != 0) { - int rootnode = FindNode (ConversationNPC->GetDefault()->Conversation); + int rootnode = FindNode (cp->ConversationNPC->GetDefault()->Conversation); if (reply->NextNode < 0) { - ConversationNPC->Conversation = StrifeDialogues[rootnode - reply->NextNode - 1]; + cp->ConversationNPC->Conversation = StrifeDialogues[rootnode - reply->NextNode - 1]; if (gameaction != ga_slideshow) { - P_StartConversation (ConversationNPC, players[consoleplayer].mo, ConversationFaceTalker, false); + P_StartConversation (cp->ConversationNPC, cp->mo, cp->ConversationFaceTalker, false); return; } else { - S_StopSound (ConversationNPC, CHAN_VOICE); + S_StopSound (cp->ConversationNPC, CHAN_VOICE); } } else { - ConversationNPC->Conversation = StrifeDialogues[rootnode + reply->NextNode - 1]; + cp->ConversationNPC->Conversation = StrifeDialogues[rootnode + reply->NextNode - 1]; } } - ConversationNPC->angle = ConversationNPCAngle; - I_SetMusicVolume(1.f); + Net_WriteByte (DEM_CONVERSATION); + Net_WriteByte (CONV_NPCANGLE); + + // [CW] Set these to NULL because we're not talking to them + // anymore. However, this can interfere with slideshows so + // we don't set them to NULL in that case. + if (gameaction != ga_slideshow) + { + Net_WriteByte (DEM_CONVERSATION); + Net_WriteByte (CONV_SETNULL); + } + + I_SetMusicVolume (1.f); } //============================================================================ @@ -1058,19 +1079,96 @@ void CleanupConversationMenu () DialogueLines = NULL; } ConversationItems.Clear (); - I_SetMusicVolume(1.f); + I_SetMusicVolume (1.f); } //============================================================================ // // ConversationMenuEscaped // -// Called when the user presses escape to leave tho conversation menu. +// Called when the user presses escape to leave the conversation menu. // //============================================================================ void ConversationMenuEscaped () { CleanupConversationMenu (); - ConversationNPC->angle = ConversationNPCAngle; + + Net_WriteByte (DEM_CONVERSATION); + Net_WriteByte (CONV_NPCANGLE); + + Net_WriteByte (DEM_CONVERSATION); + Net_WriteByte (CONV_SETNULL); +} + +//============================================================================ +// +// P_ConversationCommand +// +// Complete a conversation command. +// +//============================================================================ + +void P_ConversationCommand (int player, BYTE **stream) +{ + int type = ReadByte (stream); + + switch (type) + { + case CONV_NPCANGLE: + players[player].ConversationNPC->angle = players[player].ConversationNPCAngle; + break; + + case CONV_ANIMATE: + players[player].ConversationNPC->ConversationAnimation (ReadByte (stream)); + break; + + case CONV_GIVEINVENTORY: + { + AInventory *item = static_cast (Spawn (ReadString (stream), 0, 0, 0, NO_REPLACE)); + // Items given here should not count as items! + if (item->flags & MF_COUNTITEM) + { + level.total_items--; + item->flags &= ~MF_COUNTITEM; + } + if (item->IsA(RUNTIME_CLASS(AFlameThrower))) + { + // The flame thrower gives less ammo when given in a dialog + static_cast(item)->AmmoGive1 = 40; + } + item->flags |= MF_DROPPED; + if (!item->TryPickup (players[player].mo)) + { + item->Destroy (); + Conversation_TakeStuff = false; + } + } + break; + + case CONV_TAKEINVENTORY: + { + AInventory *item = players[player].ConversationPC->FindInventory (PClass::FindClass (ReadString (stream))); + + if (item != NULL) + { + item->Amount -= ReadWord (stream); + if (item->Amount <= 0) + { + item->Destroy (); + } + } + } + break; + + case CONV_SETNULL: + players[player].ConversationFaceTalker = NULL; + players[player].ConversationNPC = NULL; + players[player].ConversationPC = NULL; + players[player].ConversationNPCAngle = NULL; + break; + + default: + break; + } } diff --git a/src/p_conversation.h b/src/p_conversation.h index 8ee35c279..bf270efbf 100644 --- a/src/p_conversation.h +++ b/src/p_conversation.h @@ -5,8 +5,7 @@ // users can edit as simple text files. Particularly useful would be // the ability to call ACS functions to implement AppearsWhen properties // and ACS scripts to implement ActionTaken properties. -// TODO: Make this work in multiplayer and in demos. Multiplayer probably -// isn't possible for Strife conversations, but demo playback should be. +// TODO: Make this work in demos. struct FStrifeDialogueReply; class FTexture; @@ -48,6 +47,16 @@ struct FStrifeDialogueReply FBrokenLines *ReplyLines; }; +// [CW] These are used to make conversations work. +enum +{ + CONV_NPCANGLE, + CONV_ANIMATE, + CONV_GIVEINVENTORY, + CONV_TAKEINVENTORY, + CONV_SETNULL, +}; + extern TArray StrifeDialogues; // There were 344 types in Strife, and Strife conversations refer @@ -62,4 +71,6 @@ void P_FreeStrifeConversations (); void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveangle); void P_ResumeConversation (); +void P_ConversationCommand (int player, BYTE **stream); + #endif diff --git a/src/p_user.cpp b/src/p_user.cpp index 89104a50d..0090632dc 100644 --- a/src/p_user.cpp +++ b/src/p_user.cpp @@ -281,7 +281,11 @@ player_s::player_s() crouchdir(0), crouchfactor(0), crouchoffset(0), - crouchviewdelta(0) + crouchviewdelta(0), + ConversationNPC(0), + ConversationPC(0), + ConversationNPCAngle(0), + ConversationFaceTalker(0) { memset (&cmd, 0, sizeof(cmd)); memset (&userinfo, 0, sizeof(userinfo)); @@ -315,6 +319,8 @@ size_t player_s::FixPointers (const DObject *old, DObject *rep) if (last_mate == old) last_mate = replacement, changed++; if (ReadyWeapon == old) ReadyWeapon = static_cast(rep), changed++; if (PendingWeapon == old) PendingWeapon = static_cast(rep), changed++; + if (ConversationNPC == old) ConversationNPC = replacement, changed++; + if (ConversationPC == old) ConversationPC = replacement, changed++; return changed; } @@ -331,6 +337,8 @@ size_t player_s::PropagateMark() GC::Mark(mate); GC::Mark(last_mate); GC::Mark(ReadyWeapon); + GC::Mark(ConversationNPC); + GC::Mark(ConversationPC); if (PendingWeapon != WP_NOCHANGE) { GC::Mark(PendingWeapon); @@ -2419,7 +2427,11 @@ void player_s::Serialize (FArchive &arc) << BlendB << BlendA << accuracy << stamina - << LogText; + << LogText + << ConversationNPC + << ConversationPC + << ConversationNPCAngle + << ConversationFaceTalker; for (i = 0; i < MAXPLAYERS; i++) arc << frags[i]; diff --git a/src/version.h b/src/version.h index d4040ab6a..4686afd0a 100644 --- a/src/version.h +++ b/src/version.h @@ -54,7 +54,7 @@ // Version identifier for network games. // Bump it every time you do a release unless you're certain you // didn't change anything that will affect sync. -#define NETGAMEVERSION 215 +#define NETGAMEVERSION 216 // Version stored in the ini's [LastRun] section. // Bump it if you made some configuration change that you want to @@ -64,7 +64,7 @@ // Protocol version used in demos. // Bump it if you change existing DEM_ commands or add new ones. // Otherwise, it should be safe to leave it alone. -#define DEMOGAMEVERSION 0x20B +#define DEMOGAMEVERSION 0x20C // Minimum demo version we can play. // Bump it whenever you change or remove existing DEM_ commands. @@ -75,7 +75,7 @@ // SAVESIG should match SAVEVER. // MINSAVEVER is the minimum level snapshot version that can be loaded. -#define MINSAVEVER 849 +#define MINSAVEVER 854 #if SVN_REVISION_NUMBER < MINSAVEVER // Never write a savegame with a version lower than what we need