// Copyright (C) 2001-2002 Raven Software. // // g_misc.c #include "g_local.h" /*QUAKED func_group (0 0 0) ? Used to group brushes together just for editor convenience. They are turned into normal brushes by the utilities. */ /*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) Used as a positional target for in-game calculation, like jumppad targets. target_position does the same thing */ void SP_info_notnull( gentity_t *self ) { G_SetOrigin( self, self->s.origin ); } /* ================================================================================= TeleportPlayer ================================================================================= */ void TeleportPlayer ( gentity_t *player, vec3_t origin, vec3_t angles ) { gentity_t *tent; // use temp events at source and destination to prevent the effect // from getting dropped by a second player event if ( !G_IsClientSpectating ( player->client ) ) { tent = G_TempEntity( player->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); tent->s.clientNum = player->s.clientNum; tent = G_TempEntity( origin, EV_PLAYER_TELEPORT_IN ); tent->s.clientNum = player->s.clientNum; } // unlink to make sure it can't possibly interfere with G_KillBox trap_UnlinkEntity (player); VectorCopy ( origin, player->client->ps.origin ); player->client->ps.origin[2] += 1; // spit the player out AngleVectors( angles, player->client->ps.velocity, NULL, NULL ); VectorScale( player->client->ps.velocity, 400, player->client->ps.velocity ); player->client->ps.pm_time = 160; // hold time player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; // toggle the teleport bit so the client knows to not lerp player->client->ps.eFlags ^= EF_TELEPORT_BIT; // set angles SetClientViewAngle( player, angles ); // kill anything at the destination if ( !G_IsClientSpectating ( player->client ) ) { G_KillBox (player); } // save results of pmove BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue ); // use the precise origin for linking VectorCopy( player->client->ps.origin, player->r.currentOrigin ); if ( !G_IsClientSpectating ( player->client ) ) { trap_LinkEntity (player); } } /*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) Point teleporters at these. Now that we don't have teleport destination pads, this is just an info_notnull */ void SP_misc_teleporter_dest( gentity_t *ent ) { } //=========================================================== /*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16) RMG this model is inserted into the bsp file "model" arbitrary .md3 file to display */ void SP_misc_model( gentity_t *ent ) { #if 0 ent->s.modelindex = G_ModelIndex( ent->model ); VectorSet (ent->mins, -16, -16, -16); VectorSet (ent->maxs, 16, 16, 16); trap_LinkEntity (ent); G_SetOrigin( ent, ent->s.origin ); VectorCopy( ent->s.angles, ent->s.apos.trBase ); #else G_FreeEntity( ent ); #endif } /*QUAKED misc_G2model (1 0 0) (-16 -16 -16) (16 16 16) "model" arbitrary .glm file to display */ void SP_misc_G2model( gentity_t *ent ) { #if 0 char name1[200] = "models/players/kyle/modelmp.glm"; trap_G2API_InitGhoul2Model(&ent->s, name1, G_ModelIndex( name1 ), 0, 0, 0, 0); trap_G2API_SetBoneAnim(ent->s.ghoul2, 0, "model_root", 0, 12, BONE_ANIM_OVERRIDE_LOOP, 1.0f, level.time, -1, -1); ent->s.radius = 150; // VectorSet (ent->mins, -16, -16, -16); // VectorSet (ent->maxs, 16, 16, 16); trap_LinkEntity (ent); G_SetOrigin( ent, ent->s.origin ); VectorCopy( ent->s.angles, ent->s.apos.trBase ); #else G_FreeEntity( ent ); #endif } //=========================================================== void locateCamera( gentity_t *ent ) { vec3_t dir; gentity_t *target; gentity_t *owner; owner = G_PickTarget( ent->target ); if ( !owner ) { Com_Printf( "Couldn't find target for misc_partal_surface\n" ); G_FreeEntity( ent ); return; } ent->r.ownerNum = owner->s.number; // frame holds the rotate speed if ( owner->spawnflags & 1 ) { ent->s.frame = 25; } else if ( owner->spawnflags & 2 ) { ent->s.frame = 75; } // swing camera ? if ( owner->spawnflags & 4 ) { // set to 0 for no rotation at all ent->s.gametypeitems = 0; } else { ent->s.gametypeitems = 1; } // clientNum holds the rotate offset ent->s.clientNum = owner->s.clientNum; VectorCopy( owner->s.origin, ent->s.origin2 ); // see if the portal_camera has a target target = G_PickTarget( owner->target ); if ( target ) { VectorSubtract( target->s.origin, owner->s.origin, dir ); VectorNormalize( dir ); } else { G_SetMovedir( owner->s.angles, dir ); } ent->s.eventParm = DirToByte( dir ); } /*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8) The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted. This must be within 64 world units of the surface! */ void SP_misc_portal_surface(gentity_t *ent) { VectorClear( ent->r.mins ); VectorClear( ent->r.maxs ); trap_LinkEntity (ent); ent->r.svFlags = SVF_PORTAL; ent->s.eType = ET_PORTAL; if ( !ent->target ) { VectorCopy( ent->s.origin, ent->s.origin2 ); } else { ent->think = locateCamera; ent->nextthink = level.time + 100; } } /*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate noswing The target for a misc_portal_director. You can set either angles or target another entity to determine the direction of view. "roll" an angle modifier to orient the camera around the target vector; */ void SP_misc_portal_camera(gentity_t *ent) { float roll; VectorClear( ent->r.mins ); VectorClear( ent->r.maxs ); trap_LinkEntity (ent); G_SpawnFloat( "roll", "0", &roll ); ent->s.clientNum = roll/360.0 * 256; } /*QUAKED misc_bsp (1 0 0) (-16 -16 -16) (16 16 16) "bspmodel" arbitrary .bsp file to display */ void SP_misc_bsp(gentity_t *ent) { char temp[MAX_QPATH]; char *out; float newAngle; int tempint; G_SpawnFloat( "angle", "0", &newAngle ); if (newAngle != 0.0) { ent->s.angles[1] = newAngle; } // don't support rotation any other way ent->s.angles[0] = 0.0; ent->s.angles[2] = 0.0; G_SpawnString("bspmodel", "", &out); ent->s.eFlags = EF_PERMANENT; // Mainly for debugging G_SpawnInt( "spacing", "0", &tempint); ent->s.time2 = tempint; G_SpawnInt( "flatten", "0", &tempint); ent->s.time = tempint; Com_sprintf(temp, MAX_QPATH, "#%s", out); trap_SetBrushModel( ent, temp ); // SV_SetBrushModel -- sets mins and maxs G_BSPIndex(temp); level.mNumBSPInstances++; Com_sprintf(temp, MAX_QPATH, "%d-", level.mNumBSPInstances); VectorCopy(ent->s.origin, level.mOriginAdjust); level.mRotationAdjust = ent->s.angles[1]; level.mTargetAdjust = temp; level.hasBspInstances = qtrue; level.mBSPInstanceDepth++; G_SpawnString("filter", "", &out); strcpy(level.mFilter, out); G_SpawnString("teamfilter", "", &out); strcpy(level.mTeamFilter, out); VectorCopy( ent->s.origin, ent->s.pos.trBase ); VectorCopy( ent->s.origin, ent->r.currentOrigin ); VectorCopy( ent->s.angles, ent->s.apos.trBase ); VectorCopy( ent->s.angles, ent->r.currentAngles ); ent->s.eType = ET_MOVER; trap_LinkEntity (ent); trap_SetActiveSubBSP(ent->s.modelindex); G_SpawnEntitiesFromString(qtrue); trap_SetActiveSubBSP(-1); level.mBSPInstanceDepth--; level.mFilter[0] = level.mTeamFilter[0] = 0; if ( g_debugRMG.integer ) { G_SpawnDebugCylinder ( ent->s.origin, ent->s.time2, &g_entities[0], 2000, COLOR_WHITE ); if ( ent->s.time ) { G_SpawnDebugCylinder ( ent->s.origin, ent->s.time, &g_entities[0], 2000, COLOR_RED ); } } } /*QUAKED terrain (1.0 1.0 1.0) ? Terrain entity It will stretch to the full height of the brush numPatches - integer number of patches to split the terrain brush into (default 200) terxels - integer number of terxels on a patch side (default 4) (2 <= count <= 8) seed - integer seed for random terrain generation (default 0) textureScale - float scale of texture (default 0.005) heightMap - name of heightmap data image to use terrainDef - defines how the game textures the terrain (file is base/ext_data/*.terrain - default is grassyhills) instanceDef - defines which bsp instances appear miscentDef - defines which client models spawn on the terrain (file is base/ext_data/*.miscents) densityMap - how dense the client models are packed */ void SP_terrain(gentity_t *ent) { char temp[MAX_INFO_STRING]; char final[MAX_QPATH]; char seed[MAX_QPATH]; char missionType[MAX_QPATH]; char soundSet[MAX_QPATH]; int shaderNum, i; char *value; int terrainID; VectorClear (ent->s.angles); trap_SetBrushModel( ent, ent->model ); // Get the shader from the top of the brush // shaderNum = gi.CM_GetShaderNum(s.modelindex); shaderNum = 0; if (RMG.integer) { // Grab the default terrain file from the RMG cvar trap_Cvar_VariableStringBuffer("RMG_terrain", temp, MAX_QPATH); Com_sprintf(final, MAX_QPATH, "%s", temp); AddSpawnField("terrainDef", temp); trap_Cvar_VariableStringBuffer("RMG_instances", temp, MAX_QPATH); Com_sprintf(final, MAX_QPATH, "%s", temp); AddSpawnField("instanceDef", temp); trap_Cvar_VariableStringBuffer("RMG_miscents", temp, MAX_QPATH); Com_sprintf(final, MAX_QPATH, "%s", temp); AddSpawnField("miscentDef", temp); trap_Cvar_VariableStringBuffer("RMG_seed", seed, MAX_QPATH); trap_Cvar_VariableStringBuffer("RMG_mission", missionType, MAX_QPATH); trap_Cvar_VariableStringBuffer("RMG_soundset", soundSet, MAX_QPATH); trap_SetConfigstring(CS_AMBIENT_SOUNDSETS, soundSet ); } // Arbitrary (but sane) limits to the number of terxels // if((mTerxels < MIN_TERXELS) || (mTerxels > MAX_TERXELS)) { // Com_printf("G_Terrain: terxels out of range - defaulting to 4\n"); // mTerxels = 4; } // Get info required for the common init temp[0] = 0; G_SpawnString("heightmap", "", &value); Info_SetValueForKey(temp, "heightMap", value); G_SpawnString("numpatches", "400", &value); Info_SetValueForKey(temp, "numPatches", va("%d", atoi(value))); G_SpawnString("terxels", "4", &value); Info_SetValueForKey(temp, "terxels", va("%d", atoi(value))); Info_SetValueForKey(temp, "seed", seed); Info_SetValueForKey(temp, "minx", va("%f", ent->r.mins[0])); Info_SetValueForKey(temp, "miny", va("%f", ent->r.mins[1])); Info_SetValueForKey(temp, "minz", va("%f", ent->r.mins[2])); Info_SetValueForKey(temp, "maxx", va("%f", ent->r.maxs[0])); Info_SetValueForKey(temp, "maxy", va("%f", ent->r.maxs[1])); Info_SetValueForKey(temp, "maxz", va("%f", ent->r.maxs[2])); Info_SetValueForKey(temp, "modelIndex", va("%d", ent->s.modelindex)); G_SpawnString("terraindef", "grassyhills", &value); Info_SetValueForKey(temp, "terrainDef", value); G_SpawnString("instancedef", "", &value); Info_SetValueForKey(temp, "instanceDef", value); G_SpawnString("miscentdef", "", &value); Info_SetValueForKey(temp, "miscentDef", value); Info_SetValueForKey(temp, "missionType", missionType); for(i = 0; i < MAX_INSTANCE_TYPES; i++) { trap_Cvar_VariableStringBuffer(va("RMG_instance%d", i), final, MAX_QPATH); if(strlen(final)) { Info_SetValueForKey(temp, va("inst%d", i), final); } } // Set additional data required on the client only G_SpawnString("densitymap", "", &value); Info_SetValueForKey(temp, "densityMap", value); Info_SetValueForKey(temp, "shader", va("%d", shaderNum)); G_SpawnString("texturescale", "0.005", &value); Info_SetValueForKey(temp, "texturescale", va("%f", atof(value))); // Initialise the common aspects of the terrain terrainID = trap_CM_RegisterTerrain(temp); // SetCommon(common); Info_SetValueForKey(temp, "terrainId", va("%d", terrainID)); // Let the entity know if it is random generated or not // SetIsRandom(common->GetIsRandom()); // Let the game remember everything level.landScapes[terrainID] = ent; // Send all the data down to the client trap_SetConfigstring(CS_TERRAINS + terrainID, temp); // Make sure the contents are properly set ent->r.contents = CONTENTS_TERRAIN; ent->r.svFlags = SVF_NOCLIENT; ent->s.eFlags = EF_PERMANENT; ent->s.eType = ET_TERRAIN; // Hook into the world so physics will work trap_LinkEntity(ent); // If running RMG then initialize the terrain and handle team skins if ( RMG.integer ) { trap_RMG_Init(terrainID); if ( level.gametypeData->teams ) { char temp[MAX_QPATH]; // Red team change from RMG ? trap_GetConfigstring ( CS_GAMETYPE_REDTEAM, temp, MAX_QPATH ); if ( Q_stricmp ( temp, level.gametypeTeam[TEAM_RED] ) ) { level.gametypeTeam[TEAM_RED] = trap_VM_LocalStringAlloc ( temp ); } // Blue team change from RMG ? trap_GetConfigstring ( CS_GAMETYPE_BLUETEAM, temp, MAX_QPATH ); if ( Q_stricmp ( temp, level.gametypeTeam[TEAM_BLUE] ) ) { level.gametypeTeam[TEAM_BLUE] = trap_VM_LocalStringAlloc ( temp ); } } } } /* ================================================================================= G_DebugCylinderThink ================================================================================= */ void G_DebugCylinderThink ( gentity_t* ent ) { vec3_t vec1; vec3_t vec2; ent->nextthink = level.time + 1000; VectorCopy ( ent->parent->client->ps.origin, vec1 ); VectorCopy ( ent->r.currentOrigin, vec2 ); vec1[2] = 0; vec2[2] = 0; // IF we are too far away then kill it if ( Distance ( vec1, vec2 ) > ent->speed ) { trap_UnlinkEntity ( ent ); return; } trap_LinkEntity ( ent ); } /* ================================================================================= G_SpawnDebugCylinder ================================================================================= */ void G_SpawnDebugCylinder ( vec3_t origin, float radius, gentity_t* clientent, float viewRadius, int colorIndex ) { gentity_t* ent; if ( !g_cheats.integer ) { return; } ent = G_Spawn ( ); ent->s.eType = ET_DEBUG_CYLINDER; ent->s.time = colorIndex; ent->s.time2 = (int) radius; ent->parent = clientent; ent->speed = viewRadius; VectorCopy ( origin, ent->s.origin ); VectorCopy ( origin, ent->r.currentOrigin ); ent->nextthink = level.time + 1000; ent->think = G_DebugCylinderThink; trap_LinkEntity ( ent ); } void fx_think( gentity_t *ent ) { int time; // play fx G_PlayEffect( ent->health, ent->s.origin, ent->pos1 ); if( ent->count >= 0 ) // stops it from rolling over, yeah kinda lame... { ent->count--; } if( !ent->count ) // effect is suppose to play mCount times then dissapear { G_FreeEntity(ent); return; } // calc next play time time = (ent->wait + flrand(0.0f, ent->random)) * 1000; // need it in milliseconds ent->think = fx_think; ent->nextthink = level.time + time; } /*QUAKED fx_play_effect (.2 .5 .8) (-8 -8 -8) (8 8 8) START_OFF Plays specified effect file "effect" name of .efx file "wait" seconds between triggerings, default 0.3 "random" wait variance in seconds, default 0 "target" direction of effect, default up "count" plays effect this many times then deletes itself, default -1 = infinite START_OFF fx starts off */ void SP_fx_play_effect(gentity_t *ent) { gentity_t *target; char *fxName; G_SetOrigin( ent, ent->s.origin ); G_SpawnString("effect", "", &fxName); ent->health = G_EffectIndex(fxName); if (ent->wait == 0.0) { ent->wait = 0.3; } target = G_Find(0, FOFS(targetname), ent->target); if (target) { VectorSubtract( target->s.origin, ent->s.origin, ent->pos1 ); VectorNormalize( ent->pos1 ); // find angles vectoangles( ent->pos1, ent->r.currentAngles ); // copy over to other angles VectorCopy( ent->r.currentAngles, ent->s.angles ); VectorCopy( ent->r.currentAngles, ent->s.apos.trBase ); } else { ent->pos1[0] = ent->pos1[1] = 0.0; ent->pos1[2] = 1.0; } ent->think = fx_think; ent->nextthink = level.time + 100; }