- Added Karate Chris's submission for multiplayer Strife conversations.

SVN r855 (trunk)
This commit is contained in:
Christoph Oelckers 2008-03-26 08:29:02 +00:00
parent 8d0c48bf81
commit a01aaf35ad
10 changed files with 251 additions and 93 deletions

View file

@ -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.

View file

@ -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;
}

View file

@ -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;

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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
@ -701,6 +702,9 @@ void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveang
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<AInventory *> (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<AWeapon*>(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;
}
@ -991,7 +1001,7 @@ static void PickConversationReply ()
// Update the quest log, if needed.
if (reply->LogNumber != 0)
{
players[consoleplayer].SetLogNumber (reply->LogNumber);
cp->SetLogNumber (reply->LogNumber);
}
if (replyText != NULL)
@ -1000,31 +1010,42 @@ 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;
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);
}
@ -1065,12 +1086,89 @@ void CleanupConversationMenu ()
//
// 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<AInventory *> (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<AWeapon*>(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;
}
}

View file

@ -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<FStrifeDialogueNode *> 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

View file

@ -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<AWeapon *>(rep), changed++;
if (PendingWeapon == old) PendingWeapon = static_cast<AWeapon *>(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];

View file

@ -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