Beispiel #1
0
/**
 * Parse a string according to a property type, and allocate a raw value to the static memory
 *
 * @param action Action to initialize
 * @param node Current node we are parsing, only used for error message
 * @param property Type of the value to parse, if nullptr the string is not stored as string
 * @param string String value to parse
 * @return True if the action is initialized
 * @todo remove node param and catch error where we call that function
 */
bool UI_InitRawActionValue (uiAction_t* action, uiNode_t* node, const value_t* property, const char* string)
{
	if (property == nullptr) {
		action->type = EA_VALUE_STRING;
		action->d.terminal.d1.data = UI_AllocStaticString(string, 0);
		action->d.terminal.d2.integer = 0;
		return true;
	}

	if (property->type == V_UI_SPRITEREF) {
		uiSprite_t* sprite = UI_GetSpriteByName(string);
		if (sprite == nullptr) {
			Com_Printf("UI_ParseSetAction: sprite '%s' not found (%s)\n", string, UI_GetPath(node));
			return false;
		}
		action->type = EA_VALUE_RAW;
		action->d.terminal.d1.data = sprite;
		action->d.terminal.d2.integer = property->type;
		return true;
	} else {
		const int baseType = property->type & V_UI_MASK;
		if (baseType != 0 && baseType != V_UI_CVAR) {
			Com_Printf("UI_ParseRawValue: setter for property '%s' (type %d, 0x%X) is not supported (%s)\n", property->string, property->type, property->type, UI_GetPath(node));
			return false;
		}
		ui_global.curadata = (byte*) Com_AlignPtr(ui_global.curadata, (valueTypes_t) (property->type & V_BASETYPEMASK));
		action->type = EA_VALUE_RAW;
		action->d.terminal.d1.data = ui_global.curadata;
		action->d.terminal.d2.integer = property->type;
		/** @todo we should hide use of ui_global.curadata */
		ui_global.curadata += Com_EParseValue(ui_global.curadata, string, (valueTypes_t) (property->type & V_BASETYPEMASK), 0, property->size);
		return true;
	}
}
Beispiel #2
0
static bool UI_ParseExcludeRect (uiNode_t* node, const char** text, const char** token, const char* errhead)
{
	uiExcludeRect_t rect;
	uiExcludeRect_t* newRect;

	/* get parameters */
	*token = Com_EParse(text, errhead, node->name);
	if (!*text)
		return false;
	if ((*token)[0] != '{') {
		Com_Printf("UI_ParseExcludeRect: node with bad excluderect ignored (node \"%s\")\n", UI_GetPath(node));
		return true;
	}

	do {
		*token = Com_EParse(text, errhead, node->name);
		if (!*text)
			return false;
		/** @todo move it into a property array */
		if (Q_streq(*token, "pos")) {
			*token = Com_EParse(text, errhead, node->name);
			if (!*text)
				return false;
			Com_EParseValue(&rect, *token, V_POS, offsetof(uiExcludeRect_t, pos), sizeof(vec2_t));
		} else if (Q_streq(*token, "size")) {
			*token = Com_EParse(text, errhead, node->name);
			if (!*text)
				return false;
			Com_EParseValue(&rect, *token, V_POS, offsetof(uiExcludeRect_t, size), sizeof(vec2_t));
		}
	} while ((*token)[0] != '}');

	newRect = (uiExcludeRect_t*) UI_AllocHunkMemory(sizeof(*newRect), STRUCT_MEMORY_ALIGN, false);
	if (newRect == nullptr) {
		Com_Printf("UI_ParseExcludeRect: ui hunk memory exceeded.");
		return false;
	}

	/* move data to final memory and link to node */
	*newRect = rect;
	newRect->next = node->firstExcludeRect;
	node->firstExcludeRect = newRect;
	return true;
}
Beispiel #3
0
void GAME_ParseModes (const char *name, const char **text)
{
	const char *errhead = "GAME_ParseModes: unexpected end of file (cgame ";
	cgameType_t *ed;
	const char *token;
	const value_t *vp;
	int i;

	/* search for equipments with same name */
	for (i = 0; i < numCGameTypes; i++)
		if (Q_streq(name, cgameTypes[i].id))
			break;

	if (i < numCGameTypes) {
		Com_Printf("GAME_ParseModes: cgame def \"%s\" with same name found, second ignored\n", name);
		return;
	}

	if (i >= MAX_CGAMETYPES)
		Sys_Error("GAME_ParseModes: MAX_CGAMETYPES exceeded\n");

	/* initialize the equipment definition */
	ed = &cgameTypes[numCGameTypes++];
	OBJZERO(*ed);

	Q_strncpyz(ed->id, name, sizeof(ed->id));

	/* get it's body */
	token = Com_Parse(text);

	if (!*text || *token != '{') {
		Com_Printf("GAME_ParseModes: cgame def \"%s\" without body ignored\n", name);
		numCGameTypes--;
		return;
	}

	do {
		token = Com_EParse(text, errhead, name);
		if (!*text || *token == '}')
			return;

		for (vp = cgame_vals; vp->string; vp++)
			if (Q_streq(token, vp->string)) {
				/* found a definition */
				token = Com_EParse(text, errhead, name);
				if (!*text)
					return;
				Com_EParseValue(ed, token, vp->type, vp->ofs, vp->size);
				break;
			}

		if (!vp->string) {
			Sys_Error("unknown token in cgame definition %s: '%s'", ed->id, token);
		}
	} while (*text);
}
Beispiel #4
0
/**
 * @brief Parses particle used on maps.
 * @param[in,out] ptl Pointer to particle being parsed and updated.
 * @param[in] es Entity string to parse the particle from
 * @param[in] afterwards If this is true you can modify the particle after the init
 * function for the particle was already called
 */
