void G_ParseCampaigns(void) { int i; qboolean mapFound = qfalse; level.campaignCount = 0; level.currentCampaign = -1; memset(&g_campaigns, 0, sizeof(g_campaignInfo_t) * MAX_CAMPAIGNS); if (g_gametype.integer != GT_WOLF_CAMPAIGN) { trap_Cvar_Set("g_oldCampaign", ""); trap_Cvar_Set("g_currentCampaign", ""); trap_Cvar_Set("g_currentCampaignMap", "0"); return; } if (g_campaignFile.string[0]) { if (G_LoadCampaignsFromFile(g_campaignFile.string)) { mapFound = qtrue; } } if (!mapFound) { // get all campaigns from .campaign files int dirlen; int numdirs = trap_FS_GetFileList("scripts", ".campaign", bigTextBuffer, sizeof(bigTextBuffer)); char filename[MAX_QPATH]; // was 128 char *dirptr = bigTextBuffer; for (i = 0; i < numdirs; i++, dirptr += dirlen + 1) { // log a warning if server has more than MAX_CAMPAIGNS if (level.campaignCount >= MAX_CAMPAIGNS) { G_LogPrintf("WARNING G_ParseCampaigns: number of campaigns larger then MAX_CAMPAIGNS\n"); break; } dirlen = strlen(dirptr); strcpy(filename, "scripts/"); strcat(filename, dirptr); if (G_LoadCampaignsFromFile(filename)) { mapFound = qtrue; } } } if (!mapFound) { // map isn't found in the current campaign, see if it's the first map in another campaign for (i = 0; i < level.campaignCount; i++) { if (!Q_stricmp(g_campaigns[i].mapnames[0], level.rawmapname)) { // someone manually specified a /map command, and it's the first map in a campaign trap_Cvar_Set("g_currentCampaign", g_campaigns[i].shortname); trap_Cvar_Set("g_currentCampaignMap", "0"); level.newCampaign = qtrue; g_campaigns[level.campaignCount].current = 0; level.currentCampaign = i; break; } } if (i == level.campaignCount) { char buf[MAX_STRING_CHARS]; if (trap_Argc() < 1) // command not found, throw error { G_Error("Usage 'map <mapname>\n'"); } trap_Argv(0, buf, sizeof(buf)); if (!(*buf)) // command not found, throw error { G_Error("Usage 'map <mapname>\n'"); } // no campaign found, fallback to GT_WOLF // and reload the map trap_Cvar_Set("g_gametype", "2"); trap_SendConsoleCommand(EXEC_APPEND, va("%s %s\n", buf, level.rawmapname)); } } }
/*QUAKED worldspawn (0 0 0) ? NO_GT_WOLF NO_GT_STOPWATCH NO_GT_CHECKPOINT NO_LMS Every map should have exactly one worldspawn. "music" Music wav file "gravity" 800 is default gravity "message" Text to print during connection process "ambient" Ambient light value (must use '_color') "_color" Ambient light color (must be used with 'ambient') "sun" Shader to use for 'sun' image */ void SP_worldspawn(void) { char *s; 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 G_SpawnString("cclayers", "0", &s); if(atoi(s)) { level.ccLayers = qtrue; } level.mapcoordsValid = qfalse; if(G_SpawnVector2D("mapcoordsmins", "-128 128", level.mapcoordsMins) && // top left G_SpawnVector2D("mapcoordsmaxs", "128 -128", level.mapcoordsMaxs)) { // bottom right level.mapcoordsValid = qtrue; } BG_InitLocations(level.mapcoordsMins, level.mapcoordsMaxs); trap_SetConfigstring(CS_MOTD, g_motd.string); // message of the day G_SpawnString("gravity", "800", &s); trap_Cvar_Set("g_gravity", s); G_SpawnString("spawnflags", "0", &s); g_entities[ENTITYNUM_WORLD].spawnflags = atoi(s); g_entities[ENTITYNUM_WORLD].r.worldflags = g_entities[ENTITYNUM_WORLD].spawnflags; g_entities[ENTITYNUM_WORLD].s.number = ENTITYNUM_WORLD; g_entities[ENTITYNUM_WORLD].classname = "worldspawn"; // see if we want a warmup time trap_SetConfigstring(CS_WARMUP, ""); if(g_restarted.integer) { trap_Cvar_Set("g_restarted", "0"); level.warmupTime = 0; } if(g_gamestate.integer == GS_PLAYING) { G_initMatch(); } }
static void EnumerateField(const save_field_t *pField, const byte *pbBase) { void *pv = (void *)(pbBase + pField->iOffset); switch (pField->eFieldType) { case F_STRING: *(int *)pv = GetStringNum(*(char **)pv); break; case F_GENTITY: *(int *)pv = GetGEntityNum(*(gentity_t **)pv); break; case F_GROUP: *(int *)pv = GetGroupNumber(*(AIGroupInfo_t **)pv); break; case F_GCLIENT: *(int *)pv = GetGClientNum(*(gclient_t **)pv, (gentity_t *) pbBase); break; case F_ITEM: *(int *)pv = GetGItemNum(*(gitem_t **)pv); break; case F_VEHINFO: *(int *)pv = GetVehicleInfoNum(*(vehicleInfo_t **)pv); break; case F_BEHAVIORSET: { const char **p = (const char **) pv; for (int i=0; i<NUM_BSETS; i++) { pv = &p[i]; // since you can't ++ a void ptr *(int *)pv = GetStringNum(*(char **)pv); } } break; /*MCG case F_BODYQUEUE: { gentity_t **p = (gentity_t **) pv; for (int i=0; i<BODY_QUEUE_SIZE; i++) { pv = &p[i]; // since you can't ++ a void ptr *(int *)pv = GetGEntityNum(*(gentity_t **)pv); } } break; */ case F_ALERTEVENT: // convert all gentity_t ptrs in an alertEvent array into indexes... { alertEvent_t* p = (alertEvent_t *) pv; for (int i=0; i<MAX_ALERT_EVENTS; i++) { p[i].owner = (gentity_t *) GetGEntityNum(p[i].owner); } } break; case F_AIGROUPS: // convert to ptrs within this into indexes... { AIGroupInfo_t* p = (AIGroupInfo_t *) pv; for (int i=0; i<MAX_FRAME_GROUPS; i++) { p[i].enemy = (gentity_t *) GetGEntityNum(p[i].enemy); p[i].commander = (gentity_t *) GetGEntityNum(p[i].commander); } } break; case F_ANIMFILESETS: { animFileSet_t* p = (animFileSet_t *) pv; for (int i=0; i<MAX_ANIM_FILES; i++) { for ( int j=0; j<MAX_ANIM_EVENTS; j++ ) { const char *ptAnimEventStringData = p[i].torsoAnimEvents[j].stringData; *(int *)(&p[i].torsoAnimEvents[j].stringData) = GetStringNum( ptAnimEventStringData ); const char *plAnimEventStringData = p[i].legsAnimEvents[j].stringData; *(int *)(&p[i].legsAnimEvents[j].stringData) = GetStringNum( plAnimEventStringData ); } } } break; case F_BOOLPTR: *(qboolean *)pv = !!(*(int *)pv); break; // These are pointers that are always recreated case F_NULL: *(void **)pv = NULL; break; case F_IGNORE: break; default: G_Error ("EnumerateField: unknown field type"); break; } }
/* =========== SelectNearestSpawnPoint for teleporting you back after falling into the void ============ */ gentity_t *SelectNearestSpawnPoint ( vec3_t Point, vec3_t origin, vec3_t angles ) { gentity_t *spot; vec3_t delta; float dist, nearestDist; gentity_t *nearestSpot; nearestDist = 999999; nearestSpot = NULL; spot = NULL; while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { VectorSubtract( spot->s.origin, Point, delta ); dist = VectorLength( delta ); if ( dist < nearestDist ) { nearestDist = dist; nearestSpot = spot; } } while ((spot = G_Find (spot, FOFS(classname), "team_CTF_redplayer")) != NULL) { VectorSubtract( spot->s.origin, Point, delta ); dist = VectorLength( delta ); if ( dist < nearestDist ) { nearestDist = dist; nearestSpot = spot; } } while ((spot = G_Find (spot, FOFS(classname), "team_CTF_blueplayer")) != NULL) { VectorSubtract( spot->s.origin, Point, delta ); dist = VectorLength( delta ); if ( dist < nearestDist ) { nearestDist = dist; nearestSpot = spot; } } while ((spot = G_Find (spot, FOFS(classname), "team_CTF_redspawn")) != NULL) { VectorSubtract( spot->s.origin, Point, delta ); dist = VectorLength( delta ); if ( dist < nearestDist ) { nearestDist = dist; nearestSpot = spot; } } while ((spot = G_Find (spot, FOFS(classname), "team_CTF_bluespawn")) != NULL) { VectorSubtract( spot->s.origin, Point, delta ); dist = VectorLength( delta ); if ( dist < nearestDist ) { nearestDist = dist; nearestSpot = spot; } } // find a single player start spot if (!nearestSpot) { G_Error( "Couldn't find a spawn point" ); } VectorCopy (nearestSpot->s.origin, origin); origin[2] += 9; VectorCopy (nearestSpot->s.angles, angles); return nearestSpot; }
void G_RunFrame( int levelTime ) { int i; gentity_t *ent; int ents_inuse=0; // someone's gonna be pissed I put this here... #if AI_TIMERS AITime = 0; navTime = 0; #endif// AI_TIMERS level.framenum++; level.previousTime = level.time; level.time = levelTime; //msec = level.time - level.previousTime; NAV_CheckCalcPaths(); //ResetTeamCounters(); AI_UpdateGroups(); if ( d_altRoutes->integer ) { navigator.CheckAllFailedEdges(); } navigator.ClearCheckedNodes(); //remember last waypoint, clear current one // for ( i = 0, ent = &g_entities[0]; i < globals.num_entities ; i++, ent++) for ( i = 0; i < globals.num_entities ; i++) { // if ( !ent->inuse ) // continue; if(!PInUse(i)) continue; ent = &g_entities[i]; if ( ent->waypoint != WAYPOINT_NONE && ent->noWaypointTime < level.time ) { ent->lastWaypoint = ent->waypoint; ent->waypoint = WAYPOINT_NONE; } if ( d_altRoutes->integer ) { navigator.CheckFailedNodes( ent ); } } //Look to clear out old events ClearPlayerAlertEvents(); //Run the frame for all entities // for ( i = 0, ent = &g_entities[0]; i < globals.num_entities ; i++, ent++) for ( i = 0; i < globals.num_entities ; i++) { // if ( !ent->inuse ) // continue; if(!PInUse(i)) continue; ents_inuse++; ent = &g_entities[i]; // clear events that are too old if ( level.time - ent->eventTime > EVENT_VALID_MSEC ) { if ( ent->s.event ) { ent->s.event = 0; // &= EV_EVENT_BITS; if ( ent->client ) { ent->client->ps.externalEvent = 0; } } if ( ent->freeAfterEvent ) { // tempEntities or dropped items completely go away after their event G_FreeEntity( ent ); continue; } else if ( ent->unlinkAfterEvent ) { // items that will respawn will hide themselves after their pickup event ent->unlinkAfterEvent = qfalse; gi.unlinkentity( ent ); } } // temporary entities don't think if ( ent->freeAfterEvent ) continue; G_CheckTasksCompleted(ent); G_Roff( ent ); if( !ent->client ) { if ( !(ent->svFlags & SVF_SELF_ANIMATING) ) {//FIXME: make sure this is done only for models with frames? //Or just flag as animating? if ( ent->s.eFlags & EF_ANIM_ONCE ) { ent->s.frame++; } else if ( !(ent->s.eFlags & EF_ANIM_ALLFAST) ) { G_Animate( ent ); } } } G_CheckSpecialPersistentEvents( ent ); if ( ent->s.eType == ET_MISSILE ) { G_RunMissile( ent ); continue; } if ( ent->s.eType == ET_ITEM ) { G_RunItem( ent ); continue; } if ( ent->s.eType == ET_MOVER ) { if ( ent->model && Q_stricmp( "models/test/mikeg/tie_fighter.md3", ent->model ) == 0 ) { TieFighterThink( ent ); } G_RunMover( ent ); continue; } //The player if ( i == 0 ) { // decay batteries if the goggles are active if ( cg.zoomMode == 1 && ent->client->ps.batteryCharge > 0 ) { ent->client->ps.batteryCharge--; } else if ( cg.zoomMode == 3 && ent->client->ps.batteryCharge > 0 ) { ent->client->ps.batteryCharge -= 2; if ( ent->client->ps.batteryCharge < 0 ) { ent->client->ps.batteryCharge = 0; } } G_CheckEndLevelTimers( ent ); //Recalculate the nearest waypoint for the coming NPC updates NAV_FindPlayerWaypoint(); if( ent->taskManager && !stop_icarus ) { ent->taskManager->Update(); } //dead if ( ent->health <= 0 ) { if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) {//on the ground pitch_roll_for_slope( ent, NULL ); } } continue; // players are ucmd driven } G_RunThink( ent ); // be aware that ent may be free after returning from here, at least one func frees them ClearNPCGlobals(); // but these 2 funcs are ok //UpdateTeamCounters( ent ); // to call anyway on a freed ent. } // perform final fixups on the player ent = &g_entities[0]; if ( ent->inuse ) { ClientEndFrame( ent ); } if( g_numEntities->integer ) { gi.Printf( S_COLOR_WHITE"Number of Entities in use : %d\n", ents_inuse ); } //DEBUG STUFF NAV_ShowDebugInfo(); NPC_ShowDebugInfo(); G_DynamicMusicUpdate(); #if AI_TIMERS AITime -= navTime; if ( AITime > 20 ) { gi.Printf( S_COLOR_RED"ERROR: total AI time: %d\n", AITime ); } else if ( AITime > 10 ) { gi.Printf( S_COLOR_YELLOW"WARNING: total AI time: %d\n", AITime ); } else if ( AITime > 2 ) { gi.Printf( S_COLOR_GREEN"total AI time: %d\n", AITime ); } if ( navTime > 20 ) { gi.Printf( S_COLOR_RED"ERROR: total nav time: %d\n", navTime ); } else if ( navTime > 10 ) { gi.Printf( S_COLOR_YELLOW"WARNING: total nav time: %d\n", navTime ); } else if ( navTime > 2 ) { gi.Printf( S_COLOR_GREEN"total nav time: %d\n", navTime ); } #endif// AI_TIMERS #ifndef FINAL_BUILD if ( delayedShutDown != 0 && delayedShutDown < level.time ) { G_Error( "Game Errors. Scroll up the console to read them." ); } #endif #ifdef _DEBUG if(!(level.framenum&0xff)) { ValidateInUseBits(); } #endif }
/* ================= G_ScriptAction_FaceAngles syntax: faceangles <pitch> <yaw> <roll> <duration/GOTOTIME> [ACCEL/DECCEL] The entity will face the given angles, taking <duration> to get there. If the GOTOTIME is given instead of a timed duration, the duration calculated from the last gotomarker command will be used instead. ================= */ qboolean G_ScriptAction_FaceAngles( gentity_t *ent, char *params ) { char *pString, *token; int duration, i; vec3_t diff; vec3_t angles; int trType = TR_LINEAR_STOP; if ( !params || !params[0] ) { G_Error( "G_Scripting: syntax: faceangles <pitch> <yaw> <roll> <duration/GOTOTIME>\n" ); } if ( ent->scriptStatus.scriptStackChangeTime == level.time ) { pString = params; for ( i = 0; i < 3; i++ ) { token = COM_Parse( &pString ); if ( !token || !token[0] ) { G_Error( "G_Scripting: syntax: faceangles <pitch> <yaw> <roll> <duration/GOTOTIME>\n" ); } angles[i] = atoi( token ); } token = COM_Parse( &pString ); if ( !token || !token[0] ) { G_Error( "G_Scripting: faceangles requires a <pitch> <yaw> <roll> <duration/GOTOTIME>\n" ); } if ( !Q_strcasecmp( token, "gototime" ) ) { duration = ent->s.pos.trDuration; } else { duration = atoi( token ); } token = COM_Parse( &pString ); if ( token && token[0] ) { if ( !Q_strcasecmp( token, "accel" ) ) { trType = TR_ACCELERATE; } if ( !Q_strcasecmp( token, "deccel" ) ) { trType = TR_DECCELERATE; } } for ( i = 0; i < 3; i++ ) { diff[i] = AngleDifference( angles[i], ent->s.angles[i] ); while ( diff[i] > 180 ) diff[i] -= 360; while ( diff[i] < -180 ) diff[i] += 360; } VectorCopy( ent->s.angles, ent->s.apos.trBase ); if ( duration ) { VectorScale( diff, 1000.0 / (float)duration, ent->s.apos.trDelta ); } else { VectorClear( ent->s.apos.trDelta ); } ent->s.apos.trDuration = duration; ent->s.apos.trTime = level.time; ent->s.apos.trType = TR_LINEAR_STOP; if ( trType != TR_LINEAR_STOP ) { // accel / deccel logic // calc the speed from duration and start/end delta for ( i = 0; i < 3; i++ ) { ent->s.apos.trDelta[i] = 2.0 * 1000.0 * diff[i] / (float)duration; } ent->s.apos.trType = trType; } } else if ( ent->s.apos.trTime + ent->s.apos.trDuration <= level.time ) { // finished turning BG_EvaluateTrajectory( &ent->s.apos, ent->s.apos.trTime + ent->s.apos.trDuration, ent->s.angles ); VectorCopy( ent->s.angles, ent->s.apos.trBase ); VectorCopy( ent->s.angles, ent->r.currentAngles ); ent->s.apos.trTime = level.time; ent->s.apos.trDuration = 0; ent->s.apos.trType = TR_STATIONARY; VectorClear( ent->s.apos.trDelta ); script_linkentity( ent ); return qtrue; } BG_EvaluateTrajectory( &ent->s.apos, level.time, ent->r.currentAngles ); script_linkentity( ent ); return qfalse; }
//initization for misc_model_breakable void misc_model_breakable_init( gentity_t *ent ) { int type; type = MDL_OTHER; if (!ent->model) { G_Error("no model set on %s at (%.1f %.1f %.1f)\n", ent->classname, ent->s.origin[0],ent->s.origin[1],ent->s.origin[2]); } //Main model ent->s.modelindex = ent->sound2to1 = G_ModelIndex( ent->model ); if ( ent->spawnflags & 1 ) {//Blocks movement ent->r.contents = CONTENTS_SOLID|CONTENTS_OPAQUE|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//Was CONTENTS_SOLID, but only architecture should be this ent->s.solid = 2; //SOLID_BBOX ent->clipmask = MASK_PLAYERSOLID; } else if ( ent->health ) {//Can only be shot ent->r.contents = CONTENTS_SHOTCLIP; } if (type == MDL_OTHER) { ent->use = misc_model_use; } else if ( type == MDL_ARMOR_HEALTH ) { // G_SoundIndex("sound/player/suithealth.wav"); ent->use = health_use; if (!ent->count) { ent->count = 100; } ent->health = 60; } else if ( type == MDL_AMMO ) { // G_SoundIndex("sound/player/suitenergy.wav"); //RAFIXME: add this use function ent->use = ammo_use; if (!ent->count) { ent->count = 100; } ent->health = 60; } if ( ent->health ) { G_SoundIndex("sound/weapons/explosions/cargoexplode.wav"); ent->maxHealth = ent->health; G_ScaleNetHealth(ent); ent->takedamage = qtrue; ent->pain = misc_model_breakable_pain; //RACC - I think should be ->die ent->die = misc_model_breakable_die; //ent->think = misc_model_breakable_die; } ent->touch = misc_model_breakable_touch; }
void SP_target_speaker( gentity_t *ent ) { char buffer[MAX_QPATH]; char *s; G_SpawnFloat( "wait", "0", &ent->wait ); G_SpawnFloat( "random", "0", &ent->random ); if ( !G_SpawnString( "noise", "NOSOUND", &s ) ) { G_Error( "target_speaker without a noise key at %s", vtos( ent->s.origin ) ); } // force all client reletive sounds to be "activator" speakers that // play on the entity that activates it if ( s[0] == '*' ) { ent->spawnflags |= 8; } // Ridah, had to disable this so we can use sound scripts // don't worry, if the script isn't found, it'll default back to // .wav on the client-side //if (!strstr( s, ".wav" )) { // Com_sprintf (buffer, sizeof(buffer), "%s.wav", s ); //} else { Q_strncpyz( buffer, s, sizeof(buffer) ); //} ent->noise_index = G_SoundIndex(buffer); // a repeating speaker can be done completely client side ent->s.eType = ET_SPEAKER; ent->s.eventParm = ent->noise_index; ent->s.frame = ent->wait * 10; ent->s.clientNum = ent->random * 10; // check for prestarted looping sound if ( ent->spawnflags & 1 ) { ent->s.loopSound = ent->noise_index; } ent->use = Use_Target_Speaker; // GLOBAL if (ent->spawnflags & (4|32)) { ent->r.svFlags |= SVF_BROADCAST; } VectorCopy( ent->s.origin, ent->s.pos.trBase ); if (ent->spawnflags & 16) { ent->think = target_speaker_multiple; ent->nextthink = level.time + 50; } // NO_PVS if(ent->spawnflags & 32) ent->s.density = 1; else ent->s.density = 0; if(ent->radius) ent->s.dmgFlags = ent->radius; // store radius in dmgflags else ent->s.dmgFlags = 0; // must link the entity so we get areas and clusters so // the server can determine who to send updates to trap_LinkEntity( ent ); }
// Should be the only function that ever creates a fireteam void G_RegisterFireteam(/*const char* name,*/ int entityNum) { fireteamData_t *ft; gentity_t *leader; int count, ident; if (entityNum < 0 || entityNum >= MAX_CLIENTS) { G_Error("G_RegisterFireteam: invalid client\n"); } leader = &g_entities[entityNum]; if (!leader->client) { G_Error("G_RegisterFireteam: attempting to register a Fireteam to an entity with no client\n"); } if (G_IsOnFireteam(entityNum, NULL)) { G_ClientPrint(entityNum, "You are already on a fireteam, leave it first"); return; } if ((ft = G_FindFreeFireteam()) == NULL) { G_ClientPrint(entityNum, "No free fireteams available"); return; } if (leader->client->sess.sessionTeam != TEAM_AXIS && leader->client->sess.sessionTeam != TEAM_ALLIES) { G_ClientPrint(entityNum, "Only players on a team can create a fireteam"); return; } count = G_CountTeamFireteams(leader->client->sess.sessionTeam); if (count >= MAX_FIRETEAMS / 2) { G_ClientPrint(entityNum, "Your team already has the maximum number of fireteams allowed"); return; } ident = G_FindFreeFireteamIdent(leader->client->sess.sessionTeam) + 1; if (ident == 0) { G_ClientPrint(entityNum, "Um, something is broken, spoink Gordon"); return; } // good to go now, i hope! ft->inuse = qtrue; memset(ft->joinOrder, -1, sizeof(level.fireTeams[0].joinOrder)); ft->joinOrder[0] = leader - g_entities; ft->ident = ident; if (g_autoFireteams.integer) { ft->priv = qfalse; trap_SendServerCommand(entityNum, "aft -1"); leader->client->pers.autofireteamEndTime = level.time + 20500; } else { ft->priv = qfalse; } #ifdef FEATURE_OMNIBOT Bot_Event_FireTeamCreated(entityNum, ft->ident); Bot_Event_JoinedFireTeam(leader - g_entities, leader); #endif G_UpdateFireteamConfigString(ft); }
/* ============== G_Script_ScriptParse Parses the script for the given entity ============== */ void G_Script_ScriptParse(gentity_t *ent) { char *pScript; char *token; qboolean wantName; qboolean inScript; int eventNum; g_script_event_t events[G_MAX_SCRIPT_STACK_ITEMS]; int numEventItems; g_script_event_t *curEvent; // DHM - Nerve :: Some of our multiplayer script commands have longer parameters //char params[MAX_QPATH]; char params[MAX_INFO_STRING]; // dhm - end g_script_stack_action_t *action; int i; int bracketLevel; qboolean buildScript; if (!ent->scriptName) { return; } if (!level.scriptEntity) { return; } buildScript = trap_Cvar_VariableIntegerValue("com_buildScript"); pScript = level.scriptEntity; wantName = qtrue; inScript = qfalse; COM_BeginParseSession("G_Script_ScriptParse"); bracketLevel = 0; numEventItems = 0; memset(events, 0, sizeof(events)); while (1) { token = COM_Parse(&pScript); if (!token[0]) { if (!wantName) { G_Error("G_Script_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine()); } break; } // end of script if (token[0] == '}') { if (inScript) { break; } if (wantName) { G_Error("G_Script_ScriptParse(), Error (line %d): '}' found, but not expected.\n", COM_GetCurrentParseLine()); } wantName = qtrue; } else if (token[0] == '{') { if (wantName) { G_Error("G_Script_ScriptParse(), Error (line %d): '{' found, NAME expected.\n", COM_GetCurrentParseLine()); } } else if (wantName) { if (!Q_stricmp(token, "bot")) { // a bot, skip this whole entry SkipRestOfLine(&pScript); // skip this section SkipBracedSection(&pScript); // continue; } if (!Q_stricmp(token, "entity")) { // this is an entity, so go back to look for a name continue; } if (!Q_stricmp(ent->scriptName, token)) { inScript = qtrue; numEventItems = 0; } wantName = qfalse; } else if (inScript) { Q_strlwr(token); eventNum = G_Script_EventForString(token); if (eventNum < 0) { G_Error("G_Script_ScriptParse(), Error (line %d): unknown event: %s.\n", COM_GetCurrentParseLine(), token); } if (numEventItems >= G_MAX_SCRIPT_STACK_ITEMS) { G_Error("G_Script_ScriptParse(), Error (line %d): G_MAX_SCRIPT_STACK_ITEMS reached (%d)\n", COM_GetCurrentParseLine(), G_MAX_SCRIPT_STACK_ITEMS); } curEvent = &events[numEventItems]; curEvent->eventNum = eventNum; memset(params, 0, sizeof(params)); // parse any event params before the start of this event's actions while ((token = COM_Parse(&pScript)) != NULL && (token[0] != '{')) { if (!token[0]) { G_Error("G_Script_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine()); } if (strlen(params)) // add a space between each param { Q_strcat(params, sizeof(params), " "); } Q_strcat(params, sizeof(params), token); } if (strlen(params)) // copy the params into the event { curEvent->params = G_Alloc(strlen(params) + 1); Q_strncpyz(curEvent->params, params, strlen(params) + 1); } // parse the actions for this event while ((token = COM_Parse(&pScript)) != NULL && (token[0] != '}')) { if (!token[0]) { G_Error("G_Script_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine()); } action = G_Script_ActionForString(token); if (!action) { G_Error("G_Script_ScriptParse(), Error (line %d): unknown action: %s.\n", COM_GetCurrentParseLine(), token); } curEvent->stack.items[curEvent->stack.numItems].action = action; memset(params, 0, sizeof(params)); // Ikkyo - Parse for {}'s if this is a set command if (!Q_stricmp(action->actionString, "set")) { token = COM_Parse(&pScript); if (token[0] != '{') { COM_ParseError("'{' expected, found: %s.\n", token); } while ((token = COM_Parse(&pScript)) && (token[0] != '}')) { if (strlen(params)) // add a space between each param { Q_strcat(params, sizeof(params), " "); } if (strrchr(token, ' ')) // need to wrap this param in quotes since it has more than one word { Q_strcat(params, sizeof(params), "\""); } Q_strcat(params, sizeof(params), token); if (strrchr(token, ' ')) // need to wrap this param in quotes since it has mor { Q_strcat(params, sizeof(params), "\""); } } } else // hackly precaching of custom characters if (!Q_stricmp(token, "spawnbot")) { // this is fairly indepth, so I'll move it to a separate function for readability G_Script_ParseSpawnbot(&pScript, params, MAX_INFO_STRING); } else { token = COM_ParseExt(&pScript, qfalse); for (i = 0; token[0]; i++) { if (strlen(params)) // add a space between each param { Q_strcat(params, sizeof(params), " "); } if (i == 0) { // Special case: playsound's need to be cached on startup to prevent in-game pauses if (!Q_stricmp(action->actionString, "playsound")) { G_SoundIndex(token); } else if (!Q_stricmp(action->actionString, "changemodel")) { G_ModelIndex(token); } else if (buildScript && ( !Q_stricmp(action->actionString, "mu_start") || !Q_stricmp(action->actionString, "mu_play") || !Q_stricmp(action->actionString, "mu_queue") || !Q_stricmp(action->actionString, "startcam")) ) { if (strlen(token)) // we know there's a [0], but don't know if it's '0' { trap_SendServerCommand(-1, va("addToBuild %s\n", token)); } } } if (i == 0 || i == 1) { if (!Q_stricmp(action->actionString, "remapshader")) { G_ShaderIndex(token); } } if (strrchr(token, ' ')) // need to wrap this param in quotes since it has more than one word { Q_strcat(params, sizeof(params), "\""); } Q_strcat(params, sizeof(params), token); if (strrchr(token, ' ')) // need to wrap this param in quotes since it has more than one word { Q_strcat(params, sizeof(params), "\""); } token = COM_ParseExt(&pScript, qfalse); } } if (strlen(params)) // copy the params into the event { curEvent->stack.items[curEvent->stack.numItems].params = G_Alloc(strlen(params) + 1); Q_strncpyz(curEvent->stack.items[curEvent->stack.numItems].params, params, strlen(params) + 1); } curEvent->stack.numItems++; if (curEvent->stack.numItems >= G_MAX_SCRIPT_STACK_ITEMS) { G_Error("G_Script_ScriptParse(): script exceeded G_MAX_SCRIPT_STACK_ITEMS (%d), line %d\n", G_MAX_SCRIPT_STACK_ITEMS, COM_GetCurrentParseLine()); } } numEventItems++; } else // skip this character completely { // TTimo gcc: suggest parentheses around assignment used as truth value while ((token = COM_Parse(&pScript)) != NULL) { if (!token[0]) { G_Error("G_Script_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine()); } else if (token[0] == '{') { bracketLevel++; } else if (token[0] == '}') { if (!--bracketLevel) { break; } } } } } // alloc and copy the events into the gentity_t for this cast if (numEventItems > 0) { ent->scriptEvents = G_Alloc(sizeof(g_script_event_t) * numEventItems); memcpy(ent->scriptEvents, events, sizeof(g_script_event_t) * numEventItems); ent->numScriptEvents = numEventItems; } }
/*QUAKED misc_model_breakable (1 0 0) (-16 -16 -16) (16 16 16) SOLID AUTOANIMATE DEADSOLID NO_DMODEL NO_SMOKE USE_MODEL USE_NOT_BREAK PLAYER_USE NO_EXPLOSION SOLID - Movement is blocked by it, if not set, can still be broken by explosions and shots if it has health AUTOANIMATE - Will cycle it's anim DEADSOLID - Stay solid even when destroyed (in case damage model is rather large). NO_DMODEL - Makes it NOT display a damage model when destroyed, even if one exists USE_MODEL - When used, will toggle to it's usemodel (model name + "_u1.md3")... this obviously does nothing if USE_NOT_BREAK is not checked USE_NOT_BREAK - Using it, doesn't make it break, still can be destroyed by damage PLAYER_USE - Player can use it with the use button NO_EXPLOSION - By default, will explode when it dies...this is your override. "model" arbitrary .md3 file to display "health" how much health to have - default is zero (not breakable) If you don't set the SOLID flag, but give it health, it can be shot but will not block NPCs or players from moving "targetname" when used, dies and displays damagemodel, if any (if not, removes itself) "target" What to use when it dies "target2" What to use when it's repaired "target3" What to use when it's used while it's broken "paintarget" target to fire when hit (but not destroyed) "count" the amount of armor/health/ammo given (default 50) "gravity" if set to 1, this will be affected by gravity "radius" Chunk code tries to pick a good volume of chunks, but you can alter this to scale the number of spawned chunks. (default 1) (.5) is half as many chunks, (2) is twice as many chunks Damage: default is none "splashDamage" - damage to do (will make it explode on death) "splashRadius" - radius for above damage "team" - This cannot take damage from members of this team: "player" "neutral" "enemy" "material" - default is "8 - MAT_NONE" - choose from this list: 0 = MAT_METAL (grey metal) 1 = MAT_GLASS 2 = MAT_ELECTRICAL (sparks only) 3 = MAT_ELEC_METAL (METAL chunks and sparks) 4 = MAT_DRK_STONE (brown stone chunks) 5 = MAT_LT_STONE (tan stone chunks) 6 = MAT_GLASS_METAL (glass and METAL chunks) 7 = MAT_METAL2 (blue/grey metal) 8 = MAT_NONE (no chunks-DEFAULT) 9 = MAT_GREY_STONE (grey colored stone) 10 = MAT_METAL3 (METAL and METAL2 chunk combo) 11 = MAT_CRATE1 (yellow multi-colored crate chunks) 12 = MAT_GRATE1 (grate chunks--looks horrible right now) 13 = MAT_ROPE (for yavin_trial, no chunks, just wispy bits ) 14 = MAT_CRATE2 (red multi-colored crate chunks) 15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout ) FIXME/TODO: set size better? multiple damage models? custom explosion effect/sound? */ void SP_misc_model_breakable( gentity_t *ent ) { char damageModel[MAX_QPATH]; char chunkModel[MAX_QPATH]; char useModel[MAX_QPATH]; int len; // Chris F. requested default for misc_model_breakable to be NONE...so don't arbitrarily change this. G_SpawnInt( "material", "8", (int*)&ent->material ); G_SpawnFloat( "radius", "1", &ent->radius ); // used to scale chunk code if desired by a designer CacheChunkEffects( ent->material ); misc_model_breakable_init( ent ); len = strlen( ent->model ) - 4; strncpy( damageModel, ent->model, len ); damageModel[len] = 0; //chop extension strncpy( chunkModel, damageModel, sizeof(chunkModel)); strncpy( useModel, damageModel, sizeof(useModel)); if (ent->takedamage) { //Dead/damaged model if( !(ent->spawnflags & 8) ) { //no dmodel strcat( damageModel, "_d1.md3" ); ent->s.modelindex2 = G_ModelIndex( damageModel ); } //Chunk model strcat( chunkModel, "_c1.md3" ); ent->s.modelindex3 = G_ModelIndex( chunkModel ); } //Use model if( ent->spawnflags & 32 ) { //has umodel strcat( useModel, "_u1.md3" ); ent->sound1to2 = G_ModelIndex( useModel ); } if ( !ent->mins[0] && !ent->mins[1] && !ent->mins[2] ) { VectorSet (ent->mins, -16, -16, -16); } if ( !ent->maxs[0] && !ent->maxs[1] && !ent->maxs[2] ) { VectorSet (ent->maxs, 16, 16, 16); } if ( ent->spawnflags & 2 ) { ent->s.eFlags |= EF_ANIM_ALLFAST; } G_SetOrigin( ent, ent->s.origin ); G_SetAngles( ent, ent->s.angles ); gi.linkentity (ent); if ( ent->spawnflags & 128 ) {//Can be used by the player's BUTTON_USE ent->svFlags |= SVF_PLAYER_USABLE; } if ( ent->team && ent->team[0] ) { ent->noDamageTeam = TranslateTeamName( ent->team ); if ( ent->noDamageTeam == TEAM_FREE ) { G_Error("team name %s not recognized\n", ent->team); } } ent->team = NULL; //HACK if ( ent->model && Q_stricmp( "models/map_objects/ships/tie_fighter.md3", ent->model ) == 0 ) {//run a think G_EffectIndex( "fighter_explosion2" ); G_SoundIndex( "sound/weapons/tie_fighter/tiepass1.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/tiepass2.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/tiepass3.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/tiepass4.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/tiepass5.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/tie_fire.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/tie_fire2.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/tie_fire3.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" ); ent->e_ThinkFunc = thinkF_TieFighterThink; ent->nextthink = level.time + FRAMETIME; } float grav = 0; G_SpawnFloat( "gravity", "0", &grav ); if ( grav ) {//affected by gravity G_SetAngles( ent, ent->s.angles ); G_SetOrigin( ent, ent->currentOrigin ); misc_model_breakable_gravity_init( ent, qtrue ); } }
/*QUAKED script_mover (0.5 0.25 1.0) ? TRIGGERSPAWN SOLID EXPLOSIVEDAMAGEONLY RESURECTABLE COMPASS ALLIED AXIS MOUNTED_GUN Scripted brush entity. A simplified means of moving brushes around based on events. "modelscale" - Scale multiplier (defaults to 1, and scales uniformly) "modelscale_vec" - Set scale per-axis. Overrides "modelscale", so if you have both the "modelscale" is ignored "model2" optional md3 to draw over the solid clip brush "scriptname" name used for scripting purposes (like aiName in AI scripting) "health" optionally make this entity damagable "description" used with health, if the entity is damagable, it draws a healthbar with this description above it. */ void SP_script_mover(gentity_t *ent) { float scale[3] = { 1, 1, 1 }; vec3_t scalevec; char tagname[MAX_QPATH]; char *modelname; char *tagent; char cs[MAX_INFO_STRING]; char *s; if (!ent->model) { G_Error("script_mover must have a \"model\"\n"); } if (!ent->scriptName) { G_Error("script_mover must have a \"scriptname\"\n"); } ent->blocked = script_mover_blocked; // first position at start VectorCopy(ent->s.origin, ent->pos1); // VectorCopy( ent->r.currentOrigin, ent->pos1 ); VectorCopy(ent->pos1, ent->pos2); // don't go anywhere just yet trap_SetBrushModel(ent, ent->model); InitMover(ent); ent->reached = NULL; ent->s.animMovetype = 0; ent->s.density = 0; if (ent->spawnflags & 256) { ent->s.density |= 2; } if (ent->spawnflags & 8) { ent->use = script_mover_use; } if (ent->spawnflags & 16) { ent->s.time2 = 1; } else { ent->s.time2 = 0; } if (ent->spawnflags & 32) { ent->s.teamNum = TEAM_ALLIES; } else if (ent->spawnflags & 64) { ent->s.teamNum = TEAM_AXIS; } else { ent->s.teamNum = TEAM_FREE; } if (ent->spawnflags & 1) { ent->use = script_mover_use; trap_UnlinkEntity(ent); // make sure it's not visible return; } G_SetAngle(ent, ent->s.angles); G_SpawnInt("health", "0", &ent->health); if (ent->health) { ent->takedamage = qtrue; ent->count = ent->health; // client needs to know about it as well ent->s.effect1Time = ent->count; ent->s.dl_intensity = 255; if (G_SpawnString("description", "", &s)) { trap_GetConfigstring(CS_SCRIPT_MOVER_NAMES, cs, sizeof(cs)); Info_SetValueForKey(cs, va("%i", ent - g_entities), s); trap_SetConfigstring(CS_SCRIPT_MOVER_NAMES, cs); } } else { ent->count = 0; } ent->die = script_mover_die; // look for general scaling if (G_SpawnFloat("modelscale", "1", &scale[0])) { scale[2] = scale[1] = scale[0]; } if (G_SpawnString("model2", "", &modelname)) { COM_StripExtension(modelname, tagname); Q_strcat(tagname, MAX_QPATH, ".tag"); ent->tagNumber = trap_LoadTag(tagname); /* if( !(ent->tagNumber = trap_LoadTag( tagname )) ) { Com_Error( ERR_DROP, "Failed to load Tag File (%s)\n", tagname ); }*/ } // look for axis specific scaling if (G_SpawnVector("modelscale_vec", "1 1 1", &scalevec[0])) { VectorCopy(scalevec, scale); } if (scale[0] != 1 || scale[1] != 1 || scale[2] != 1) { ent->s.density |= 1; // scale is stored in 'angles2' VectorCopy(scale, ent->s.angles2); } if (ent->spawnflags & 128) { ent->s.density |= 4; ent->waterlevel = 0; if (G_SpawnString("gun", "", &modelname)) { if (!Q_stricmp(modelname, "browning")) { ent->s.density |= 8; } } G_SpawnString("tagent", "", &tagent); Q_strncpyz(ent->tagBuffer, tagent, 16); ent->s.powerups = -1; } ent->think = script_mover_spawn; ent->nextthink = level.time + FRAMETIME; }
void *G_Alloc( int size ) { // Find a free block and allocate. // Does two passes, attempts to fill same-sized free slot first. struct freememnode *fmn, *prev, *next, *smallest; int allocsize, smallestsize; char *endptr; int *ptr; allocsize = ( size + sizeof( int ) + ROUNDBITS ) & ~ROUNDBITS; // Round to 32-byte boundary ptr = NULL; smallest = NULL; smallestsize = POOLSIZE + 1; // Guaranteed not to miss any slots :) for ( fmn = freehead; fmn; fmn = fmn->next ) { if ( fmn->cookie != FREEMEMCOOKIE ) { G_Error( "G_Alloc: Memory corruption detected!\n" ); } if ( fmn->size >= allocsize ) { // We've got a block if ( fmn->size == allocsize ) { // Same size, just remove prev = fmn->prev; next = fmn->next; if ( prev ) { prev->next = next; // Point previous node to next } if ( next ) { next->prev = prev; // Point next node to previous } if ( fmn == freehead ) { freehead = next; // Set head pointer to next } ptr = ( int * ) fmn; break; // Stop the loop, this is fine } else { // Keep track of the smallest free slot if ( fmn->size < smallestsize ) { smallest = fmn; smallestsize = fmn->size; } } } } if ( !ptr && smallest ) { // We found a slot big enough smallest->size -= allocsize; endptr = ( char * ) smallest + smallest->size; ptr = ( int * ) endptr; } if ( ptr ) { freemem -= allocsize; if ( g_debugAlloc.integer ) { G_Printf( "G_Alloc of %i bytes (%i left)\n", allocsize, freemem ); } memset( ptr, 0, allocsize ); *ptr++ = allocsize; // Store a copy of size for deallocation return ( ( void * ) ptr ); } G_Error( "G_Alloc: failed on allocation of %i bytes\n", size ); return ( NULL ); }
/* =========== G_SelectRandomFurthestSpawnPoint Chooses a player start, deathmatch start, etc ============ */ gentity_t *G_SelectRandomFurthestSpawnPoint( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { gentity_t *spot = NULL; vec3_t delta; float dist; float list_dist[ 64 ]; gentity_t *list_spot[ 64 ]; int numSpots, rnd, i, j; numSpots = 0; while ( ( spot = G_IterateEntitiesOfClass( spot, S_POS_PLAYER_SPAWN ) ) != NULL ) { if ( SpotWouldTelefrag( spot ) ) { continue; } VectorSubtract( spot->s.origin, avoidPoint, delta ); dist = VectorLength( delta ); for ( i = 0; i < numSpots; i++ ) { if ( dist > list_dist[ i ] ) { if ( numSpots >= 64 ) { numSpots = 64 - 1; } for ( j = numSpots; j > i; j-- ) { list_dist[ j ] = list_dist[ j - 1 ]; list_spot[ j ] = list_spot[ j - 1 ]; } list_dist[ i ] = dist; list_spot[ i ] = spot; numSpots++; if ( numSpots > 64 ) { numSpots = 64; } break; } } if ( i >= numSpots && numSpots < 64 ) { list_dist[ numSpots ] = dist; list_spot[ numSpots ] = spot; numSpots++; } } if ( !numSpots ) { spot = G_IterateEntitiesOfClass( NULL, S_POS_PLAYER_SPAWN ); if ( !spot ) { G_Error( "Couldn't find a spawn point" ); } VectorCopy( spot->s.origin, origin ); origin[ 2 ] += 9; VectorCopy( spot->s.angles, angles ); return spot; } // select a random spot from the spawn points furthest away rnd = random() * ( numSpots / 2 ); VectorCopy( list_spot[ rnd ]->s.origin, origin ); origin[ 2 ] += 9; VectorCopy( list_spot[ rnd ]->s.angles, angles ); return list_spot[ rnd ]; }
/* =============== G_ScriptAction_GotoMarker syntax: gotomarker <targetname> <speed> [accel/deccel] [turntotarget] [wait] NOTE: speed may be modified to round the duration to the next 50ms for smooth transitions =============== */ qboolean G_ScriptAction_GotoMarker( gentity_t *ent, char *params ) { char *pString, *token; gentity_t *target; vec3_t vec; float speed, dist; qboolean wait = qfalse, turntotarget = qfalse; int trType; int duration, i; vec3_t diff; vec3_t angles; if ( params && ( ent->scriptStatus.scriptFlags & SCFL_GOING_TO_MARKER ) ) { // we can't process a new movement until the last one has finished return qfalse; } if ( !params || ent->scriptStatus.scriptStackChangeTime < level.time ) { // we are waiting for it to reach destination if ( ent->s.pos.trTime + ent->s.pos.trDuration <= level.time ) { // we made it ent->scriptStatus.scriptFlags &= ~SCFL_GOING_TO_MARKER; // set the angles at the destination BG_EvaluateTrajectory( &ent->s.apos, ent->s.apos.trTime + ent->s.apos.trDuration, ent->s.angles ); VectorCopy( ent->s.angles, ent->s.apos.trBase ); VectorCopy( ent->s.angles, ent->r.currentAngles ); ent->s.apos.trTime = level.time; ent->s.apos.trDuration = 0; ent->s.apos.trType = TR_STATIONARY; VectorClear( ent->s.apos.trDelta ); // stop moving BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->s.origin ); VectorCopy( ent->s.origin, ent->s.pos.trBase ); VectorCopy( ent->s.origin, ent->r.currentOrigin ); ent->s.pos.trTime = level.time; ent->s.pos.trDuration = 0; ent->s.pos.trType = TR_STATIONARY; VectorClear( ent->s.pos.trDelta ); script_linkentity( ent ); return qtrue; } } else { // we have just started this command pString = params; token = COM_ParseExt( &pString, qfalse ); if ( !token[0] ) { G_Error( "G_Scripting: gotomarker must have an targetname\n" ); } // find the entity with the given "targetname" target = G_Find( NULL, FOFS( targetname ), token ); if ( !target ) { G_Error( "G_Scripting: can't find entity with \"targetname\" = \"%s\"\n", token ); } VectorSubtract( target->r.currentOrigin, ent->r.currentOrigin, vec ); token = COM_ParseExt( &pString, qfalse ); if ( !token[0] ) { G_Error( "G_Scripting: gotomarker must have a speed\n" ); } speed = atof( token ); trType = TR_LINEAR_STOP; while ( token[0] ) { token = COM_ParseExt( &pString, qfalse ); if ( token[0] ) { if ( !Q_stricmp( token, "accel" ) ) { trType = TR_ACCELERATE; } else if ( !Q_stricmp( token, "deccel" ) ) { trType = TR_DECCELERATE; } else if ( !Q_stricmp( token, "wait" ) ) { wait = qtrue; } else if ( !Q_stricmp( token, "turntotarget" ) ) { turntotarget = qtrue; } } } // start the movement if ( ent->s.eType == ET_MOVER ) { VectorCopy( vec, ent->movedir ); VectorCopy( ent->r.currentOrigin, ent->pos1 ); VectorCopy( target->r.currentOrigin, ent->pos2 ); ent->speed = speed; dist = VectorDistance( ent->pos1, ent->pos2 ); // setup the movement with the new parameters InitMover( ent ); // start the movement SetMoverState( ent, MOVER_1TO2, level.time ); if ( trType != TR_LINEAR_STOP ) { // allow for acceleration/decceleration ent->s.pos.trDuration = 1000.0 * dist / ( speed / 2.0 ); ent->s.pos.trType = trType; } ent->reached = NULL; if ( turntotarget ) { duration = ent->s.pos.trDuration; VectorCopy( target->s.angles, angles ); for ( i = 0; i < 3; i++ ) { diff[i] = AngleDifference( angles[i], ent->s.angles[i] ); while ( diff[i] > 180 ) diff[i] -= 360; while ( diff[i] < -180 ) diff[i] += 360; } VectorCopy( ent->s.angles, ent->s.apos.trBase ); if ( duration ) { VectorScale( diff, 1000.0 / (float)duration, ent->s.apos.trDelta ); } else { VectorClear( ent->s.apos.trDelta ); } ent->s.apos.trDuration = duration; ent->s.apos.trTime = level.time; ent->s.apos.trType = TR_LINEAR_STOP; if ( trType != TR_LINEAR_STOP ) { // allow for acceleration/decceleration ent->s.pos.trDuration = 1000.0 * dist / ( speed / 2.0 ); ent->s.pos.trType = trType; } } } else { // calculate the trajectory ent->s.pos.trType = TR_LINEAR_STOP; ent->s.pos.trTime = level.time; VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); dist = VectorNormalize( vec ); VectorScale( vec, speed, ent->s.pos.trDelta ); ent->s.pos.trDuration = 1000 * ( dist / speed ); if ( turntotarget ) { duration = ent->s.pos.trDuration; VectorCopy( target->s.angles, angles ); for ( i = 0; i < 3; i++ ) { diff[i] = AngleDifference( angles[i], ent->s.angles[i] ); while ( diff[i] > 180 ) diff[i] -= 360; while ( diff[i] < -180 ) diff[i] += 360; } VectorCopy( ent->s.angles, ent->s.apos.trBase ); if ( duration ) { VectorScale( diff, 1000.0 / (float)duration, ent->s.apos.trDelta ); } else { VectorClear( ent->s.apos.trDelta ); } ent->s.apos.trDuration = duration; ent->s.apos.trTime = level.time; ent->s.apos.trType = TR_LINEAR_STOP; } } if ( !wait ) { // round the duration to the next 50ms if ( ent->s.pos.trDuration % 50 ) { float frac; frac = (float)( ( ( ent->s.pos.trDuration / 50 ) * 50 + 50 ) - ent->s.pos.trDuration ) / (float)( ent->s.pos.trDuration ); if ( frac < 1 ) { VectorScale( ent->s.pos.trDelta, 1.0 / ( 1.0 + frac ), ent->s.pos.trDelta ); ent->s.pos.trDuration = ( ent->s.pos.trDuration / 50 ) * 50 + 50; } } // set the goto flag, so we can keep processing the move until we reach the destination ent->scriptStatus.scriptFlags |= SCFL_GOING_TO_MARKER; return qtrue; // continue to next command } } BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin ); BG_EvaluateTrajectory( &ent->s.apos, level.time, ent->r.currentAngles ); script_linkentity( ent ); return qfalse; }
// The only way a client should be removed from a fireteam void G_RemoveClientFromFireteams(int entityNum, qboolean update, qboolean print) { fireteamData_t *ft; int i; if ((entityNum < 0 || entityNum >= MAX_CLIENTS) || !g_entities[entityNum].client) { G_Error("G_RemoveClientFromFireteams: invalid client\n"); } if (G_IsOnFireteam(entityNum, &ft)) { int j, firstHuman; for (i = 0; i < MAX_FIRETEAM_MEMBERS && i < g_maxclients.integer; ++i) { if (ft->joinOrder[i] == entityNum) { if (i == 0) { if (ft->joinOrder[1] == -1) { ft->inuse = qfalse; ft->ident = -1; } else { // core: only bots left in the fireteam? we disband the fireteam.. if (G_OnlyBotsInFireteam(ft, entityNum, &firstHuman)) { // empty the fireteam for (j = 0; j < g_maxclients.integer - 1; j++) { #ifdef FEATURE_OMNIBOT Bot_Event_LeftFireTeam(ft->joinOrder[j]); #endif ft->joinOrder[j] = -1; } ft->inuse = qfalse; ft->ident = -1; G_UpdateFireteamConfigString(ft); return; } else { // core: a bot as FT-leader? we try to pick a human.. if (g_entities[(int)(ft->joinOrder[1])].r.svFlags & SVF_BOT) { if (firstHuman != -1) { // Swap first human with first bot.. int tmp = ft->joinOrder[1]; ft->joinOrder[1] = ft->joinOrder[firstHuman]; ft->joinOrder[firstHuman] = tmp; } } else { firstHuman = 1; } // Inform client of promotion to leader if (firstHuman != -1) { trap_SendServerCommand(ft->joinOrder[firstHuman], "cpm \"You are now the leader of your fireteam\"\n"); } } } } for (j = i; j < g_maxclients.integer - 1; j++) { ft->joinOrder[j] = ft->joinOrder[j + 1]; } ft->joinOrder[g_maxclients.integer - 1] = -1; break; } } } else { return; } #ifdef FEATURE_OMNIBOT Bot_Event_LeftFireTeam(entityNum); #endif if (print) { for (i = 0; i < MAX_CLIENTS; i++) { if (ft->joinOrder[i] == -1) { break; } trap_SendServerCommand(ft->joinOrder[i], va("cpm \"%s ^7has left the Fireteam\"\n", level.clients[entityNum].pers.netname)); } } if (update) { G_UpdateFireteamConfigString(ft); } }
/* ================= G_ScriptAction_Accum syntax: accum <buffer_index> <command> <paramater> Commands: accum <n> inc <m> accum <n> abort_if_less_than <m> accum <n> abort_if_greater_than <m> accum <n> abort_if_not_equal <m> accum <n> abort_if_equal <m> accum <n> set <m> accum <n> random <m> accum <n> bitset <m> accum <n> bitreset <m> accum <n> abort_if_bitset <m> accum <n> abort_if_not_bitset <m> ================= */ qboolean G_ScriptAction_Accum( gentity_t *ent, char *params ) { char *pString, *token, lastToken[MAX_QPATH]; int bufferIndex; pString = params; token = COM_ParseExt( &pString, qfalse ); if ( !token[0] ) { G_Error( "G_Scripting: accum without a buffer index\n" ); } bufferIndex = atoi( token ); if ( bufferIndex >= G_MAX_SCRIPT_ACCUM_BUFFERS ) { G_Error( "G_Scripting: accum buffer is outside range (0 - %i)\n", G_MAX_SCRIPT_ACCUM_BUFFERS ); } token = COM_ParseExt( &pString, qfalse ); if ( !token[0] ) { G_Error( "G_Scripting: accum without a command\n" ); } Q_strncpyz( lastToken, token, sizeof( lastToken ) ); token = COM_ParseExt( &pString, qfalse ); if ( !Q_stricmp( lastToken, "inc" ) ) { if ( !token[0] ) { G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); } ent->scriptAccumBuffer[bufferIndex] += atoi( token ); } else if ( !Q_stricmp( lastToken, "abort_if_less_than" ) ) { if ( !token[0] ) { G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); } if ( ent->scriptAccumBuffer[bufferIndex] < atoi( token ) ) { // abort the current script ent->scriptStatus.scriptStackHead = ent->scriptEvents[ent->scriptStatus.scriptEventIndex].stack.numItems; } } else if ( !Q_stricmp( lastToken, "abort_if_greater_than" ) ) { if ( !token[0] ) { G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); } if ( ent->scriptAccumBuffer[bufferIndex] > atoi( token ) ) { // abort the current script ent->scriptStatus.scriptStackHead = ent->scriptEvents[ent->scriptStatus.scriptEventIndex].stack.numItems; } } else if ( !Q_stricmp( lastToken, "abort_if_not_equal" ) ) { if ( !token[0] ) { G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); } if ( ent->scriptAccumBuffer[bufferIndex] != atoi( token ) ) { // abort the current script ent->scriptStatus.scriptStackHead = ent->scriptEvents[ent->scriptStatus.scriptEventIndex].stack.numItems; } } else if ( !Q_stricmp( lastToken, "abort_if_equal" ) ) { if ( !token[0] ) { G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); } if ( ent->scriptAccumBuffer[bufferIndex] == atoi( token ) ) { // abort the current script ent->scriptStatus.scriptStackHead = ent->scriptEvents[ent->scriptStatus.scriptEventIndex].stack.numItems; } } else if ( !Q_stricmp( lastToken, "bitset" ) ) { if ( !token[0] ) { G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); } ent->scriptAccumBuffer[bufferIndex] |= ( 1 << atoi( token ) ); } else if ( !Q_stricmp( lastToken, "bitreset" ) ) { if ( !token[0] ) { G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); } ent->scriptAccumBuffer[bufferIndex] &= ~( 1 << atoi( token ) ); } else if ( !Q_stricmp( lastToken, "abort_if_bitset" ) ) { if ( !token[0] ) { G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); } if ( ent->scriptAccumBuffer[bufferIndex] & ( 1 << atoi( token ) ) ) { // abort the current script ent->scriptStatus.scriptStackHead = ent->scriptEvents[ent->scriptStatus.scriptEventIndex].stack.numItems; } } else if ( !Q_stricmp( lastToken, "abort_if_not_bitset" ) ) { if ( !token[0] ) { G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); } if ( !( ent->scriptAccumBuffer[bufferIndex] & ( 1 << atoi( token ) ) ) ) { // abort the current script ent->scriptStatus.scriptStackHead = ent->scriptEvents[ent->scriptStatus.scriptEventIndex].stack.numItems; } } else if ( !Q_stricmp( lastToken, "set" ) ) { if ( !token[0] ) { G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); } ent->scriptAccumBuffer[bufferIndex] = atoi( token ); } else if ( !Q_stricmp( lastToken, "random" ) ) { if ( !token[0] ) { G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); } ent->scriptAccumBuffer[bufferIndex] = rand() % atoi( token ); } else { G_Error( "Scripting: accum: \"%s\": unknown command\n", params ); } return qtrue; }
/* ====================== CG_ParseAnimationFile Read a configuration file containing animation coutns and rates models/players/visor/animation.cfg, etc ====================== */ qboolean G_ParseAnimationFile( const char *af_filename ) { const char *text_p; int len; int i; const char *token; float fps; int skip; char text[40000]; int animNum; animation_t *animations = level.knownAnimFileSets[level.numKnownAnimFileSets].animations; len = gi.RE_GetAnimationCFG(af_filename, NULL, 0); if (len <= 0) { return qfalse; } if ( len <= 0 ) { return qfalse; } if ( len >= sizeof( text ) - 1 ) { G_Error( "G_ParseAnimationFile: File %s too long\n (%d > %d)", af_filename, len, sizeof( text ) - 1); return qfalse; } len = gi.RE_GetAnimationCFG(af_filename, text, sizeof(text)); // parse the text text_p = text; skip = 0; // quiet the compiler warning //FIXME: have some way of playing anims backwards... negative numFrames? //initialize anim array so that from 0 to MAX_ANIMATIONS, set default values of 0 1 0 100 for(i = 0; i < MAX_ANIMATIONS; i++) { animations[i].firstFrame = 0; animations[i].numFrames = 0; animations[i].loopFrames = -1; animations[i].frameLerp = 100; animations[i].initialLerp = 100; } // read information for each frame while(1) { token = COM_Parse( &text_p ); if ( !token || !token[0]) { break; } animNum = GetIDForString(animTable, token); if(animNum == -1) { //#ifndef FINAL_BUILD #ifdef _DEBUG Com_Printf(S_COLOR_RED"WARNING: Unknown token %s in %s\n", token, af_filename); #endif continue; } token = COM_Parse( &text_p ); if ( !token ) { break; } animations[animNum].firstFrame = atoi( token ); token = COM_Parse( &text_p ); if ( !token ) { break; } animations[animNum].numFrames = atoi( token ); token = COM_Parse( &text_p ); if ( !token ) { break; } animations[animNum].loopFrames = atoi( token ); token = COM_Parse( &text_p ); if ( !token ) { break; } fps = atof( token ); if ( fps == 0 ) { fps = 1;//Don't allow divide by zero error } if ( fps < 0 ) {//backwards animations[animNum].frameLerp = floor(1000.0f / fps); } else { animations[animNum].frameLerp = ceil(1000.0f / fps); } animations[animNum].initialLerp = ceil(1000.0f / fabs(fps)); } #ifdef CONVENIENT_ANIMATION_FILE_DEBUG_THING if (strstr(af_filename, "humanoid")) { SpewDebugStuffToFile(animations); } #endif return qtrue; }
void teleport_touch() { gedict_t *t; vec3_t org; if ( self->s.v.targetname ) { if ( self->s.v.nextthink < g_globalvars.time ) { return; // not fired yet } } if ( ( int ) ( self->s.v.spawnflags ) & PLAYER_ONLY ) { if ( strneq( other->s.v.classname, "player" ) ) return; } // only teleport living creatures if ( other->s.v.health <= 0 || other->s.v.solid != SOLID_SLIDEBOX ) return; // activator = other; SUB_UseTargets(); //put a tfog where the player was spawn_tfog( other->s.v.origin ); t = find( world, FOFS( s.v.targetname ), self->s.v.target ); if ( !t ) G_Error( "couldn't find target" ); // spawn a tfog flash in front of the destination makevectors( t->mangle ); org[0] = t->s.v.origin[0] + 32 * g_globalvars.v_forward[0]; org[1] = t->s.v.origin[1] + 32 * g_globalvars.v_forward[1]; org[2] = t->s.v.origin[2] + 32 * g_globalvars.v_forward[2]; spawn_tfog( org ); spawn_tdeath( t->s.v.origin, other ); // move the player and lock him down for a little while if ( !other->s.v.health ) { VectorCopy( t->s.v.origin, other->s.v.origin ); other->s.v.velocity[0] = ( g_globalvars.v_forward[0] * other->s.v.velocity[0] ) + ( g_globalvars.v_forward[0] * other->s.v.velocity[1] ); other->s.v.velocity[1] = ( g_globalvars.v_forward[1] * other->s.v.velocity[0] ) + ( g_globalvars.v_forward[1] * other->s.v.velocity[1] ); other->s.v.velocity[2] = ( g_globalvars.v_forward[2] * other->s.v.velocity[0] ) + ( g_globalvars.v_forward[2] * other->s.v.velocity[1] ); //other->s.v.velocity = (v_forward * other->s.v.velocity[0]) + (v_forward * other->s.v.velocity[1]); return; } setorigin( other, PASSVEC3( t->s.v.origin ) ); VectorCopy( t->mangle, other->s.v.angles ); // other.angles = t.mangle; if ( streq( other->s.v.classname, "player" ) ) { other->s.v.fixangle = 1; // turn this way immediately other->s.v.teleport_time = g_globalvars.time + 0.7; if ( ( int ) other->s.v.flags & FL_ONGROUND ) other->s.v.flags = other->s.v.flags - FL_ONGROUND; VectorScale( g_globalvars.v_forward, 300, other->s.v.velocity ); // other->s.v.velocity = v_forward * 300; } other->s.v.flags -= ( int ) other->s.v.flags & FL_ONGROUND; }
/* =============== RespawnItem =============== */ void RespawnItem( gentity_t *ent ) { // select from teamed entities if (ent->team) { gentity_t *master; int count; int choice; if ( !ent->teammaster ) { G_Error( "RespawnItem: bad teammaster"); } if( ent->spawnflags & 2) // randomly select from the group { master = ent->teammaster; for (count = 0, ent = master; ent; ent = ent->teamchain, count++) ; choice = rand() % count; for (count = 0, ent = master; count < choice; ent = ent->teamchain, count++) ; } else // loop through the group { if(ent->teamchain) ent = ent->teamchain; else ent = ent->teammaster; } } if(ent->team) { if ( !ent->teammaster ) G_Error( "RespawnItem: bad teammaster"); } ent->r.contents = CONTENTS_TRIGGER; ent->s.eFlags &= ~EF_NODRAW; ent->r.svFlags &= ~SVF_NOCLIENT; trap_LinkEntity (ent); if ( ent->item->giType == IT_POWERUP ) { // play powerup spawn sound to all clients gentity_t *te; // if the powerup respawn sound should Not be global if (ent->speed) { te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND ); } else { te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND ); } te->s.eventParm = G_SoundIndex( "sounds/items/powerup_respawn" ); te->r.svFlags |= SVF_BROADCAST; } // play the normal respawn sound only to nearby clients G_AddEvent( ent, EV_ITEM_RESPAWN, 0 ); ent->nextthink = 0; }
/*QUAKED misc_model_breakable (1 0 0) (-16 -16 -16) (16 16 16) SOLID AUTOANIMATE DEADSOLID NO_DMODEL NO_SMOKE USE_MODEL USE_NOT_BREAK PLAYER_USE NO_EXPLOSION START_OFF SOLID - Movement is blocked by it, if not set, can still be broken by explosions and shots if it has health AUTOANIMATE - Will cycle it's anim DEADSOLID - Stay solid even when destroyed (in case damage model is rather large). NO_DMODEL - Makes it NOT display a damage model when destroyed, even if one exists USE_MODEL - When used, will toggle to it's usemodel (model + "_u1.md3")... this obviously does nothing if USE_NOT_BREAK is not checked USE_NOT_BREAK - Using it, doesn't make it break, still can be destroyed by damage PLAYER_USE - Player can use it with the use button NO_EXPLOSION - By default, will explode when it dies...this is your override. START_OFF - Will start off and will not appear until used. "model" arbitrary .md3 file to display "modelscale" "x" uniform scale "modelscale_vec" "x y z" scale model in each axis - height, width and length - bbox will scale with it "health" how much health to have - default is zero (not breakable) If you don't set the SOLID flag, but give it health, it can be shot but will not block NPCs or players from moving "targetname" when used, dies and displays damagemodel (model + "_d1.md3"), if any (if not, removes itself) "target" What to use when it dies "target2" What to use when it's repaired "target3" What to use when it's used while it's broken "paintarget" target to fire when hit (but not destroyed) "count" the amount of armor/health/ammo given (default 50) "radius" Chunk code tries to pick a good volume of chunks, but you can alter this to scale the number of spawned chunks. (default 1) (.5) is half as many chunks, (2) is twice as many chunks "NPC_targetname" - Only the NPC with this name can damage this "forcevisible" - When you turn on force sight (any level), you can see these draw through the entire level... "redCrosshair" - crosshair turns red when you look at this "gravity" if set to 1, this will be affected by gravity "throwtarget" if set (along with gravity), this thing, when used, will throw itself at the entity whose targetname matches this string "mass" if gravity is on, this determines how much damage this thing does when it hits someone. Default is the size of the object from one corner to the other, that works very well. Only override if this is an object whose mass should be very high or low for it's size (density) Damage: default is none "splashDamage" - damage to do (will make it explode on death) "splashRadius" - radius for above damage "team" - This cannot take damage from members of this team: "player" "neutral" "enemy" "material" - default is "8 - MAT_NONE" - choose from this list: 0 = MAT_METAL (grey metal) 1 = MAT_GLASS 2 = MAT_ELECTRICAL (sparks only) 3 = MAT_ELEC_METAL (METAL chunks and sparks) 4 = MAT_DRK_STONE (brown stone chunks) 5 = MAT_LT_STONE (tan stone chunks) 6 = MAT_GLASS_METAL (glass and METAL chunks) 7 = MAT_METAL2 (blue/grey metal) 8 = MAT_NONE (no chunks-DEFAULT) 9 = MAT_GREY_STONE (grey colored stone) 10 = MAT_METAL3 (METAL and METAL2 chunk combo) 11 = MAT_CRATE1 (yellow multi-colored crate chunks) 12 = MAT_GRATE1 (grate chunks--looks horrible right now) 13 = MAT_ROPE (for yavin_trial, no chunks, just wispy bits ) 14 = MAT_CRATE2 (red multi-colored crate chunks) 15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout ) */ void SP_misc_model_breakable( gentity_t *ent ) { char damageModel[MAX_QPATH]; char chunkModel[MAX_QPATH]; char useModel[MAX_QPATH]; int len; qboolean bHasScale; float grav = 0; //[CoOp] int forceVisible = 0; //[/CoOp] int redCrosshair = 0; // Chris F. requested default for misc_model_breakable to be NONE...so don't arbitrarily change this. G_SpawnInt( "material", "8", (int*)&ent->material ); G_SpawnFloat( "radius", "1", &ent->radius ); // used to scale chunk code if desired by a designer bHasScale = G_SpawnVector("modelscale_vec", "0 0 0", ent->modelScale); if (!bHasScale) { float temp; G_SpawnFloat( "modelscale", "0", &temp); if (temp != 0.0f) { ent->modelScale[ 0 ] = ent->modelScale[ 1 ] = ent->modelScale[ 2 ] = temp; bHasScale = qtrue; } } CacheChunkEffects( ent->material ); misc_model_breakable_init( ent ); len = strlen( ent->model ) - 4; assert(ent->model[len]=='.');//we're expecting ".md3" strncpy( damageModel, ent->model, sizeof(damageModel) ); damageModel[len] = 0; //chop extension strncpy( chunkModel, damageModel, sizeof(chunkModel)); strncpy( useModel, damageModel, sizeof(useModel)); if (ent->takedamage) { //Dead/damaged model if( !(ent->spawnflags & 8) ) { //no dmodel strcat( damageModel, "_d1.md3" ); ent->s.modelindex2 = G_ModelIndex( damageModel ); } /* RAFIXME - add modelindex3 //Chunk model strcat( chunkModel, "_c1.md3" ); ent->s.modelindex3 = G_ModelIndex( chunkModel ); */ } //Use model if( ent->spawnflags & 32 ) { //has umodel strcat( useModel, "_u1.md3" ); ent->sound1to2 = G_ModelIndex( useModel ); } G_SpawnVector("mins", "-16 -16 -16", ent->r.mins); G_SpawnVector("maxs", "16 16 16", ent->r.maxs); /* This code wasn't working right if ( !ent->r.mins[0] && !ent->r.mins[1] && !ent->r.mins[2] ) { VectorSet (ent->r.mins, -16, -16, -16); } if ( !ent->r.maxs[0] && !ent->r.maxs[1] && !ent->r.maxs[2] ) { VectorSet (ent->r.maxs, 16, 16, 16); } */ // Scale up the tie-bomber bbox a little. if ( ent->model && Q_stricmp( "models/map_objects/ships/tie_bomber.md3", ent->model ) == 0 ) { VectorSet (ent->r.mins, -80, -80, -80); VectorSet (ent->r.maxs, 80, 80, 80); //ent->s.modelScale[ 0 ] = ent->s.modelScale[ 1 ] = ent->s.modelScale[ 2 ] *= 2.0f; //bHasScale = qtrue; } if (bHasScale) { float oldMins2; //scale the x axis of the bbox up. ent->r.maxs[0] *= ent->modelScale[0];//*scaleFactor; ent->r.mins[0] *= ent->modelScale[0];//*scaleFactor; //scale the y axis of the bbox up. ent->r.maxs[1] *= ent->modelScale[1];//*scaleFactor; ent->r.mins[1] *= ent->modelScale[1];//*scaleFactor; //scale the z axis of the bbox up and adjust origin accordingly ent->r.maxs[2] *= ent->modelScale[2]; oldMins2 = ent->r.mins[2]; ent->r.mins[2] *= ent->modelScale[2]; ent->s.origin[2] += (oldMins2-ent->r.mins[2]); } if ( ent->spawnflags & 2 ) { ent->s.eFlags |= EF_ANIM_ALLFAST; } G_SetOrigin( ent, ent->s.origin ); G_SetAngles( ent, ent->s.angles ); trap_LinkEntity(ent); if ( ent->spawnflags & 128 ) {//Can be used by the player's BUTTON_USE ent->r.svFlags |= SVF_PLAYER_USABLE; } if ( ent->team && ent->team[0] ) { ent->teamnodmg= (team_t)GetIDForString( TeamTable, ent->team ); if ( ent->teamnodmg == TEAM_FREE ) { G_Error("team name %s not recognized\n", ent->team); } } ent->team = NULL; //HACK if ( ent->model && Q_stricmp( "models/map_objects/ships/x_wing_nogear.md3", ent->model ) == 0 ) { if( ent->splashDamage > 0 && ent->splashRadius > 0 ) { ent->s.loopSound = G_SoundIndex( "sound/vehicles/x-wing/loop.wav" ); /* RAFIXME - impliment this flag? ent->s.eFlags |= EF_LESS_ATTEN; */ } } else if ( ent->model && Q_stricmp( "models/map_objects/ships/tie_fighter.md3", ent->model ) == 0 ) {//run a think G_EffectIndex( "explosions/fighter_explosion2" ); G_SoundIndex( "sound/weapons/tie_fighter/tiepass1.wav" ); /* G_SoundIndex( "sound/weapons/tie_fighter/tiepass2.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/tiepass3.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/tiepass4.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/tiepass5.wav" );*/ G_SoundIndex( "sound/weapons/tie_fighter/tie_fire.wav" ); /* G_SoundIndex( "sound/weapons/tie_fighter/tie_fire2.wav" ); G_SoundIndex( "sound/weapons/tie_fighter/tie_fire3.wav" );*/ G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" ); //RAFIXME - add Tie Fighter weapon? //RegisterItem( BG_FindItemForWeapon( WP_TIE_FIGHTER )); /* RAFIXME - impliment this flag? ent->s.eFlags |= EF_LESS_ATTEN; */ if( ent->splashDamage > 0 && ent->splashRadius > 0 ) { // Yeah, I could have just made this value changable from the editor, but I // need it immediately! float light; vec3_t color; qboolean lightSet, colorSet; ent->s.loopSound = G_SoundIndex( "sound/vehicles/tie-bomber/loop.wav" ); //ent->e_ThinkFunc = thinkF_TieFighterThink; //ent->e_UseFunc = thinkF_TieFighterThink; //ent->nextthink = level.time + FRAMETIME; //RAFIXME: create this use //ent->use = useF_TieFighterUse; // if the "color" or "light" keys are set, setup constantLight lightSet = qtrue;//G_SpawnFloat( "light", "100", &light ); light = 255; //colorSet = "1 1 1"//G_SpawnVector( "color", "1 1 1", color ); colorSet = qtrue; color[0] = 1; color[1] = 1; color[2] = 1; if ( lightSet || colorSet ) { int r, g, b, i; r = color[0] * 255; if ( r > 255 ) { r = 255; } g = color[1] * 255; if ( g > 255 ) { g = 255; } b = color[2] * 255; if ( b > 255 ) { b = 255; } i = light / 4; if ( i > 255 ) { i = 255; } ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 ); } } } else if ( ent->model && Q_stricmp( "models/map_objects/ships/tie_bomber.md3", ent->model ) == 0 ) { G_EffectIndex( "ships/tiebomber_bomb_falling" ); G_EffectIndex( "ships/tiebomber_explosion2" ); G_EffectIndex( "explosions/fighter_explosion2" ); G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" ); //RAFIXME: create this function //ent->e_ThinkFunc = thinkF_TieBomberThink; ent->nextthink = level.time + FRAMETIME; ent->attackDebounceTime = level.time + 1000; // We only take damage from a heavy weapon class missiles. ent->flags |= FL_DMG_BY_HEAVY_WEAP_ONLY; ent->s.loopSound = G_SoundIndex( "sound/vehicles/tie-bomber/loop.wav" ); //RAFIXME: create this //ent->s.eFlags |= EF_LESS_ATTEN; } G_SpawnFloat( "gravity", "0", &grav ); if ( grav ) {//affected by gravity G_SetAngles( ent, ent->s.angles ); G_SetOrigin( ent, ent->r.currentOrigin ); G_SpawnString( "throwtarget", NULL, &ent->target4 ); // used to throw itself at something misc_model_breakable_gravity_init( ent, qtrue ); } // Start off. if ( ent->spawnflags & 4096 ) { //RAFIXME - need to fix this guy somehow //ent->spawnContents = ent->contents; // It Navs can temporarly turn it "on" ent->s.solid = 0; ent->r.contents = 0; ent->clipmask = 0; ent->r.svFlags |= SVF_NOCLIENT; ent->s.eFlags |= EF_NODRAW; ent->count = 0; } G_SpawnInt( "forcevisible", "0", &forceVisible ); if ( forceVisible ) {//can see these through walls with force sight, so must be broadcast //[CoOp] ent->r.svFlags |= SVF_BROADCAST; //RAFIXME - impliment this flag ent->s.eFlags |= EF_FORCE_VISIBLE; //[/CoOp] } G_SpawnInt( "redCrosshair", "0", &redCrosshair ); if ( redCrosshair ) {//can see these through walls with force sight, so must be broadcast //RAFIXME - impliment this //ent->flags |= FL_RED_CROSSHAIR; } }
/* =============== RegisterItem The item will be added to the precache list =============== */ void RegisterItem( gitem_t *item ) { if ( !item ) { G_Error( "RegisterItem: NULL" ); } itemRegistered[ item - bg_itemlist ] = qtrue; }
void SP_trigger_objective_info(gentity_t *ent) { char *scorestring; char *customimage; int cix, cia, objflags; if (!ent->track) { G_Error("'trigger_objective_info' does not have a 'track' \n"); } if ((ent->spawnflags & MESSAGE_OVERRIDE) && !ent->spawnitem) { G_Error("'trigger_objective_info' has override flag set but no override text\n"); } // Gordon: for specifying which commandmap objectives this entity "belongs" to G_SpawnInt("objflags", "0", &objflags); if (G_SpawnString("customimage", "", &customimage)) { cix = cia = G_ShaderIndex(customimage); } else { if (G_SpawnString("customaxisimage", "", &customimage)) { cix = G_ShaderIndex(customimage); } else { cix = 0; } if (G_SpawnString("customalliesimage", "", &customimage)) { cia = G_ShaderIndex(customimage); } else if (G_SpawnString("customalliedimage", "", &customimage)) { cia = G_ShaderIndex(customimage); } else { cia = 0; } } G_SetConfigStringValue(CS_OID_DATA + level.numOidTriggers, "e", va("%d", (int)(ent - g_entities))); G_SetConfigStringValue(CS_OID_DATA + level.numOidTriggers, "o", va("%i", objflags)); G_SetConfigStringValue(CS_OID_DATA + level.numOidTriggers, "cix", va("%i", cix)); G_SetConfigStringValue(CS_OID_DATA + level.numOidTriggers, "cia", va("%i", cia)); G_SetConfigStringValue(CS_OID_DATA + level.numOidTriggers, "s", va("%i", ent->spawnflags)); G_SetConfigStringValue(CS_OID_DATA + level.numOidTriggers, "n", ent->message ? ent->message : ""); if (level.numOidTriggers >= MAX_OID_TRIGGERS) { G_Error("Exceeded maximum number of 'trigger_objective_info' entities\n"); } // JPW NERVE -- if this trigger has a "score" field set, then blowing up an objective // inside of this field will add "score" to the right player team. storing this // in ent->accuracy since that's unused. G_SpawnString("score", "0", &scorestring); ent->accuracy = atof(scorestring); trap_SetConfigstring(CS_OID_TRIGGERS + level.numOidTriggers, ent->track); InitTrigger(ent); if (ent->s.origin[0] || ent->s.origin[1] || ent->s.origin[2]) { G_SetConfigStringValue(CS_OID_DATA + level.numOidTriggers, "x", va("%i", (int)ent->s.origin[0])); G_SetConfigStringValue(CS_OID_DATA + level.numOidTriggers, "y", va("%i", (int)ent->s.origin[1])); G_SetConfigStringValue(CS_OID_DATA + level.numOidTriggers, "z", va("%i", (int)ent->s.origin[2])); } else { vec3_t mid; VectorAdd(ent->r.absmin, ent->r.absmax, mid); VectorScale(mid, 0.5f, mid); G_SetConfigStringValue(CS_OID_DATA + level.numOidTriggers, "x", va("%i", (int)mid[0])); G_SetConfigStringValue(CS_OID_DATA + level.numOidTriggers, "y", va("%i", (int)mid[1])); G_SetConfigStringValue(CS_OID_DATA + level.numOidTriggers, "z", va("%i", (int)mid[2])); } ent->s.teamNum = level.numOidTriggers++; // unlike other triggers, we need to send this one to the client ent->r.svFlags &= ~SVF_NOCLIENT; ent->s.eType = ET_OID_TRIGGER; if (!ent->target) { // no target - just link and go trap_LinkEntity(ent); } else { // Arnout: finalize spawing on fourth frame to allow for proper linking with targets ent->nextthink = level.time + (3 * FRAMETIME); ent->think = Think_SetupObjectiveInfo; } }
/* =========== ClientSpawn Called every time a client is placed fresh in the world: after the first ClientBegin, and after each respawn Initializes all non-persistant parts of playerState ============ */ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles ) { int index; vec3_t spawn_origin, spawn_angles; gclient_t *client; int i; clientPersistant_t saved; clientSession_t savedSess; int persistant[ MAX_PERSISTANT ]; gentity_t *spawnPoint = NULL; int flags; int savedPing; int teamLocal; int eventSequence; char userinfo[ MAX_INFO_STRING ]; vec3_t up = { 0.0f, 0.0f, 1.0f }; int maxAmmo, maxClips; weapon_t weapon; index = ent - g_entities; client = ent->client; teamLocal = client->pers.teamSelection; //if client is dead and following teammate, stop following before spawning if( client->sess.spectatorClient != -1 ) { client->sess.spectatorClient = -1; client->sess.spectatorState = SPECTATOR_FREE; } // only start client if chosen a class and joined a team if( client->pers.classSelection == PCL_NONE && teamLocal == TEAM_NONE ) client->sess.spectatorState = SPECTATOR_FREE; else if( client->pers.classSelection == PCL_NONE ) client->sess.spectatorState = SPECTATOR_LOCKED; // if client is dead and following teammate, stop following before spawning if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) G_StopFollowing( ent ); if( origin != NULL ) VectorCopy( origin, spawn_origin ); if( angles != NULL ) VectorCopy( angles, spawn_angles ); // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this client if( client->sess.spectatorState != SPECTATOR_NOT ) { if( teamLocal == TEAM_NONE ) spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); else if( teamLocal == TEAM_ALIENS ) spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); else if( teamLocal == TEAM_HUMANS ) spawnPoint = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); } else { if( spawn == NULL ) { G_Error( "ClientSpawn: spawn is NULL\n" ); return; } spawnPoint = spawn; if( ent != spawn ) { //start spawn animation on spawnPoint G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, qtrue ); if( spawnPoint->buildableTeam == TEAM_ALIENS ) spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; else if( spawnPoint->buildableTeam == TEAM_HUMANS ) spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; } } // toggle the teleport bit so the client knows to not lerp flags = ( ent->client->ps.eFlags & EF_TELEPORT_BIT ) ^ EF_TELEPORT_BIT; G_UnlaggedClear( ent ); // clear everything but the persistant data saved = client->pers; savedSess = client->sess; savedPing = client->ps.ping; for( i = 0; i < MAX_PERSISTANT; i++ ) persistant[ i ] = client->ps.persistant[ i ]; eventSequence = client->ps.eventSequence; memset( client, 0, sizeof( *client ) ); client->pers = saved; client->sess = savedSess; client->ps.ping = savedPing; client->lastkilled_client = -1; for( i = 0; i < MAX_PERSISTANT; i++ ) client->ps.persistant[ i ] = persistant[ i ]; client->ps.eventSequence = eventSequence; // increment the spawncount so the client will detect the respawn client->ps.persistant[ PERS_SPAWN_COUNT ]++; client->ps.persistant[ PERS_SPECSTATE ] = client->sess.spectatorState; client->airOutTime = level.time + 12000; trap_GetUserinfo( index, userinfo, sizeof( userinfo ) ); client->ps.eFlags = flags; //Com_Printf( "ent->client->pers->pclass = %i\n", ent->client->pers.classSelection ); ent->s.groundEntityNum = ENTITYNUM_NONE; ent->client = &level.clients[ index ]; ent->takedamage = qtrue; ent->inuse = qtrue; ent->classname = "player"; ent->r.contents = CONTENTS_BODY; ent->clipmask = MASK_PLAYERSOLID; ent->die = player_die; ent->waterlevel = 0; ent->watertype = 0; ent->flags = 0; // calculate each client's acceleration ent->evaluateAcceleration = qtrue; client->ps.stats[ STAT_MISC ] = 0; client->ps.eFlags = flags; client->ps.clientNum = index; BG_ClassBoundingBox( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL ); if( client->sess.spectatorState == SPECTATOR_NOT ) client->ps.stats[ STAT_MAX_HEALTH ] = BG_Class( ent->client->pers.classSelection )->health; else client->ps.stats[ STAT_MAX_HEALTH ] = 100; // clear entity values if( ent->client->pers.classSelection == PCL_HUMAN ) { BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats ); weapon = client->pers.humanItemSelection; } else if( client->sess.spectatorState == SPECTATOR_NOT ) weapon = BG_Class( ent->client->pers.classSelection )->startWeapon; else weapon = WP_NONE; maxAmmo = BG_Weapon( weapon )->maxAmmo; maxClips = BG_Weapon( weapon )->maxClips; client->ps.stats[ STAT_WEAPON ] = weapon; client->ps.ammo = maxAmmo; client->ps.clips = maxClips; // We just spawned, not changing weapons client->ps.persistant[ PERS_NEWWEAPON ] = 0; ent->client->ps.stats[ STAT_CLASS ] = ent->client->pers.classSelection; ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; ent->client->ps.stats[ STAT_STATE ] = 0; VectorSet( ent->client->ps.grapplePoint, 0.0f, 0.0f, 1.0f ); // health will count down towards max_health ent->health = client->ps.stats[ STAT_HEALTH ] = client->ps.stats[ STAT_MAX_HEALTH ]; //* 1.25; //if evolving scale health if( ent == spawn ) { ent->health *= ent->client->pers.evolveHealthFraction; client->ps.stats[ STAT_HEALTH ] *= ent->client->pers.evolveHealthFraction; } //clear the credits array for( i = 0; i < MAX_CLIENTS; i++ ) ent->credits[ i ] = 0; client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, client->ps.origin ); #define UP_VEL 150.0f #define F_VEL 50.0f //give aliens some spawn velocity if( client->sess.spectatorState == SPECTATOR_NOT && client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) { if( ent == spawn ) { //evolution particle system G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) ); } else { spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); if( spawnPoint->s.origin2[ 2 ] > 0.0f ) { vec3_t forward, dir; AngleVectors( spawn_angles, forward, NULL, NULL ); VectorScale( forward, F_VEL, forward ); VectorAdd( spawnPoint->s.origin2, forward, dir ); VectorNormalize( dir ); VectorScale( dir, UP_VEL, client->ps.velocity ); } G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); } } else if( client->sess.spectatorState == SPECTATOR_NOT && client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); } // the respawned flag will be cleared after the attack and jump keys come up client->ps.pm_flags |= PMF_RESPAWNED; trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); G_SetClientViewAngle( ent, spawn_angles ); if( client->sess.spectatorState == SPECTATOR_NOT ) { trap_LinkEntity( ent ); // force the base weapon up if( client->pers.teamSelection == TEAM_HUMANS ) G_ForceWeaponChange( ent, weapon ); client->ps.weaponstate = WEAPON_READY; } // don't allow full run speed for a bit client->ps.pm_flags |= PMF_TIME_KNOCKBACK; client->ps.pm_time = 100; client->respawnTime = level.time; ent->nextRegenTime = level.time; client->inactivityTime = level.time + g_inactivity.integer * 1000; client->latched_buttons = 0; // set default animations client->ps.torsoAnim = TORSO_STAND; client->ps.legsAnim = LEGS_IDLE; if( level.intermissiontime ) MoveClientToIntermission( ent ); else { // fire the targets of the spawn point if( !spawn ) G_UseTargets( spawnPoint, ent ); // select the highest weapon number available, after any // spawn given items have fired client->ps.weapon = 1; for( i = WP_NUM_WEAPONS - 1; i > 0 ; i-- ) { if( BG_InventoryContainsWeapon( i, client->ps.stats ) ) { client->ps.weapon = i; break; } } } // run a client frame to drop exactly to the floor, // initialize animations and other things client->ps.commandTime = level.time - 100; ent->client->pers.cmd.serverTime = level.time; ClientThink( ent-g_entities ); // positively link the client, even if the command times are weird if( client->sess.spectatorState == SPECTATOR_NOT ) { BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); trap_LinkEntity( ent ); } // must do this here so the number of active clients is calculated CalculateRanks( ); // run the presend to set anything else ClientEndFrame( ent ); // clear entity state values BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); }
/** Returns qfalse if the move is blocked */ static qboolean G_TryPushingEntity(gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove) { vec3_t matrix[3], transpose[3]; vec3_t org, org2, move2; gentity_t *block; // EF_MOVER_STOP will just stop when contacting another entity // instead of pushing it, but entities can still ride on top of it if ((pusher->s.eFlags & EF_MOVER_STOP) && check->s.groundEntityNum != pusher->s.number) { return qfalse; } // save off the old position if (pushed_p > &pushed[MAX_GENTITIES]) { G_Error("pushed_p > &pushed[MAX_GENTITIES]"); } pushed_p->ent = check; VectorCopy(check->s.pos.trBase, pushed_p->origin); VectorCopy(check->s.apos.trBase, pushed_p->angles); if (check->client) { pushed_p->deltayaw = check->client->ps.delta_angles[YAW]; VectorCopy(check->client->ps.origin, pushed_p->origin); } pushed_p++; // try moving the contacted entity // figure movement due to the pusher's amove G_CreateRotationMatrix(amove, transpose); G_TransposeMatrix(transpose, matrix); if (check->client) { VectorSubtract(check->client->ps.origin, pusher->r.currentOrigin, org); } else { VectorSubtract(check->s.pos.trBase, pusher->r.currentOrigin, org); } VectorCopy(org, org2); G_RotatePoint(org2, matrix); VectorSubtract(org2, org, move2); // add movement VectorAdd(check->s.pos.trBase, move, check->s.pos.trBase); VectorAdd(check->s.pos.trBase, move2, check->s.pos.trBase); if (check->client) { VectorAdd(check->client->ps.origin, move, check->client->ps.origin); VectorAdd(check->client->ps.origin, move2, check->client->ps.origin); // make sure the client's view rotates when on a rotating mover check->client->ps.delta_angles[YAW] += ANGLE2SHORT(amove[YAW]); } // may have pushed them off an edge if (check->s.groundEntityNum != pusher->s.number) { check->s.groundEntityNum = ENTITYNUM_NONE; } block = G_TestEntityPosition(check); if (!block) { // pushed ok if (check->client) { VectorCopy(check->client->ps.origin, check->r.currentOrigin); } else { VectorCopy(check->s.pos.trBase, check->r.currentOrigin); } trap_LinkEntity (check); return qtrue; } // if it is ok to leave in the old position, do it // this is only relevent for riding entities, not pushed // Sliding trapdoors can cause this. VectorCopy((pushed_p-1)->origin, check->s.pos.trBase); if (check->client) { VectorCopy((pushed_p-1)->origin, check->client->ps.origin); } VectorCopy((pushed_p-1)->angles, check->s.apos.trBase); block = G_TestEntityPosition (check); if (!block) { check->s.groundEntityNum = ENTITYNUM_NONE; pushed_p--; return qtrue; } // blocked return qfalse; }
/* =========== G_SelectRandomFurthestSpawnPoint Chooses a player start, deathmatch start, etc ============ */ gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { gentity_t *spot; vec3_t delta; float dist; float list_dist[ 64 ]; gentity_t *list_spot[ 64 ]; int numSpots, rnd, i, j; numSpots = 0; spot = NULL; while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL ) { if( SpotWouldTelefrag( spot ) ) continue; VectorSubtract( spot->s.origin, avoidPoint, delta ); dist = VectorLength( delta ); for( i = 0; i < numSpots; i++ ) { if( dist > list_dist[ i ] ) { if( numSpots >= 64 ) numSpots = 64 - 1; for( j = numSpots; j > i; j-- ) { list_dist[ j ] = list_dist[ j - 1 ]; list_spot[ j ] = list_spot[ j - 1 ]; } list_dist[ i ] = dist; list_spot[ i ] = spot; numSpots++; if( numSpots > 64 ) numSpots = 64; break; } } if( i >= numSpots && numSpots < 64 ) { list_dist[ numSpots ] = dist; list_spot[ numSpots ] = spot; numSpots++; } } if( !numSpots ) { spot = G_Find( NULL, FOFS( classname ), "info_player_deathmatch" ); if( !spot ) G_Error( "Couldn't find a spawn point" ); VectorCopy( spot->s.origin, origin ); origin[ 2 ] += 9; VectorCopy( spot->s.angles, angles ); return spot; } // select a random spot from the spawn points furthest away rnd = random( ) * ( numSpots / 2 ); VectorCopy( list_spot[ rnd ]->s.origin, origin ); origin[ 2 ] += 9; VectorCopy( list_spot[ rnd ]->s.angles, angles ); return list_spot[ rnd ]; }
/* =========== SelectRandomFurthestSpawnPoint Chooses a player start, deathmatch start, etc ============ */ gentity_t *SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles, qboolean isbot ) { gentity_t *spot; vec3_t delta; float dist; float list_dist[MAX_SPAWN_POINTS]; gentity_t *list_spot[MAX_SPAWN_POINTS]; int numSpots, rnd, i, j; numSpots = 0; spot = NULL; while((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { if(SpotWouldTelefrag(spot)) continue; if(((spot->flags & FL_NO_BOTS) && isbot) || ((spot->flags & FL_NO_HUMANS) && !isbot)) { // spot is not for this human/bot player continue; } VectorSubtract( spot->s.origin, avoidPoint, delta ); dist = VectorLength( delta ); for (i = 0; i < numSpots; i++) { if(dist > list_dist[i]) { if (numSpots >= MAX_SPAWN_POINTS) numSpots = MAX_SPAWN_POINTS - 1; for(j = numSpots; j > i; j--) { list_dist[j] = list_dist[j-1]; list_spot[j] = list_spot[j-1]; } list_dist[i] = dist; list_spot[i] = spot; numSpots++; break; } } if(i >= numSpots && numSpots < MAX_SPAWN_POINTS) { list_dist[numSpots] = dist; list_spot[numSpots] = spot; numSpots++; } } if(!numSpots) { spot = G_Find(NULL, FOFS(classname), "info_player_deathmatch"); if (!spot) G_Error( "Couldn't find a spawn point" ); VectorCopy (spot->s.origin, origin); origin[2] += 9; VectorCopy (spot->s.angles, angles); return spot; } // select a random spot from the spawn points furthest away rnd = random() * (numSpots / 2); VectorCopy (list_spot[rnd]->s.origin, origin); origin[2] += 9; VectorCopy (list_spot[rnd]->s.angles, angles); return list_spot[rnd]; }
/* ================= G_ScriptAction_PlayAnim syntax: playanim <startframe> <endframe> [looping <FOREVER/duration>] [rate <FPS>] NOTE: all source animations must be at 20fps ================= */ qboolean G_ScriptAction_PlayAnim( gentity_t *ent, char *params ) { char *pString, *token, tokens[2][MAX_QPATH]; int i, endtime = 0; // TTimo: init qboolean looping = qfalse, forever = qfalse; int startframe, endframe, idealframe; int rate = 20; if ( ( ent->scriptStatus.scriptFlags & SCFL_ANIMATING ) && ( ent->scriptStatus.scriptStackChangeTime == level.time ) ) { // this is a new call, so cancel the previous animation ent->scriptStatus.scriptFlags &= ~SCFL_ANIMATING; } pString = params; for ( i = 0; i < 2; i++ ) { token = COM_ParseExt( &pString, qfalse ); if ( !token || !token[0] ) { G_Printf( "G_Scripting: syntax error\n\nplayanim <startframe> <endframe> [LOOPING <duration>]\n" ); return qtrue; } else { Q_strncpyz( tokens[i], token, sizeof( tokens[i] ) ); } } startframe = atoi( tokens[0] ); endframe = atoi( tokens[1] ); // check for optional parameters token = COM_ParseExt( &pString, qfalse ); if ( token[0] ) { if ( !Q_strcasecmp( token, "looping" ) ) { looping = qtrue; token = COM_ParseExt( &pString, qfalse ); if ( !token || !token[0] ) { G_Printf( "G_Scripting: syntax error\n\nplayanim <startframe> <endframe> [LOOPING <duration>]\n" ); return qtrue; } if ( !Q_strcasecmp( token, "untilreachmarker" ) ) { if ( level.time < ent->s.pos.trTime + ent->s.pos.trDuration ) { endtime = level.time + 100; } else { endtime = 0; } } else if ( !Q_strcasecmp( token, "forever" ) ) { ent->scriptStatus.animatingParams = params; ent->scriptStatus.scriptFlags |= SCFL_ANIMATING; endtime = level.time + 100; // we don't care when it ends, since we are going forever! forever = qtrue; } else { endtime = ent->scriptStatus.scriptStackChangeTime + atoi( token ); } token = COM_ParseExt( &pString, qfalse ); } if ( token[0] && !Q_strcasecmp( token, "rate" ) ) { token = COM_ParseExt( &pString, qfalse ); if ( !token[0] ) { G_Error( "G_Scripting: playanim has RATE parameter without an actual rate specified" ); } rate = atoi( token ); } if ( !looping ) { endtime = ent->scriptStatus.scriptStackChangeTime + ( ( endframe - startframe ) * ( 1000 / 20 ) ); } } idealframe = startframe + (int)floor( (float)( level.time - ent->scriptStatus.scriptStackChangeTime ) / ( 1000.0 / (float)rate ) ); if ( looping ) { ent->s.frame = startframe + ( idealframe - startframe ) % ( endframe - startframe ); ent->s.eFlags |= EF_MOVER_ANIMATE; } else { if ( idealframe > endframe ) { ent->s.frame = endframe; ent->s.eFlags &= ~EF_MOVER_ANIMATE; // stop interpolation, since we have gone passed the endframe } else { ent->s.frame = idealframe; ent->s.eFlags |= EF_MOVER_ANIMATE; } } if ( forever ) { ent->s.eFlags |= EF_MOVER_ANIMATE; return qtrue; // continue to the next command } if ( endtime <= level.time ) { ent->s.eFlags &= ~EF_MOVER_ANIMATE; // stop animating return qtrue; } else { return qfalse; } };
static void EvaluateField(const save_field_t *pField, byte *pbBase, byte *pbOriginalRefData/* may be NULL*/) { void *pv = (void *)(pbBase + pField->iOffset); void *pvOriginal = (void *)(pbOriginalRefData + pField->iOffset); switch (pField->eFieldType) { case F_STRING: *(char **)pv = GetStringPtr(*(int *)pv, pbOriginalRefData?*(char**)pvOriginal:NULL); break; case F_GENTITY: *(gentity_t **)pv = GetGEntityPtr(*(int *)pv); break; case F_GROUP: *(AIGroupInfo_t **)pv = GetGroupPtr(*(int *)pv); break; case F_GCLIENT: *(gclient_t **)pv = GetGClientPtr(*(int *)pv); break; case F_ITEM: *(gitem_t **)pv = GetGItemPtr(*(int *)pv); break; case F_VEHINFO: *(vehicleInfo_t **)pv = GetVehicleInfoPtr(*(int *)pv); break; case F_BEHAVIORSET: { char **p = (char **) pv; char **pO= (char **) pvOriginal; for (int i=0; i<NUM_BSETS; i++, p++, pO++) { *p = GetStringPtr(*(int *)p, pbOriginalRefData?*(char **)pO:NULL); } } break; /*MCG case F_BODYQUEUE: { gentity_t **p = (gentity_t **) pv; for (int i=0; i<BODY_QUEUE_SIZE; i++, p++) { *p = GetGEntityPtr(*(int *)p); } } break; */ case F_ALERTEVENT: { alertEvent_t* p = (alertEvent_t *) pv; for (int i=0; i<MAX_ALERT_EVENTS; i++) { p[i].owner = GetGEntityPtr((intptr_t)(p[i].owner)); } } break; case F_AIGROUPS: // convert to ptrs within this into indexes... { AIGroupInfo_t* p = (AIGroupInfo_t *) pv; for (int i=0; i<MAX_FRAME_GROUPS; i++) { p[i].enemy = GetGEntityPtr((intptr_t)(p[i].enemy)); p[i].commander = GetGEntityPtr((intptr_t)(p[i].commander)); } } break; case F_ANIMFILESETS: { animFileSet_t* p = (animFileSet_t *) pv; char *pO; for (int i=0; i<MAX_ANIM_FILES; i++) { for ( int j=0; j<MAX_ANIM_EVENTS; j++ ) { pO = pbOriginalRefData ? level.knownAnimFileSets[i].torsoAnimEvents[j].stringData : NULL; p[i].torsoAnimEvents[j].stringData = GetStringPtr((intptr_t)p[i].torsoAnimEvents[j].stringData, pO); pO = pbOriginalRefData ? level.knownAnimFileSets[i].legsAnimEvents[j].stringData : NULL; p[i].legsAnimEvents[j].stringData = GetStringPtr((intptr_t)p[i].legsAnimEvents[j].stringData, pO); } } } break; // // These fields are patched in when their relevant owners are loaded case F_BOOLPTR: case F_NULL: break; case F_IGNORE: break; default: G_Error ("EvaluateField: unknown field type"); break; } }
static void G_AddBot(const char *name, int skill, const char *team, const char *spawnPoint, int playerClass, int playerWeapon, int characerIndex, const char *respawn, const char *scriptName, int rank, int skills[], qboolean pow) { #define MAX_BOTNAMES 1024 int clientNum; char *botinfo; gentity_t *bot; char *key; char *s; char *botname; // char *model; char userinfo[MAX_INFO_STRING]; // get the botinfo from bots.txt botinfo = G_GetBotInfoByName("wolfbot"); if (!botinfo) { G_Printf(S_COLOR_RED "Error: Bot '%s' not defined\n", name); return; } // create the bot's userinfo userinfo[0] = '\0'; botname = Info_ValueForKey(botinfo, "funname"); if (!botname[0]) { botname = Info_ValueForKey(botinfo, "name"); } Info_SetValueForKey(userinfo, "name", botname); Info_SetValueForKey(userinfo, "rate", "25000"); Info_SetValueForKey(userinfo, "snaps", "20"); Info_SetValueForKey(userinfo, "skill", va("%i", skill)); s = Info_ValueForKey(botinfo, "aifile"); if (!*s) { trap_Printf(S_COLOR_RED "Error: bot has no aifile specified\n"); return; } // have the server allocate a client slot clientNum = trap_BotAllocateClient(0); // Arnout: 0 means no prefered clientslot if (clientNum == -1) { G_Printf(S_COLOR_RED "Unable to add bot. All player slots are in use.\n"); G_Printf(S_COLOR_RED "Start server with more 'open' slots (or check setting of sv_maxclients cvar).\n"); return; } // initialize the bot settings if (!team || !*team) { if (PickTeam(clientNum) == TEAM_AXIS) { team = "red"; } else { team = "blue"; } } Info_SetValueForKey(userinfo, "characterfile", Info_ValueForKey(botinfo, "aifile")); //Info_SetValueForKey( userinfo, "skill", va( "%i", skill ) ); Info_SetValueForKey(userinfo, "team", team); if (spawnPoint && spawnPoint[0]) { Info_SetValueForKey(userinfo, "spawnPoint", spawnPoint); } if (scriptName && scriptName[0]) { Info_SetValueForKey(userinfo, "scriptName", scriptName); } /* if (playerClass > 0) { Info_SetValueForKey( userinfo, "pClass", va("%i", playerClass) ); } if (playerWeapon) { Info_SetValueForKey( userinfo, "pWeapon", va("%i", playerWeapon) ); }*/ // END Mad Doc - TDF key = "wolfbot"; if (!Q_stricmp((char *)name, key)) { // read the botnames file, and pick a name that doesnt exist fileHandle_t f; int len, i, j, k; qboolean setname = qfalse; char botnames[8192], *pbotnames, *listbotnames[MAX_BOTNAMES], *token, *oldpbotnames; int lengthbotnames[MAX_BOTNAMES]; len = trap_FS_FOpenFile("botfiles/botnames.txt", &f, FS_READ); if (len >= 0) { if (len > sizeof(botnames)) { G_Error("botfiles/botnames.txt is too big (max = %i)", (int)sizeof(botnames)); } memset(botnames, 0, sizeof(botnames)); trap_FS_Read(botnames, len, f); pbotnames = botnames; // read them in i = 0; oldpbotnames = pbotnames; while ((token = COM_Parse(&pbotnames))) { if (!token[0]) { break; } listbotnames[i] = strstr(oldpbotnames, token); lengthbotnames[i] = strlen(token); listbotnames[i][lengthbotnames[i]] = 0; oldpbotnames = pbotnames; if (++i == MAX_BOTNAMES) { break; } } // if (i > 2) { j = rand() % (i - 1); // start at a random spot inthe list for (k = j + 1; k != j; k++) { if (k == i) { k = -1; // gets increased on next loop continue; } if (ClientFromName(listbotnames[k]) == -1) { // found an unused name Info_SetValueForKey(userinfo, "name", listbotnames[k]); setname = qtrue; break; } } } // trap_FS_FCloseFile(f); } if (!setname) { Info_SetValueForKey(userinfo, "name", va("wolfbot_%i", clientNum + 1)); } } else { Info_SetValueForKey(userinfo, "name", name); } // if a character was specified, put the index of that character filename in the CS_CHARACTERS table in the userinfo if (characerIndex != -1) { Info_SetValueForKey(userinfo, "ch", va("%i", characerIndex)); } // if a rank was specified, use that /* if (rank != -1) { Info_SetValueForKey(userinfo, "rank", va("%i", rank)); }*/ // END Mad Doc - TDF bot = &g_entities[clientNum]; bot->r.svFlags |= SVF_BOT; if (pow) { bot->r.svFlags |= SVF_POW; } bot->inuse = qtrue; bot->aiName = bot->client->pers.netname; // register the userinfo trap_SetUserinfo(clientNum, userinfo); // have it connect to the game as a normal client if ((s = ClientConnect(clientNum, qtrue, qtrue))) { G_Printf(S_COLOR_RED "Unable to add bot: %s\n", s); return; } SetTeam(bot, (char *)team, qtrue, -1, -1, qfalse); /* if( skills ) { int i; for( i = 0; i < SK_NUM_SKILLS; i++ ) { bot->client->sess.skill[i] = skills[i]; } }*/ return; }