mirror of
https://github.com/ReactionQuake3/reaction.git
synced 2024-11-29 07:22:47 +00:00
995 lines
28 KiB
C
995 lines
28 KiB
C
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Id$
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Log$
|
|
// Revision 1.52 2005/09/07 20:27:42 makro
|
|
// Entity attachment trees
|
|
//
|
|
// Revision 1.51 2005/02/15 16:33:39 makro
|
|
// Tons of updates (entity tree attachment system, UI vectors)
|
|
//
|
|
// Revision 1.50 2004/01/26 21:26:08 makro
|
|
// no message
|
|
//
|
|
// Revision 1.49 2003/09/18 00:05:05 makro
|
|
// Lens flares. Opendoor trigger_multiple fixes
|
|
//
|
|
// Revision 1.48 2003/09/10 22:46:05 makro
|
|
// Cooler breath puffs. Locked r_fastSky on maps with global fog.
|
|
// Some other things I can't remember.
|
|
//
|
|
// Revision 1.47 2003/09/08 19:19:20 makro
|
|
// New code for respawning entities in TP
|
|
//
|
|
// Revision 1.46 2003/09/07 20:02:51 makro
|
|
// no message
|
|
//
|
|
// Revision 1.45 2003/08/10 20:13:26 makro
|
|
// no message
|
|
//
|
|
// Revision 1.44 2003/04/26 22:33:06 jbravo
|
|
// Wratted all calls to G_FreeEnt() to avoid crashing and provide debugging
|
|
//
|
|
// Revision 1.43 2003/02/27 19:52:34 makro
|
|
// dlights
|
|
//
|
|
// Revision 1.42 2003/01/06 00:23:29 makro
|
|
// no message
|
|
//
|
|
// Revision 1.41 2003/01/05 22:36:50 makro
|
|
// Added "inactive" field for entities
|
|
// New "target_activate" entity
|
|
//
|
|
// Revision 1.40 2002/09/01 21:15:08 makro
|
|
// Sky portal tweaks
|
|
//
|
|
// Revision 1.39 2002/08/30 00:00:16 makro
|
|
// Sky portals
|
|
//
|
|
// Revision 1.38 2002/08/25 00:46:52 niceass
|
|
// q3f atmosphere
|
|
//
|
|
// Revision 1.37 2002/07/19 04:29:18 niceass
|
|
// typo fix
|
|
//
|
|
// Revision 1.36 2002/07/13 22:43:59 makro
|
|
// Semi-working fog hull, semi-working sky portals (cgame code commented out)
|
|
// Basically, semi-working stuff :P
|
|
//
|
|
// Revision 1.35 2002/06/29 04:15:15 jbravo
|
|
// CTF is now CTB. no weapons while the case is in hand other than pistol or knife
|
|
//
|
|
// Revision 1.34 2002/06/24 05:51:51 jbravo
|
|
// CTF mode is now semi working
|
|
//
|
|
// Revision 1.33 2002/06/21 21:02:07 niceass
|
|
// worldspawn laserfog check
|
|
//
|
|
// Revision 1.32 2002/06/16 20:06:14 jbravo
|
|
// Reindented all the source files with "indent -kr -ut -i8 -l120 -lc120 -sob -bad -bap"
|
|
//
|
|
// Revision 1.31 2002/06/16 17:38:00 jbravo
|
|
// Removed the MISSIONPACK ifdefs and missionpack only code.
|
|
//
|
|
// Revision 1.30 2002/06/12 11:14:35 makro
|
|
// Fixed knives/pistols not spawning bug
|
|
//
|
|
// Revision 1.29 2002/06/08 11:41:48 makro
|
|
// weapon_grenadelauncher = weapon_pistol
|
|
//
|
|
// Revision 1.28 2002/05/30 21:18:28 makro
|
|
// Bots should reload/bandage when roaming around
|
|
// Added "pathtarget" key to all the entities
|
|
//
|
|
// Revision 1.27 2002/05/25 10:40:31 makro
|
|
// Loading screen
|
|
//
|
|
// Revision 1.26 2002/05/23 15:55:25 makro
|
|
// Elevators
|
|
//
|
|
// Revision 1.25 2002/05/11 12:45:25 makro
|
|
// Spectators can go through breakables and doors with
|
|
// a targetname or health. Bots should crouch more/jump less
|
|
// often when attacking at long range
|
|
//
|
|
// Revision 1.24 2002/05/05 15:18:02 makro
|
|
// Fixed some crash bugs. Bot stuff. Triggerable func_statics.
|
|
// Made flags only spawn in CTF mode
|
|
//
|
|
// Revision 1.23 2002/05/04 06:28:58 makro
|
|
// no message
|
|
//
|
|
// Revision 1.22 2002/05/04 01:03:43 makro
|
|
// Bots
|
|
//
|
|
// Revision 1.21 2002/05/02 23:05:25 makro
|
|
// Loading screen. Jump kicks. Bot stuff
|
|
//
|
|
// Revision 1.20 2002/05/02 12:44:58 makro
|
|
// Customizable color for the loading screen text. Bot stuff
|
|
//
|
|
// Revision 1.19 2002/04/30 12:23:35 jbravo
|
|
// Warningfix
|
|
//
|
|
// Revision 1.18 2002/04/30 11:54:37 makro
|
|
// Bots rule ! Also, added clips to give all. Maybe some other things
|
|
//
|
|
// Revision 1.17 2002/04/23 06:01:39 niceass
|
|
// pressure stuff
|
|
//
|
|
// Revision 1.16 2002/04/20 02:34:57 jbravo
|
|
// Changed weapon and ammo classnames at Sze's request
|
|
//
|
|
// Revision 1.15 2002/04/03 15:51:01 jbravo
|
|
// Small warning fixes
|
|
//
|
|
// Revision 1.14 2002/04/03 03:13:16 blaze
|
|
// NEW BREAKABLE CODE - will break all old breakables(wont appear in maps)
|
|
//
|
|
// Revision 1.13 2002/03/31 03:31:24 jbravo
|
|
// Compiler warning cleanups
|
|
//
|
|
// Revision 1.12 2002/03/23 05:17:43 jbravo
|
|
// Major cleanup of game -> cgame communication with LCA vars.
|
|
//
|
|
// Revision 1.11 2002/03/20 22:58:27 blaze
|
|
// changed dlight to light_d
|
|
//
|
|
// Revision 1.10 2002/03/10 21:42:44 blaze
|
|
// allow RQ3_weapon_name to map to weapon_name to make radiant work better
|
|
//
|
|
// Revision 1.9 2002/01/24 14:20:53 jbravo
|
|
// Adding func_explosive and a few new surfaceparms
|
|
//
|
|
// Revision 1.8 2002/01/11 20:20:58 jbravo
|
|
// Adding TP to main branch
|
|
//
|
|
// Revision 1.7 2002/01/11 19:48:30 jbravo
|
|
// Formatted the source in non DOS format.
|
|
//
|
|
// Revision 1.6 2001/12/31 16:28:42 jbravo
|
|
// I made a Booboo with the Log tag.
|
|
//
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 1999-2000 Id Software, Inc.
|
|
//
|
|
|
|
#include "g_local.h"
|
|
|
|
qboolean G_SpawnString(const char *key, const char *defaultString, char **out)
|
|
{
|
|
int i;
|
|
|
|
if (!level.spawning) {
|
|
*out = (char *) defaultString;
|
|
// G_Error( "G_SpawnString() called while not spawning" );
|
|
}
|
|
|
|
for (i = 0; i < level.numSpawnVars; i++) {
|
|
if (!Q_stricmp(key, level.spawnVars[i][0])) {
|
|
*out = level.spawnVars[i][1];
|
|
return qtrue;
|
|
}
|
|
}
|
|
|
|
*out = (char *) defaultString;
|
|
return qfalse;
|
|
}
|
|
|
|
qboolean G_SpawnFloat(const char *key, const char *defaultString, float *out)
|
|
{
|
|
char *s;
|
|
qboolean present;
|
|
|
|
present = G_SpawnString(key, defaultString, &s);
|
|
*out = atof(s);
|
|
return present;
|
|
}
|
|
|
|
qboolean G_SpawnInt(const char *key, const char *defaultString, int *out)
|
|
{
|
|
char *s;
|
|
qboolean present;
|
|
|
|
present = G_SpawnString(key, defaultString, &s);
|
|
*out = atoi(s);
|
|
return present;
|
|
}
|
|
|
|
qboolean G_SpawnVector(const char *key, const char *defaultString, float *out)
|
|
{
|
|
char *s;
|
|
qboolean present;
|
|
|
|
present = G_SpawnString(key, defaultString, &s);
|
|
sscanf(s, "%f %f %f", &out[0], &out[1], &out[2]);
|
|
return present;
|
|
}
|
|
|
|
//
|
|
// fields are needed for spawning from the entity string
|
|
//
|
|
typedef enum {
|
|
F_INT,
|
|
F_FLOAT,
|
|
F_STRING,
|
|
F_VECTOR,
|
|
F_ANGLEHACK,
|
|
} fieldtype_t;
|
|
|
|
typedef struct {
|
|
char *name;
|
|
size_t ofs;
|
|
fieldtype_t type;
|
|
} field_t;
|
|
|
|
field_t fields[] = {
|
|
{"classname", FOFS(classname), F_STRING},
|
|
{"origin", FOFS(s.origin), F_VECTOR},
|
|
{"model", FOFS(model), F_STRING},
|
|
{"model2", FOFS(model2), F_STRING},
|
|
{"spawnflags", FOFS(spawnflags), F_INT},
|
|
{"speed", FOFS(speed), F_FLOAT},
|
|
{"target", FOFS(target), F_STRING},
|
|
{"targetname", FOFS(targetname), F_STRING},
|
|
{"message", FOFS(message), F_STRING},
|
|
{"team", FOFS(team), F_STRING},
|
|
{"wait", FOFS(wait), F_FLOAT},
|
|
{"random", FOFS(random), F_FLOAT},
|
|
{"count", FOFS(count), F_INT},
|
|
{"health", FOFS(health), F_INT},
|
|
// {"light", 0, F_IGNORE},
|
|
{"dmg", FOFS(damage), F_INT},
|
|
{"angles", FOFS(s.angles), F_VECTOR},
|
|
//Makro - only used by the sky portal code
|
|
{"portalspeed", FOFS(movedir), F_VECTOR},
|
|
{"angle", FOFS(s.angles), F_ANGLEHACK},
|
|
{"targetShaderName", FOFS(targetShaderName), F_STRING},
|
|
{"targetShaderNewName", FOFS(targetShaderNewName), F_STRING},
|
|
{"distance", FOFS(distance), F_FLOAT}, // VALKYRIE: for rotating doors
|
|
{"targetinactive", FOFS(targetInactive), F_STRING}, // Makro - target to be fired when inactive
|
|
{"pathtarget", FOFS(pathtarget), F_STRING}, // Makro - for func_trains
|
|
{"inactive", FOFS(inactive), F_INT}, // Makro - for inactive objects
|
|
{"activatename", FOFS(activatename), F_STRING},
|
|
{"alias", FOFS(alias), F_STRING}, //Makro - entity id strings
|
|
{"moveparent", FOFS(moveParent), F_STRING}, //Makro - entity id strings
|
|
{"attachto", FOFS(moveParent), F_STRING}, //
|
|
{"noreset", FOFS(noreset), F_INT}, //Makro - for entities that shouldn't respawn in TP
|
|
{NULL}
|
|
};
|
|
|
|
typedef struct {
|
|
char *name;
|
|
void (*spawn) (gentity_t * ent);
|
|
} spawn_t;
|
|
|
|
void SP_info_player_start(gentity_t * ent);
|
|
void SP_info_player_deathmatch(gentity_t * ent);
|
|
void SP_info_player_intermission(gentity_t * ent);
|
|
//void SP_info_firstplace(gentity_t * ent);
|
|
//void SP_info_secondplace(gentity_t * ent);
|
|
//void SP_info_thirdplace(gentity_t * ent);
|
|
//void SP_info_podium(gentity_t * ent);
|
|
|
|
void SP_func_plat(gentity_t * ent);
|
|
void SP_func_static(gentity_t * ent);
|
|
void SP_func_rotating(gentity_t * ent);
|
|
void SP_func_bobbing(gentity_t * ent);
|
|
void SP_func_pendulum(gentity_t * ent);
|
|
void SP_func_button(gentity_t * ent);
|
|
void SP_func_door(gentity_t * ent);
|
|
void SP_func_train(gentity_t * ent);
|
|
void SP_func_timer(gentity_t * self);
|
|
void SP_func_breakable(gentity_t * ent); //Blaze: Breakable glass
|
|
void SP_func_pressure(gentity_t * ent); //NiceAss: pressure entity
|
|
|
|
void SP_trigger_always(gentity_t * ent);
|
|
void SP_trigger_multiple(gentity_t * ent);
|
|
void SP_trigger_push(gentity_t * ent);
|
|
void SP_trigger_teleport(gentity_t * ent);
|
|
void SP_trigger_hurt(gentity_t * ent);
|
|
|
|
void SP_target_remove_powerups(gentity_t * ent);
|
|
void SP_target_give(gentity_t * ent);
|
|
void SP_target_delay(gentity_t * ent);
|
|
void SP_target_speaker(gentity_t * ent);
|
|
void SP_target_print(gentity_t * ent);
|
|
void SP_target_laser(gentity_t * self);
|
|
//void SP_target_character(gentity_t * ent);
|
|
void SP_target_score(gentity_t * ent);
|
|
void SP_target_teleporter(gentity_t * ent);
|
|
void SP_target_relay(gentity_t * ent);
|
|
void SP_target_kill(gentity_t * ent);
|
|
//Makro - added
|
|
void SP_target_activate(gentity_t * ent);
|
|
void SP_target_position(gentity_t * ent);
|
|
void SP_target_location(gentity_t * ent);
|
|
void SP_target_push(gentity_t * ent);
|
|
|
|
void SP_light(gentity_t * self);
|
|
void SP_dlight(gentity_t * self); // Elder: dlight entity
|
|
void SP_misc_lens_flare(gentity_t *ent); //Makro - lens flare
|
|
void SP_func_shadow(gentity_t *ent); //Makro - fake shadow
|
|
void SP_misc_corona(gentity_t *ent); //Makro - fake shadow
|
|
void SP_info_null(gentity_t * self);
|
|
void SP_info_notnull(gentity_t * self);
|
|
void SP_info_camp(gentity_t * self);
|
|
void SP_path_corner(gentity_t * self);
|
|
|
|
void SP_misc_teleporter_dest(gentity_t * self);
|
|
void SP_misc_model(gentity_t * ent);
|
|
void SP_misc_portal_camera(gentity_t * ent);
|
|
void SP_misc_portal_surface(gentity_t * ent);
|
|
//Makro - sky portals
|
|
void SP_misc_sky_portal(gentity_t * ent);
|
|
|
|
//Blaze: These functions are nolonger here
|
|
//void SP_shooter_rocket( gentity_t *ent );
|
|
//void SP_shooter_plasma( gentity_t *ent );
|
|
//void SP_shooter_grenade( gentity_t *ent );
|
|
|
|
void SP_team_CTF_redplayer(gentity_t * ent);
|
|
void SP_team_CTF_blueplayer(gentity_t * ent);
|
|
|
|
void SP_team_CTF_redspawn(gentity_t * ent);
|
|
void SP_team_CTF_bluespawn(gentity_t * ent);
|
|
|
|
void SP_func_door_rotating(gentity_t * ent); // VALKYRIE: for rotating doors
|
|
|
|
// JBravo: SP_item_botroam doesnt really exsist.
|
|
// Makro - still, bots are supposed to use these
|
|
void SP_item_botroam(gentity_t * ent) {}
|
|
|
|
//Blaze: merged func_explosive into func_breakable
|
|
|
|
// JBravo: adding explosive
|
|
//void SP_func_explosive (gentity_t *self);
|
|
|
|
spawn_t spawns[] = {
|
|
// info entities don't do anything at all, but provide positional
|
|
// information for things controlled by other processes
|
|
{"info_player_start", SP_info_player_start},
|
|
{"info_player_deathmatch", SP_info_player_deathmatch},
|
|
{"info_player_intermission", SP_info_player_intermission},
|
|
{"info_null", SP_info_null},
|
|
{"info_notnull", SP_info_notnull}, // use target_position instead
|
|
{"info_camp", SP_info_camp},
|
|
|
|
{"func_plat", SP_func_plat},
|
|
{"func_button", SP_func_button},
|
|
{"func_door", SP_func_door},
|
|
{"func_static", SP_func_static},
|
|
{"func_rotating", SP_func_rotating},
|
|
{"func_bobbing", SP_func_bobbing},
|
|
{"func_pendulum", SP_func_pendulum},
|
|
{"func_train", SP_func_train},
|
|
{"func_group", SP_info_null},
|
|
{"func_timer", SP_func_timer}, // rename trigger_timer?
|
|
{"func_breakable", SP_func_breakable}, // Blaze: Breakable glass
|
|
{"func_pressure", SP_func_pressure}, // NiceAss: pressure entity
|
|
|
|
// Triggers are brush objects that cause an effect when contacted
|
|
// by a living player, usually involving firing targets.
|
|
// While almost everything could be done with
|
|
// a single trigger class and different targets, triggered effects
|
|
// could not be client side predicted (push and teleport).
|
|
{"trigger_always", SP_trigger_always},
|
|
{"trigger_multiple", SP_trigger_multiple},
|
|
{"trigger_push", SP_trigger_push},
|
|
{"trigger_teleport", SP_trigger_teleport},
|
|
{"trigger_hurt", SP_trigger_hurt},
|
|
|
|
// targets perform no action by themselves, but must be triggered
|
|
// by another entity
|
|
{"target_give", SP_target_give},
|
|
{"target_remove_powerups", SP_target_remove_powerups},
|
|
{"target_delay", SP_target_delay},
|
|
{"target_speaker", SP_target_speaker},
|
|
{"target_print", SP_target_print},
|
|
{"target_laser", SP_target_laser},
|
|
{"target_score", SP_target_score},
|
|
{"target_teleporter", SP_target_teleporter},
|
|
{"target_relay", SP_target_relay},
|
|
{"target_kill", SP_target_kill},
|
|
//Makro - added
|
|
{"target_activate", SP_target_activate},
|
|
{"target_position", SP_target_position},
|
|
{"target_location", SP_target_location},
|
|
{"target_push", SP_target_push},
|
|
|
|
{"light", SP_light},
|
|
{"func_dlite", SP_dlight}, // Elder: dlight entity
|
|
{"light_d", SP_dlight}, //Makro - for compatibility with older maps
|
|
{"func_shadow", SP_func_shadow}, //Makro - fake shadow
|
|
{"misc_corona", SP_misc_corona}, //Makro - coronas
|
|
{"misc_lens_flare", SP_misc_lens_flare}, //Makro - lens flare
|
|
{"path_corner", SP_path_corner},
|
|
|
|
{"misc_teleporter_dest", SP_misc_teleporter_dest},
|
|
{"misc_model", SP_misc_model},
|
|
{"misc_portal_surface", SP_misc_portal_surface},
|
|
{"misc_portal_camera", SP_misc_portal_camera},
|
|
//Makro - sky portal !
|
|
{"misc_sky_portal", SP_misc_sky_portal},
|
|
//Blaze: This removes rocket traps I think
|
|
// {"shooter_rocket", SP_shooter_rocket},
|
|
// {"shooter_grenade", SP_shooter_grenade},
|
|
// {"shooter_plasma", SP_shooter_plasma},
|
|
|
|
{"team_CTF_redplayer", SP_team_CTF_redplayer},
|
|
{"team_CTF_blueplayer", SP_team_CTF_blueplayer},
|
|
|
|
{"team_CTF_redspawn", SP_team_CTF_redspawn},
|
|
{"team_CTF_bluespawn", SP_team_CTF_bluespawn},
|
|
|
|
{"func_door_rotating", SP_func_door_rotating}, // VALKYRIE: for rotating doors
|
|
|
|
//Blaze: Merged func_explosive into func_breakable
|
|
// {"func_explosive", SP_func_explosive}, // JBravo: for explosive.
|
|
{"item_botroam", SP_item_botroam},
|
|
|
|
{NULL, 0}
|
|
};
|
|
|
|
/*
|
|
===============
|
|
G_CallSpawn
|
|
|
|
Finds the spawn function for the entity and calls it,
|
|
returning qfalse if not found
|
|
===============
|
|
*/
|
|
qboolean G_CallSpawn(gentity_t * ent)
|
|
{
|
|
spawn_t *s;
|
|
gitem_t *item;
|
|
|
|
if (!ent->classname) {
|
|
G_Printf("G_CallSpawn: NULL classname\n");
|
|
return qfalse;
|
|
}
|
|
|
|
//Blaze: allow for Reaction specific spawns to be used
|
|
//Elder: map Q3DM weapons -> RQ3 weapons
|
|
if (!strcmp(ent->classname, "weapon_gauntlet"))
|
|
ent->classname = "weapon_knife";
|
|
else if (!strcmp(ent->classname, "weapon_machinegun"))
|
|
ent->classname = "weapon_pistol";
|
|
else if (!strcmp(ent->classname, "weapon_shotgun"))
|
|
ent->classname = "weapon_m3";
|
|
else if (!strcmp(ent->classname, "weapon_plasmagun"))
|
|
ent->classname = "weapon_mp5";
|
|
else if (!strcmp(ent->classname, "weapon_rocketlauncher"))
|
|
ent->classname = "weapon_handcannon";
|
|
else if (!strcmp(ent->classname, "weapon_railgun"))
|
|
ent->classname = "weapon_ssg3000";
|
|
else if (!strcmp(ent->classname, "weapon_bfg"))
|
|
ent->classname = "weapon_m4";
|
|
else if (!strcmp(ent->classname, "ammo_grenades"))
|
|
ent->classname = "weapon_grenade";
|
|
//Makro - this was missing
|
|
else if (!strcmp(ent->classname, "weapon_grenadelauncher"))
|
|
ent->classname = "weapon_pistol";
|
|
|
|
//Elder: map Q3DM ammo -> RQ3 ammo
|
|
if (!strcmp(ent->classname, "ammo_bullets"))
|
|
ent->classname = "ammo_mk23";
|
|
else if (!strcmp(ent->classname, "ammo_slugs"))
|
|
ent->classname = "ammo_ssg3000";
|
|
else if (!strcmp(ent->classname, "ammo_cells"))
|
|
ent->classname = "ammo_mp5";
|
|
else if (!strcmp(ent->classname, "ammo_bfg"))
|
|
ent->classname = "ammo_m4";
|
|
else if (!strcmp(ent->classname, "ammo_rockets"))
|
|
ent->classname = "ammo_shells";
|
|
|
|
//Blaze: let us support RQ3_weapon name so radiant works nicer
|
|
if (!strcmp(ent->classname, "RQ3weapon_knife"))
|
|
ent->classname = "weapon_knife";
|
|
else if (!strcmp(ent->classname, "RQ3weapon_pistol"))
|
|
ent->classname = "weapon_pistol";
|
|
else if (!strcmp(ent->classname, "RQ3weapon_m3"))
|
|
ent->classname = "weapon_m3";
|
|
else if (!strcmp(ent->classname, "RQ3weapon_mp5"))
|
|
ent->classname = "weapon_mp5";
|
|
else if (!strcmp(ent->classname, "RQ3weapon_handcannon"))
|
|
ent->classname = "weapon_handcannon";
|
|
else if (!strcmp(ent->classname, "RQ3weapon_ssg3000"))
|
|
ent->classname = "weapon_ssg3000";
|
|
else if (!strcmp(ent->classname, "RQ3weapon_m4"))
|
|
ent->classname = "weapon_m4";
|
|
else if (!strcmp(ent->classname, "RQ3weapon_grenade"))
|
|
ent->classname = "weapon_grenade";
|
|
// JBravo: Briefcases
|
|
else if (!strcmp(ent->classname, "RQ3case_black"))
|
|
ent->classname = "team_CTF_blueflag";
|
|
else if (!strcmp(ent->classname, "RQ3case_silver"))
|
|
ent->classname = "team_CTF_redflag";
|
|
//Elder: map Q3DM ammo -> RQ3 ammo
|
|
if (!strcmp(ent->classname, "RQ3ammo_mk23"))
|
|
ent->classname = "ammo_mk23";
|
|
else if (!strcmp(ent->classname, "RQ3ammo_ssg3000"))
|
|
ent->classname = "ammo_ssg3000";
|
|
else if (!strcmp(ent->classname, "RQ3ammo_mp5"))
|
|
ent->classname = "ammo_mp5";
|
|
else if (!strcmp(ent->classname, "RQ3ammo_m4"))
|
|
ent->classname = "ammo_m4";
|
|
else if (!strcmp(ent->classname, "RQ3ammo_shells"))
|
|
ent->classname = "ammo_shells";
|
|
|
|
/*
|
|
//Elder: old stuff
|
|
if (!strcmp(ent->classname,"weapon_gauntlet")) ent->classname = "weapon_knife";
|
|
else if (!strcmp(ent->classname,"weapon_railgun")) ent->classname = "weapon_ssg3000";
|
|
else if (!strcmp(ent->classname,"weapon_shotgun")) ent->classname = "weapon_m3";
|
|
else if (!strcmp(ent->classname,"weapon_machinegun")) ent->classname = "weapon_mp5";
|
|
else if (!strcmp(ent->classname,"weapon_rocketlauncher")) ent->classname = "weapon_handcannon";
|
|
else if (!strcmp(ent->classname,"weapon_bfg")) ent->classname = "weapon_m4";
|
|
else if (!strcmp(ent->classname,"weapon_grenadelauncher")) ent->classname = "weapon_pistol";
|
|
else if (!strcmp(ent->classname,"ammo_grenades")) ent->classname = "weapon_grenade";
|
|
*/
|
|
// check item spawn functions
|
|
|
|
// JBravo: No weapons and items on the maps in teamplay
|
|
/*
|
|
if (g_gametype.integer != GT_TEAMPLAY) {
|
|
for ( item=bg_itemlist+1 ; item->classname ; item++ ) {
|
|
if ( !strcmp(item->classname, ent->classname) ) {
|
|
G_SpawnItem( ent, item );
|
|
return qtrue;
|
|
}
|
|
}
|
|
} */
|
|
//Makro - new code
|
|
|
|
for (item = bg_itemlist + 1; item->classname; item++) {
|
|
if (!strcmp(item->classname, ent->classname)) {
|
|
//if it's a flag
|
|
if (item->giType == IT_TEAM && (item->giTag == PW_REDFLAG || item->giTag == PW_BLUEFLAG)) {
|
|
//only spawn it in CTF
|
|
if (g_gametype.integer == GT_CTF) {
|
|
G_SpawnItem(ent, item);
|
|
}
|
|
return qtrue;
|
|
} else {
|
|
// JBravo: no spawning in CTF
|
|
if (g_gametype.integer != GT_CTF) {
|
|
G_SpawnItem(ent, item);
|
|
G_BotOnlyItem(ent);
|
|
}
|
|
return qtrue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// check normal spawn functions
|
|
for (s = spawns; s->name; s++) {
|
|
if (!strcmp(s->name, ent->classname)) {
|
|
// found it
|
|
s->spawn(ent);
|
|
return qtrue;
|
|
}
|
|
}
|
|
// JBravo: getting rid of warnings when the game starts up.
|
|
// JBravo: FIXME! This is just supressing the message, not the problem.
|
|
if (g_gametype.integer != GT_TEAMPLAY)
|
|
G_Printf("%s doesn't have a spawn function\n", ent->classname);
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
G_NewString
|
|
|
|
Builds a copy of the string, translating \n to real linefeeds
|
|
so message texts can be multi-line
|
|
=============
|
|
*/
|
|
char *G_NewString(const char *string)
|
|
{
|
|
char *newb, *new_p;
|
|
int i, l;
|
|
|
|
l = strlen(string) + 1;
|
|
|
|
newb = G_Alloc(l);
|
|
|
|
new_p = newb;
|
|
|
|
// turn \n into a real linefeed
|
|
for (i = 0; i < l; i++) {
|
|
if (string[i] == '\\' && i < l - 1) {
|
|
i++;
|
|
if (string[i] == 'n') {
|
|
*new_p++ = '\n';
|
|
} else {
|
|
*new_p++ = '\\';
|
|
}
|
|
} else {
|
|
*new_p++ = string[i];
|
|
}
|
|
}
|
|
|
|
return newb;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
G_ParseField
|
|
|
|
Takes a key/value pair and sets the binary values
|
|
in a gentity
|
|
===============
|
|
*/
|
|
void G_ParseField(const char *key, const char *value, gentity_t * ent)
|
|
{
|
|
field_t *f;
|
|
byte *b;
|
|
float v;
|
|
vec3_t vec;
|
|
|
|
for (f = fields; f->name; f++) {
|
|
if (!Q_stricmp(f->name, key)) {
|
|
// found it
|
|
b = (byte *) ent;
|
|
|
|
switch (f->type) {
|
|
case F_STRING:
|
|
*(char **) (b + f->ofs) = G_NewString(value);
|
|
break;
|
|
case F_VECTOR:
|
|
sscanf(value, "%f %f %f", &vec[0], &vec[1], &vec[2]);
|
|
((float *) (b + f->ofs))[0] = vec[0];
|
|
((float *) (b + f->ofs))[1] = vec[1];
|
|
((float *) (b + f->ofs))[2] = vec[2];
|
|
break;
|
|
case F_INT:
|
|
*(int *) (b + f->ofs) = atoi(value);
|
|
break;
|
|
case F_FLOAT:
|
|
*(float *) (b + f->ofs) = atof(value);
|
|
break;
|
|
case F_ANGLEHACK:
|
|
v = atof(value);
|
|
((float *) (b + f->ofs))[0] = 0;
|
|
((float *) (b + f->ofs))[1] = v;
|
|
((float *) (b + f->ofs))[2] = 0;
|
|
break;
|
|
// default:
|
|
// case F_IGNORE:
|
|
// break;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define ADJUST_AREAPORTAL() \
|
|
if(ent->s.eType == ET_MOVER) \
|
|
{ \
|
|
trap_LinkEntity(ent); \
|
|
trap_AdjustAreaPortalState(ent, qtrue); \
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
G_SpawnGEntityFromSpawnVars
|
|
|
|
Spawn an entity and fill in all of the level fields from
|
|
level.spawnVars[], then call the class specfic spawn function
|
|
===================
|
|
*/
|
|
void G_SpawnGEntityFromSpawnVars(void)
|
|
{
|
|
int i;
|
|
gentity_t *ent;
|
|
char *s, *value, *gametypeName;
|
|
|
|
// JBravo: added teamplay
|
|
static char *gametypeNames[] =
|
|
{ "ffa", "tournament", "single", "team", "teamplay", "ctf", "oneflag", "obelisk", "harvester",
|
|
"teamtournament"
|
|
};
|
|
|
|
// get the next free entity
|
|
ent = G_Spawn();
|
|
|
|
for (i = 0; i < level.numSpawnVars; i++) {
|
|
G_ParseField(level.spawnVars[i][0], level.spawnVars[i][1], ent);
|
|
}
|
|
|
|
// check for "notsingle" flag
|
|
if (g_gametype.integer == GT_SINGLE_PLAYER) {
|
|
G_SpawnInt("notsingle", "0", &i);
|
|
if (i) {
|
|
ADJUST_AREAPORTAL();
|
|
G_FreeEntity(ent);
|
|
return;
|
|
}
|
|
}
|
|
//Makro - check for "notgametype" key
|
|
if (G_SpawnInt("notgametype", "0", &i)) {
|
|
if ((i & (1 << g_gametype.integer)) != 0) {
|
|
ADJUST_AREAPORTAL();
|
|
G_FreeEntity(ent);
|
|
return;
|
|
}
|
|
// check for "notteam" flag (GT_FFA, GT_TOURNAMENT, GT_SINGLE_PLAYER)
|
|
} else if (g_gametype.integer >= GT_TEAM) {
|
|
G_SpawnInt("notteam", "0", &i);
|
|
if (i) {
|
|
ADJUST_AREAPORTAL();
|
|
G_FreeEntity(ent);
|
|
return;
|
|
}
|
|
} else {
|
|
G_SpawnInt("notfree", "0", &i);
|
|
if (i) {
|
|
ADJUST_AREAPORTAL();
|
|
G_FreeEntity(ent);
|
|
return;
|
|
}
|
|
}
|
|
|
|
G_SpawnInt("notq3a", "0", &i);
|
|
if (i) {
|
|
ADJUST_AREAPORTAL();
|
|
G_FreeEntity(ent);
|
|
return;
|
|
}
|
|
|
|
if (G_SpawnString("gametype", NULL, &value)) {
|
|
if (g_gametype.integer >= GT_FFA && g_gametype.integer < GT_MAX_GAME_TYPE) {
|
|
gametypeName = gametypeNames[g_gametype.integer];
|
|
|
|
s = strstr(value, gametypeName);
|
|
if (!s) {
|
|
ADJUST_AREAPORTAL();
|
|
G_FreeEntity(ent);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
// move editor origin to pos
|
|
VectorCopy(ent->s.origin, ent->s.pos.trBase);
|
|
VectorCopy(ent->s.origin, ent->r.currentOrigin);
|
|
|
|
// if we didn't get a classname, don't bother spawning anything
|
|
if (!G_CallSpawn(ent)) {
|
|
G_FreeEntity(ent);
|
|
}
|
|
|
|
//Makro - is the entity in a sky portal ?
|
|
if (G_SpawnInt("skyportalent", "0", &i)) {
|
|
if (i) {
|
|
ent->s.eFlags |= EF_HEADLESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
G_AddSpawnVarToken
|
|
====================
|
|
*/
|
|
char *G_AddSpawnVarToken(const char *string)
|
|
{
|
|
int l;
|
|
char *dest;
|
|
|
|
l = strlen(string);
|
|
if (level.numSpawnVarChars + l + 1 > MAX_SPAWN_VARS_CHARS) {
|
|
G_Error("G_AddSpawnVarToken: MAX_SPAWN_VARS_CHARS");
|
|
}
|
|
|
|
dest = level.spawnVarChars + level.numSpawnVarChars;
|
|
memcpy(dest, string, l + 1);
|
|
|
|
level.numSpawnVarChars += l + 1;
|
|
|
|
return dest;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
G_ParseSpawnVars
|
|
|
|
Parses a brace bounded set of key / value pairs out of the
|
|
level's entity strings into level.spawnVars[]
|
|
|
|
This does not actually spawn an entity.
|
|
====================
|
|
*/
|
|
qboolean G_ParseSpawnVars(void)
|
|
{
|
|
char keyname[MAX_TOKEN_CHARS];
|
|
char com_token[MAX_TOKEN_CHARS];
|
|
|
|
level.numSpawnVars = 0;
|
|
level.numSpawnVarChars = 0;
|
|
|
|
// parse the opening brace
|
|
if (!trap_GetEntityToken(com_token, sizeof(com_token))) {
|
|
// end of spawn string
|
|
return qfalse;
|
|
}
|
|
if (com_token[0] != '{') {
|
|
G_Error("G_ParseSpawnVars: found %s when expecting {", com_token);
|
|
}
|
|
// go through all the key / value pairs
|
|
while (1) {
|
|
// parse key
|
|
if (!trap_GetEntityToken(keyname, sizeof(keyname))) {
|
|
G_Error("G_ParseSpawnVars: EOF without closing brace");
|
|
}
|
|
|
|
if (keyname[0] == '}') {
|
|
break;
|
|
}
|
|
// parse value
|
|
if (!trap_GetEntityToken(com_token, sizeof(com_token))) {
|
|
G_Error("G_ParseSpawnVars: EOF without closing brace");
|
|
}
|
|
|
|
if (com_token[0] == '}') {
|
|
G_Error("G_ParseSpawnVars: closing brace without data");
|
|
}
|
|
if (level.numSpawnVars == MAX_SPAWN_VARS) {
|
|
G_Error("G_ParseSpawnVars: MAX_SPAWN_VARS");
|
|
}
|
|
level.spawnVars[level.numSpawnVars][0] = G_AddSpawnVarToken(keyname);
|
|
level.spawnVars[level.numSpawnVars][1] = G_AddSpawnVarToken(com_token);
|
|
level.numSpawnVars++;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
/*QUAKED worldspawn (0 0 0) ?
|
|
|
|
Every map should have exactly one worldspawn.
|
|
"music" music wav file
|
|
"gravity" 800 is default gravity
|
|
"message" Text to print during connection process
|
|
*/
|
|
void SP_worldspawn(void)
|
|
{
|
|
char *s;
|
|
|
|
vec3_t color;
|
|
//int nodetail = 0;
|
|
int i;
|
|
char info[MAX_INFO_STRING];
|
|
|
|
G_SpawnString("classname", "", &s);
|
|
if (Q_stricmp(s, "worldspawn")) {
|
|
G_Error("SP_worldspawn: The first entity isn't 'worldspawn'");
|
|
}
|
|
// make some data visible to connecting client
|
|
trap_SetConfigstring(CS_GAME_VERSION, GAME_VERSION);
|
|
|
|
trap_SetConfigstring(CS_LEVEL_START_TIME, va("%i", level.startTime));
|
|
|
|
G_SpawnString("music", "", &s);
|
|
trap_SetConfigstring(CS_MUSIC, s);
|
|
|
|
G_SpawnString("message", "", &s);
|
|
trap_SetConfigstring(CS_MESSAGE, s); // map specific message
|
|
|
|
/* Makro - no longer
|
|
//Makro - color for the loading screen text
|
|
G_SpawnVector( "_text_color", "0.75 0.75 0.75", color );
|
|
Info_SetValueForKey(info, "r1", va("%f", color[0]));
|
|
Info_SetValueForKey(info, "g1", va("%f", color[1]));
|
|
Info_SetValueForKey(info, "b1", va("%f", color[2]));
|
|
G_SpawnVector( "_text_color2", "1 1 1", color );
|
|
Info_SetValueForKey(info, "r2", va("%f", color[0]));
|
|
Info_SetValueForKey(info, "g2", va("%f", color[1]));
|
|
Info_SetValueForKey(info, "b2", va("%f", color[2]));
|
|
//skip detail ?
|
|
G_SpawnInt( "nodetail", "0", &nodetail );
|
|
Info_SetValueForKey(info, "nodetail", va("%i", nodetail));
|
|
//save settings
|
|
trap_SetConfigstring( CS_LOADINGSCREEN, info );
|
|
*/
|
|
|
|
//Makro - fog hull replacement
|
|
if (G_SpawnVector("_rq3_fog_color", "0 0 0", color)) {
|
|
memset(info, 0, sizeof(info));
|
|
Info_SetValueForKey(info, "r", va("%f", color[0]));
|
|
Info_SetValueForKey(info, "g", va("%f", color[1]));
|
|
Info_SetValueForKey(info, "b", va("%f", color[2]));
|
|
trap_SetConfigstring( CS_FOGHULL, info );
|
|
}
|
|
|
|
trap_SetConfigstring(CS_MOTD, g_motd.string); // message of the day
|
|
|
|
G_SpawnString("gravity", "800", &s);
|
|
trap_Cvar_Set("g_gravity", s);
|
|
|
|
G_SpawnString("enableDust", "0", &s);
|
|
trap_Cvar_Set("g_enableDust", s);
|
|
|
|
G_SpawnString("enableBreath", "0", &s);
|
|
trap_Cvar_Set("g_enableBreath", s);
|
|
|
|
//Makro - read func_breakable types
|
|
if (G_SpawnInt("numbreakabletypes", "0", &i)) {
|
|
int j;
|
|
for (j=0; j<i && j<RQ3_MAX_BREAKABLES; j++) {
|
|
char *type, *force, *lift;
|
|
if (!G_SpawnString(va("b%d_type", j), "", &type))
|
|
continue;
|
|
G_SpawnString(va("b%d_force", j), "7", &force);
|
|
G_SpawnString(va("b%d_lift", j), "5", &lift);
|
|
info[0] = 0;
|
|
Info_SetValueForKey(info, "type", type);
|
|
Info_SetValueForKey(info, "velocity", force);
|
|
Info_SetValueForKey(info, "jump", lift);
|
|
trap_SetConfigstring(CS_BREAKABLES + j, info);
|
|
}
|
|
}
|
|
|
|
|
|
// q3f atmospheric stuff:
|
|
G_SpawnString( "atmosphere", "", &s );
|
|
trap_SetConfigstring( CS_ATMOSEFFECT, s ); // Atmospheric effect
|
|
|
|
G_SpawnString("enableLaserFog", "1", &s);
|
|
trap_Cvar_Set("g_enableLaserFog", s);
|
|
|
|
|
|
g_entities[ENTITYNUM_WORLD].s.number = ENTITYNUM_WORLD;
|
|
g_entities[ENTITYNUM_WORLD].r.ownerNum = ENTITYNUM_NONE;
|
|
g_entities[ENTITYNUM_WORLD].classname = "worldspawn";
|
|
|
|
g_entities[ENTITYNUM_NONE].s.number = ENTITYNUM_NONE;
|
|
g_entities[ENTITYNUM_NONE].r.ownerNum = ENTITYNUM_NONE;
|
|
g_entities[ENTITYNUM_NONE].classname = "nothing";
|
|
|
|
// see if we want a warmup time
|
|
trap_SetConfigstring(CS_WARMUP, "");
|
|
if (g_restarted.integer) {
|
|
trap_Cvar_Set("g_restarted", "0");
|
|
level.warmupTime = 0;
|
|
} else if (g_doWarmup.integer) { // Turn it on
|
|
level.warmupTime = -1;
|
|
trap_SetConfigstring(CS_WARMUP, va("%i", level.warmupTime));
|
|
G_LogPrintf("Warmup:\n");
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
==============
|
|
G_SpawnEntitiesFromString
|
|
|
|
Parses textual entity definitions out of an entstring and spawns gentities.
|
|
==============
|
|
*/
|
|
void G_SpawnEntitiesFromString(void)
|
|
{
|
|
// allow calls to G_Spawn*()
|
|
level.spawning = qtrue;
|
|
level.numSpawnVars = 0;
|
|
|
|
// the worldspawn is not an actual entity, but it still
|
|
// has a "spawn" function to perform any global setup
|
|
// needed by a level (setting configstrings or cvars, etc)
|
|
if (!G_ParseSpawnVars()) {
|
|
G_Error("SpawnEntities: no entities");
|
|
}
|
|
SP_worldspawn();
|
|
|
|
// parse ents
|
|
while (G_ParseSpawnVars()) {
|
|
G_SpawnGEntityFromSpawnVars();
|
|
}
|
|
|
|
level.spawning = qfalse; // any future calls to G_Spawn*() will be errors
|
|
}
|