static void CL_ParseMapParticle (ptl_t *ptl, const char *es, bool afterwards)
{
	char keyname[MAX_VAR];
	const char *token;
	char *key;
	const value_t *pp;

	key = keyname + 1;
	do {
		/* get keyname */
		token = Com_Parse(&es);
		if (token[0] == '}')
			break;
		if (!es)
			Com_Error(ERR_DROP, "CL_ParseMapParticle: EOF without closing brace");

		Q_strncpyz(keyname, token, sizeof(keyname));

		/* parse value */
		token = Com_Parse(&es);
		if (!es)
			Com_Error(ERR_DROP, "CL_ParseMapParticle: EOF without closing brace");

		if (token[0] == '}')
			Com_Error(ERR_DROP, "CL_ParseMapParticle: closing brace without data");

		if (!afterwards && keyname[0] != '-')
			continue;
		if (afterwards && keyname[0] != '+')
			continue;

		for (pp = pps; pp->string; pp++) {
			if (Q_streq(key, pp->string)) {
				/* found a normal particle value */
				Com_EParseValue(ptl, token, pp->type, pp->ofs, pp->size);
				break;
			}
		}

		if (!pp->string) {
			/* register art */
			if (Q_streq(key, "image"))
				ptl->pic = CL_ParticleGetArt(token, ptl->frame, ART_PIC);
			else if (Q_streq(key, "model"))
				ptl->model = CL_ParticleGetArt(token, ptl->frame, ART_MODEL);
			else if (Q_streq(key, "program")) {
				ptl->program = R_LoadProgram(token, R_InitParticleProgram, R_UseParticleProgram);
				if (ptl->program)
					ptl->program->userdata = ptl;
			}
		}
	} while (token);
}
Beispiel #5
0
/**
 * @brief Parse 2D objects like text and images
 * @return 1 - increase the command position of the sequence by one
 * @sa seq2D_vals
 * @sa CL_SequenceFind2D
 */
static int SEQ_ExecuteObj2D (sequenceContext_t *context, const char *name, const char *data)
{
	seq2D_t *s2d;
	const value_t *vp;
	int i;

	/* get sequence text */
	s2d = SEQ_Find2D(context, name);
	if (!s2d) {
		/* create new sequence text */
		for (i = 0, s2d = context->obj2Ds; i < context->numObj2Ds; i++, s2d++)
			if (!s2d->inuse)
				break;
		if (i >= context->numObj2Ds) {
			if (context->numObj2Ds >= MAX_SEQ2DS)
				Com_Error(ERR_FATAL, "Too many sequence 2d objects");
			s2d = &context->obj2Ds[context->numObj2Ds++];
		}
		/* allocate */
		OBJZERO(*s2d);
		for (i = 0; i < 4; i++)
			s2d->color[i] = 1.0f;
		s2d->inuse = true;
		Q_strncpyz(s2d->font, "f_big", sizeof(s2d->font));	/* default font */
		Q_strncpyz(s2d->name, name, sizeof(s2d->name));
	}

	/* get values */
	while (*data) {
		for (vp = seq2D_vals; vp->string; vp++)
			if (Q_streq(data, vp->string)) {
				data += strlen(data) + 1;
				switch (vp->type) {
				case V_TRANSLATION_STRING:
					data++;
				case V_HUNK_STRING:
					Mem_PoolStrDupTo(data, &Com_GetValue<char*>(s2d, vp), cl_genericPool, 0);
					break;

				default:
					Com_EParseValue(s2d, data, vp->type, vp->ofs, vp->size);
					break;
				}
				break;
			}
		if (!vp->string)
			Com_Printf("SEQ_ExecuteObj2D: unknown token '%s'\n", data);

		data += strlen(data) + 1;
	}
	return 1;
}
Beispiel #6
0
/**
 * @brief Parse values for a sequence model
 * @return 1 - increase the command position of the sequence by one
 * @sa seqEnt_vals
 * @sa CL_SequenceFindEnt
 */
