From c367fdc8445a33ca3f112c6bfb17f24f20f07a50 Mon Sep 17 00:00:00 2001 From: MotoLegacy Date: Sun, 24 Nov 2024 17:34:06 -0800 Subject: [PATCH] CLIENT: Add automatic Q3 shader generation for alpha transparency --- source/client/defs/fte.qc | 2 +- source/client/main.qc | 100 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/source/client/defs/fte.qc b/source/client/defs/fte.qc index 470322d..aa3aaa5 100644 --- a/source/client/defs/fte.qc +++ b/source/client/defs/fte.qc @@ -2093,7 +2093,7 @@ vector(entity e, float s, vector p) getsurfaceclippedpoint = #439; /* Part of DP float(string s) tokenize = #441; /* Part of KRIMZON_SV_PARSECLIENTCOMMAND*/ string(float n) argv = #442; /* Part of KRIMZON_SV_PARSECLIENTCOMMAND*/ void(entity e, entity tagentity, string tagname) setattachment = #443; /* Part of DP_GFX_QUAKE3MODELTAGS*/ -searchhandle(string pattern, enumflags:float{SB_CASEINSENSITIVE=1<<0,SB_FULLPACKAGEPATH=1<<1,SB_ALLOWDUPES=1<<2,SB_FORCESEARCH=1<<3} flags, float quiet, optional string filterpackage) search_begin = #444; /* Part of DP_QC_FS_SEARCH, DP_QC_FS_SEARCH_PACKFILE +searchhandle(string pattern, enumflags:float{SB_CASEINSENSITIVE=1<<0,SB_FULLPACKAGEPATH=1<<1,SB_ALLOWDUPES=1<<2,SB_FORCESEARCH=1<<3,SB_MULTISEARCH=1<<4} flags, float quiet, optional string filterpackage) search_begin = #444; /* Part of DP_QC_FS_SEARCH, DP_QC_FS_SEARCH_PACKFILE initiate a filesystem scan based upon filenames. Be sure to call search_end on the returned handle. SB_FULLPACKAGEPATH interprets the filterpackage arg as a full package path to avoid gamedir ambiguity, equivelent to whichpack's WP_FULLPACKAGEPATH flag. SB_ALLOWDUPES allows returning multiple entries with the same name (but different package, useful with search_fopen). SB_FORCESEARCH requires use of the filterpackage and SB_FULLPACKAGEPATH flag, initiating searches from gamedirs/packages which are not currently active. */ void(searchhandle handle) search_end = #445; /* Part of DP_QC_FS_SEARCH, DP_QC_FS_SEARCH_PACKFILE*/ diff --git a/source/client/main.qc b/source/client/main.qc index facd443..f873bc5 100644 --- a/source/client/main.qc +++ b/source/client/main.qc @@ -25,6 +25,8 @@ */ +float need_vid_reload; + void() ToggleMenu = { if(serverkey("constate") != "disconnected") @@ -75,6 +77,95 @@ float(float isnew) SetZombieSkinning = return PREDRAW_NEXT; }; +// +// GenerateAlphaTransparencyQ3Shaders() +// What a mouth-full! Anyway, NZ:P supports +// alpha transparency via a pseudo-hack where +// if the last character of a model is "$" +// we render with special blend modes. To +// do the same on FTE, we need to generate +// Quake III "shader" files. Returns TRUE +// if we had shader modifications. +// +float() GenerateAlphaTransparencyQ3Shaders = +{ + searchhandle alias_models; + float amod_count; + float need_reload = false; + + alias_models = search_begin("*.mdl:*/*.mdl:*/*/*.mdl:*/*/*/*.mdl:*/*/*/*/*.mdl:*/*/*/*/*/*.mdl", SB_CASEINSENSITIVE | SB_MULTISEARCH, true); // gross. + amod_count = search_getsize(alias_models); + + for (float i = 0; i < amod_count; i++) { + string full_path = search_getfilename(alias_models, i); + + // Single out character before ".mdl" extension. + string special_character = substring(full_path, strlen(full_path) - 5, 1); + + // Early out, not a special guy. + if (special_character != "$") + continue; + + // Isolate its basename.. manually :( + string basename = ""; + for (float j = strlen(full_path); j > 0; j--) { + if (str2chr(full_path, j) == str2chr("/", 0)) { + // Strip path + basename = substring(full_path, j + 1, strlen(full_path) - (j + 1)); + + // Strip extension + basename = substring(basename, 0, strlen(basename) - 4); + break; + } + } + + if (basename == "") { + print(sprintf("[ERROR]: Unable to calculate basename for [%s]!\n", full_path)); + continue; + } + + float shader_file; + string shader_path = sprintf("scripts/%s.shader", basename); + + // Check if the shader already exists. + shader_file = fopen(shader_path, FILE_READ); + if (shader_file != -1) { + fclose(shader_file); + continue; + } + + // Begin to write. + shader_file = fopen(shader_path, FILE_WRITE); + if (shader_file == -1) { + print(sprintf("[ERROR]: Unable to generate Q3 shader for [%s]!\n", full_path)); + continue; + } + + // Body of our shader file we're writing. + string shader_content = sprintf( + "//\n" + "// Quake III Shader generated automatically by Nazi Zombies: Portable. Do not modify.\n" + "//\n" + "\n" + "%s_0.lmp\n" // full_path + "{\n" + " program defaultskin\n" + " progblendfunc GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA\n" + " diffusemap %s_0.tga\n" // full_path + " alphafunc ge128\n" + "}\n" + , full_path, full_path); + + fputs(shader_file, shader_content); + fclose(shader_file); + + need_reload = true; + } + + search_end(alias_models); + return need_reload; +}; + noref void(float apiver, string enginename, float enginever) CSQC_Init = { setwindowcaption("Nazi Zombies: Portable"); @@ -192,10 +283,19 @@ noref void(float apiver, string enginename, float enginever) CSQC_Init = } InitKerningMap(); + + // If we've made shader changes, we should perform + // a vid_reload at a reasonable time. + need_vid_reload = GenerateAlphaTransparencyQ3Shaders(); }; noref void() CSQC_WorldLoaded = { + if (need_vid_reload == true) { + need_vid_reload = false; + localcmd("vid_reload\n"); + } + Achievement_Init(); Particles_Init(); nameprint_time = time + 8;