Dialogue 2

This commit is contained in:
Sally Coolatta 2023-08-24 17:28:53 -04:00
parent f02e6dbe3c
commit 16e6aa423e
14 changed files with 567 additions and 52 deletions

View file

@ -149,6 +149,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
k_mapuser.c
k_powerup.cpp
k_hitlag.c
k_dialogue.cpp
music.cpp
music_manager.cpp
)

View file

@ -43,6 +43,8 @@
#include "../k_podium.h"
#include "../k_bot.h"
#include "../z_zone.h"
#include "../r_draw.h"
#include "../k_dialogue.hpp"
#include "call-funcs.hpp"
@ -663,6 +665,46 @@ bool CallFunc_CameraWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::
return true; // Execution interrupted
}
/*--------------------------------------------------
bool CallFunc_DialogueWaitDismiss(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
Pauses the thread until the current
dialogue box is dismissed.
--------------------------------------------------*/
bool CallFunc_DialogueWaitDismiss(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
{
(void)argV;
(void)argC;
thread->state = {
ACSVM::ThreadState::WaitTag,
0,
ACS_TAGTYPE_DIALOGUE
};
return true; // Execution interrupted
}
/*--------------------------------------------------
bool CallFunc_DialogueWaitText(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
Pauses the thread until the current
dialogue box finishes rendering its text.
--------------------------------------------------*/
bool CallFunc_DialogueWaitText(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
{
(void)argV;
(void)argC;
thread->state = {
ACSVM::ThreadState::WaitTag,
1,
ACS_TAGTYPE_DIALOGUE
};
return true; // Execution interrupted
}
/*--------------------------------------------------
bool CallFunc_ChangeFloor(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
@ -1856,6 +1898,98 @@ bool CallFunc_AddBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word
return false;
}
/*--------------------------------------------------
bool CallFunc_DialogueSetSpeaker(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
Set the dialogue speaker to a skin.
--------------------------------------------------*/
bool CallFunc_DialogueSetSpeaker(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
{
ACSVM::MapScope *map = nullptr;
ACSVM::String *skinStr = nullptr;
const char *skinName = nullptr;
int spriteFrame = 0;
(void)argC;
map = thread->scopeMap;
skinStr = map->getString(argV[0]);
skinName = skinStr->str;
spriteFrame = argV[1];
g_dialogue.SetSpeaker(skinName, spriteFrame);
return false;
}
/*--------------------------------------------------
bool CallFunc_DialogueSetCustomSpeaker(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
Set the dialogue speaker to specific graphics.
--------------------------------------------------*/
bool CallFunc_DialogueSetCustomSpeaker(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
{
ACSVM::MapScope *map = nullptr;
ACSVM::String *nametagStr = nullptr;
const char *nametag = nullptr;
ACSVM::String *patchStr = nullptr;
const char *patchName = nullptr;
ACSVM::String *colorStr = nullptr;
const char *colorName = nullptr;
skincolornum_t colorID = SKINCOLOR_NONE;
UINT8 *colormap = nullptr;
(void)argC;
map = thread->scopeMap;
nametagStr = map->getString(argV[0]);
nametag = nametagStr->str;
patchStr = map->getString(argV[1]);
patchName = patchStr->str;
colorStr = map->getString(argV[1]);
colorName = colorStr->str;
if (ACS_GetColorFromString(colorName, &colorID) == true)
{
colormap = R_GetTranslationColormap(TC_DEFAULT, colorID, GTC_CACHE);
}
g_dialogue.SetSpeaker(nametag, patchName, colormap);
return false;
}
/*--------------------------------------------------
bool CallFunc_DialogueNewText(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
Change the current dialogue text.
--------------------------------------------------*/
bool CallFunc_DialogueNewText(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
{
ACSVM::MapScope *map = nullptr;
ACSVM::String *textStr = nullptr;
const char *text = nullptr;
(void)argC;
map = thread->scopeMap;
textStr = map->getString(argV[0]);
text = textStr->str;
g_dialogue.NewText(text);
return false;
}
/*--------------------------------------------------
bool CallFunc_Get/SetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)

View file

@ -42,6 +42,8 @@ bool CallFunc_ThingCount(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::
bool CallFunc_TagWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_PolyWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_CameraWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_DialogueWaitDismiss(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_DialogueWaitText(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_ChangeFloor(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_ChangeCeiling(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_LineSide(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
@ -88,6 +90,10 @@ bool CallFunc_SetLineRenderStyle(ACSVM::Thread *thread, const ACSVM::Word *argV,
bool CallFunc_MapWarp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_AddBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_DialogueSetSpeaker(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_DialogueSetCustomSpeaker(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_DialogueNewText(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_GetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_SetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_GetSideProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);

View file

@ -27,6 +27,7 @@
#include "../w_wad.h"
#include "../z_zone.h"
#include "../p_local.h"
#include "../k_dialogue.hpp"
#include "environment.hpp"
#include "thread.hpp"
@ -171,6 +172,12 @@ Environment::Environment()
addFuncDataACS0( 503, addCallFunc(CallFunc_SetLineRenderStyle));
addFuncDataACS0( 504, addCallFunc(CallFunc_MapWarp));
addFuncDataACS0( 505, addCallFunc(CallFunc_AddBot));
addFuncDataACS0( 600, addCallFunc(CallFunc_DialogueSetSpeaker));
addFuncDataACS0( 601, addCallFunc(CallFunc_DialogueSetCustomSpeaker));
addFuncDataACS0( 602, addCallFunc(CallFunc_DialogueNewText));
addFuncDataACS0( 603, addCallFunc(CallFunc_DialogueWaitDismiss));
addFuncDataACS0( 604, addCallFunc(CallFunc_DialogueWaitText));
}
ACSVM::Thread *Environment::allocThread()
@ -284,6 +291,20 @@ bool Environment::checkTag(ACSVM::Word type, ACSVM::Word tag)
return (camera->tracer == nullptr || P_MobjWasRemoved(camera->tracer) == true);
}
case ACS_TAGTYPE_DIALOGUE:
{
if (tag == 0) // cheeky reuse
{
// wait for dismissal
return (!g_dialogue.Active());
}
else
{
// wait for text to finish
return (g_dialogue.TextDone());
}
}
}
return true;

View file

@ -52,6 +52,7 @@ enum acs_tagType_e
ACS_TAGTYPE_POLYOBJ,
ACS_TAGTYPE_SECTOR,
ACS_TAGTYPE_CAMERA,
ACS_TAGTYPE_DIALOGUE,
};
class ThreadInfo : public ACSVM::ThreadInfo

View file

@ -915,6 +915,7 @@ char spr2names[NUMPLAYERSPRITES][5] =
"SIGN", // Finish signpost
"XTRA", // Three Faces of Darkness
"TALK", // Dialogue
};
playersprite_t free_spr2 = SPR2_FIRSTFREESLOT;
@ -956,6 +957,7 @@ playersprite_t spr2defaults[NUMPLAYERSPRITES] = {
0, // SPR2_SIGN
0, // SPR2_XTRA
0, // SPR2_TALK
};
// Doesn't work with g++, needs actionf_p1 (don't modify this comment)

View file

@ -1468,6 +1468,7 @@ typedef enum playersprite
SPR2_DEAD,
SPR2_SIGN,
SPR2_XTRA,
SPR2_TALK,
SPR2_FIRSTFREESLOT,
SPR2_LASTFREESLOT = 0x7f,

280
src/k_dialogue.cpp Normal file
View file

@ -0,0 +1,280 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) by Sonic Team Junior
// Copyright (C) by Kart Krew
// Copyright (C) by Sally "TehRealSalt" Cochenour
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file k_dialogue.cpp
/// \brief Basic text prompts
#include "k_dialogue.hpp"
#include "k_dialogue.h"
#include <string>
#include <algorithm>
#include "info.h"
#include "g_game.h"
#include "v_video.h"
#include "r_draw.h"
#include "r_skins.h"
#include "z_zone.h"
#include "acs/interface.h"
using srb2::Dialogue;
void Dialogue::SetSpeaker(void)
{
// Unset speaker
speaker.clear();
portrait = nullptr;
portraitColormap = nullptr;
}
void Dialogue::SetSpeaker(std::string skinName, int portraitID)
{
// Set speaker based on a skin
int skinID = -1;
if (!skinName.empty())
{
skinID = R_SkinAvailable(skinName.c_str());
}
if (skinID >= 0 && skinID < numskins)
{
const skin_t *skin = &skins[skinID];
const spritedef_t *sprdef = &skin->sprites[SPR2_TALK];
if (sprdef->numframes > 0)
{
portraitID %= sprdef->numframes;
}
const spriteframe_t *sprframe = &sprdef->spriteframes[portraitID];
speaker = skin->realname;
portrait = static_cast<patch_t *>( W_CachePatchNum(sprframe->lumppat[0], PU_CACHE) );
portraitColormap = R_GetTranslationColormap(skinID, static_cast<skincolornum_t>(skin->prefcolor), GTC_CACHE);
}
else
{
SetSpeaker();
}
}
void Dialogue::SetSpeaker(std::string customName, std::string customPatch, UINT8 *customColormap)
{
// Set custom speaker
speaker = customName;
if (speaker.empty())
{
portrait = nullptr;
portraitColormap = nullptr;
return;
}
portrait = static_cast<patch_t *>( W_CachePatchName(customPatch.c_str(), PU_CACHE) );
portraitColormap = customColormap;
}
void Dialogue::NewText(std::string newText)
{
text.clear();
textDest = newText;
std::reverse(textDest.begin(), textDest.end());
textTimer = kTextPunctPause;
textSpeed = kTextSpeedDefault;
textDone = false;
}
bool Dialogue::Active(void)
{
return (!speaker.empty());
}
bool Dialogue::TextDone(void)
{
return textDone;
}
void Dialogue::WriteText(void)
{
textTimer -= textSpeed;
while (textTimer <= 0 && !textDest.empty())
{
char c = textDest.back();
text.push_back(c);
if (c == '.' || c == '!' || c == '?')
{
// slow down for punctuation
textTimer += kTextPunctPause;
}
else
{
textTimer += FRACUNIT;
}
textDest.pop_back();
}
textDone = (textTimer <= 0 && textDest.empty());
}
void Dialogue::CompleteText(void)
{
while (!textDest.empty())
{
text.push_back( textDest.back() );
textDest.pop_back();
}
textTimer = 0;
textDone = true;
}
void Dialogue::Tick(void)
{
if (Active() == false)
{
return;
}
WriteText();
bool pressed = (
((players[serverplayer].cmd.buttons & BT_VOTE) == BT_VOTE) &&
((players[serverplayer].oldcmd.buttons & BT_VOTE) == 0)
);
if (pressed == true)
{
if (textDone)
{
SetSpeaker();
}
else
{
CompleteText();
}
}
}
void Dialogue::UpdatePatches(void)
{
if (bgPatch == nullptr)
{
bgPatch = static_cast<patch_t *>(W_CachePatchName("TUTDIAG1", PU_HUDGFX) );
}
if (confirmPatch == nullptr)
{
confirmPatch = static_cast<patch_t *>(W_CachePatchName("TUTDIAG2", PU_HUDGFX) );
}
}
void Dialogue::Draw(void)
{
if (Active() == false)
{
return;
}
UpdatePatches();
V_DrawFixedPatch(
0, 0,
FRACUNIT,
V_SNAPTOTOP,
bgPatch,
nullptr
);
if (portrait != nullptr)
{
V_DrawFixedPatch(
10 * FRACUNIT, 41 * FRACUNIT,
FRACUNIT,
V_SNAPTOTOP,
portrait,
portraitColormap
);
}
V_DrawString(
45, 39,
V_SNAPTOTOP,
speaker.c_str()
);
V_DrawString(
10, 3,
V_SNAPTOTOP,
text.c_str()
);
if (textDone)
{
V_DrawFixedPatch(
304 * FRACUNIT, 7 * FRACUNIT,
FRACUNIT,
V_SNAPTOTOP,
confirmPatch,
nullptr
);
}
}
void Dialogue::Dismiss(void)
{
if (Active() == false)
{
return;
}
SetSpeaker();
text.clear();
textDest.clear();
if (G_GamestateUsesLevel() == true && !script.empty())
{
ACS_Execute(script.c_str(), nullptr, 0, nullptr);
}
script.clear();
}
/*
Ideally, the Dialogue class would be on player_t instead of in global space
for full multiplayer compatibility, but right now it's only being used for
the tutorial, and I don't feel like writing network code. If you feel like
doing that, then you can remove g_dialogue entirely.
*/
Dialogue g_dialogue;
void K_DismissDialogue(void)
{
g_dialogue.Dismiss();
}
void K_DrawDialogue(void)
{
g_dialogue.Draw();
}
void K_TickDialogue(void)
{
g_dialogue.Tick();
}

32
src/k_dialogue.h Normal file
View file

@ -0,0 +1,32 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) by Sonic Team Junior
// Copyright (C) by Kart Krew
// Copyright (C) by Sally "TehRealSalt" Cochenour
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file k_dialogue.h
/// \brief Basic text prompts
#ifndef __K_DIALOGUE__
#define __K_DIALOGUE__
#include "doomtype.h"
#include "doomdef.h"
#ifdef __cplusplus
extern "C" {
#endif
void K_DismissDialogue(void);
void K_DrawDialogue(void);
void K_TickDialogue(void);
#ifdef __cplusplus
} // extern "C"
#endif
#endif //__K_DIALOGUE__

77
src/k_dialogue.hpp Normal file
View file

@ -0,0 +1,77 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) by Sonic Team Junior
// Copyright (C) by Kart Krew
// Copyright (C) by Sally "TehRealSalt" Cochenour
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file k_dialogue.hpp
/// \brief Basic text prompts
#ifndef __K_DIALOGUE_HPP__
#define __K_DIALOGUE_HPP__
#include <string>
#include "doomdef.h"
#include "doomtype.h"
#include "typedef.h"
#include "v_video.h"
namespace srb2
{
class Dialogue
{
private:
patch_t *bgPatch;
patch_t *confirmPatch;
bool active;
std::string speaker;
patch_t *portrait;
UINT8 *portraitColormap;
std::string text;
std::string textDest;
fixed_t textTimer;
fixed_t textSpeed;
bool textDone;
bool freeze;
std::string script;
void UpdatePatches(void);
void WriteText(void);
void CompleteText(void);
public:
static constexpr fixed_t kTextSpeedDefault = FRACUNIT;
static constexpr fixed_t kTextPunctPause = FRACUNIT * TICRATE * 3 / 5;
void SetSpeaker(void);
void SetSpeaker(std::string skinName, int portraitID);
void SetSpeaker(std::string customName, std::string customPatch, UINT8 *customColormap);
void NewText(std::string newText);
bool Active(void);
bool TextDone(void);
void Tick(void);
void Draw(void);
void Dismiss(void);
};
}; // namespace srb2
extern srb2::Dialogue g_dialogue;
#endif //__K_DIALOGUE_HPP__

View file

@ -105,6 +105,7 @@
#include "k_rank.h"
#include "k_mapuser.h"
#include "music.h"
#include "k_dialogue.h"
// Replay names have time
#if !defined (UNDER_CE)
@ -1282,7 +1283,6 @@ static void P_LoadSidedefs(UINT8 *data)
case 425: // Calls P_SetMobjState on calling mobj
case 442: // Calls P_SetMobjState on mobjs of a given type in the tagged sectors
case 443: // Calls a named Lua function
case 459: // Control text prompt (named tag)
case 461: // Spawns an object on the map based on texture offsets
case 463: // Colorizes an object
case 475: // ACS_Execute
@ -5994,25 +5994,6 @@ static void P_ConvertBinaryLinedefTypes(void)
lines[i].args[3] = (lines[i].sidenum[1] != 0xffff) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : 0;
lines[i].args[4] = !!(lines[i].flags & ML_NOSKEW);
break;
case 459: //Control text prompt
lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
lines[i].args[2] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
if (lines[i].flags & ML_BLOCKPLAYERS)
lines[i].args[3] |= TMP_CLOSE;
if (lines[i].flags & ML_SKEWTD)
lines[i].args[3] |= TMP_RUNPOSTEXEC;
if (lines[i].flags & ML_TFERLINE)
lines[i].args[3] |= TMP_CALLBYNAME;
if (lines[i].flags & ML_NOSKEW)
lines[i].args[3] |= TMP_KEEPCONTROLS;
if (lines[i].flags & ML_MIDPEG)
lines[i].args[3] |= TMP_KEEPREALTIME;
/*if (lines[i].flags & ML_NOCLIMB)
lines[i].args[3] |= TMP_ALLPLAYERS;
if (lines[i].flags & ML_MIDSOLID)
lines[i].args[3] |= TMP_FREEZETHINKERS;*/
lines[i].args[4] = (lines[i].sidenum[1] != 0xFFFF) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : tag;
break;
case 460: //Award rings
lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;

View file

@ -3366,13 +3366,10 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha
}
break;
case 437: // Disable Player Controls
case 437: // Toggle Player Controls
if (mo && mo->player)
{
UINT16 fractime = (UINT16)(args[0]);
if (fractime < 1)
fractime = 1; //instantly wears off upon leaving
mo->player->nocontrol = fractime;
mo->player->nocontrol = ((args[0] != 0) ? UINT16_MAX : 0);
}
break;
@ -4153,33 +4150,6 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha
}
break;
case 459: // Control Text Prompt
// console player only
if (mo && mo->player && P_IsLocalPlayer(mo->player))
{
INT32 promptnum = max(0, args[1] - 1);
INT32 pagenum = max(0, args[2] - 1);
INT32 postexectag = abs(args[4]);
boolean closetextprompt = (args[3] & TMP_CLOSE);
//boolean allplayers = (args[3] & TMP_ALLPLAYERS);
boolean runpostexec = (args[3] & TMP_RUNPOSTEXEC);
boolean blockcontrols = !(args[3] & TMP_KEEPCONTROLS);
boolean freezerealtime = !(args[3] & TMP_KEEPREALTIME);
//boolean freezethinkers = (args[3] & TMP_FREEZETHINKERS);
boolean callbynamedtag = (args[3] & TMP_CALLBYNAME);
if (closetextprompt)
F_EndTextPrompt(false, false);
else
{
if (callbynamedtag && stringargs[0] && stringargs[0][0])
F_GetPromptPageByNamedTag(stringargs[0], &promptnum, &pagenum);
F_StartTextPrompt(promptnum, pagenum, mo, runpostexec ? postexectag : 0, blockcontrols, freezerealtime);
}
}
break;
case 460: // Award rings
{
if (gametyperules & GTR_SPHERES)

View file

@ -43,6 +43,7 @@
#include "acs/interface.h"
#include "k_objects.h"
#include "music.h"
#include "k_dialogue.h"
#ifdef PARANOIA
#include "deh_tables.h" // MOBJTYPE_LIST
@ -1033,6 +1034,11 @@ void P_Ticker(boolean run)
P_RunChaseCameras();
}
if (run)
{
K_TickDialogue();
}
LUA_HOOK(PostThinkFrame);
if (run)

View file

@ -52,6 +52,7 @@
#include "k_zvote.h"
#include "music.h"
#include "i_sound.h"
#include "k_dialogue.h"
UINT16 objectsdrawn = 0;
@ -1244,6 +1245,8 @@ static void ST_overlayDrawer(void)
}
K_DrawMidVote();
K_DrawDialogue();
}
void ST_DrawDemoTitleEntry(void)