thirtyflightsofloving/game/p_text.c
2021-07-14 05:27:46 -04:00

779 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, sizeof(filename), basedir->string);
if (strlen(gamedir->string))
{
Q_strncatz(filename, sizeof(filename), "\\");
Q_strncatz(filename, sizeof(filename), gamedir->string);
}
*/
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, sizeof(filename), "\\maps\\");
Q_strncatz(filename, sizeof(filename), message);
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, sizeof(hnd->background_image), "textdisplay");
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);
if (sscanf(p2, "%d", &hnd->page_length) == EOF) {
gi.dprintf ("Do_Text_Display: invalid value '%s' for page length.\n", p2);
}
hnd->page_length += 1;
}
p2 = strstr(p1,"W=");
if (p2 && (p2 < p3))
{
p2 += 2;
// sscanf(p2, "%d", &hnd->page_width);
if (sscanf(p2, "%d", &hnd->page_width) == EOF) {
gi.dprintf ("Do_Text_Display: invalid value '%s' for page width.\n", p2);
}
}
p2 = strstr(p1,"I=");
if (p2 && (p2 < p3))
{
p2 += 2;
// sscanf(p2, "%s", hnd->background_image);
if (sscanf(p2, "%s", hnd->background_image) == EOF) {
gi.dprintf ("Do_Text_Display: invalid string '%s' for background image.\n", p2);
}
}
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, sizeof(sound), p1+1);
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;
}