Quake3World.com Forums
     Programming Discussion
        Substringy stuff


Post new topicReply to topic
Login | Profile | | FAQ | Search | IRC




Print view Previous topic | Next topic 
Topic Starter Topic: Substringy stuff

Cool #9
Cool #9
Joined: 01 Dec 2000
Posts: 44138
PostPosted: 10-09-2011 11:54 AM           Profile   Send private message  E-mail  Edit post Reply with quote


In my mod (EntityPlus) I'm printing objective messages to the scoreboard. Since the scoreboard only has place for 60 characters next to each other, I've made it line-break the objective text and print the remainder on the next line, with a maximum of 4 lines. I did this as follows:

Code:
        const char *s;
        [...]
        objlen = strlen(s);
   for (i = 0; i < 4; i++) {
      if ( objlen < (i * 60) + 1)
         break;
      
      CG_DrawSmallStringColor( 82, 266 + (SMALLCHAR_HEIGHT * i), va("%.60s", &s[i * 60]), color_black);
      CG_DrawSmallStringColor( 80, 264 + (SMALLCHAR_HEIGHT * i), va("%.60s", &s[i * 60]), color);
   }


(note that there are two calls to CG_DrawSmallStringColor because one of them draws the text in black at a small offset to get a dropshadow effect)

I've tried applying this same logic to a piece of code in q3_ui:

Code:
const char desc[MAX_DESCRIPTIONLENGTH];
Q_strncpyz(desc, epMenuInfo.mapdescriptions[epMenuInfo.currentmap], sizeof(desc));
objlen = strlen(desc);

for (i = 0; i < MAX_DESCRIPTIONLINES; i++) {
   if ( objlen < (i * MAX_DESCRIPTIONLINELENGTH) + 1)
      break;

   epMenuInfo.mapDescriptionLines[i].string = va("%.40s", &desc[i * MAX_DESCRIPTIONLINELENGTH]);
}


While the objectives code works fine, the q3_ui code does not. The input for that function is
"This is just a piece of text that was entered to test how this functionality works."
but on-screen I get to see this:

ks.
tered to test how this functionality wor
ks.

I'm not so smart when it comes to pointers and char arrays and stuffs like that and I do realize that there's a difference between desc (which is declared as char array with a specific length) and s (which is declared as a pointer to a char) but I'm not sure how to do this otherwise without the whole thing crashing. Anyone know what I'm doing wrong here?

If I echo the same thing to console though:

Code:
Com_Printf("%s\n",  va("%.40s", &desc[i * MAX_DESCRIPTIONLINELENGTH]));


The console shows me exactly what I want to see.




Top
                 

Mentor
Mentor
Joined: 12 Mar 2005
Posts: 3958
PostPosted: 10-09-2011 02:19 PM           Profile Send private message  E-mail  Edit post Reply with quote


Code:
if ( objlen < (i * MAX_DESCRIPTIONLINELENGTH) + 1)

Code:
epMenuInfo.mapDescriptionLines[i].string = va("%.40s", &desc[i * MAX_DESCRIPTIONLINELENGTH]);

Should probably be:
Code:
if ( objlen < (i * 40) + 1)

Code:
epMenuInfo.mapDescriptionLines[i].string = va("%.40s", &desc[i * 40]);




Top
                 

Cool #9
Cool #9
Joined: 01 Dec 2000
Posts: 44138
PostPosted: 10-09-2011 09:43 PM           Profile   Send private message  E-mail  Edit post Reply with quote


That doesn't make any difference. That MAX_DESCRIPTIONLINELENGTH was defined as
#define MAX_DESCRIPTIONLINELENGTH 40 by the way.




Top
                 

Insane Quaker
Insane Quaker
Joined: 05 Mar 2010
Posts: 384
PostPosted: 10-09-2011 11:38 PM           Profile Send private message  E-mail  Edit post Reply with quote


Q2 uses trigger_help for primary/secondary goal setting. A spawnflag of 1 makes it secondary, otherwise it is primary.

This is the spawn function:

g_target.c:
Code:
/*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1
When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars.
*/
void SP_target_help(edict_t *ent)
{
   if (deathmatch->value)
   {   // auto-remove for deathmatch
      G_FreeEdict (ent);
      return;
   }

   if (!ent->message)
   {
      gi.dprintf ("%s with no message at %s\n", ent->classname, vtos(ent->s.origin));
      G_FreeEdict (ent);
      return;
   }
   ent->use = Use_Target_Help;
}


Just before that, this is what happens when the server fires a target_help:
Code:
void Use_Target_Help (edict_t *ent, edict_t *other, edict_t *activator)
{
   if (ent->spawnflags & 1)
      strncpy (game.helpmessage1, ent->message, sizeof(game.helpmessage2)-1);
   else
      strncpy (game.helpmessage2, ent->message, sizeof(game.helpmessage1)-1);

   game.helpchanged++;
}