static int SEQ_ExecuteModel (sequenceContext_t *context, const char *name, const char *data)
{
	/* get sequence entity */
	seqEnt_t *se = SEQ_FindEnt(context, name);
	if (!se) {
		int i;
		/* create new sequence entity */
		for (i = 0, se = context->ents; i < context->numEnts; i++, se++)
			if (!se->inuse)
				break;
		if (i >= context->numEnts) {
			if (context->numEnts >= MAX_SEQENTS)
				Com_Error(ERR_FATAL, "Too many sequence entities");
			se = &context->ents[context->numEnts++];
		}
		/* allocate */
		OBJZERO(*se);
		se->inuse = true;
		Q_strncpyz(se->name, name, sizeof(se->name));
		VectorSet(se->color, 0.7, 0.7, 0.7);
	}

	/* get values */
	while (*data) {
		const value_t *vp;
		for (vp = seqEnt_vals; vp->string; vp++)
			if (Q_streq(data, vp->string)) {
				data += strlen(data) + 1;
				Com_EParseValue(se, data, vp->type, vp->ofs, vp->size);
				break;
			}
		if (!vp->string) {
			if (Q_streq(data, "model")) {
				data += strlen(data) + 1;
				Com_DPrintf(DEBUG_CLIENT, "Registering model: %s\n", data);
				se->model = R_FindModel(data);
				if (se->model == NULL)
					se->inuse = false;
			} else if (Q_streq(data, "anim")) {
				if (se->model == NULL)
					Com_Error(ERR_FATAL, "could not change the animation - no model loaded yet");
				data += strlen(data) + 1;
				Com_DPrintf(DEBUG_CLIENT, "Change anim to: %s\n", data);
				R_AnimChange(&se->as, se->model, data);
			} else
				Com_Printf("SEQ_ExecuteModel: unknown token '%s'\n", data);
		}

		data += strlen(data) + 1;
	}
	return 1;
}
Beispiel #7
0
/**
 * @brief Parse the values for the camera like given in seqCamera
 */
static int SEQ_ExecuteCamera (sequenceContext_t *context, const char *name, const char *data)
{
	/* get values */
	while (*data) {
		const value_t *vp;
		for (vp = seqCamera_vals; vp->string; vp++)
			if (Q_streq(data, vp->string)) {
				data += strlen(data) + 1;
				Com_EParseValue(&context->camera, data, vp->type, vp->ofs, vp->size);
				break;
			}
		if (!vp->string)
			Com_Printf("SEQ_ExecuteCamera: unknown token '%s'\n", data);

		data += strlen(data) + 1;
	}
	return 1;
}
Beispiel #8
0
/**
 * @brief parses the models.ufo and all files where UI models (menu_model) are defined
 * @sa CL_ParseClientData
 */
