/* =========================================================================== 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; ipage_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; kallocated = 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 's so lines are delineated with 's only p1 = hnd->buffer+hnd->start_char; while (p1 < hnd->buffer+hnd->size) { if (*p1 == 13) { for (p2=p1, p3=p1+1; p2buffer+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; }