thirtyflightsofloving/game/p_text.c
Knightmare66 d16b46e3cf Added re-implementation of func_plat2 and func_door2 from rogue to default Lazarus DLL.
Overhauled child entity movement in default Lazarus DLL.
Added bbox versions of various triggers to default Lazarus DLL.
Added level.maptype field to default Lazarus DLL.
Added entity class IDs to default Lazarus DLL.
Incremented savegame version for default Lazarus DLL.
2020-10-27 02:00:05 -04:00

770 lines
18 KiB
C

/*
===========================================================================
Copyright (C) 1997-2001 Id Software, Inc.
Copyright (C) 2000-2002 Mr. Hyde and Mad Dog
This file is part of Lazarus Quake 2 Mod source code.
Lazarus Quake 2 Mod source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Lazarus Quake 2 Mod source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Lazarus Quake 2 Mod source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "g_local.h"
#include "pak.h"
#define MAX_LINES 24
#define MAX_LINE_LENGTH 35
text_t text[MAX_LINES];
void Text_Open(edict_t *ent)
{
if (!ent->client)
return;
ent->client->showscores = true;
ent->client->inmenu = true;
ent->client->textdisplay->last_update = 0;
Text_Update(ent);
}
void Text_Close(edict_t *ent)
{
if (!ent->client) return;
if (!ent->client->textdisplay) return;
if (ent->client->textdisplay->buffer)
{
gi.TagFree(ent->client->textdisplay->buffer);
ent->client->textdisplay->buffer = NULL;
}
gi.TagFree(ent->client->textdisplay);
ent->client->textdisplay = NULL;
ent->client->showscores = false;
}
void Text_BuildDisplay(texthnd_t *hnd)
{
int i, imax, n;
char *p1, *p2, *p3;
for (i=0; i<hnd->page_length+2; i++)
text[i].text = NULL;
if (!(hnd->flags & 2))
{
text[hnd->page_length+1].text = "Esc to quit";
if (hnd->nlines > hnd->page_length)
text[hnd->page_length].text = "Use [ and ] to scroll";
}
p1 = hnd->buffer+hnd->start_char;
p3 = hnd->buffer+hnd->size-1;
if (hnd->curline > 0)
{
// Scan for hnd->curline'th 0 byte, point to following character
n = hnd->curline;
while (p1 < p3 && n)
{
if (*p1==0) n--;
p1++;
}
}
i = 0;
p2 = p1;
text[i].text = p2;
if (hnd->nlines > hnd->page_length)
imax = hnd->page_length-2;
else
imax = hnd->page_length-1;
while (p2 <= p3 && i < imax)
{
if (*p2 == 0 && p2 < p3)
{
i++;
p2++;
text[i].text = p2;
}
else
p2++;
}
}
void Text_Update(edict_t *ent)
{
int align;
int i;
int x0, y0;
text_t *p;
int x, xlast;
char *t, *tnext;
qboolean alt = false;
char string[2048];
texthnd_t *hnd;
if (!ent->client->textdisplay) {
gi.dprintf("warning: ent has no text display\n");
return;
}
hnd = ent->client->textdisplay;
if (hnd->last_update + 2*FRAMETIME > level.time) return;
hnd->last_update = level.time;
x0 = (35 - hnd->page_width)*4;
y0 = (22 - hnd->page_length)*4;
if (!(hnd->flags & 2))
{
Com_sprintf (string, sizeof(string), "xv %d yv %d picn %s ",
x0, y0, hnd->background_image);
}
else // Knightmare- we NEED to have a placeholder image here
Com_sprintf (string, sizeof(string), "xv %d yv %d picn blank ", x0, y0);
xlast = 9999;
for (i = 0, p = hnd->lines; i < hnd->page_length+2; i++, p++)
{
if (!p->text || !*(p->text)) // crashes here on load
continue; // blank line
t = p->text;
if (*t == '*') {
alt = true;
t++;
}
align = TEXT_LEFT;
if (*t == '\\')
{
tnext = t;
tnext++;
if (*tnext == 'c')
{
align = TEXT_CENTER;
t++;
t++;
}
if (*tnext == 'r')
{
align = TEXT_RIGHT;
t++;
t++;
}
}
if (strlen(t))
{
// sprintf(string + strlen(string), "yv %d ", y0 + 24 + i * 8);
Com_sprintf (string + strlen(string), sizeof(string)-strlen(string), "yv %d ", y0 + 24 + i * 8);
if (align == TEXT_CENTER)
x = x0 + 20 + (hnd->page_width-1-(int)strlen(t))*4;
else if (align == TEXT_RIGHT)
x = x0 + 20 + (hnd->page_width-1-(int)strlen(t))*8;
else
x = x0 + 20;
if (x != xlast)
{
// sprintf(string + strlen(string), "xv %d ",x);
Com_sprintf (string + strlen(string), sizeof(string)-strlen(string), "xv %d ",x);
xlast = x;
}
if (alt) {
// sprintf(string + strlen(string), "string2 \"%s\" ", t);
Com_sprintf (string + strlen(string), sizeof(string)-strlen(string), "string2 \"%s\" ", t);
}
else {
// sprintf(string + strlen(string), "string \"%s\" ", t);
Com_sprintf (string + strlen(string), sizeof(string)-strlen(string), "string \"%s\" ", t);
}
}
alt = false;
}
// if (strlen(string) > 1000)
// gi.dprintf("WARNING: formatted string length (%d) > 1000\n",strlen(string));
gi.WriteByte (svc_layout);
gi.WriteString (string);
gi.unicast (ent, true);
}
void Text_Next(edict_t *ent)
{
int current;
int displayed_lines;
texthnd_t *hnd;
if (!ent->client->textdisplay) {
gi.dprintf("warning: ent has no text display\n");
return;
}
hnd = ent->client->textdisplay;
displayed_lines = hnd->page_length;
if (hnd->nlines > hnd->page_length) displayed_lines--;
if (hnd->curline+displayed_lines+1 < hnd->nlines)
{
current = hnd->curline;
// hnd->curline = min(hnd->curline+MAX_LINES/2,hnd->nlines-displayed_lines-1);
hnd->curline = hnd->curline+hnd->page_length-1;
if (hnd->curline > current)
{
Text_BuildDisplay(hnd);
Text_Update(ent);
}
}
}
void Text_Prev(edict_t *ent)
{
texthnd_t *hnd;
if (!ent->client->textdisplay) {
gi.dprintf("warning: ent has no text display\n");
return;
}
hnd = ent->client->textdisplay;
if (hnd->curline > 0)
{
// hnd->curline = max(0, hnd->curline-MAX_LINES/2);
hnd->curline = max(0, hnd->curline-hnd->page_length+1);
Text_BuildDisplay(hnd);
Text_Update(ent);
}
}
void Do_Text_Display(edict_t *activator, int flags, char *message)
{
int /*i,*/ L;
byte *p1, *p2, *p3; // was char *
char sound[64];
texthnd_t *hnd;
byte *temp_buffer;
int line_length;
int new_line_length;
qboolean alt, centered, right_justified;
qboolean linebreak;
qboolean do_linebreaks;
hnd = gi.TagMalloc(sizeof(*hnd), TAG_LEVEL);
// If a file, open and read it
if (flags & 1)
{
#ifdef KMQUAKE2_ENGINE_MOD // use new engine file loading function instead
char textname[128];
int textsize;
byte *readbuffer;
// sprintf(textname,"maps/%s", message);
Com_sprintf(textname, sizeof(textname), "maps/%s", message);
textsize = gi.LoadFile(textname, (void **)&readbuffer);
if (textsize < 2) // file not found
{
gi.dprintf("File not found: %s\n",textname);
return;
}
hnd->allocated = textsize + 128; // add some slop for additional control characters
hnd->buffer = gi.TagMalloc(hnd->allocated, TAG_LEVEL);
if (!hnd->buffer)
{
gi.dprintf("Memory allocation failure on target_text\n");
Text_Close(activator);
return;
}
memset(hnd->buffer,0,hnd->allocated);
memcpy(hnd->buffer, readbuffer, textsize);
hnd->buffer[textsize] = 0;
gi.FreeFile(readbuffer);
#else
cvar_t *basedir, *gamedir;
char filename[256];
char pakfile[256];
char textname[128];
int i, k, num, numitems;
qboolean in_pak;
FILE *f;
pak_header_t pakheader;
pak_item_t pakitem;
basedir = gi.cvar("basedir", "", 0);
gamedir = gi.cvar("gamedir", "", 0);
/*
Q_strncpyz(filename, basedir->string, sizeof(filename));
if (strlen(gamedir->string))
{
Q_strncatz(filename, "\\", sizeof(filename));
Q_strncatz(filename, gamedir->string, sizeof(filename));
}
*/
if (strlen(gamedir->string))
Com_sprintf(filename, sizeof(filename), "%s\\%s", basedir->string, gamedir->string);
else
Com_sprintf(filename, sizeof(filename), "%s\\baseq2", basedir->string);
// First check for existence of text file in pak0.pak -> pak9.pak
in_pak = false;
for (i=0; i<=9 && !in_pak; i++)
{
Com_sprintf(pakfile, sizeof(pakfile), "%s\\pak%d.pak", filename, i);
if (NULL != (f = fopen(pakfile, "rb")))
{
num = (int)fread(&pakheader,1,sizeof(pak_header_t),f);
if (num >= sizeof(pak_header_t))
{
if ( pakheader.id[0] == 'P' &&
pakheader.id[1] == 'A' &&
pakheader.id[2] == 'C' &&
pakheader.id[3] == 'K' )
{
numitems = pakheader.dsize/sizeof(pak_item_t);
// sprintf(textname,"maps/%s",message);
Com_sprintf(textname, sizeof(textname), "maps/%s", message);
fseek(f, pakheader.dstart, SEEK_SET);
for (k=0; k<numitems && !in_pak; k++)
{
fread(&pakitem, 1, sizeof(pak_item_t), f);
if (!Q_stricmp(pakitem.name,textname))
{
in_pak = true;
fseek(f, pakitem.start, SEEK_SET);
hnd->allocated = pakitem.size + 128; // add some slop for additional control characters
hnd->buffer = gi.TagMalloc(hnd->allocated, TAG_LEVEL);
if (!hnd->buffer)
{
fclose(f);
gi.dprintf("Memory allocation failure on target_text\n");
Text_Close(activator);
return;
}
memset(hnd->buffer, 0, hnd->allocated);
fread(hnd->buffer, 1, pakitem.size, f);
hnd->buffer[pakitem.size] = 0;
}
}
}
}
fclose(f);
}
}
if (!in_pak)
{
// strncat(filename, "\\maps\\");
// strncat(filename, message);
Q_strncatz(filename, "\\maps\\", sizeof(filename));
Q_strncatz(filename, message, sizeof(filename));
f = fopen(filename,"rb");
if (!f)
{
gi.dprintf("File not found:%s\n",filename);
return;
}
fseek(f,0,SEEK_END);
L = ftell (f);
fseek(f,0,SEEK_SET);
hnd->allocated = L+128;
hnd->buffer = gi.TagMalloc(hnd->allocated, TAG_LEVEL);
if (!hnd->buffer)
{
gi.dprintf("Memory allocation failure on target_text\n");
Text_Close(activator);
return;
}
memset(hnd->buffer,0,hnd->allocated);
fread(hnd->buffer,1,L,f);
fclose(f);
}
#endif // KMQUAKE2_ENGINE_MOD
if (!hnd->buffer)
{
gi.dprintf("Umm... how'd you get here?\n");
Text_Close(activator);
return;
}
}
else
{
L = (int)strlen(message);
hnd->allocated = L+128;
hnd->buffer = gi.TagMalloc(hnd->allocated, TAG_LEVEL);
if (!hnd->buffer)
{
gi.dprintf("Memory allocation failure\n");
Text_Close(activator);
return;
}
memset(hnd->buffer,0,hnd->allocated);
memcpy(hnd->buffer,message,L);
}
hnd->size = (int)strlen(hnd->buffer) + 1;
// Default page length:
hnd->page_length = MAX_LINES-2;
hnd->page_width = MAX_LINE_LENGTH;
// strncpy(hnd->background_image,"textdisplay");
Q_strncpyz(hnd->background_image,"textdisplay", sizeof(hnd->background_image));
hnd->start_char = 0;
do_linebreaks = true;
// If 1st line starts with $, read page length, width, and image name
p1 = hnd->buffer;
if (*p1 == '$')
{
p3 = p1;
while ((p3 < hnd->buffer+hnd->size) && (*p3 != 13))
p3++;
p2 = strstr(p1,"L=");
if (p2 && (p2 < p3))
{
p2 += 2;
sscanf(p2,"%d",&hnd->page_length);
hnd->page_length += 1;
}
p2 = strstr(p1,"W=");
if (p2 && (p2 < p3))
{
p2 += 2;
sscanf(p2,"%d",&hnd->page_width);
}
p2 = strstr(p1,"I=");
if (p2 && (p2 < p3))
{
p2 += 2;
sscanf(p2,"%s",hnd->background_image);
}
p3++;
if (*p3 == 10) p3++;
hnd->start_char = p3-p1;
do_linebreaks = false;
}
// Eliminate all <CR>'s so lines are delineated with <LF>'s only
p1 = hnd->buffer+hnd->start_char;
while (p1 < hnd->buffer+hnd->size)
{
if (*p1 == 13)
{
for (p2=p1, p3=p1+1; p2<hnd->buffer+hnd->size; p2++, p3++)
*p2 = *p3;
hnd->size--;
}
else
p1++;
}
// Count number of lines and replace all line feeds with 0's
hnd->nlines = 1;
for (p1 = hnd->buffer+hnd->start_char; p1 < hnd->buffer+hnd->size; p1++)
{
if (*p1 == 10)
{
hnd->nlines++;
*p1 = 0;
}
}
// Line break stuff
if (!do_linebreaks)
goto done_linebreaks;
line_length = 0;
p1 = hnd->buffer+hnd->start_char;
alt = false;
centered = false;
right_justified = false;
while (p1 < hnd->buffer+hnd->size)
{
// Don't count control characters
if (line_length == 0) {
if (*p1 == '*') {
p1++;
alt = true;
} else {
alt = false;
}
if (*p1 == '\\') {
p1++;
if (*p1 == 'c') {
p1++;
centered = true;
right_justified = false;
} else if (*p1 == 'r') {
p1++;
centered = false;
right_justified = true;
} else {
centered = false;
right_justified = false;
}
} else {
centered = false;
right_justified = false;
}
}
if ((line_length == 0) && (*p1 == '\\')) p1 += 2;
if (*p1 != 0) line_length++;
linebreak = false;
if (line_length > hnd->page_width)
{
if (*p1 == 32)
{
// We're at a space... good deal, just replace space with
// a line-break 0 and move on
*p1 = 0;
hnd->nlines++;
linebreak = true;
}
else
{
// back up from current position to last space character and
// replace with a 0 (but don't go past previous 0)
p2 = p1;
while (p1 > hnd->buffer+hnd->start_char && *p1 != 0)
{
if (*p1 == 32)
{
*p1 = 0;
hnd->nlines++;
linebreak = true;
}
else
p1--;
}
if (!linebreak) {
// Must be an ugly Mad Dog test trying my patience - say
// a 40-character line with no spaces. Back up one space,
// add a hyphen then a 0.
hnd->size += 2;
if (hnd->size >= hnd->allocated) {
hnd->allocated += 128;
temp_buffer = hnd->buffer;
hnd->buffer = gi.TagMalloc(hnd->allocated, TAG_LEVEL);
if (!hnd->buffer)
{
gi.dprintf("Memory allocation failure\n");
Text_Close(activator);
return;
}
memset(hnd->buffer,0,hnd->allocated);
memcpy(hnd->buffer,temp_buffer,hnd->size);
p1 = hnd->buffer + (p2-temp_buffer);
p2 = p1;
gi.TagFree(temp_buffer);
}
p1 = p2-1;
p2 = hnd->buffer + hnd->size;
p3 = p2 - 2;
while (p3 >= p1) {
*p2 = *p3;
p2--;
p3--;
}
*p1 = '-';
p1++;
*p1 = 0;
hnd->nlines++;
linebreak = true;
}
}
}
if (linebreak && alt) {
// We broke a line and the line was green text. Insert another
// '*' at beginning of next line
hnd->size += 1;
if (hnd->size > hnd->allocated) {
hnd->allocated += 128;
temp_buffer = hnd->buffer;
hnd->buffer = gi.TagMalloc(hnd->allocated, TAG_LEVEL);
if (!hnd->buffer)
{
gi.dprintf("Memory allocation failure\n");
Text_Close(activator);
return;
}
memset(hnd->buffer,0,hnd->allocated);
memcpy(hnd->buffer,temp_buffer,hnd->size);
p2 = p1;
p1 = hnd->buffer + (p2-temp_buffer);
gi.TagFree(temp_buffer);
}
p2 = hnd->buffer + hnd->size;
p3 = p2 - 1;
while (p3 >= p1) {
*p2 = *p3;
p2--;
p3--;
}
p2 = p1+1;
*p2 = '*';
}
if (linebreak && (centered || right_justified)) {
// We broke a line and the line had other than left justification. Insert another
// '\c' or '\r' at beginning of next line
hnd->size += 2;
if (hnd->size > hnd->allocated) {
hnd->allocated += 128;
temp_buffer = hnd->buffer;
hnd->buffer = gi.TagMalloc(hnd->allocated, TAG_LEVEL);
if (!hnd->buffer)
{
gi.dprintf("Memory allocation failure\n");
Text_Close(activator);
return;
}
memset(hnd->buffer,0,hnd->allocated);
memcpy(hnd->buffer,temp_buffer,hnd->size);
p2 = p1;
p1 = hnd->buffer + (p2-temp_buffer);
gi.TagFree(temp_buffer);
}
p2 = hnd->buffer + hnd->size;
p3 = p2 - 2;
while (p3 >= p1) {
*p2 = *p3;
p2--;
p3--;
}
p2 = p1+1;
if (alt) p2++;
*p2 = '\\';
p2++;
if (centered)
*p2 = 'c';
else
*p2 = 'r';
}
if (*p1=='\\') {
p2 = p1+1;
if (*p2=='n') {
*p1 = 0;
p3 = p2 + 1;
while (p3 < hnd->buffer + hnd->size) {
*p2 = *p3;
p2++;
p3++;
}
hnd->nlines++;
linebreak = true;
centered = false;
right_justified = false;
alt = false;
}
}
// If we're at a 0, check to see if subsequent words will fit on this line
if ((!linebreak) && (*p1 == 0) && (p1 < hnd->buffer+hnd->size-1) &&
(line_length < hnd->page_width) )
{
// Don't do this if 2 consecutive 0's are found (end of paragraph)
// or if 1st character in next line is '*' or '\'
p2 = p1;
p2--;
if (*p2 != 0)
{
p2++;
p2++;
if (*p2 != 0 && *p2 != '*' && *p2 != '\\' && p2 < hnd->buffer+hnd->size)
{
new_line_length = line_length+2;
while (p2 < hnd->buffer+hnd->size && *p2 != 32 && *p2 != 0)
{
new_line_length++;
p2++;
}
if (new_line_length <= hnd->page_width)
{
*p1 = 32;
line_length++; // include the space that was a 0
hnd->nlines--;
}
}
}
}
if (*p1 == 0) line_length = 0;
p1++;
}
done_linebreaks:
// Finally, scan for a \a code (embedded audio). If present remove that line
// and play the sound
p1 = hnd->buffer+hnd->start_char;
while (p1 < hnd->buffer+hnd->size)
{
if ((*p1 == 0) || (p1 == hnd->buffer+hnd->start_char))
{
if (*p1 == 0)
p1++;
if (*p1 == '\\')
{
p1++;
if (*p1 == 'a')
{
// strncpy(sound, p1+1);
Q_strncpyz(sound, p1+1, sizeof(sound));
p1--;
p2=p1;
while (*p2 != 0)
p2++;
p2++;
memcpy(p1,p2,hnd->buffer+hnd->size-p2+1);
hnd->nlines--;
// Found one (only one is allowed)
gi.sound (activator, CHAN_AUTO, gi.soundindex (sound), 1, ATTN_NORM, 0);
}
}
}
p1++;
}
hnd->curline = 0;
hnd->flags = flags;
Text_BuildDisplay(hnd);
hnd->lines = text;
activator->client->textdisplay = hnd;
Text_Open(activator);
}
void Use_Target_Text(edict_t *self, edict_t *other, edict_t *activator)
{
if (!activator || !activator->client) return;
activator->client->showinventory = false;
activator->client->showscores = false;
activator->client->showhelp = false;
Text_Close(activator);
Do_Text_Display(activator, self->spawnflags, self->message);
}
void SP_target_text(edict_t *self)
{
if (!self->message)
{
gi.dprintf("target_text with no message at %s\n",
vtos(self->s.origin));
G_FreeEdict (self);
return;
}
self->class_id = ENTITY_TARGET_TEXT;
self->use = Use_Target_Text;
}