bool UI_ParseUIModel (const char* name, const char** text)
{
	const char* errhead = "UI_ParseUIModel: unexpected end of file (names ";

	/* search for a UI models with same name */
	for (int i = 0; i < ui_global.numModels; i++)
		if (Q_streq(ui_global.models[i].id, name)) {
			Com_Printf("UI_ParseUIModel: menu_model \"%s\" with same name found, second ignored\n", name);
			return false;
		}

	if (ui_global.numModels >= UI_MAX_MODELS) {
		Com_Printf("UI_ParseUIModel: Max UI models reached\n");
		return false;
	}

	/* initialize the model */
	uiModel_t* model = &ui_global.models[ui_global.numModels];
	OBJZERO(*model);

	Vector4Set(model->color, 1, 1, 1, 1);

	model->id = Mem_PoolStrDup(name, ui_sysPool, 0);
	Com_DPrintf(DEBUG_CLIENT, "Found UI model %s (%i)\n", model->id, ui_global.numModels);

	/* get it's body */
	const char* token = Com_Parse(text);

	if (!*text || token[0] != '{') {
		Com_Printf("UI_ParseUIModel: Model \"%s\" without body ignored\n", model->id);
		return false;
	}

	ui_global.numModels++;

	do {
		const value_t* v = nullptr;
		/* get the name type */
		token = Com_EParse(text, errhead, name);
		if (!*text)
			return false;
		if (token[0] == '}')
			break;

		v = UI_FindPropertyByName(uiModelProperties, token);
		if (!v) {
			Com_Printf("UI_ParseUIModel: unknown token \"%s\" ignored (UI model %s)\n", token, name);
			return false;
		}

		if (v->type == V_NULL) {
			if (Q_streq(v->string, "need")) {
				token = Com_EParse(text, errhead, name);
				if (!*text)
					return false;
				if (model->next != nullptr)
					Sys_Error("UI_ParseUIModel: second 'need' token found in model %s", name);
				model->next = UI_GetUIModel(token);
				 if (!model->next)
					Com_Printf("Could not find UI model %s", token);
			}
		} else {
			token = Com_EParse(text, errhead, name);
			if (!*text)
				return false;
			switch (v->type) {
			case V_HUNK_STRING:
				Mem_PoolStrDupTo(token, &Com_GetValue<char*>(model, v), ui_sysPool, 0);
				break;
			default:
				Com_EParseValue(model, token, v->type, v->ofs, v->size);
				break;
			}
		}
	} while (*text);

	return true;
}
Beispiel #9
0
/**
 * @brief Parse the map entity string and spawns those entities that are client-side only
 * @sa G_SendEdictsAndBrushModels
 * @sa CL_AddBrushModel
 */
void CL_SpawnParseEntitystring (void)
{
	const char *es = cl.mapData->mapEntityString;
	int entnum = 0, maxLevel;

	if (cl.mapMaxLevel > 0 && cl.mapMaxLevel < PATHFINDING_HEIGHT)
		maxLevel = cl.mapMaxLevel;
	else
		maxLevel = PATHFINDING_HEIGHT;

	/* vid restart? */
	if (cl.numMapParticles || cl.numLMs)
		return;

	OBJZERO(sun);

	/* parse ents */
	while (1) {
		localEntityParse_t entData;
		/* parse the opening brace */
		const char *entityToken = Com_Parse(&es);
		/* memorize the start */
		if (!es)
			break;

		if (entityToken[0] != '{')
			Com_Error(ERR_DROP, "V_ParseEntitystring: found %s when expecting {", entityToken);

		/* initialize */
		OBJZERO(entData);
		VectorSet(entData.scale, 1, 1, 1);
		entData.volume = SND_VOLUME_DEFAULT;
		entData.maxLevel = maxLevel;
		entData.entStringPos = es;
		entData.entnum = entnum;
		entData.maxMultiplayerTeams = TEAM_MAX_HUMAN;
		entData.attenuation = SOUND_ATTN_IDLE;

		/* go through all the dictionary pairs */
		while (1) {
			const value_t *v;
			/* parse key */
			entityToken = Com_Parse(&es);
			if (entityToken[0] == '}')
				break;
			if (!es)
				Com_Error(ERR_DROP, "V_ParseEntitystring: EOF without closing brace");

			for (v = localEntityValues; v->string; v++)
				if (Q_streq(entityToken, v->string)) {
					/* found a definition */
					entityToken = Com_Parse(&es);
					if (!es)
						Com_Error(ERR_DROP, "V_ParseEntitystring: EOF without closing brace");
					Com_EParseValue(&entData, entityToken, v->type, v->ofs, v->size);
					break;
				}
		}
		CL_SpawnCall(&entData);

		entnum++;
	}

	/* add the appropriate directional source to the list of active light sources*/
	R_AddLightsource(&sun);

	/* after we have parsed all the entities we can resolve the target, targetname
	 * connections for the misc_model entities */
	LM_Think();
}
Beispiel #10
0
/**
 * @sa CL_ParseClientData
 */
