Substringy stuff

Locked
User avatar
Eraser
Posts: 19177
Joined: Fri Dec 01, 2000 8:00 am

Substringy stuff

Post by Eraser »

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: Select all

        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: Select all

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: Select all

Com_Printf("%s\n",  va("%.40s", &desc[i * MAX_DESCRIPTIONLINELENGTH]));
The console shows me exactly what I want to see.
^misantropia^
Posts: 4022
Joined: Sat Mar 12, 2005 6:24 pm

Re: Substringy stuff

Post by ^misantropia^ »

Code: Select all

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

Code: Select all

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

Code: Select all

if ( objlen < (i * 40) + 1)

Code: Select all

epMenuInfo.mapDescriptionLines[i].string = va("%.40s", &desc[i * 40]);
User avatar
Eraser
Posts: 19177
Joined: Fri Dec 01, 2000 8:00 am

Re: Substringy stuff

Post by Eraser »

That doesn't make any difference. That MAX_DESCRIPTIONLINELENGTH was defined as
#define MAX_DESCRIPTIONLINELENGTH 40 by the way.
themuffinman
Posts: 384
Joined: Fri Mar 05, 2010 5:29 pm

Re: Substringy stuff

Post by themuffinman »

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: Select all

/*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: Select all

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: Select all

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: Select all

/*
==================
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: Select all

/*
================
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
^misantropia^
Posts: 4022
Joined: Sat Mar 12, 2005 6:24 pm

Re: Substringy stuff

Post by ^misantropia^ »

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.
User avatar
Eraser
Posts: 19177
Joined: Fri Dec 01, 2000 8:00 am

Re: Substringy stuff

Post by Eraser »

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

Code: Select all

 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.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.
^misantropia^
Posts: 4022
Joined: Sat Mar 12, 2005 6:24 pm

Re: Substringy stuff

Post by ^misantropia^ »

`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: Select all

char mapDescriptionLines[8][MAX_DESCRIPTIONLENGTH];
User avatar
Eraser
Posts: 19177
Joined: Fri Dec 01, 2000 8:00 am

Re: Substringy stuff

Post by Eraser »

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

menutext_s mapDescriptionLines[MAX_DESCRIPTIONLINES];
User avatar
Eraser
Posts: 19177
Joined: Fri Dec 01, 2000 8:00 am

Re: Substringy stuff

Post by Eraser »

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

Code: Select all

char lines[MAX_DESCRIPTIONLINES][MAX_DESCRIPTIONLINELENGTH];
In the function itself I'm doing this:

Code: Select all

 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| }
User avatar
Eraser
Posts: 19177
Joined: Fri Dec 01, 2000 8:00 am

Re: Substringy stuff

Post by Eraser »

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