/* =============== SV_StatusString Builds the string that is sent as heartbeats and status replies =============== */ static const char *SV_StatusString() { guard(SV_StatusString); static char status[MAX_MSGLEN - 16]; if (sv.attractloop) return ""; int statusLength = appSprintf(ARRAY_ARG(status), "%s\n", Cvar_BitInfo(CVAR_SERVERINFO)); for (int i = 0; i < sv_maxclients->integer; i++) { client_t *cl = &svs.clients[i]; if (cl->state == cs_connected || cl->state == cs_spawned) { char player[256]; int playerLength = appSprintf(ARRAY_ARG(player), "%d %d \"%s\"\n", cl->edict->client->ps.stats[STAT_FRAGS], cl->ping, *cl->Name); if (statusLength + playerLength >= sizeof(status)) break; // can't hold any more memcpy(status + statusLength, player, playerLength+1); statusLength += playerLength; } } return status; unguard; }
const char *appSymbolName(address_t addr) { static char buf[256]; #if USE_DBGHELP if (appSymbolName(addr, ARRAY_ARG(buf))) return buf; #endif #if GET_EXTENDED_INFO HMODULE hModule = NULL; char moduleName[256]; char *s; MEMORY_BASIC_INFORMATION mbi; if (!VirtualQuery((void*)addr, &mbi, sizeof(mbi))) goto simple; if (!(hModule = (HMODULE)mbi.AllocationBase)) goto simple; if (!GetModuleFileName(hModule, ARRAY_ARG(moduleName))) goto simple; // if (s = strrchr(moduleName, '.')) // cut extension // *s = 0; if (s = strrchr(moduleName, '\\')) strcpy(moduleName, s+1); // remove "path\" part appSprintf(ARRAY_ARG(buf), "%s+0x%X", moduleName, (int)(addr - (size_t)hModule)); return buf; #endif // GET_EXTENDED_INFO simple: appSprintf(ARRAY_ARG(buf), "%08X", addr); return buf; }
bool UIProgressDialog::Tick() { char buffer[64]; appSprintf(ARRAY_ARG(buffer), "%d MBytes", (int)(GTotalAllocationSize >> 20)); MemoryLabel->SetText(buffer); appSprintf(ARRAY_ARG(buffer), "%d", UObject::GObjObjects.Num()); ObjectsLabel->SetText(buffer); return PumpMessages(); }
static bool ScanGameDirectory(const char *dir, bool recurse) { guard(ScanGameDirectory); char Path[MAX_PACKAGE_PATH]; bool res = true; // printf("Scan %s\n", dir); #if _WIN32 appSprintf(ARRAY_ARG(Path), "%s/*.*", dir); _finddatai64_t found; long hFind = _findfirsti64(Path, &found); if (hFind == -1) return true; do { if (found.name[0] == '.') continue; // "." or ".." appSprintf(ARRAY_ARG(Path), "%s/%s", dir, found.name); // directory -> recurse if (found.attrib & _A_SUBDIR) { if (recurse) res = ScanGameDirectory(Path, recurse); else res = true; } else res = RegisterGameFile(Path); } while (res && _findnexti64(hFind, &found) != -1); _findclose(hFind); #else DIR *find = opendir(dir); if (!find) return true; struct dirent *ent; while (/*res &&*/ (ent = readdir(find))) { if (ent->d_name[0] == '.') continue; // "." or ".." appSprintf(ARRAY_ARG(Path), "%s/%s", dir, ent->d_name); // directory -> recurse // note: using 'stat64' here because 'stat' ignores large files struct stat64 buf; if (stat64(Path, &buf) < 0) continue; // or break? if (S_ISDIR(buf.st_mode)) { if (recurse) res = ScanGameDirectory(Path, recurse); else res = true; } else res = RegisterGameFile(Path); } closedir(find); #endif return res; unguard; }
void appUnwindPrefix(const char *fmt) { char buf[512]; appSprintf(ARRAY_ARG(buf), WasError ? " <- %s:" : "%s:", fmt); LogHistory(buf); WasError = false; }
bool CFont::Load(const char *name) { TString<64> Filename; Filename.sprintf("fonts/%s.font", name); char* buf = (char*)GFileSystem->LoadFile(Filename); if (!buf) { appWPrintf("File %s does not exist\n", *Filename); return false; } CSimpleParser text; text.InitFromBuf(buf); loadingFont = this; while (const char *line = text.GetLine()) { if (!ExecuteCommand(line, ARRAY_ARG(fontCommands))) appWPrintf("%s: invalid line [%s]\n", name, line); } // setup remaining fields Name = name; spacing = FONT_SPACING; delete buf; return true; }
static bool LoadWeaponInfo(const char *filename, weaponInfo_t &weap) { char *buf = (char*) GFileSystem->LoadFile(va("models/weapons/%s.cfg", filename)); if (!buf) return false; memset(&weap, 0, sizeof(weaponInfo_t)); loadingWeap = &weap; // defaults weap.drawScale = 1; bool result = true; CSimpleParser text; text.InitFromBuf(buf); while (const char *line = text.GetLine()) { if (!ExecuteCommand(line, ARRAY_ARG(weapCommands))) { appWPrintf("Invalid line [%s] in \"%s.cfg\"\n", line, filename); result = false; break; } } weap.origin.Scale(weap.drawScale); delete buf; return result && weap.model != NULL; //?? NOTE: currently, "false" result ignored ... }
int SV_PointContents(const CVec3 &p) { guard(SV_PointContents); // get base contents from world unsigned contents = CM_PointContents(p, 0); contents |= CM_PointModelContents(p); edict_t *list[MAX_EDICTS]; int num = SV_AreaEdicts(p, p, ARRAY_ARG(list), AREA_SOLID); for (int i = 0; i < num; i++) { edict_t *edict = list[i]; entityHull_t &ent = ents[NUM_FOR_EDICT(edict)]; if (ent.model) contents |= CM_TransformedPointContents(p, ent.model->headnode, edict->s.origin, ent.axis); else contents |= CM_TransformedPointContents(p, CM_HeadnodeForBox(ent.bounds), edict->s.origin, nullVec3); } return contents; unguard; }
void V_Init() { CVAR_BEGIN(vars) #if !NO_DEBUG CVAR_VAR(cl_testblend, 0, 0), CVAR_VAR(cl_testentities, 0, 0), CVAR_VAR(cl_testlights, 0, CVAR_CHEAT), CVAR_VAR(r_playerpos, 0, CVAR_CHEAT), CVAR_VAR(r_surfinfo, 0, CVAR_CHEAT), CVAR_VAR(r_showBrush, -1, CVAR_CHEAT), #endif // NO_DEBUG CVAR_VAR(r_drawfps, 0, 0), CVAR_VAR(scr_viewsize, 100, CVAR_ARCHIVE) //?? should rename: not scr var CVAR_END Cvar_GetVars(ARRAY_ARG(vars)); #if GUN_DEBUG RegisterCommand("gun_next", Gun_Next_f); RegisterCommand("gun_prev", Gun_Prev_f); RegisterCommand("gun_model", Gun_Model_f); #endif #if !NO_DEBUG RegisterCommand("setlight", SetLight_f); #endif RegisterCommand("sky", Sky_f); }
const char *appTimestamp() { static char ctime[64]; time_t itime; time(&itime); strftime(ARRAY_ARG(ctime), "%b %d %Y %H:%M:%S", localtime(&itime)); return ctime; }
void SV_InitVars() { CVAR_BEGIN(vars) // LATCH cvars (note: all are SERVERINFO) CVAR_FULL(&sv_deathmatch, "deathmatch", "0", CVAR_SERVERINFO|CVAR_LATCH), CVAR_FULL(&sv_coop, "coop", "0", CVAR_SERVERINFO|CVAR_LATCH), CVAR_FULL(&sv_maxclients, "maxclients", "1", CVAR_SERVERINFO|CVAR_LATCH), CVAR_VAR(sv_extProtocol, 1, CVAR_SERVERINFO|CVAR_LATCH), // cvars from outside the server: here for LATCH updating only //?? may be, move this cvars here COMPLETELY? CVAR_FULL(NULL, "cheats", "", CVAR_SERVERINFO|CVAR_LATCH|CVAR_NODEFAULT), CVAR_FULL(NULL, "game", "", CVAR_SERVERINFO|CVAR_LATCH|CVAR_NODEFAULT), // other SERVERINFO vars CVAR_NULL(fraglimit, 0, CVAR_SERVERINFO), CVAR_NULL(timelimit, 0, CVAR_SERVERINFO), CVAR_VAR(hostname, noname, CVAR_SERVERINFO), CVAR_NULL(dmflags, 16, CVAR_SERVERINFO), //?? old default value: 0 for game, DF_INSTANT_ITEMS (16) for server CVAR_VAR(sv_camperSounds, 1, CVAR_SERVERINFO), // read-only SERVERINFO CVAR_FULL(NULL, "protocol", STR(PROTOCOL_VERSION), CVAR_SERVERINFO|CVAR_NOSET), // non-archive LATCH vars CVAR_VAR(sv_airaccelerate, 0, CVAR_LATCH), // send: CS_AIRACCEL -> CL_PredictMovement() -> pm_airaccelerate; // no other use (including server/game!) CVAR_VAR(timeout, 125, 0), CVAR_VAR(zombietime, 2, 0), CVAR_FULL(&rcon_password, "rcon_password", "", 0), CVAR_NULL(skill, 1, 0), CVAR_FULL(&sv_showclamp, "showclamp", "0", 0), CVAR_FULL(&sv_paused, "paused", "0", CVAR_CHEAT), CVAR_VAR(allow_download, 1, CVAR_ARCHIVE), CVAR_VAR(allow_download_players, 0, CVAR_ARCHIVE), CVAR_VAR(allow_download_models, 1, CVAR_ARCHIVE), CVAR_VAR(allow_download_sounds, 1, CVAR_ARCHIVE), CVAR_VAR(allow_download_maps, 1, CVAR_ARCHIVE), CVAR_VAR(sv_enforcetime, 0, 0), //?? CVAR_VAR(sv_noreload, 0, 0), //?? CVAR_FULL(&public_server, "public", "0", 0), CVAR_VAR(sv_shownet, 0, 0), CVAR_VAR(sv_reconnect_limit, 3, CVAR_ARCHIVE), #if MAX_DEBUG CVAR_VAR(sv_labels, 0, CVAR_CHEAT), #endif #if FPU_EXCEPTIONS CVAR_VAR(g_fpuXcpt, 0, CVAR_ARCHIVE), #endif // CVAR_VAR(sv_fps, 20, 0) // archive/serverinfo ?? CVAR_END Cvar_GetVars(ARRAY_ARG(vars)); }
bool appSymbolName(address_t addr, char *buffer, int size) { InitSymbols(); char SymBuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME]; PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)SymBuffer; pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); pSymbol->MaxNameLen = MAX_SYM_NAME; DWORD64 dwDisplacement = 0; if (SymFromAddr(hProcess, addr, &dwDisplacement, pSymbol)) { char OffsetBuffer[32]; if (dwDisplacement) appSprintf(ARRAY_ARG(OffsetBuffer), "+%X", dwDisplacement); else OffsetBuffer[0] = 0; #if EXTRA_UNDECORATE char undecBuffer[256]; if (UnDecorateSymbolName(pSymbol->Name, ARRAY_ARG(undecBuffer), UNDNAME_NO_LEADING_UNDERSCORES|UNDNAME_NO_LEADING_UNDERSCORES|UNDNAME_NO_ALLOCATION_LANGUAGE|UNDNAME_NO_ACCESS_SPECIFIERS)) { StripPrefix(undecBuffer, "virtual "); StripPrefix(undecBuffer, "class "); StripPrefix(undecBuffer, "struct "); appSprintf(buffer, size, "%s%s", undecBuffer, OffsetBuffer); } else { appSprintf(buffer, size, "%s%s", pSymbol->Name, OffsetBuffer); } #else appSprintf(buffer, size, "%s%s", pSymbol->Name, OffsetBuffer); #endif // EXTRA_UNDECORATE } else { appSprintf(buffer, size, "%08X", addr); } return true; }
void Error(char *fmt, ...) { va_list argptr; va_start(argptr, fmt); char buf[4096]; int len = _vsnprintf(ARRAY_ARG(buf), fmt, argptr); va_end(argptr); if (len < 0 || len >= sizeof(buf) - 1) exit(1); puts(buf); exit(1); }
void appSetNotifyHeader(const char *fmt, ...) { if (!fmt) { NotifyBuf[0] = 0; return; } va_list argptr; va_start(argptr, fmt); vsnprintf(ARRAY_ARG(NotifyBuf), fmt, argptr); va_end(argptr); }
void appError(const char *fmt, ...) { va_list argptr; va_start(argptr, fmt); char buf[4096]; int len = vsnprintf(ARRAY_ARG(buf), fmt, argptr); va_end(argptr); assert(len >= 0 && len < ARRAY_COUNT(buf) - 1); GIsSwError = true; #if DO_GUARD // appNotify("ERROR: %s\n", buf); strcpy(GErrorHistory, buf); appStrcatn(ARRAY_ARG(GErrorHistory), "\n"); THROW; #else fprintf(stderr, "Fatal Error: %s\n", buf); if (GLogFile) fprintf(GLogFile, "Fatal Error: %s\n", buf); exit(1); #endif }
const char* GetGameTag(int gameEnum) { static char buf[64]; int Count = ARRAY_COUNT(GListOfGames) - 1; // exclude TABLE_END marker const char* value = NULL; for (int i = 0; i < Count; i++) { if (GListOfGames[i].Enum == gameEnum) { value = GListOfGames[i].Switch; break; } } #if UNREAL4 if (!value && gameEnum >= GAME_UE4_BASE) { // generate tag int ue4ver = GAME_UE4_GET_MINOR(gameEnum); if (gameEnum == GAME_UE4(ue4ver)) { // exactly matching, i.e. not a custom UE4 version appSprintf(ARRAY_ARG(buf), "ue4.%d", ue4ver); return buf; } } #endif // UNREAL4 if (!value) { appSprintf(ARRAY_ARG(buf), "%X", gameEnum); return buf; } return value; }
FArchive *appCreateFileReader(const CGameFileInfo *info) { if (!info->FileSystem) { // regular file char buf[MAX_PACKAGE_PATH]; appSprintf(ARRAY_ARG(buf), "%s/%s", RootDirectory, info->RelativeName); return new FFileReader(buf); } else { // file from virtual file system return info->FileSystem->CreateReader(info->RelativeName); } }
void InitFileSystem() { guard(InitFileSystem); // initialize interface CVAR_BEGIN(vars) CVAR_FULL(&fs_configfile, "cfgfile", CONFIGNAME, CVAR_NOSET), //?? config system will be changed later // cddir <path> -- allow game to be partially installed - read missing data from CD CVAR_FULL(&fs_cddir, "cddir", "", CVAR_NOSET), CVAR_FULL(&fs_game, "game", "", CVAR_SERVERINFO|CVAR_LATCH), CVAR_VAR(fs_logFile, 0, 0) CVAR_END Cvar_GetVars(ARRAY_ARG(vars)); CFileContainer *Mnt; FSLog = GNull; appInitFileSystem(FS); CFileSystem::RegisterFormat(CFileContainerPak::Create); CFileSystem::RegisterFormat(CFileContainerZip::Create); #if !DEDICATED_ONLY // mount resources Mnt = CZResource::Mount("resources", zresource, "."); Mnt->locked = true; #endif // mount CDROM if (fs_cddir->string[0]) { Mnt = FS.MountDirectory(fs_cddir->string); Mnt->locked = true; appPrintf("FS: using \"%s\" as CDROM image\n", fs_cddir->string); } // mount root file container Mnt = FS.MountDirectory("."); Mnt->locked = true; // mount basedir GDefMountPoint = BASEDIRNAME; FS.GameDir = BASEDIRNAME; FS.MountGame(BASEDIRNAME); // change game, if needed FS.SetGameDir(fs_game->string); // load configuration LoadGameConfig(); unguard; }
void appPrintf(const char *fmt, ...) { va_list argptr; va_start(argptr, fmt); char buf[4096]; int len = vsnprintf(ARRAY_ARG(buf), fmt, argptr); va_end(argptr); assert(len >= 0 && len < ARRAY_COUNT(buf) - 1); fwrite(buf, len, 1, stdout); if (GLogFile) fwrite(buf, len, 1, GLogFile); #if VSTUDIO_INTEGRATION if (IsDebuggerPresent()) OutputDebugString(buf); #endif }
bool UIProgressDialog::Progress(const char* package, int index, int total) { // do not update UI too often int tick = appMilliseconds(); if (tick - lastTick < 50) return true; lastTick = tick; char buffer[512]; appSprintf(ARRAY_ARG(buffer), "%s %d/%d", DescriptionText, index+1, total); DescriptionLabel->SetText(buffer); PackageLabel->SetText(package); ProgressBar->SetValue((float)(index+1) / total); return Tick(); }
void S_Init(void) { cvar_t *cv = Cvar_Get("nosound", "0", 0); if (cv->integer) appPrintf(S_CYAN"Sound disabled\n"); else { CVAR_BEGIN(vars) CVAR_VAR(s_volume, 0.7, CVAR_ARCHIVE), CVAR_VAR(s_khz, 22, CVAR_ARCHIVE), CVAR_VAR(s_loadas8bit, 0, CVAR_ARCHIVE), CVAR_VAR(s_reverse_stereo, 0, CVAR_ARCHIVE), CVAR_VAR(s_mixahead, 0.2, CVAR_ARCHIVE), CVAR_VAR(s_show, 0, 0), CVAR_VAR(s_testsound, 0, 0), CVAR_VAR(s_primary, 0, CVAR_ARCHIVE) //?? win32 specific CVAR_END Cvar_GetVars(ARRAY_ARG(vars)); appPrintf("\n------- Sound initialization -------\n"); if (!SNDDMA_Init()) return; RegisterCommand("play", S_Play_f); RegisterCommand("stopsound", S_StopAllSounds_f); RegisterCommand("soundlist", S_SoundList_f); RegisterCommand("soundinfo", S_SoundInfo_f); S_InitScaletable(); sound_started = true; num_sfx = 0; soundtime = 0; paintedtime = 0; appPrintf("sound sampling rate: %d\n", dma.speed); S_StopAllSounds_f(); appPrintf("------------------------------------\n"); } }
static void DecodeContents(int i) { static const flagInfo_t contentsNames[] = { #define T(name) {CONTENTS_##name, #name} T(SOLID), T(WINDOW), T(AUX), T(LAVA), T(SLIME), T(WATER), T(MIST), T(ALPHA), T(AREAPORTAL), T(PLAYERCLIP), T(MONSTERCLIP), T(CURRENT_0), T(CURRENT_90), T(CURRENT_180), T(CURRENT_270), T(CURRENT_UP), T(CURRENT_DOWN),T(ORIGIN), T(MONSTER), T(DEADMONSTER), T(DETAIL), T(TRANSLUCENT), T(LADDER) #undef T }; // RE_DrawTextLeft("Contents:", RGB(0.4, 0.4, 0.6)); // RE_DrawTextLeft("---------", RGB(0.4, 0.4, 0.6)); if (!i) RE_DrawTextLeft("CONTENTS_EMPTY", RGB(0.3, 0.6, 0.4)); else DrawFlag(i, ARRAY_ARG(contentsNames), "CONTENTS_"); }
static void ExportMaterial(UUnrealMaterial* Mat, FArchive& Ar, int index, bool bLast) { char dummyName[64]; appSprintf(ARRAY_ARG(dummyName), "dummy_material_%d", index); CVec3 Color = GetMaterialDebugColor(index); Ar.Printf( " {\n" " \"name\" : \"%s\",\n" " \"pbrMetallicRoughness\" : {\n" " \"baseColorFactor\" : [ %g, %g, %g, 1.0 ],\n" " \"metallicFactor\" : 0.1,\n" " \"roughnessFactor\" : 0.5\n" " }\n" " }%s\n", Mat ? Mat->Name : dummyName, Color[0], Color[1], Color[2], bLast ? "" : "," ); }
static void SV_ConnectionlessPacket() { guard(SV_ConnectionlessPacket); net_message.BeginReading(); MSG_ReadLong(&net_message); // skip the -1 marker const char *s = MSG_ReadString(&net_message); //?? "s" may be too long (userinfo etc) if (sv_shownet->integer) appPrintf("SV: packet from %s: %s\n", NET_AdrToString(&net_from), s); if (SV_AddressBanned(&net_from)) { Com_DPrintf("... banned\n"); return; } if (!ExecuteCommand(s, ARRAY_ARG(connectionlessCmds))) appWPrintf("Bad connectionless packet from %s: \"%s\"\n", NET_AdrToString(&net_from), s); unguard; }
void appNotify(const char *fmt, ...) { va_list argptr; va_start(argptr, fmt); char buf[4096]; int len = vsnprintf(ARRAY_ARG(buf), fmt, argptr); va_end(argptr); assert(len >= 0 && len < ARRAY_COUNT(buf) - 1); fflush(stdout); // a bit ugly code: printing the same thing into 3 streams // print to notify file if (FILE *f = fopen("notify.log", "a")) { if (NotifyBuf[0]) fprintf(f, "\n******** %s ********\n\n", NotifyBuf); fprintf(f, "%s\n", buf); fclose(f); } // print to log file if (GLogFile) { if (NotifyBuf[0]) fprintf(GLogFile, "******** %s ********\n", NotifyBuf); fprintf(GLogFile, "*** %s\n", buf); fflush(GLogFile); } // print to console if (NotifyBuf[0]) fprintf(stderr, "******** %s ********\n", NotifyBuf); fprintf(stderr, "*** %s\n", buf); fflush(stderr); // clean notify header NotifyBuf[0] = 0; }
static bool RegisterGameFile(const char *FullName, FVirtualFileSystem* parentVfs = NULL) { guard(RegisterGameFile); // printf("..file %s\n", FullName); // return false when MAX_GAME_FILES if (GNumGameFiles >= ARRAY_COUNT(GameFiles)) return false; if (!parentVfs) // no nested VFSs { const char* ext = strrchr(FullName, '.'); if (ext) { guard(MountVFS); ext++; FVirtualFileSystem* vfs = NULL; FArchive* reader = NULL; #if SUPPORT_ANDROID if (!stricmp(ext, "obb")) { GForcePlatform = PLATFORM_ANDROID; reader = new FFileReader(FullName); if (!reader) return true; reader->Game = GAME_UE3; vfs = new FObbVFS(); } #endif // SUPPORT_ANDROID #if UNREAL4 if (!stricmp(ext, "pak")) { reader = new FFileReader(FullName); if (!reader) return true; reader->Game = GAME_UE4; vfs = new FPakVFS(); //!! detect game by file name } #endif // UNREAL4 //!! process other VFS types here if (vfs) { assert(reader); // read VF directory if (!vfs->AttachReader(reader)) { // something goes wrong delete vfs; delete reader; return true; } // add game files int NumVFSFiles = vfs->NumFiles(); for (int i = 0; i < NumVFSFiles; i++) { if (!RegisterGameFile(vfs->FileName(i), vfs)) return false; } return true; } unguard; } } bool IsPackage; if (FindExtension(FullName, ARRAY_ARG(PackageExtensions))) { IsPackage = true; } else { #if HAS_SUPORT_FILES if (!FindExtension(FullName, ARRAY_ARG(KnownExtensions))) #endif { // perhaps this file was exported by our tool - skip it if (FindExtension(FullName, ARRAY_ARG(SkipExtensions))) return true; // unknown file type if (++GNumForeignFiles >= MAX_FOREIGN_FILES) appError("Too much unknown files - bad root directory (%s)?", RootDirectory); return true; } IsPackage = false; } // create entry CGameFileInfo *info = new CGameFileInfo; GameFiles[GNumGameFiles++] = info; info->IsPackage = IsPackage; info->FileSystem = parentVfs; if (IsPackage) GNumPackageFiles++; if (!parentVfs) { // regular file FILE* f = fopen(FullName, "rb"); if (f) { fseek(f, 0, SEEK_END); info->SizeInKb = (ftell(f) + 512) / 1024; fclose(f); } else { info->SizeInKb = 0; } // cut RootDirectory from filename const char *s = FullName + strlen(RootDirectory) + 1; assert(s[-1] == '/'); appStrncpyz(info->RelativeName, s, ARRAY_COUNT(info->RelativeName)); } else { // file in virtual file system info->SizeInKb = parentVfs->GetFileSize(FullName); appStrncpyz(info->RelativeName, FullName, ARRAY_COUNT(info->RelativeName)); } // find filename const char* s = strrchr(info->RelativeName, '/'); if (s) s++; else s = info->RelativeName; info->ShortFilename = s; // find extension s = strrchr(info->ShortFilename, '.'); if (s) s++; info->Extension = s; // printf(".. -> %s (pkg=%d)\n", info->ShortFilename, info->IsPackage); #if UNREAL3 if (info->IsPackage && (strnicmp(info->ShortFilename, "startup", 7) == 0)) { // Register a startup package // possible name variants: // - startup // - startup_int // - startup_* int startupWeight = 0; if (info->ShortFilename[7] == '.') startupWeight = 30; // "startup.upk" else if (strnicmp(info->ShortFilename+7, "_int.", 5) == 0) startupWeight = 20; // "startup_int.upk" else if (strnicmp(info->ShortFilename+7, "_loc_int.", 9) == 0) startupWeight = 20; // "startup_int.upk" else if (info->ShortFilename[7] == '_') startupWeight = 1; // non-int locale, lower priority - use if when other is not detected if (startupWeight > GStartupPackageInfoWeight) { GStartupPackageInfoWeight = startupWeight; GStartupPackageInfo = info; } } #endif // UNREAL3 // insert CGameFileInfo into hash table int hash = GetHashForFileName(info->ShortFilename); info->HashNext = GGameFileHash[hash]; GGameFileHash[hash] = info; return true; unguardf("%s", FullName); }
static void ExportAnimations(ExportContext& Context, FArchive& Ar) { guard(ExportAnimations); const CAnimSet* Anim = Context.SkelMesh->Anim; int NumBones = Context.SkelMesh->RefSkeleton.Num(); // Build mesh to anim bone map TArray<int> BoneMap; BoneMap.Init(-1, NumBones); TArray<int> AnimBones; AnimBones.Empty(NumBones); for (int i = 0; i < NumBones; i++) { const CSkelMeshBone &B = Context.SkelMesh->RefSkeleton[i]; for (int j = 0; j < Anim->TrackBoneNames.Num(); j++) { if (!stricmp(B.Name, Anim->TrackBoneNames[j])) { BoneMap[i] = j; // lookup CAnimSet bone by mesh bone index AnimBones.Add(i); // indicate that the bone has animation break; } } } Ar.Printf( " \"animations\" : [\n" ); int FirstDataIndex = Context.Data.Num(); // Iterate over all animations for (int SeqIndex = 0; SeqIndex < Anim->Sequences.Num(); SeqIndex++) { const CAnimSequence &Seq = *Anim->Sequences[SeqIndex]; Ar.Printf( " {\n" " \"name\" : \"%s\",\n", *Seq.Name ); struct AnimSampler { enum ChannelType { TRANSLATION, ROTATION }; int BoneNodeIndex; ChannelType Type; const CAnimTrack* Track; }; TArray<AnimSampler> Samplers; Samplers.Empty(AnimBones.Num() * 2); //!! Optimization: //!! 1. there will be missing tracks (AnimRotationOnly etc) - drop such samplers //!! 2. store all time tracks in a single BufferView, all rotation tracks in another, and all position track in 3rd one - this //!! will reduce amount of BufferViews in json text (combine them by data type) // Prepare channels array Ar.Printf(" \"channels\" : [\n"); for (int BoneIndex = 0; BoneIndex < AnimBones.Num(); BoneIndex++) { int MeshBoneIndex = AnimBones[BoneIndex]; int AnimBoneIndex = BoneMap[MeshBoneIndex]; const CAnimTrack* Track = Seq.Tracks[AnimBoneIndex]; int TranslationSamplerIndex = Samplers.Num(); AnimSampler* Sampler = new (Samplers) AnimSampler; Sampler->Type = AnimSampler::TRANSLATION; Sampler->BoneNodeIndex = MeshBoneIndex + FIRST_BONE_NODE; Sampler->Track = Track; int RotationSamplerIndex = Samplers.Num(); Sampler = new (Samplers) AnimSampler; Sampler->Type = AnimSampler::ROTATION; Sampler->BoneNodeIndex = MeshBoneIndex + FIRST_BONE_NODE; Sampler->Track = Track; // Print glTF information. Not using usual formatting here to make output a little bit more compact. Ar.Printf( " { \"sampler\" : %d, \"target\" : { \"node\" : %d, \"path\" : \"%s\" } },\n", TranslationSamplerIndex, MeshBoneIndex + FIRST_BONE_NODE, "translation" ); Ar.Printf( " { \"sampler\" : %d, \"target\" : { \"node\" : %d, \"path\" : \"%s\" } }%s\n", RotationSamplerIndex, MeshBoneIndex + FIRST_BONE_NODE, "rotation", BoneIndex == AnimBones.Num()-1 ? "" : "," ); } Ar.Printf(" ],\n"); // Prepare samplers Ar.Printf(" \"samplers\" : [\n"); for (int SamplerIndex = 0; SamplerIndex < Samplers.Num(); SamplerIndex++) { const AnimSampler& Sampler = Samplers[SamplerIndex]; // Prepare time array const TArray<float>* TimeArray = (Sampler.Type == AnimSampler::TRANSLATION) ? &Sampler.Track->KeyPosTime : &Sampler.Track->KeyQuatTime; if (TimeArray->Num() == 0) { // For this situation, use track's time array TimeArray = &Sampler.Track->KeyTime; } int NumKeys = Sampler.Type == (AnimSampler::TRANSLATION) ? Sampler.Track->KeyPos.Num() : Sampler.Track->KeyQuat.Num(); int TimeBufIndex = Context.Data.AddZeroed(); BufferData& TimeBuf = Context.Data[TimeBufIndex]; TimeBuf.Setup(NumKeys, "SCALAR", BufferData::FLOAT, sizeof(float)); float RateScale = 1.0f / Seq.Rate; float LastFrameTime = 0; if (TimeArray->Num() == 0 || NumKeys == 1) { // Fill with equally spaced values for (int i = 0; i < NumKeys; i++) { TimeBuf.Put(i * RateScale); } LastFrameTime = NumKeys-1; } else { for (int i = 0; i < TimeArray->Num(); i++) { TimeBuf.Put((*TimeArray)[i] * RateScale); } LastFrameTime = (*TimeArray)[TimeArray->Num()-1]; } // Prepare min/max values for time track, it's required by glTF standard TimeBuf.BoundsMin = "[ 0 ]"; char buf[64]; appSprintf(ARRAY_ARG(buf), "[ %g ]", LastFrameTime * RateScale); TimeBuf.BoundsMax = buf; // Try to reuse TimeBuf from previous tracks TimeBufIndex = Context.GetFinalIndexForLastBlock(FirstDataIndex); // Prepare data int DataBufIndex = Context.Data.AddZeroed(); BufferData& DataBuf = Context.Data[DataBufIndex]; if (Sampler.Type == AnimSampler::TRANSLATION) { // Translation track DataBuf.Setup(NumKeys, "VEC3", BufferData::FLOAT, sizeof(CVec3)); for (int i = 0; i < NumKeys; i++) { CVec3 Pos = Sampler.Track->KeyPos[i]; TransformPosition(Pos); DataBuf.Put(Pos); } } else { // Rotation track DataBuf.Setup(NumKeys, "VEC4", BufferData::FLOAT, sizeof(CQuat)); for (int i = 0; i < NumKeys; i++) { CQuat Rot = Sampler.Track->KeyQuat[i]; TransformRotation(Rot); if (Sampler.BoneNodeIndex - FIRST_BONE_NODE == 0) { Rot.Conjugate(); } DataBuf.Put(Rot); } } // Try to reuse data block as well DataBufIndex = Context.GetFinalIndexForLastBlock(FirstDataIndex); // Write glTF info Ar.Printf( " { \"input\" : %d, \"output\" : %d }%s\n", TimeBufIndex, DataBufIndex, SamplerIndex == Samplers.Num()-1 ? "" : "," ); } Ar.Printf(" ]\n"); Ar.Printf(" }%s\n", SeqIndex == Anim->Sequences.Num()-1 ? "" : ","); } Ar.Printf(" ],\n"); unguard; }
static void ExportSection(ExportContext& Context, const CBaseMeshLod& Lod, const CMeshVertex* Verts, int SectonIndex, FArchive& Ar) { guard(ExportSection); int VertexSize = Context.IsSkeletal() ? sizeof(CSkelMeshVertex) : sizeof(CStaticMeshVertex); const CMeshSection& S = Lod.Sections[SectonIndex]; bool bLast = (SectonIndex == Lod.Sections.Num()-1); // Remap section indices to local indices CIndexBuffer::IndexAccessor_t GetIndex = Lod.Indices.GetAccessor(); TArray<int> indexRemap; // old vertex index -> new vertex index indexRemap.Init(-1, Lod.NumVerts); int numLocalVerts = 0; int numLocalIndices = S.NumFaces * 3; for (int idx = 0; idx < numLocalIndices; idx++) { int vertIndex = GetIndex(S.FirstIndex + idx); if (indexRemap[vertIndex] == -1) { indexRemap[vertIndex] = numLocalVerts++; } } // Prepare buffers int IndexBufIndex = Context.Data.AddZeroed(); int PositionBufIndex = Context.Data.AddZeroed(); int NormalBufIndex = Context.Data.AddZeroed(); int TangentBufIndex = Context.Data.AddZeroed(); int BonesBufIndex = -1; int WeightsBufIndex = -1; if (Context.IsSkeletal()) { BonesBufIndex = Context.Data.AddZeroed(); WeightsBufIndex = Context.Data.AddZeroed(); } int UVBufIndex[MAX_MESH_UV_SETS]; for (int i = 0; i < Lod.NumTexCoords; i++) { UVBufIndex[i] = Context.Data.AddZeroed(); } BufferData& IndexBuf = Context.Data[IndexBufIndex]; BufferData& PositionBuf = Context.Data[PositionBufIndex]; BufferData& NormalBuf = Context.Data[NormalBufIndex]; BufferData& TangentBuf = Context.Data[TangentBufIndex]; BufferData* UVBuf[MAX_MESH_UV_SETS]; BufferData* BonesBuf = NULL; BufferData* WeightsBuf = NULL; PositionBuf.Setup(numLocalVerts, "VEC3", BufferData::FLOAT, sizeof(CVec3)); NormalBuf.Setup(numLocalVerts, "VEC3", BufferData::FLOAT, sizeof(CVec3)); TangentBuf.Setup(numLocalVerts, "VEC4", BufferData::FLOAT, sizeof(CVec4)); for (int i = 0; i < Lod.NumTexCoords; i++) { UVBuf[i] = &Context.Data[UVBufIndex[i]]; UVBuf[i]->Setup(numLocalVerts, "VEC2", BufferData::FLOAT, sizeof(CMeshUVFloat)); } if (Context.IsSkeletal()) { BonesBuf = &Context.Data[BonesBufIndex]; WeightsBuf = &Context.Data[WeightsBufIndex]; BonesBuf->Setup(numLocalVerts, "VEC4", BufferData::UNSIGNED_SHORT, sizeof(uint16)*4); WeightsBuf->Setup(numLocalVerts, "VEC4", BufferData::UNSIGNED_BYTE, sizeof(uint32), /*InNormalized=*/ true); } // Prepare and build indices TArray<int> localIndices; localIndices.AddUninitialized(numLocalIndices); int* pIndex = localIndices.GetData(); for (int i = 0; i < numLocalIndices; i++) { *pIndex++ = GetIndex(S.FirstIndex + i); } if (numLocalVerts <= 65536) { IndexBuf.Setup(numLocalIndices, "SCALAR", BufferData::UNSIGNED_SHORT, sizeof(uint16)); for (int idx = 0; idx < numLocalIndices; idx++) { IndexBuf.Put<uint16>(indexRemap[localIndices[idx]]); } } else { IndexBuf.Setup(numLocalIndices, "SCALAR", BufferData::UNSIGNED_INT, sizeof(uint32)); for (int idx = 0; idx < numLocalIndices; idx++) { IndexBuf.Put<uint32>(indexRemap[localIndices[idx]]); } } // Build reverse index map for fast lookup of vertex by its new index. // It maps new vertex index to old vertex index. TArray<int> revIndexMap; revIndexMap.AddUninitialized(numLocalVerts); for (int i = 0; i < indexRemap.Num(); i++) { int newIndex = indexRemap[i]; if (newIndex != -1) { revIndexMap[newIndex] = i; } } // Build vertices for (int i = 0; i < numLocalVerts; i++) { int vertIndex = revIndexMap[i]; const CMeshVertex& V = VERT(vertIndex); CVec3 Position = V.Position; CVec4 Normal, Tangent; Unpack(Normal, V.Normal); Unpack(Tangent, V.Tangent); // Unreal (and we are) using normal.w for computing binormal. glTF // uses tangent.w for that. Make this value exactly 1.0 of -1.0 to make glTF // validator happy. #if 0 // There's some problem: V.Normal.W == 0x80 -> -1.008 instead of -1.0 if (Normal.w > 1.001 || Normal.w < -1.001) { appError("%X -> %g\n", V.Normal.Data, Normal.w); } #endif Tangent.w = (Normal.w < 0) ? -1 : 1; TransformPosition(Position); TransformDirection(Normal); TransformDirection(Tangent); Normal.Normalize(); Tangent.Normalize(); // Fill buffers PositionBuf.Put(Position); NormalBuf.Put(Normal.xyz); TangentBuf.Put(Tangent); UVBuf[0]->Put(V.UV); } // Compute bounds for PositionBuf CVec3 Mins, Maxs; ComputeBounds((CVec3*)PositionBuf.Data, numLocalVerts, sizeof(CVec3), Mins, Maxs); char buf[256]; appSprintf(ARRAY_ARG(buf), "[ %g, %g, %g ]", VECTOR_ARG(Mins)); PositionBuf.BoundsMin = buf; appSprintf(ARRAY_ARG(buf), "[ %g, %g, %g ]", VECTOR_ARG(Maxs)); PositionBuf.BoundsMax = buf; if (Context.IsSkeletal()) { for (int i = 0; i < numLocalVerts; i++) { int vertIndex = revIndexMap[i]; const CMeshVertex& V0 = VERT(vertIndex); const CSkelMeshVertex& V = static_cast<const CSkelMeshVertex&>(V0); int16 Bones[NUM_INFLUENCES]; static_assert(NUM_INFLUENCES == 4, "Code designed for 4 influences"); static_assert(sizeof(Bones) == sizeof(V.Bone), "Unexpected V.Bones size"); memcpy(Bones, V.Bone, sizeof(Bones)); for (int j = 0; j < NUM_INFLUENCES; j++) { // We have INDEX_NONE as list terminator, should replace with something else for glTF if (Bones[j] == INDEX_NONE) { Bones[j] = 0; } } BonesBuf->Put(*(uint64*)&Bones); WeightsBuf->Put(V.PackedWeights); } } // Secondary UVs for (int uvIndex = 1; uvIndex < Lod.NumTexCoords; uvIndex++) { BufferData* pBuf = UVBuf[uvIndex]; const CMeshUVFloat* srcUV = Lod.ExtraUV[uvIndex-1]; for (int i = 0; i < numLocalVerts; i++) { int vertIndex = revIndexMap[i]; pBuf->Put(srcUV[vertIndex]); } } // Write primitive information to json Ar.Printf( " {\n" " \"attributes\" : {\n" " \"POSITION\" : %d,\n" " \"NORMAL\" : %d,\n" " \"TANGENT\" : %d,\n", PositionBufIndex, NormalBufIndex, TangentBufIndex ); if (Context.IsSkeletal()) { Ar.Printf( " \"JOINTS_0\" : %d,\n" " \"WEIGHTS_0\" : %d,\n", BonesBufIndex, WeightsBufIndex ); } for (int i = 0; i < Lod.NumTexCoords; i++) { Ar.Printf( " \"TEXCOORD_%d\" : %d%s\n", i, UVBufIndex[i], i < (Lod.NumTexCoords-1) ? "," : "" ); } Ar.Printf( " },\n" " \"indices\" : %d,\n" " \"material\" : %d\n" " }%s\n", IndexBufIndex, SectonIndex, SectonIndex < (Lod.Sections.Num()-1) ? "," : "" ); unguard; }
static void SV_ClipMoveToEntities(trace_t &trace, const CVec3 &start, const CVec3 &end, const CBox &bounds, edict_t *passedict, int contentmask) { guard(SV_ClipMoveToEntities); if (trace.allsolid) return; int i; CVec3 amins, amaxs; for (i = 0; i < 3; i++) { if (start[i] < end[i]) { amins[i] = bounds.mins[i] + start[i]; amaxs[i] = bounds.maxs[i] + end[i]; } else { amins[i] = bounds.mins[i] + end[i]; amaxs[i] = bounds.maxs[i] + start[i]; } } edict_t *list[MAX_EDICTS]; int num = SV_AreaEdicts(amins, amaxs, ARRAY_ARG(list), AREA_SOLID); if (!num) return; float b1 = dot(bounds.mins, bounds.mins); float b2 = dot(bounds.maxs, bounds.maxs); float t = max(b1, b2); float traceWidth = SQRTFAST(t); CVec3 traceDir; VectorSubtract(end, start, traceDir); float traceLen = traceDir.Normalize() + traceWidth; for (i = 0; i < num; i++) { edict_t *edict = list[i]; entityHull_t &ent = ents[NUM_FOR_EDICT(edict)]; // if (!ent->linked) continue; if (edict->solid == SOLID_NOT || edict == passedict) continue; if (passedict) { if (edict->owner == passedict) continue; // don't clip against own missiles if (passedict->owner == edict) continue; // don't clip against owner } if (!(contentmask & CONTENTS_DEADMONSTER) && (edict->svflags & SVF_DEADMONSTER)) continue; CVec3 eCenter; VectorSubtract(ent.center, start, eCenter); // check position of point projection on line float entPos = dot(eCenter, traceDir); if (entPos < -traceWidth - ent.radius || entPos > traceLen + ent.radius) continue; // too near / too far // check distance between point and line CVec3 tmp; VectorMA(eCenter, -entPos, traceDir, tmp); float dist2 = dot(tmp, tmp); float dist0 = ent.radius + traceWidth; if (dist2 >= dist0 * dist0) continue; trace_t tr; if (ent.model) CM_TransformedBoxTrace(tr, start, end, bounds, ent.model->headnode, contentmask, edict->s.origin, ent.axis); else CM_TransformedBoxTrace(tr, start, end, bounds, CM_HeadnodeForBox(ent.bounds), contentmask, edict->s.origin, nullVec3); if (CM_CombineTrace(trace, tr)) trace.ent = edict; if (trace.allsolid) return; } unguard; }
void SV_LinkEdict(edict_t *ent) { guard(SV_LinkEdict); int i, j, k; //!! HOOK - move outside ? if (bspfile.type != map_q2) { if (ent->s.modelindex >= 0 && ent->s.modelindex < MAX_MODELS) { // link model hook const char *modelName = sv.configstrings[CS_MODELS + ent->s.modelindex]; if (!strcmp(modelName, "models/objects/dmspot/tris.md2")) { // teleporter (source+target) was found // appPrintf(S_CYAN"teleport: %g %g %g\n", VECTOR_ARG(ent->s.origin)); return; // simply do not link model; trigger entity will be added anyway } } } if (ent->area.prev) SV_UnlinkEdict(ent); // unlink from old position (i.e. relink edict) if (ent == ge->edicts) return; // don't add the world if (!ent->inuse) return; entityHull_t &ex = ents[NUM_FOR_EDICT(ent)]; memset(&ex, 0, sizeof(entityHull_t)); ex.owner = ent; ex.axis.FromEuler(ent->s.angles); // set the size VectorSubtract(ent->bounds.maxs, ent->bounds.mins, ent->size); // encode the size into the entity_state for client prediction if (ent->solid == SOLID_BBOX) { // assume that x/y are equal and symetric i = appRound(ent->bounds.maxs[0] / 8); // z is not symetric j = appRound(-ent->bounds.mins[2] / 8); // and z maxs can be negative... k = appRound((ent->bounds.maxs[2] + 32) / 8); // original Q2 have bounded i/j/k/ with lower margin==1 (for client prediction only); this will // produce incorrect collision test when bbox mins/maxs is (0,0,0) i = bound(i, 0, 31); // mins/maxs[0,1] range is -248..0/0..248 j = bound(j, 0, 31); // mins[2] range is [-248..0] k = bound(k, 0, 63); // maxs[2] range is [-32..472] // if SVF_DEADMONSTER, s.solid should be 0 ent->s.solid = (ent->svflags & SVF_DEADMONSTER) ? 0 : (k<<10) | (j<<5) | i; i *= 8; j *= 8; k *= 8; ex.bounds.mins.Set(-i, -i, -j); ex.bounds.maxs.Set(i, i, k - 32); ex.bounds.GetCenter(ex.center); ex.center.Add(ent->s.origin); ex.model = NULL; ex.radius = VectorDistance(ex.bounds.maxs, ex.bounds.mins) / 2; } else if (ent->solid == SOLID_BSP) { ex.model = sv.models[ent->s.modelindex]; if (!ex.model) Com_DropError("MOVETYPE_PUSH with a non bsp model"); CVec3 v; ex.model->bounds.GetCenter(v); UnTransformPoint(ent->s.origin, ex.axis, v, ex.center); ex.radius = ex.model->radius; ent->s.solid = 31; // a SOLID_BBOX will never create this value (mins=(-248,-248,0) maxs=(248,248,-32)) } else if (ent->solid == SOLID_TRIGGER) { ent->s.solid = 0; // check for model link ex.model = sv.models[ent->s.modelindex]; if (!ex.model) { // model not attached by game, check entstring //?? can optimize: add 'bool spawningEnts', set to 'true' before SpawnEntities() //?? and 'false' after; skip code below when 'false' for (triggerModelLink_t *link = bspfile.modelLinks; link; link = link->next) #define CMP(n) (fabs(ent->s.origin[n] - link->origin[n]) < 0.5f) if (CMP(0) && CMP(1) && CMP(2)) { CBspModel *model = CM_InlineModel(link->modelIdx); VectorSubtract(model->bounds.maxs, ent->s.origin, ent->bounds.maxs); VectorSubtract(model->bounds.mins, ent->s.origin, ent->bounds.mins); break; } #undef CMP } } else ent->s.solid = 0; // set the abs box if (ent->solid == SOLID_BSP && (ent->s.angles[0] || ent->s.angles[1] || ent->s.angles[2])) { // expand for rotation for (i = 0; i < 3 ; i++) { ent->absBounds.mins[i] = ex.center[i] - ex.radius; ent->absBounds.maxs[i] = ex.center[i] + ex.radius; } } else { // normal VectorAdd(ent->s.origin, ent->bounds.mins, ent->absBounds.mins); VectorAdd(ent->s.origin, ent->bounds.maxs, ent->absBounds.maxs); } // because movement is clipped an epsilon away from an actual edge, // we must fully check even when bounding boxes don't quite touch for (i = 0; i < 3; i++) { ent->absBounds.mins[i] -= 1; ent->absBounds.maxs[i] += 1; } // link to PVS leafs ent->num_clusters = 0; ent->zonenum = 0; ent->zonenum2 = 0; // get all leafs, including solids CBspLeaf *leafs[MAX_TOTAL_ENT_LEAFS]; int topnode; int num_leafs = CM_BoxLeafs(ent->absBounds, ARRAY_ARG(leafs), &topnode); // set zones int clusters[MAX_TOTAL_ENT_LEAFS]; for (i = 0; i < num_leafs; i++) { clusters[i] = leafs[i]->cluster; int zone = leafs[i]->zone; if (zone) { // doors may legally straggle two zones, // but nothing should evern need more than that if (ent->zonenum && ent->zonenum != zone) { if (ent->zonenum2 && ent->zonenum2 != zone && sv.state == ss_loading) Com_DPrintf("Object touching 3 zones at %g %g %g\n", VECTOR_ARG(ent->absBounds.mins)); ent->zonenum2 = zone; } else ent->zonenum = zone; } } if (num_leafs >= MAX_TOTAL_ENT_LEAFS) { // assume we missed some leafs, and mark by headnode ent->num_clusters = -1; ent->headnode = topnode; } else { ent->num_clusters = 0; for (i = 0; i < num_leafs; i++) { if (clusters[i] == -1) continue; // not a visible leaf for (j = 0; j < i; j++) if (clusters[j] == clusters[i]) break; if (j == i) { if (ent->num_clusters == MAX_ENT_CLUSTERS) { // assume we missed some leafs, and mark by headnode ent->num_clusters = -1; ent->headnode = topnode; break; } ent->clusternums[ent->num_clusters++] = clusters[i]; } } } // if first time, make sure old_origin is valid if (!ent->linkcount) ent->s.old_origin = ent->s.origin; ent->linkcount++; if (ent->solid == SOLID_NOT) return; // find the first node that the ent's box crosses areanode_t *node = areaNodes; while (node->axis != -1) { if (ent->absBounds.mins[node->axis] > node->dist) node = node->children[0]; else if (ent->absBounds.maxs[node->axis] < node->dist) node = node->children[1]; else break; // crosses the node } // link it in areanode_t *node2 = node; if (ent->solid == SOLID_TRIGGER) { InsertLinkBefore(ent->area, node->trigEdicts); for ( ; node2; node2 = node2->parent) node2->numTrigEdicts++; } else { InsertLinkBefore(ent->area, node->solidEdicts); for ( ; node2; node2 = node2->parent) node2->numSolidEdicts++; } ex.area = node; unguard; }