bool UI_ParseFont (const char* name, const char** text)
{
	const char* errhead = "UI_ParseFont: unexpected end of file (font";

	/* search for font with same name */
	if (UI_GetFontByID(name)) {
		Com_Printf("UI_ParseFont: font \"%s\" with same name found, second ignored\n", name);
		return false;
	}

	if (numFonts >= MAX_FONTS) {
		Com_Printf("UI_ParseFont: Max fonts reached\n");
		return false;
	}

	/* initialize the UI */
	uiFont_t* font = &fonts[numFonts];
	OBJZERO(*font);

	font->name = Mem_PoolStrDup(name, ui_sysPool, 0);

	Com_DPrintf(DEBUG_CLIENT, "...found font %s (%i)\n", font->name, numFonts);

	/* get it's body */
	const char* token = Com_Parse(text);

	if (!*text || *token != '{') {
		Com_Printf("UI_ParseFont: font \"%s\" without body ignored\n", name);
		return false;
	}

	numFonts++;

	do {
		/* get the name type */
		token = Com_EParse(text, errhead, name);
		if (!*text)
			return false;
		if (*token == '}')
			break;

		const value_t* v;
		for (v = fontValues; v->string; v++)
			if (Q_streq(token, v->string)) {
				/* found a definition */
				token = Com_EParse(text, errhead, name);
				if (!*text)
					return false;

				switch (v->type) {
				case V_TRANSLATION_STRING:
					token++;
				case V_HUNK_STRING:
					Mem_PoolStrDupTo(token, &Com_GetValue<char*>(font, v), ui_sysPool, 0);
					break;
				default:
					Com_EParseValue(font, token, v->type, v->ofs, v->size);
					break;
				}
				break;
			}

		if (!v->string)
			Com_Printf("UI_ParseFont: unknown token \"%s\" ignored (font %s)\n", token, name);
	} while (*text);

	UI_RegisterFont(font);
	return true;
}
Beispiel #11
0
static void CL_ParsePtlCmds (const char *name, const char **text)
{
	ptlCmd_t *pc;
	const value_t *pp;
	const char *errhead = "CL_ParsePtlCmds: unexpected end of file";
	const char *token;
	int i, j;

	/* get it's body */
	token = Com_Parse(text);

	if (!*text || *token != '{') {
		Com_Printf("CL_ParsePtlCmds: particle cmds \"%s\" without body ignored\n", name);
		return;
	}

	do {
		token = Com_EParse(text, errhead, name);
		if (!*text)
			break;
		if (*token == '}')
			break;

		for (i = 0; i < PC_NUM_PTLCMDS; i++)
			if (Q_streq(token, pc_strings[i])) {
				/* allocate an new cmd */
				if (numPtlCmds >= MAX_PTLCMDS)
					Com_Error(ERR_DROP, "CL_ParsePtlCmds: MAX_PTLCMDS exceeded");
				pc = &ptlCmd[numPtlCmds++];
				OBJZERO(*pc);

				pc->cmd = i;

				if (!pc_types[i])
					break;

				/* get parameter type */
				token = Com_EParse(text, errhead, name);
				if (!*text)
					return;

				/* operate on the top element on the stack */
				if (token[0] == '#') {
					pc->ref = RSTACK;
					if (token[1] == '.')
						pc->ref -= (token[2] - '0');
					break;
				}

				if (token[0] == '*') {
					int len;
					char baseComponentToken[MAX_VAR];

					/* it's a variable reference */
					token++;

					/* we maybe have to modify it */
					Q_strncpyz(baseComponentToken, token, sizeof(baseComponentToken));

					/* check for component specifier */
					len = strlen(baseComponentToken);
					/* it's possible to change only the second value of e.g. a vector
					 * just defined e.g. 'size.2' to modify the second value of size */
					if (len >= 2 && baseComponentToken[len - 2] == '.') {
						baseComponentToken[len - 2] = 0;
					} else
						len = 0;

					for (pp = pps; pp->string; pp++)
						if (Q_streq(baseComponentToken, pp->string))
							break;

					if (!pp->string) {
						Com_Printf("CL_ParsePtlCmds: bad reference \"%s\" specified (particle %s)\n", token, name);
						numPtlCmds--;
						break;
					}

					if ((pc_types[i] & PTL_ONLY_ONE_TYPE)) {
						if ((pc_types[i] & ~PTL_ONLY_ONE_TYPE) != pp->type) {
							Com_Printf("CL_ParsePtlCmds: bad type in var \"%s\" (PTL_ONLY_ONE_TYPE) specified (particle %s) (ptl type: %i (pc_type: %i), string: %s)\n", token, name, pp->type, pc_types[i], pc_strings[i]);
							numPtlCmds--;
							break;
						}
					} else if (pp->type >= V_NUM_TYPES || !((1 << pp->type) & pc_types[i])) {
						Com_Printf("CL_ParsePtlCmds: bad type in var \"%s\" specified (particle %s) (ptl type: %i (pc_type: %i), string: %s)\n", token, name, pp->type, pc_types[i], pc_strings[i]);
						numPtlCmds--;
						break;
					}

					if (len) {
						/* get single component */
						if ((1 << pp->type) & V_VECS) {
							const int component = (baseComponentToken[len - 1] - '1');
							/* get the component we want to modify */
							if (component > 3) {
								Com_Printf("CL_ParsePtlCmds: bad component value - it's bigger than 3: %i (particle %s)\n", component, name);
								numPtlCmds--;
								break;
							}
							pc->type = V_FLOAT;
							/* go to component offset */
							pc->ref = -((int)pp->ofs) - component * sizeof(float);
							break;
						} else {
							Com_Printf("CL_ParsePtlCmds: can't get components of a non-vector type (particle %s)\n", name);
							numPtlCmds--;
							break;
						}
					}

					/* set the values */
					pc->type = pp->type;
					pc->ref = -((int)pp->ofs);
					break;
				}

				/* get the type */
				if (pc_types[i] & PTL_ONLY_ONE_TYPE)
					/* extract the real type */
					j = pc_types[i] & ~PTL_ONLY_ONE_TYPE;
				else {
					for (j = 0; j < V_NUM_TYPES; j++)
						if (Q_streq(token, vt_names[j]))
							break;

					if (j >= V_NUM_TYPES || !((1 << j) & pc_types[i])) {
						Com_Printf("CL_ParsePtlCmds: bad type \"%s\" specified (particle %s)\n", token, name);
						numPtlCmds--;
						break;
					}

					/* get the value */
					token = Com_EParse(text, errhead, name);
					if (!*text)
						return;
				}

				/* set the values */
				pc->type = j;

				pcmdPos = (byte*) Com_AlignPtr(pcmdPos, (valueTypes_t)pc->type);
				pc->ref = (int) (pcmdPos - pcmdData);
				pcmdPos += Com_EParseValue(pcmdPos, token, (valueTypes_t)pc->type, 0, 0);

/*				Com_Printf("%s %s %i\n", vt_names[pc->type], token, pcmdPos - pc->ref, (char *)pc->ref); */
				break;
			}

		if (i < PC_NUM_PTLCMDS)
			continue;

		for (pp = pps; pp->string; pp++)
			if (Q_streq(token, pp->string)) {
				/* get parameter */
				token = Com_EParse(text, errhead, name);
				if (!*text)
					return;

				/* translate set to a push and pop */
				if (numPtlCmds >= MAX_PTLCMDS - 1)
					Com_Error(ERR_DROP, "CL_ParsePtlCmds: MAX_PTLCMDS exceeded");
				pc = &ptlCmd[numPtlCmds++];
				pc->cmd = PC_PUSH;
				pc->type = pp->type;

				pcmdPos = (byte*) Com_AlignPtr(pcmdPos, (valueTypes_t)pc->type);
				pc->ref = (int) (pcmdPos - pcmdData);
				pcmdPos += Com_EParseValue(pcmdPos, token, (valueTypes_t)pc->type, 0, 0);

				pc = &ptlCmd[numPtlCmds++];
				pc->cmd = PC_POP;
				pc->type = pp->type;
				pc->ref = -((int)pp->ofs);
				break;
			}

		if (!pp->string)
			Com_Printf("CL_ParsePtlCmds: unknown token \"%s\" ignored (particle %s)\n", token, name);

	} while (*text);

	/* terminalize cmd chain */
	if (numPtlCmds >= MAX_PTLCMDS)
		Com_Error(ERR_DROP, "CL_ParsePtlCmds: MAX_PTLCMDS exceeded");
	pc = &ptlCmd[numPtlCmds++];
	OBJZERO(*pc);
}