helpmessage1/2 is part of the game_locals_t struct:

g_local.h:
Code:
typedef struct
{
   char      helpmessage1[512];
   char      helpmessage2[512];
   int         helpchanged;   // flash F1 icon if non 0, play sound
                        // and increment only if 1, 2, or 3

   gclient_t   *clients;      // [maxclients]

   // can't store spawnpoint in level, because
   // it would get overwritten by the savegame restore
   char      spawnpoint[512];   // needed for coop respawns

   // store latched cvars here that we want to get at often
   int         maxclients;
   int         maxentities;

   // cross level triggers
   int         serverflags;

   // items
   int         num_items;

   qboolean   autosaved;
} game_locals_t;

extern   game_locals_t   game;


That's 512 characters. Then it's simply drawn to the HUD:

p_hud.c:
Code:
/*
==================
HelpComputer

Draw help computer.
==================
*/
void HelpComputer (edict_t *ent)
{
   char   string[1024];
   char   *sk;

   if (skill->value == 0)
      sk = "easy";
   else if (skill->value == 1)
      sk = "medium";
   else if (skill->value == 2)
      sk = "hard";
   else
      sk = "hard+";

   // send the layout
   Com_sprintf (string, sizeof(string),
      "xv 32 yv 8 picn help "         // background
      "xv 202 yv 12 string2 \"%s\" "      // skill
      "xv 0 yv 24 cstring2 \"%s\" "      // level name
      "xv 0 yv 54 cstring2 \"%s\" "      // help 1
      "xv 0 yv 110 cstring2 \"%s\" "      // help 2
      "xv 50 yv 164 string2 \" kills     goals    secrets\" "
      "xv 50 yv 172 string2 \"%3i/%3i     %i/%i       %i/%i\" ",
      sk,
      level.level_name,
      game.helpmessage1,
      game.helpmessage2,
      level.killed_monsters, level.total_monsters,
      level.found_goals, level.total_goals,
      level.found_secrets, level.total_secrets);

   gi.WriteByte (svc_layout);
   gi.WriteString (string);
   gi.unicast (ent, true);
}


The layout strings there are as follows (edited out the irrelevant parts in while):

cl_scrn.c:
Code:
/*
================
SCR_ExecuteLayoutString

================
*/
void SCR_ExecuteLayoutString (char *s)
{
   int      x, y;
   int      value;
   char   *token;
   int      width;
   int      index;
   clientinfo_t   *ci;

   if (cls.state != ca_active || !cl.refresh_prepped)
      return;

   if (!s[0])
      return;

   x = 0;
   y = 0;
   width = 3;

   while (s)
   {
      token = COM_Parse (&s);
[...]
      if (!strcmp(token, "xv"))
      {
         token = COM_Parse (&s);
         x = viddef.width/2 - 160 + atoi(token);
         continue;
      }
[...]
      if (!strcmp(token, "yv"))
      {
         token = COM_Parse (&s);
         y = viddef.height/2 - 120 + atoi(token);
         continue;
      }
[...]

      if (!strcmp(token, "cstring"))
      {
         token = COM_Parse (&s);
         DrawHUDString (token, x, y, 320, 0);
         continue;
      }

      if (!strcmp(token, "string"))
      {
         token = COM_Parse (&s);
         DrawString (x, y, token);
         continue;
      }

      if (!strcmp(token, "cstring2"))
      {
         token = COM_Parse (&s);
         DrawHUDString (token, x, y, 320,0x80);
         continue;
      }

      if (!strcmp(token, "string2"))
      {
         token = COM_Parse (&s);
         DrawAltString (x, y, token);
         continue;
      }

      if (!strcmp(token, "if"))
      {   // draw a number
         token = COM_Parse (&s);
         value = cl.frame.playerstate.stats[atoi(token)];
         if (!value)
         {   // skip to endif
            while (s && strcmp(token, "endif") )
            {
               token = COM_Parse (&s);
            }
         }

         continue;
      }


   }
}

