//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Precaches and defs for entities and other data that must always be available. // // $NoKeywords: $ //===========================================================================// #include "cbase.h" #include "soundent.h" #include "client.h" #include "decals.h" #include "EnvMessage.h" #include "player.h" #include "gamerules.h" #include "teamplay_gamerules.h" #include "physics.h" #include "isaverestore.h" #include "activitylist.h" #include "eventlist.h" #include "eventqueue.h" #include "ai_network.h" #include "ai_schedule.h" #include "ai_networkmanager.h" #include "ai_utils.h" #include "basetempentity.h" #include "world.h" #include "mempool.h" #include "igamesystem.h" #include "engine/IEngineSound.h" #include "globals.h" #include "engine/IStaticPropMgr.h" #include "particle_parse.h" #include "globalstate.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" extern CBaseEntity *g_pLastSpawn; void InitBodyQue(void); extern void W_Precache(void); extern void ActivityList_Free(void); extern CUtlMemoryPool g_EntityListPool; #define SF_DECAL_NOTINDEATHMATCH 2048 class CDecal : public CPointEntity { public: DECLARE_CLASS(CDecal, CPointEntity); void Spawn(void); bool KeyValue(const char *szKeyName, const char *szValue); // Need to apply static decals here to get them into the signon buffer for the server appropriately virtual void Activate(); void TriggerDecal(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value); // Input handlers. void InputActivate(inputdata_t &inputdata); DECLARE_DATADESC(); public: int m_nTexture; bool m_bLowPriority; private: void StaticDecal(void); }; BEGIN_DATADESC(CDecal) DEFINE_FIELD(m_nTexture, FIELD_INTEGER), DEFINE_KEYFIELD(m_bLowPriority, FIELD_BOOLEAN, "LowPriority"), // Don't mark as FDECAL_PERMANENT so not save/restored and will be reused on the client preferentially // Function pointers DEFINE_FUNCTION(StaticDecal), DEFINE_FUNCTION(TriggerDecal), DEFINE_INPUTFUNC(FIELD_VOID, "Activate", InputActivate), END_DATADESC() LINK_ENTITY_TO_CLASS(infodecal, CDecal); // UNDONE: These won't get sent to joining players in multi-player void CDecal::Spawn(void) { if (m_nTexture < 0 || (gpGlobals->deathmatch && HasSpawnFlags(SF_DECAL_NOTINDEATHMATCH))) { UTIL_Remove(this); return; } } void CDecal::Activate() { BaseClass::Activate(); if (!GetEntityName()) { StaticDecal(); } else { // if there IS a targetname, the decal sprays itself on when it is triggered. SetThink(&CDecal::SUB_DoNothing); SetUse(&CDecal::TriggerDecal); } } void CDecal::TriggerDecal(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value) { // this is set up as a USE function for info_decals that have targetnames, so that the // decal doesn't get applied until it is fired. (usually by a scripted sequence) trace_t trace; int entityIndex; UTIL_TraceLine(GetAbsOrigin() - Vector(5, 5, 5), GetAbsOrigin() + Vector(5, 5, 5), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &trace); entityIndex = trace.m_pEnt ? trace.m_pEnt->entindex() : 0; CBroadcastRecipientFilter filter; te->BSPDecal(filter, 0.0, &GetAbsOrigin(), entityIndex, m_nTexture); SetThink(&CDecal::SUB_Remove); SetNextThink(gpGlobals->curtime + 0.1f); } void CDecal::InputActivate(inputdata_t &inputdata) { TriggerDecal(inputdata.pActivator, inputdata.pCaller, USE_ON, 0); } void CDecal::StaticDecal(void) { class CTraceFilterValidForDecal : public CTraceFilterSimple { public: CTraceFilterValidForDecal(const IHandleEntity *passentity, int collisionGroup) : CTraceFilterSimple(passentity, collisionGroup) { } virtual bool ShouldHitEntity(IHandleEntity *pServerEntity, int contentsMask) { static const char *ppszIgnoredClasses[] = { "weapon_*", "item_*", "prop_ragdoll", "prop_dynamic", "prop_static", "prop_physics", "npc_bullseye", // Tracker 15335 }; CBaseEntity *pEntity = EntityFromEntityHandle(pServerEntity); // Tracker 15335: Never impact decals against entities which are not rendering, either. if (pEntity->IsEffectActive(EF_NODRAW)) return false; for (int i = 0; i < ARRAYSIZE(ppszIgnoredClasses); i++) { if (pEntity->ClassMatches(ppszIgnoredClasses[i])) return false; } return CTraceFilterSimple::ShouldHitEntity(pServerEntity, contentsMask); } }; trace_t trace; CTraceFilterValidForDecal traceFilter(this, COLLISION_GROUP_NONE); int entityIndex, modelIndex = 0; Vector position = GetAbsOrigin(); UTIL_TraceLine(position - Vector(5, 5, 5), position + Vector(5, 5, 5), MASK_SOLID, &traceFilter, &trace); bool canDraw = true; entityIndex = trace.m_pEnt ? (short)trace.m_pEnt->entindex() : 0; if (entityIndex) { CBaseEntity *ent = trace.m_pEnt; if (ent) { modelIndex = ent->GetModelIndex(); VectorITransform(GetAbsOrigin(), ent->EntityToWorldTransform(), position); canDraw = (modelIndex != 0); if (!canDraw) { Warning("Suppressed StaticDecal which would have hit entity %i (class:%s, name:%s) with modelindex = 0\n", ent->entindex(), ent->GetClassname(), STRING(ent->GetEntityName())); } } } if (canDraw) { engine->StaticDecal(position, m_nTexture, entityIndex, modelIndex, m_bLowPriority); } SUB_Remove(); } bool CDecal::KeyValue(const char *szKeyName, const char *szValue) { if (FStrEq(szKeyName, "texture")) { // FIXME: should decals all be preloaded? m_nTexture = UTIL_PrecacheDecal(szValue, true); // Found if (m_nTexture >= 0) return true; Warning("Can't find decal %s\n", szValue); } else { return BaseClass::KeyValue(szKeyName, szValue); } return true; } //----------------------------------------------------------------------------- // Purpose: Projects a decal against a prop //----------------------------------------------------------------------------- class CProjectedDecal : public CPointEntity { public: DECLARE_CLASS(CProjectedDecal, CPointEntity); void Spawn(void); bool KeyValue(const char *szKeyName, const char *szValue); // Need to apply static decals here to get them into the signon buffer for the server appropriately virtual void Activate(); void TriggerDecal(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value); // Input handlers. void InputActivate(inputdata_t &inputdata); DECLARE_DATADESC(); public: int m_nTexture; float m_flDistance; private: void ProjectDecal(CRecipientFilter& filter); void StaticDecal(void); }; BEGIN_DATADESC(CProjectedDecal) DEFINE_FIELD(m_nTexture, FIELD_INTEGER), DEFINE_KEYFIELD(m_flDistance, FIELD_FLOAT, "Distance"), // Function pointers DEFINE_FUNCTION(StaticDecal), DEFINE_FUNCTION(TriggerDecal), DEFINE_INPUTFUNC(FIELD_VOID, "Activate", InputActivate), END_DATADESC() LINK_ENTITY_TO_CLASS(info_projecteddecal, CProjectedDecal); // UNDONE: These won't get sent to joining players in multi-player void CProjectedDecal::Spawn(void) { if (m_nTexture < 0 || (gpGlobals->deathmatch && HasSpawnFlags(SF_DECAL_NOTINDEATHMATCH))) { UTIL_Remove(this); return; } } void CProjectedDecal::Activate() { BaseClass::Activate(); if (!GetEntityName()) { StaticDecal(); } else { // if there IS a targetname, the decal sprays itself on when it is triggered. SetThink(&CProjectedDecal::SUB_DoNothing); SetUse(&CProjectedDecal::TriggerDecal); } } void CProjectedDecal::InputActivate(inputdata_t &inputdata) { TriggerDecal(inputdata.pActivator, inputdata.pCaller, USE_ON, 0); } void CProjectedDecal::ProjectDecal(CRecipientFilter& filter) { te->ProjectDecal(filter, 0.0, &GetAbsOrigin(), &GetAbsAngles(), m_flDistance, m_nTexture); } void CProjectedDecal::TriggerDecal(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value) { CBroadcastRecipientFilter filter; ProjectDecal(filter); SetThink(&CProjectedDecal::SUB_Remove); SetNextThink(gpGlobals->curtime + 0.1f); } void CProjectedDecal::StaticDecal(void) { CBroadcastRecipientFilter initFilter; initFilter.MakeInitMessage(); ProjectDecal(initFilter); SUB_Remove(); } bool CProjectedDecal::KeyValue(const char *szKeyName, const char *szValue) { if (FStrEq(szKeyName, "texture")) { // FIXME: should decals all be preloaded? m_nTexture = UTIL_PrecacheDecal(szValue, true); // Found if (m_nTexture >= 0) return true; Warning("Can't find decal %s\n", szValue); } else { return BaseClass::KeyValue(szKeyName, szValue); } return true; } //======================= // CWorld // // This spawns first when each level begins. //======================= LINK_ENTITY_TO_CLASS(worldspawn, CWorld); BEGIN_DATADESC(CWorld) DEFINE_FIELD(m_flWaveHeight, FIELD_FLOAT), // keyvalues are parsed from map, but not saved/loaded DEFINE_KEYFIELD(m_iszChapterTitle, FIELD_STRING, "chaptertitle"), DEFINE_KEYFIELD(m_bStartDark, FIELD_BOOLEAN, "startdark"), DEFINE_KEYFIELD(m_bDisplayTitle, FIELD_BOOLEAN, "gametitle"), DEFINE_FIELD(m_WorldMins, FIELD_VECTOR), DEFINE_FIELD(m_WorldMaxs, FIELD_VECTOR), #ifdef _X360 DEFINE_KEYFIELD(m_flMaxOccludeeArea, FIELD_FLOAT, "maxoccludeearea_x360"), DEFINE_KEYFIELD(m_flMinOccluderArea, FIELD_FLOAT, "minoccluderarea_x360"), #else DEFINE_KEYFIELD(m_flMaxOccludeeArea, FIELD_FLOAT, "maxoccludeearea"), DEFINE_KEYFIELD(m_flMinOccluderArea, FIELD_FLOAT, "minoccluderarea"), #endif DEFINE_KEYFIELD(m_flMaxPropScreenSpaceWidth, FIELD_FLOAT, "maxpropscreenwidth"), DEFINE_KEYFIELD(m_flMinPropScreenSpaceWidth, FIELD_FLOAT, "minpropscreenwidth"), DEFINE_KEYFIELD(m_iszDetailSpriteMaterial, FIELD_STRING, "detailmaterial"), DEFINE_KEYFIELD(m_bColdWorld, FIELD_BOOLEAN, "coldworld"), END_DATADESC() // SendTable stuff. IMPLEMENT_SERVERCLASS_ST(CWorld, DT_WORLD) SendPropFloat(SENDINFO(m_flWaveHeight), 8, SPROP_ROUNDUP, 0.0f, 8.0f), SendPropVector(SENDINFO(m_WorldMins), -1, SPROP_COORD), SendPropVector(SENDINFO(m_WorldMaxs), -1, SPROP_COORD), SendPropInt(SENDINFO(m_bStartDark), 1, SPROP_UNSIGNED), SendPropFloat(SENDINFO(m_flMaxOccludeeArea), 0, SPROP_NOSCALE), SendPropFloat(SENDINFO(m_flMinOccluderArea), 0, SPROP_NOSCALE), SendPropFloat(SENDINFO(m_flMaxPropScreenSpaceWidth), 0, SPROP_NOSCALE), SendPropFloat(SENDINFO(m_flMinPropScreenSpaceWidth), 0, SPROP_NOSCALE), SendPropStringT(SENDINFO(m_iszDetailSpriteMaterial)), SendPropInt(SENDINFO(m_bColdWorld), 1, SPROP_UNSIGNED), END_SEND_TABLE() // // Just to ignore the "wad" field. // bool CWorld::KeyValue(const char *szKeyName, const char *szValue) { if (FStrEq(szKeyName, "skyname")) { // Sent over net now. ConVarRef skyname("sv_skyname"); skyname.SetValue(szValue); } else if (FStrEq(szKeyName, "newunit")) { // Single player only. Clear save directory if set if (atoi(szValue)) { extern void Game_SetOneWayTransition(); Game_SetOneWayTransition(); } } else if (FStrEq(szKeyName, "world_mins")) { Vector vec; sscanf(szValue, "%f %f %f", &vec.x, &vec.y, &vec.z); m_WorldMins = vec; } else if (FStrEq(szKeyName, "world_maxs")) { Vector vec; sscanf(szValue, "%f %f %f", &vec.x, &vec.y, &vec.z); m_WorldMaxs = vec; } else return BaseClass::KeyValue(szKeyName, szValue); return true; } extern bool g_fGameOver; static CWorld *g_WorldEntity = NULL; CWorld* GetWorldEntity() { return g_WorldEntity; } CWorld::CWorld() { AddEFlags(EFL_NO_AUTO_EDICT_ATTACH | EFL_KEEP_ON_RECREATE_ENTITIES); NetworkProp()->AttachEdict(INDEXENT(RequiredEdictIndex())); ActivityList_Init(); EventList_Init(); SetSolid(SOLID_BSP); SetMoveType(MOVETYPE_NONE); m_bColdWorld = false; } CWorld::~CWorld() { EventList_Free(); ActivityList_Free(); if (g_pGameRules) { g_pGameRules->LevelShutdown(); delete g_pGameRules; g_pGameRules = NULL; } g_WorldEntity = NULL; } //------------------------------------------------------------------------------ // Purpose : Add a decal to the world // Input : // Output : //------------------------------------------------------------------------------ void CWorld::DecalTrace(trace_t *pTrace, char const *decalName) { int index = decalsystem->GetDecalIndexForName(decalName); if (index < 0) return; CBroadcastRecipientFilter filter; if (pTrace->hitbox != 0) { te->Decal(filter, 0.0f, &pTrace->endpos, &pTrace->startpos, 0, pTrace->hitbox, index); } else { te->WorldDecal(filter, 0.0, &pTrace->endpos, index); } } void CWorld::RegisterSharedActivities(void) { ActivityList_RegisterSharedActivities(); } void CWorld::RegisterSharedEvents(void) { EventList_RegisterSharedEvents(); } void CWorld::Spawn(void) { SetLocalOrigin(vec3_origin); SetLocalAngles(vec3_angle); // NOTE: SHOULD NEVER BE ANYTHING OTHER THAN 1!!! SetModelIndex(1); // world model SetModelName(AllocPooledString(modelinfo->GetModelName(GetModel()))); AddFlag(FL_WORLDBRUSH); g_EventQueue.Init(); Precache(); GlobalEntity_Add("is_console", STRING(gpGlobals->mapname), (IsConsole()) ? GLOBAL_ON : GLOBAL_OFF); GlobalEntity_Add("is_pc", STRING(gpGlobals->mapname), (!IsConsole()) ? GLOBAL_ON : GLOBAL_OFF); } static const char *g_DefaultLightstyles[] = { // 0 normal "m", // 1 FLICKER (first variety) "mmnmmommommnonmmonqnmmo", // 2 SLOW STRONG PULSE "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba", // 3 CANDLE (first variety) "mmmmmaaaaammmmmaaaaaabcdefgabcdefg", // 4 FAST STROBE "mamamamamama", // 5 GENTLE PULSE 1 "jklmnopqrstuvwxyzyxwvutsrqponmlkj", // 6 FLICKER (second variety) "nmonqnmomnmomomno", // 7 CANDLE (second variety) "mmmaaaabcdefgmmmmaaaammmaamm", // 8 CANDLE (third variety) "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa", // 9 SLOW STROBE (fourth variety) "aaaaaaaazzzzzzzz", // 10 FLUORESCENT FLICKER "mmamammmmammamamaaamammma", // 11 SLOW PULSE NOT FADE TO BLACK "abcdefghijklmnopqrrqponmlkjihgfedcba", // 12 UNDERWATER LIGHT MUTATION // this light only distorts the lightmap - no contribution // is made to the brightness of affected surfaces "mmnnmmnnnmmnn", }; const char *GetDefaultLightstyleString(int styleIndex) { if (styleIndex < ARRAYSIZE(g_DefaultLightstyles)) { return g_DefaultLightstyles[styleIndex]; } return "m"; } void CWorld::Precache(void) { g_WorldEntity = this; g_fGameOver = false; g_pLastSpawn = NULL; ConVarRef stepsize("sv_stepsize"); stepsize.SetValue(18); ConVarRef roomtype("room_type"); roomtype.SetValue(0); // Set up game rules Assert(!g_pGameRules); if (g_pGameRules) { delete g_pGameRules; } InstallGameRules(); Assert(g_pGameRules); g_pGameRules->Init(); CSoundEnt::InitSoundEnt(); // Only allow precaching between LevelInitPreEntity and PostEntity CBaseEntity::SetAllowPrecache(true); IGameSystem::LevelInitPreEntityAllSystems(STRING(GetModelName())); // Create the player resource g_pGameRules->CreateStandardEntities(); // UNDONE: Make most of these things server systems or precache_registers // ================================================= // Activities // ================================================= ActivityList_Free(); RegisterSharedActivities(); EventList_Free(); RegisterSharedEvents(); InitBodyQue(); // init sentence group playback stuff from sentences.txt. // ok to call this multiple times, calls after first are ignored. SENTENCEG_Init(); // Precache standard particle systems PrecacheStandardParticleSystems(); // the area based ambient sounds MUST be the first precache_sounds // player precaches W_Precache(); // get weapon precaches ClientPrecache(); g_pGameRules->Precache(); // precache all temp ent stuff CBaseTempEntity::PrecacheTempEnts(); g_Language.SetValue(LANGUAGE_ENGLISH); // TODO use VGUI to get current language if (g_Language.GetInt() == LANGUAGE_GERMAN) { PrecacheModel("models/germangibs.mdl"); } else { PrecacheModel("models/gibs/hgibs.mdl"); } PrecacheScriptSound("BaseEntity.EnterWater"); PrecacheScriptSound("BaseEntity.ExitWater"); // // Setup light animation tables. 'a' is total darkness, 'z' is maxbright. // for (int i = 0; i < ARRAYSIZE(g_DefaultLightstyles); i++) { engine->LightStyle(i, GetDefaultLightstyleString(i)); } // styles 32-62 are assigned by the light program for switchable lights // 63 testing engine->LightStyle(63, "a"); // ================================================= // Load and Init AI Networks // ================================================= CAI_NetworkManager::InitializeAINetworks(); // ================================================= // Load and Init AI Schedules // ================================================= g_AI_SchedulesManager.LoadAllSchedules(); // ================================================= // Initialize NPC Relationships // ================================================= g_pGameRules->InitDefaultAIRelationships(); CBaseCombatCharacter::InitInteractionSystem(); // Call all registered precachers. CPrecacheRegister::Precache(); if (m_iszChapterTitle != NULL_STRING) { DevMsg(2, "Chapter title: %s\n", STRING(m_iszChapterTitle)); CMessage *pMessage = (CMessage *)CBaseEntity::Create("env_message", vec3_origin, vec3_angle, NULL); if (pMessage) { pMessage->SetMessage(m_iszChapterTitle); m_iszChapterTitle = NULL_STRING; // send the message entity a play message command, delayed by 1 second pMessage->AddSpawnFlags(SF_MESSAGE_ONCE); pMessage->SetThink(&CMessage::SUB_CallUseToggle); pMessage->SetNextThink(gpGlobals->curtime + 1.0f); } } g_iszFuncBrushClassname = AllocPooledString("func_brush"); } //----------------------------------------------------------------------------- // Purpose: // Output : float //----------------------------------------------------------------------------- float GetRealTime() { return engine->Time(); } bool CWorld::GetDisplayTitle() const { return m_bDisplayTitle; } bool CWorld::GetStartDark() const { return m_bStartDark; } void CWorld::SetDisplayTitle(bool display) { m_bDisplayTitle = display; } void CWorld::SetStartDark(bool startdark) { m_bStartDark = startdark; } bool CWorld::IsColdWorld(void) { return m_bColdWorld; }