//---------------------------------------------------------------------------- // EDGE Radius Trigger Parsing //---------------------------------------------------------------------------- // // Copyright (c) 1999-2005 The EDGE Team. // // This program 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. // // This program 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. // //---------------------------------------------------------------------------- #include "i_defs.h" #include "rad_trig.h" #include "rad_act.h" #include "dm_defs.h" #include "dm_state.h" #include "g_game.h" #include "lu_math.h" #include "m_argv.h" #include "p_local.h" #include "p_spec.h" #include "r_defs.h" #include "s_sound.h" #include "r_modes.h" #include "w_wad.h" #include "z_zone.h" #include "version.h" #include #include #include #include #include #include int rts_version; // global typedef struct define_s { // next in list struct define_s *next; char *name; char *value; } define_t; typedef struct rts_parser_s { // needed level: // -1 : don't care // 0 : outside any block // 1 : within START_MAP block // 2 : within RADIUS_TRIGGER block int level; // name char *name; // number of parameters int min_pars, max_pars; // parser function void (* parser)(int pnum, const char ** pars); } rts_parser_t; int rad_cur_linenum; char *rad_cur_filename; epi::strent_c rad_cur_linedata; static char tokenbuf[4096]; // -AJA- 1999/09/12: Made all these static. The variable `defines' // was clashing with the one in ddf_main.c. ARGH ! // Define List static define_t *defines; // Determine whether the code blocks are started and terminated. static int rad_cur_level = 0; // For checking when #VERSION is used static bool rad_has_start_map; static const char *rad_level_names[3] = { "outer area", "map area", "trigger area" }; // Location of current script static rad_script_t *this_rad; static char *this_map = NULL; // Pending state info for current script static int pending_wait_tics = 0; static char *pending_label = NULL; // Default tip properties (position, colour, etc) static s_tip_prop_t default_tip_props = { -1, -1, -1, -1, NULL, -1.0f, 0 }; int RAD_StringHashFunc(const char *s) { int r = 0; int c; while (*s) { r *= 36; if (*s >= 'a' && *s <= 'z') c = *s - 'a'; else if (*s >= 'A' && *s <= 'Z') c = *s - 'A'; else if (*s >= '0' && *s <= '9') c = *s - '0' + 'Z' - 'A' + 1; else c = *s; r += c % 36; s++; } return r; } // // RAD_Error // void RAD_Error(const char *err, ...) { va_list argptr; char buffer[2048]; char *pos; buffer[2047] = 0; // put actual message on first line va_start(argptr, err); vsprintf(buffer, err, argptr); va_end(argptr); pos = buffer + strlen(buffer); sprintf(pos, "Error occurred near line %d of %s\n", rad_cur_linenum, rad_cur_filename); pos += strlen(pos); if (!rad_cur_linedata.IsEmpty()) { sprintf(pos, "Line contents: %s\n", rad_cur_linedata.GetString()); pos += strlen(pos); } // check for buffer overflow if (buffer[2047] != 0) I_Error("Buffer overflow in RAD_Error.\n"); // add a blank line for readability under DOS/Linux. I_Printf("\n"); I_Error("%s", buffer); } void RAD_Warning(const char *err, ...) { va_list argptr; char buffer[1024]; if (no_warnings) return; va_start(argptr, err); vsprintf(buffer, err, argptr); va_end(argptr); I_Warning("\n"); I_Warning("Found problem near line %d of %s\n", rad_cur_linenum, rad_cur_filename); if (!rad_cur_linedata.IsEmpty()) I_Warning("with line contents: %s\n", rad_cur_linedata.GetString()); I_Warning("%s", buffer); } void RAD_WarnError(const char *err, ...) { va_list argptr; char buffer[1024]; va_start(argptr, err); vsprintf(buffer, err, argptr); va_end(argptr); if (strict_errors) RAD_Error("%s", buffer); else RAD_Warning("%s", buffer); } static void RAD_WarnError2(int ver, const char *err, ...) { va_list argptr; char buffer[1024]; va_start(argptr, err); vsprintf(buffer, err, argptr); va_end(argptr); if (strict_errors || (rts_version >= ver && ! lax_errors)) RAD_Error("%s", buffer); else RAD_Warning("%s", buffer); } // Searches through the #defines namespace for a match and returns // its value if it exists. static bool CheckForDefine(const char *s, char ** val) { define_t *tempnode = defines; for (; tempnode; tempnode = tempnode->next) { if (stricmp(s, tempnode->name) == 0) { *val = Z_StrDup(tempnode->value); return true; } } return false; } static void RAD_CheckForInt(const char *value, int *retvalue) { const char *pos = value; int count = 0; int length = strlen(value); if (strchr(value, '%')) RAD_Error("Parameter '%s' should not be a percentage.\n", value); // Accomodate for "-" as you could have -5 or something like that. if (*pos == '-') { count++; pos++; } while (isdigit(*pos++)) count++; // Is the value an integer? if (length != count) RAD_Error("Parameter '%s' is not of numeric type.\n", value); *retvalue = atoi(value); } static void RAD_CheckForFloat(const char *value, float *retvalue) { if (strchr(value, '%')) RAD_Error("Parameter '%s' should not be a percentage.\n", value); if (sscanf(value, "%f", retvalue) != 1) RAD_Error("Parameter '%s' is not of numeric type.\n", value); } // // RAD_CheckForPercent // // Reads percentages (0%..100%). // static void RAD_CheckForPercent(const char *info, void *storage) { char s[101]; char *p; float f; // just check that the string is valid Z_StrNCpy(s, info, 100); for (p = s; isdigit(*p) || *p == '.'; p++) { /* nothing here */ } // the number must be followed by % if (*p != '%') RAD_Error("Parameter '%s' is not of percent type.\n", info); *p = 0; RAD_CheckForFloat(s, &f); if (f < 0.0f || f > 100.0f) RAD_Error("Percentage out of range: %s\n", info); *(percent_t *)storage = f / 100.0f; } // // RAD_CheckForPercentAny // // Like the above routine, but don't limit to 0..100%. // static void RAD_CheckForPercentAny(const char *info, void *storage) { char s[101]; char *p; float f; // just check that the string is valid Z_StrNCpy(s, info, 100); for (p = s; isdigit(*p) || *p == '.'; p++) { /* nothing here */ } // the number must be followed by % if (*p != '%') RAD_Error("Parameter '%s' is not of percent type.\n", info); *p = 0; RAD_CheckForFloat(s, &f); *(percent_t *)storage = f / 100.0f; } // -ES- Copied from DDF_MainGetTime. // FIXME: Collect all functions that are common to DDF and RTS, // and move them to a new module for RTS+DDF common code. static void RAD_CheckForTime(const char *info, void *storage) { float val; int *dest = (int *)storage; int i; char *s; SYS_ASSERT(info && storage); // -ES- 1999/09/14 MAXT means that time should be maximal. if (!stricmp(info, "maxt")) { *dest = INT_MAX; // -ACB- 1999/09/22 Standards, Please. return; } s = strchr(info, 'T'); if (!s) s = strchr(info, 't'); if (s) { i = s-info; epi::string_c tmp_str; tmp_str.AddChars(info, 0, i); RAD_CheckForInt(tmp_str.GetString(), (int*)storage); return; } if (sscanf(info, "%f", &val) != 1) { I_Warning("Bad time value `%s'.\n", info); return; } *dest = (int)(val * (float)TICRATE); } static armour_type_e RAD_CheckForArmourType(const char *info) { if (DDF_CompareName(info, "GREEN") == 0) return ARMOUR_Green; else if (DDF_CompareName(info, "BLUE") == 0) return ARMOUR_Blue; else if (DDF_CompareName(info, "YELLOW") == 0) return ARMOUR_Yellow; else if (DDF_CompareName(info, "RED") == 0) return ARMOUR_Red; RAD_Error("Unknown armour type: %s\n", info); return ARMOUR_Green; // (0 - No such thing as ARMOUR_None) } static changetex_type_e RAD_CheckForChangetexType(const char *info) { if (DDF_CompareName(info, "LEFT_UPPER") == 0) return CHTEX_LeftUpper; else if (DDF_CompareName(info, "LEFT_MIDDLE") == 0) return CHTEX_LeftMiddle; else if (DDF_CompareName(info, "LEFT_LOWER") == 0) return CHTEX_LeftLower; if (DDF_CompareName(info, "RIGHT_UPPER") == 0) return CHTEX_RightUpper; else if (DDF_CompareName(info, "RIGHT_MIDDLE") == 0) return CHTEX_RightMiddle; else if (DDF_CompareName(info, "RIGHT_LOWER") == 0) return CHTEX_RightLower; else if (DDF_CompareName(info, "FLOOR") == 0) return CHTEX_Floor; else if (DDF_CompareName(info, "CEILING") == 0) return CHTEX_Ceiling; else if (DDF_CompareName(info, "SKY") == 0) return CHTEX_Sky; RAD_Error("Unknown ChangeTex type `%s'\n", info); return CHTEX_RightUpper; // (0 - No such thing as CHTEX_None) } // // RAD_UnquoteString // // Remove the quotes from the given string, returning a newly // allocated string. // static char *RAD_UnquoteString(const char *s) { int tokenlen = 0; // skip initial quote s++; while (*s != '"') { #ifdef DEVELOPERS if (*s == 0) I_Error("INTERNAL ERROR: bad string.\n"); #endif // -AJA- 1999/09/07: check for \n. Only temporary, awaiting bison... if (s[0] == '\\' && toupper(s[1]) == 'N') { tokenbuf[tokenlen++] = '\n'; s += 2; continue; } tokenbuf[tokenlen++] = *s++; } tokenbuf[tokenlen] = 0; return Z_StrDup(tokenbuf); } static bool CheckForBoolean(const char *s) { if (stricmp(s, "TRUE") == 0 || strcmp(s, "1") == 0) return true; if (stricmp(s, "FALSE") == 0 || strcmp(s, "0") == 0) return false; // Nope, it's an error. RAD_Error("Bad boolean value (should be TRUE or FALSE): %s\n", s); return false; } static void DoParsePlayerSet(const char *info, u32_t *set) { const char *p = info; const char *next; *set = 0; if (DDF_CompareName(info, "ALL") == 0) { *set = ~0; return; } for (;;) { if (! isdigit(p[0])) RAD_Error("Bad number in set of players: %s\n", info); int num = strtol(p, (char **) &next, 10); *set |= (1 << (num-1)); p = next; if (p[0] == 0) break; if (p[0] != ':') RAD_Error("Missing `:' in set of players: %s\n", info); p++; } } // AddStateToScript // // Adds a new action state to the tail of the current set of states // for the given radius trigger. // static void AddStateToScript(rad_script_t *R, int tics, void (* action)(struct rad_trigger_s *R, mobj_t *actor, void *param), void *param) { rts_state_t *state; state = Z_ClearNew(rts_state_t, 1); state->tics = tics; state->action = action; state->param = param; state->tics += pending_wait_tics; state->label = pending_label; pending_wait_tics = 0; pending_label = NULL; // link it in state->next = NULL; state->prev = R->last_state; if (R->last_state) R->last_state->next = state; else R->first_state = state; R->last_state = state; } // // ClearOneScripts // static void ClearOneScript(rad_script_t *scr) { Z_Free(scr->mapid); while (scr->boss_trig) { s_ondeath_t *cur = scr->boss_trig; scr->boss_trig = cur->next; Z_Free(cur); } while (scr->height_trig) { s_onheight_t *cur = scr->height_trig; scr->height_trig = cur->next; Z_Free(cur); } // free all states while (scr->first_state) { rts_state_t *cur = scr->first_state; scr->first_state = cur->next; if (cur->param) Z_Free(cur->param); Z_Free(cur); } } // ClearPreviousScripts // // Removes any radius triggers for a given map when start_map is used. // Thus triggers in later RTS files/lumps replace those in earlier RTS // files/lumps in the specified level. // static void ClearPreviousScripts(const char *mapid) { // the "ALL" keyword is not a valid map name if (DDF_CompareName(mapid, "ALL") == 0) return; rad_script_t *scr, *next; for (scr=r_scripts; scr; scr=next) { next = scr->next; if (stricmp(scr->mapid, mapid) == 0) { // unlink and free it if (scr->next) scr->next->prev = scr->prev; if (scr->prev) scr->prev->next = scr->next; else r_scripts = scr->next; ClearOneScript(scr); Z_Free(scr); } } } // ClearAllScripts // // Removes all radius triggers from all maps. // static void ClearAllScripts(void) { while (r_scripts) { rad_script_t *scr = r_scripts; r_scripts = scr->next; ClearOneScript(scr); Z_Free(scr); } } // // RAD_ComputeScriptCRC // static void RAD_ComputeScriptCRC(rad_script_t *scr) { scr->crc.Reset(); // Note: the mapid doesn't belong in the CRC if (scr->script_name) scr->crc.AddCStr(scr->script_name); scr->crc += (int) scr->tag; scr->crc += (int) scr->appear; scr->crc += (int) scr->min_players; scr->crc += (int) scr->max_players; scr->crc += (int) scr->repeat_count; scr->crc += (int) I_ROUND(scr->x); scr->crc += (int) I_ROUND(scr->y); scr->crc += (int) I_ROUND(scr->z); scr->crc += (int) I_ROUND(scr->rad_x); scr->crc += (int) I_ROUND(scr->rad_y); scr->crc += (int) I_ROUND(scr->rad_z); // lastly handle miscellaneous parts #undef M_FLAG #define M_FLAG(bit, cond) \ if cond { flags |= (1 << (bit)); } int flags = 0; M_FLAG(0, (scr->tagged_disabled)); M_FLAG(1, (scr->tagged_use)); M_FLAG(2, (scr->tagged_independent)); M_FLAG(3, (scr->tagged_immediate)); M_FLAG(4, (scr->boss_trig != NULL)); M_FLAG(5, (scr->height_trig != NULL)); M_FLAG(6, (scr->cond_trig != NULL)); M_FLAG(7, (scr->next_in_path != NULL)); scr->crc += (int) flags; // Q/ add in states ? // A/ Nah. } #undef M_FLAG // RAD_CollectParameters // // Collect the parameters from the line into an array of strings // `pars', which can hold at most `max' string pointers. // // -AJA- 2000/01/02: Moved #define handling to here. // static void RAD_CollectParameters(const char *line, int *pnum, char ** pars, int max) { int tokenlen = -1; bool in_string = false; int in_expr = 0; // add one for each open bracket. *pnum = 0; for (;;) { int ch = *line++; bool comment = (ch == ';' || (ch == '/' && *line == '/')); if (in_string) comment = false; if (ch == 0 && in_string) RAD_Error("Nonterminated string found.\n"); if ((ch == 0 || comment) && in_expr) RAD_Error("Nonterminated expression found.\n"); if (tokenlen < 0) // looking for a new token { SYS_ASSERT(!in_expr && !in_string); // end of line ? if (ch == 0 || comment) return; if (isspace(ch)) continue; // string ? or expression ? if (ch == '"') in_string = true; else if (ch == '(' && ! in_string) in_expr++; else if (ch == ')' && ! in_string) RAD_Error("Unmatched ')' bracket found\n"); // begin a new token tokenbuf[0] = ch; tokenlen = 1; continue; } bool end_token = false; if (ch == '"' && in_string) { in_string = false; if (! in_expr) { tokenbuf[tokenlen++] = ch; end_token = true; } } else if (ch == '(' && in_expr) { in_expr++; } else if (ch == ')' && in_expr) { in_expr--; if (in_expr == 0) { tokenbuf[tokenlen++] = ch; end_token = true; } } else if (!in_expr && !in_string && (ch == 0 || comment || isspace(ch))) { end_token = true; } // end of token ? if (! end_token) { tokenbuf[tokenlen++] = ch; continue; } tokenbuf[tokenlen] = 0; tokenlen = -1; if (*pnum >= max) RAD_Error("Too many tokens on line\n"); // check for defines if (! CheckForDefine(tokenbuf, &pars[*pnum])) pars[*pnum] = Z_StrDup(tokenbuf); *pnum += 1; // end of line ? if (ch == 0 || comment) break; } } // RAD_FreeParameters // // Free previously collected parameters. // static void RAD_FreeParameters(int pnum, char **pars) { while (pnum > 0) { Z_Free(pars[--pnum]); } } // ---- Primitive Parsers ---------------------------------------------- static void RAD_ParseVersion(int pnum, const char **pars) { // #Version if (rad_has_start_map) RAD_Error("The #VERSION directive must appear before all scripts.\n"); float vers; RAD_CheckForFloat(pars[1], &vers); if (vers < 0.99f || vers > 9.99f) RAD_Error("Illegal #VERSION number.\n"); vers *= 100.0f; int decimal = I_ROUND(vers); rts_version = ((decimal / 100) << 8) | (((decimal / 10) % 10) << 4) | (decimal % 10); // backwards compat (old scripts have #VERSION 1.1 in them) if (rts_version < 0x123) rts_version = 0x127; if (rts_version > EDGEVER) RAD_Error("This version of EDGE cannot handle this RTS script\n"); } static void RAD_ParseClearAll(int pnum, const char **pars) { // #ClearAll ClearAllScripts(); } static void RAD_ParseDefine(int pnum, const char **pars) { // #Define define_t *newdef; newdef = Z_ClearNew(define_t, 1); newdef->name = Z_StrDup(pars[1]); newdef->value = Z_StrDup(pars[2]); // link it in newdef->next = defines; defines = newdef; } static void RAD_ParseStartMap(int pnum, const char **pars) { // Start_Map if (rad_cur_level != 0) RAD_Error("%s found, but previous END_MAP missing !\n", pars[0]); // -AJA- 1999/08/02: New scripts replace old ones. ClearPreviousScripts(pars[1]); this_map = Z_StrDup(pars[1]); strupr(this_map); rad_cur_level++; rad_has_start_map = true; } static void RAD_ParseRadiusTrigger(int pnum, const char **pars) { // RadiusTrigger // RadiusTrigger // // RectTrigger // RectTrigger // -AJA- 1999/09/12: Reworked for having Z-restricted triggers. if (rad_cur_level == 2) RAD_Error("%s found, but previous END_RADIUS_TRIGGER missing !\n", pars[0]); if (rad_cur_level == 0) RAD_Error("%s found, but without any START_MAP !\n", pars[0]); // Set the node up,.. this_rad = Z_ClearNew(rad_script_t, 1); // set defaults this_rad->x = 0; this_rad->y = 0; this_rad->z = 0; this_rad->rad_x = -1; this_rad->rad_z = -1; this_rad->appear = DEFAULT_APPEAR; this_rad->min_players = 0; this_rad->max_players = MAXPLAYERS; this_rad->netmode = RNET_Separate; this_rad->what_players = ~0; // "ALL" this_rad->absolute_req_players = 1; this_rad->repeat_count = -1; this_rad->repeat_delay = 0; pending_wait_tics = 0; pending_label = NULL; if (DDF_CompareName("RECT_TRIGGER", pars[0]) == 0) { float x1, y1, x2, y2, z1, z2; if (pnum == 6) RAD_Error("%s: Wrong number of parameters.\n", pars[0]); RAD_CheckForFloat(pars[1], &x1); RAD_CheckForFloat(pars[2], &y1); RAD_CheckForFloat(pars[3], &x2); RAD_CheckForFloat(pars[4], &y2); if (x1 > x2) RAD_WarnError2(0x128, "%s: bad X range %1.1f to %1.1f\n", pars[0], x1, x2); if (y1 > y2) RAD_WarnError2(0x128, "%s: bad Y range %1.1f to %1.1f\n", pars[0], y1, y2); this_rad->x = (float)(x1 + x2) / 2.0f; this_rad->y = (float)(y1 + y2) / 2.0f; this_rad->rad_x = (float)fabs(x1 - x2) / 2.0f; this_rad->rad_y = (float)fabs(y1 - y2) / 2.0f; if (pnum >= 7) { RAD_CheckForFloat(pars[5], &z1); RAD_CheckForFloat(pars[6], &z2); if (z1 > z2 + 1) RAD_WarnError2(0x128, "%s: bad height range %1.1f to %1.1f\n", pars[0], z1, z2); this_rad->z = (z1 + z2) / 2.0f; this_rad->rad_z = fabs(z1 - z2) / 2.0f; } } else { if (pnum == 5) RAD_Error("%s: Wrong number of parameters.\n", pars[0]); RAD_CheckForFloat(pars[1], &this_rad->x); RAD_CheckForFloat(pars[2], &this_rad->y); RAD_CheckForFloat(pars[3], &this_rad->rad_x); this_rad->rad_y = this_rad->rad_x; if (pnum >= 6) { float z1, z2; RAD_CheckForFloat(pars[4], &z1); RAD_CheckForFloat(pars[5], &z2); if (z1 > z2) RAD_WarnError2(0x128, "%s: bad height range %1.1f to %1.1f\n", pars[0], z1, z2); this_rad->z = (z1 + z2) / 2.0f; this_rad->rad_z = fabs(z1 - z2) / 2.0f; } } // link it in this_rad->next = r_scripts; this_rad->prev = NULL; if (r_scripts) r_scripts->prev = this_rad; r_scripts = this_rad; rad_cur_level++; } static void RAD_ParseEndRadiusTrigger(int pnum, const char **pars) { // End_RadiusTrigger if (rad_cur_level != 2) RAD_Error("%s found, but without any RADIUS_TRIGGER !\n", pars[0]); // --- check stuff --- // handle any pending WAIT or LABEL values if (pending_wait_tics > 0 || pending_label) { AddStateToScript(this_rad, 0, RAD_ActNOP, NULL); } this_rad->mapid = Z_StrDup(this_map); RAD_ComputeScriptCRC(this_rad); this_rad = NULL; rad_cur_level--; } static void RAD_ParseEndMap(int pnum, const char **pars) { // End_Map if (rad_cur_level == 2) RAD_Error("%s found, but previous END_RADIUS_TRIGGER missing !\n", pars[0]); if (rad_cur_level == 0) RAD_Error("%s found, but without any START_MAP !\n", pars[0]); this_map = NULL; rad_cur_level--; } static void RAD_ParseName(int pnum, const char **pars) { // Name if (this_rad->script_name) RAD_Error("Script already has a name: `%s'\n", this_rad->script_name); this_rad->script_name = Z_StrDup(pars[1]); } static void RAD_ParseTag(int pnum, const char **pars) { // Tag if (this_rad->tag != 0) RAD_Error("Script already has a tag: `%d'\n", this_rad->tag); RAD_CheckForInt(pars[1], &this_rad->tag); } static void RAD_ParseWhenAppear(int pnum, const char **pars) { // When_Appear 1:2:3:4:5:SP:COOP:DM DDF_MainGetWhenAppear(pars[1], &this_rad->appear); } static void RAD_ParseWhenPlayerNum(int pnum, const char **pars) { // When_Player_Num [max] RAD_CheckForInt(pars[1], &this_rad->min_players); this_rad->max_players = MAXPLAYERS; if (pnum >= 3) RAD_CheckForInt(pars[2], &this_rad->max_players); if (this_rad->min_players < 0 || this_rad->min_players > this_rad->max_players) { RAD_Error("%s: Illegal range: %d..%d\n", pars[0], this_rad->min_players, this_rad->max_players); } } static void RAD_ParseNetMode(int pnum, const char **pars) { // Net_Mode SEPARATE // Net_Mode SEPARATE // // Net_Mode ABSOLUTE // Net_Mode ABSOLUTE if (DDF_CompareName(pars[1], "SEPARATE") == 0) { this_rad->netmode = RNET_Separate; if (pnum >= 3) DoParsePlayerSet(pars[2], &this_rad->what_players); return; } if (DDF_CompareName(pars[1], "ABSOLUTE") == 0) { this_rad->netmode = RNET_Absolute; if (pnum >= 3) { if (DDF_CompareName(pars[2], "ALL") == 0) this_rad->absolute_req_players = -1; else RAD_CheckForInt(pars[2], &this_rad->absolute_req_players); } return; } RAD_Error("%s: unknown mode `%s'\n", pars[0], pars[1]); } static void RAD_ParseTaggedRepeatable(int pnum, const char **pars) { // Tagged_Repeatable // Tagged_Repeatable // Tagged_Repeatable if (this_rad->repeat_count >= 0) RAD_Error("%s: can only be used once.\n", pars[0]); if (pnum >= 2) RAD_CheckForInt(pars[1], &this_rad->repeat_count); else this_rad->repeat_count = REPEAT_FOREVER; // -ES- 2000/03/03 Changed to RAD_CheckForTime. if (pnum >= 3) RAD_CheckForTime(pars[2], &this_rad->repeat_delay); else this_rad->repeat_delay = 1; } static void RAD_ParseTaggedUse(int pnum, const char **pars) { // Tagged_Use this_rad->tagged_use = true; } static void RAD_ParseTaggedIndependent(int pnum, const char **pars) { // Tagged_Independent this_rad->tagged_independent = true; } static void RAD_ParseTaggedImmediate(int pnum, const char **pars) { // Tagged_Immediate this_rad->tagged_immediate = true; } static void RAD_ParseTaggedPlayerSpecific(int pnum, const char **pars) { // Tagged_Player_Specific if (this_rad->netmode != RNET_Separate) RAD_Error("%s can only be used with NET_MODE SEPARATE\n", pars[0]); this_rad->tagged_player_specific = true; } static void RAD_ParseTaggedDisabled(int pnum, const char **pars) { // Tagged_Disabled this_rad->tagged_disabled = true; } static void RAD_ParseTaggedPath(int pnum, const char **pars) { // Tagged_Path rts_path_t *path = Z_ClearNew(rts_path_t, 1); path->next = this_rad->next_in_path; path->name = Z_StrDup(pars[1]); this_rad->next_in_path = path; this_rad->next_path_total += 1; } static void RAD_ParsePathEvent(int pnum, const char **pars) { // Path_Event