I don't know whether that's helpful or not, hopefully something there can help you (you obviously can't just blindly copy that code though since there are many differences between the 2 games). edict_t is the same as gentity_t, G_FreeEdict same as G_FreeEntity, gi.* have trap_* equivalents in Q3, etc.

Here's the cource code: http://ftp.gwdg.de/pub/misc/ftp.idsoftw ... src320.exe




Top
                 

Mentor
Mentor
Joined: 12 Mar 2005
Posts: 3958
PostPosted: 10-10-2011 12:38 AM           Profile Send private message  E-mail  Edit post Reply with quote


Eraser wrote:
That doesn't make any difference. That MAX_DESCRIPTIONLINELENGTH was defined as
#define MAX_DESCRIPTIONLINELENGTH 40 by the way.

You should've said so before. >:(

I know what the issue is: va() writes (and returns a pointer) to a static buffer. Successive calls overwrite the contents of that buffer so make a copy of the string after each call.




Top
                 

Cool #9
Cool #9
Joined: 01 Dec 2000
Posts: 44138
PostPosted: 10-10-2011 01:48 AM           Profile   Send private message  E-mail  Edit post Reply with quote


Excuse my ignorance here. I've tried your suggestion but now I get to see nothing at all.

Code:
1| #define MAX_DESCRIPTIONLINES 10
2| #define MAX_DESCRIPTIONLINELENGTH 40
3| #define MAX_DESCRIPTIONLENGTH   MAX_DESCRIPTIONLINES * MAX_DESCRIPTIONLINELENGTH

[....]

4| char desc[MAX_DESCRIPTIONLENGTH];
5| char line[MAX_DESCRIPTIONLINELENGTH];
6| int objlen;
7| Q_strncpyz(desc, epMenuInfo.mapdescriptions[epMenuInfo.currentmap], sizeof(desc));
8| objlen = strlen(desc);
9|
10| for (i = 0; i < MAX_DESCRIPTIONLINES; i++) {
11|   if ( objlen < (i * 40) + 1)
12|      break;
13|
14|   strcpy(line, va("%.40s", &desc[i * 40]));
15|   epMenuInfo.mapDescriptionLines[i].string = line;
16| }


On line 14 I try to copy the result of the va function to the line char array and feed that to the mapDescriptionLines menu element (a menutext_s struct). I'm not sure what I'm doing (wrong) here. The Q_strncpyz function and mapDescriptionLines[i].string appear to be a pointer to a char while line is defined as an array. These things are essentially the same, right? Things go boom when I define line as a pointer to char though and use strcpy instead of Q_strncpyz.

edit:
well I don't see nothing. I see some weird characters flash into view and then disappear immediately again.




Top
                 

Mentor
Mentor
Joined: 12 Mar 2005
Posts: 3958
PostPosted: 10-10-2011 05:26 PM           Profile Send private message  E-mail  Edit post Reply with quote


`line[MAX_DESCRIPTIONLINELENGTH]` seems to be allocated on the stack so you're probably deep in undefined behaviour territory by now (returning or storing the address of a stack-based variable is bad mojo).

You should probably turn `epMenuInfo.mapDescriptionLines` from an array of pointers into an array of strings, i.e.:

Code:
char mapDescriptionLines[8][MAX_DESCRIPTIONLENGTH];




Top
                 

Cool #9
Cool #9
Joined: 01 Dec 2000
Posts: 44138
PostPosted: 10-10-2011 11:22 PM           Profile   Send private message  E-mail  Edit post Reply with quote


It's an array of menutext_s structs actually...
It is defined as

menutext_s mapDescriptionLines[MAX_DESCRIPTIONLINES];




Top
                 

Cool #9
Cool #9
Joined: 01 Dec 2000
Posts: 44138
PostPosted: 10-11-2011 12:51 AM           Profile   Send private message  E-mail  Edit post Reply with quote


Oh I think I've got it fixed now! I've simply defined the lines variable as a global array of character arrays:

Code:
char lines[MAX_DESCRIPTIONLINES][MAX_DESCRIPTIONLINELENGTH];


In the function itself I'm doing this:

Code:
1| char desc[MAX_DESCRIPTIONLENGTH];
2| int objlen;
3| Q_strncpyz(desc, epMenuInfo.mapdescriptions[epMenuInfo.currentmap], sizeof(desc));
4| objlen = strlen(desc);
5|
6| for (i = 0; i < MAX_DESCRIPTIONLINES; i++) {
7|    if ( objlen < (i * MAX_DESCRIPTIONLINELENGTH) + 1)
8|       break;
9|
10|    Q_strncpyz(lines[i], &desc[i * (MAX_DESCRIPTIONLINELENGTH - 1)], sizeof(lines[i]));
11|    epMenuInfo.mapDescriptionLines[i].string = lines[i];
12| }




Top
                 

Cool #9
Cool #9
Joined: 01 Dec 2000
Posts: 44138
PostPosted: 10-11-2011 02:22 AM           Profile   Send private message  E-mail  Edit post Reply with quote


Nevermind, I rewrote the whole thing so it does word wrapping correctly without putting line breaks in the middle of words :olo:




Top
                 
Quake3World.com | Forum Index | Programming Discussion


Post new topic Reply to topic


cron
Quake3World.com
© ZeniMax. Zenimax, QUAKE III ARENA, Id Software and associated trademarks are trademarks of the ZeniMax group of companies. All rights reserved.
This is an unofficial fan website without any affiliation with or endorsement by ZeniMax.
All views and opinions expressed are those of the author.