Search the Community
Showing results for '/tags/forums/reason/'.
-
Actually, I hoped @Arcturus would explain, or someone else who was so positive about it. In my opinion, the only reason is to avoid hard clamping on over-bright colors. This situation rarely happens on existing mission, because mappers usually avoid that much light. But if you look at golden plates with a candle nearby and enable lantern, or gather several candles at the same location, then it happens. I can probably do something to fix the change in hue, but reduced contrast is totally inevitable I think.
- 79 replies
-
- color management
- tonemapping
-
(and 4 more)
Tagged with:
-
Yes, I guess it makes sense to use addon name as namespace on function calls. But as for function declarations --- a C macro can be used to avoid copy/pasting, just like everyone does for __declspec(dllexport). This will probably keep compiler changes minimal? As far as I see, the only reason you use doom scripts is that they allow to define interfaces dynamically. Rather limited interfaces, but enough for simple cases. If you wanted to implement a specific plugin, you would just write C++ header and compile TDM engine against it. But you don't know what exactly you want to implement, so you write interface dynamically in doom script header. And of course it is easier to call functions from such interface from doom script, although you can in principle call the functions directly from C++. However, the problem you'll face quickly when you try to make useful addons is that you are pretty limited on what you can customize without access to more stuff that is available to doom script interface. Which again raises a question: do we really need this? Will we have enough useful addons implemented? The Doom script header which contains the interface of the addon should definitely be provided together with the addon. Whether they are in the same PK4 and where they should be physically located is not that important. Doom script header plays the same role for an addon as public headers do for the DLL. If you want to distribute a DLL, you distribute them along with headers (and import libs on Windows), and users are expected to use them. I'm not sure I understand what you are talking about. There is doom script header which provides the interface. The compiler in TDM verifies that calls from doom scripts pass the arguments according to this signature. The implementation of addon should accept the parameters according to the signature in header as well. If addon creator is not sure in himself, he can implement checks on his side. The only issue which may happen is that the addon was built against/for a different version of interface. But the same problem exists in C/C++ world: you might accidentally get a DLL at runtime which does not match header/import libs you used when you build the problem. And there is rather standard conventional solution for this. Put "#define MYPLUGIN_VERSION 3" in doom script header, and add exported function "int getVersion()" there as well. Then also add a function "void initMyPlugin()" in doom script header, which calls getVersion and checks that it returns the number equal to the macro, and stops with error if that's not true. This way you can detect version mismatch between script header and built addon if you really want to avoid crashes, no extra systems/changes required.
- 25 replies
-
the code pieces that needs modifying are these -> /* ================== RB_GLSL_DrawInteraction ================== */ static void RB_GLSL_DrawInteraction( const drawInteraction_t *din ) { /* Half Lambertian constants */ static const float whalf[] = { 0.0f, 0.0f, 0.0f, 0.5f }; static const float wzero[] = { 0.0f, 0.0f, 0.0f, 0.0f }; static const float wone[] = { 0.0f, 0.0f, 0.0f, 1.0f }; // load all the vertex program parameters qglUniform4fv( u_light_origin, 1, din->localLightOrigin.ToFloatPtr() ); qglUniform4fv( u_view_origin, 1, din->localViewOrigin.ToFloatPtr() ); qglUniformMatrix2x4fv( u_diffMatrix, 1, GL_FALSE, RB_GLSL_MakeMatrix( DIFFMATRIX( 0 ), DIFFMATRIX( 1 ) ) ); qglUniformMatrix2x4fv( u_bumpMatrix, 1, GL_FALSE, RB_GLSL_MakeMatrix( BUMPMATRIX( 0 ), BUMPMATRIX( 1 ) ) ); qglUniformMatrix2x4fv( u_specMatrix, 1, GL_FALSE, RB_GLSL_MakeMatrix( SPECMATRIX( 0 ), SPECMATRIX( 1 ) ) ); qglUniformMatrix4fv( u_projMatrix, 1, GL_FALSE, RB_GLSL_MakeMatrix( PROJMATRIX( 0 ), PROJMATRIX( 1 ), wzero, PROJMATRIX( 2 ) ) ); qglUniformMatrix4fv( u_fallMatrix, 1, GL_FALSE, RB_GLSL_MakeMatrix( PROJMATRIX( 3 ), whalf, wzero, wone ) ); /* Lambertian constants */ static const float zero[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; static const float one[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; static const float negOne[4] = { -1.0f, -1.0f, -1.0f, -1.0f }; switch ( din->vertexColor ) { case SVC_IGNORE: qglUniform4fv( u_color_modulate, 1, zero ); qglUniform4fv( u_color_add, 1, one ); break; case SVC_MODULATE: qglUniform4fv( u_color_modulate, 1, one ); qglUniform4fv( u_color_add, 1, zero ); break; case SVC_INVERSE_MODULATE: qglUniform4fv( u_color_modulate, 1, negOne ); qglUniform4fv( u_color_add, 1, one ); break; } // set the constant colors qglUniform4fv( u_constant_diffuse, 1, din->diffuseColor.ToFloatPtr() ); qglUniform4fv( u_constant_specular, 1, din->specularColor.ToFloatPtr() ); // TODO: shader gamma for GLSL. // set the textures RB_ARB2_BindInteractionTextureSet( din ); // draw it RB_DrawElementsWithCounters( din->surf->geo ); } /* ================== RB_ARB2_DrawInteraction ================== */ static void RB_ARB2_DrawInteraction( const drawInteraction_t *din ) { // load all the vertex program parameters qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_ORIGIN, din->localLightOrigin.ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_VIEW_ORIGIN, din->localViewOrigin.ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_S, din->lightProjection[0].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_T, din->lightProjection[1].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_Q, din->lightProjection[2].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_FALLOFF_S, din->lightProjection[3].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_BUMP_MATRIX_S, din->bumpMatrix[0].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_BUMP_MATRIX_T, din->bumpMatrix[1].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_S, din->diffuseMatrix[0].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_T, din->diffuseMatrix[1].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_SPECULAR_MATRIX_S, din->specularMatrix[0].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_SPECULAR_MATRIX_T, din->specularMatrix[1].ToFloatPtr() ); // testing fragment based normal mapping if ( r_testARBProgram.GetBool() ) { qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 2, din->localLightOrigin.ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 3, din->localViewOrigin.ToFloatPtr() ); } static const float zero[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; static const float one[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; static const float negOne[4] = { -1.0f, -1.0f, -1.0f, -1.0f }; switch ( din->vertexColor ) { case SVC_IGNORE: qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, zero ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_ADD, one ); break; case SVC_MODULATE: qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, one ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_ADD, zero ); break; case SVC_INVERSE_MODULATE: qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, negOne ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_ADD, one ); break; } // set the constant colors qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, din->diffuseColor.ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 1, din->specularColor.ToFloatPtr() ); // DG: brightness and gamma in shader as program.env[4] if ( r_gammaInShader.GetBool() ) { // program.env[4].xyz are all r_brightness, program.env[4].w is 1.0f / r_gamma float parm[4]; parm[0] = parm[1] = parm[2] = r_brightness.GetFloat(); parm[3] = 1.0f / r_gamma.GetFloat(); // 1.0f / gamma so the shader doesn't have to do this calculation qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, PP_GAMMA_BRIGHTNESS, parm ); } // set the textures RB_ARB2_BindInteractionTextureSet( din ); // draw it RB_DrawElementsWithCounters( din->surf->geo ); } i left in the arb version so you can get an idea of what is going on, also this piece -> /* ================= R_LoadARBProgram ================= */ void R_LoadARBProgram( int progIndex ) { int ofs; int err; char *buffer; char *start = NULL, *end; #if D3_INTEGRATE_SOFTPART_SHADERS if ( progs[progIndex].ident == VPROG_SOFT_PARTICLE || progs[progIndex].ident == FPROG_SOFT_PARTICLE ) { // these shaders are loaded directly from a string common->Printf( "<internal> %s", progs[progIndex].name ); const char *srcstr = ( progs[progIndex].ident == VPROG_SOFT_PARTICLE ) ? softpartVShader : softpartFShader; // copy to stack memory buffer = ( char * )_alloca( strlen( srcstr ) + 1 ); strcpy( buffer, srcstr ); } else #endif // D3_INTEGRATE_SOFTPART_SHADERS { idStr fullPath = "glprogs/"; fullPath += progs[progIndex].name; char *fileBuffer; common->Printf( "%s", fullPath.c_str() ); // load the program even if we don't support it, so // fs_copyfiles can generate cross-platform data dumps fileSystem->ReadFile( fullPath.c_str(), ( void ** )&fileBuffer, NULL ); if ( !fileBuffer ) { common->Printf( ": File not found\n" ); return; } // copy to stack memory and free buffer = ( char * )_alloca( strlen( fileBuffer ) + 1 ); strcpy( buffer, fileBuffer ); fileSystem->FreeFile( fileBuffer ); } if ( !glConfig.isInitialized ) { return; } // // submit the program string at start to GL // if ( progs[progIndex].ident == 0 ) { // allocate a new identifier for this program progs[progIndex].ident = PROG_USER + progIndex; } // vertex and fragment programs can both be present in a single file, so // scan for the proper header to be the start point, and stamp a 0 in after the end if ( progs[progIndex].target == GL_VERTEX_PROGRAM_ARB ) { if ( !glConfig.ARBVertexProgramAvailable ) { common->Printf( ": GL_VERTEX_PROGRAM_ARB not available\n" ); return; } start = strstr( buffer, "!!ARBvp" ); } if ( progs[progIndex].target == GL_FRAGMENT_PROGRAM_ARB ) { if ( !glConfig.ARBFragmentProgramAvailable ) { common->Printf( ": GL_FRAGMENT_PROGRAM_ARB not available\n" ); return; } start = strstr( buffer, "!!ARBfp" ); } if ( !start ) { common->Printf( ": !!ARB not found\n" ); return; } end = strstr( start, "END" ); if ( !end ) { common->Printf( ": END not found\n" ); return; } end[3] = 0; // DG: hack gamma correction into shader. TODO shader gamma for GLSL if ( r_gammaInShader.GetBool() && progs[progIndex].target == GL_FRAGMENT_PROGRAM_ARB && strstr( start, "nodhewm3gammahack" ) == NULL ) { // note that strlen("dhewm3tmpres") == strlen("result.color") const char *tmpres = "TEMP dhewm3tmpres; # injected by dhewm3 for gamma correction\n"; // Note: program.env[21].xyz = r_brightness; program.env[21].w = 1.0/r_gamma // outColor.rgb = pow(dhewm3tmpres.rgb*r_brightness, vec3(1.0/r_gamma)) // outColor.a = dhewm3tmpres.a; const char *extraLines = "# gamma correction in shader, injected by dhewm3\n" // MUL_SAT clamps the result to [0, 1] - it must not be negative because // POW might not work with a negative base (it looks wrong with intel's Linux driver) // and clamping values > 1 to 1 is ok because when writing to result.color // it's clamped anyway and pow(base, exp) is always >= 1 for base >= 1 "MUL_SAT dhewm3tmpres.xyz, program.env[21], dhewm3tmpres;\n" // first multiply with brightness "POW result.color.x, dhewm3tmpres.x, program.env[21].w;\n" // then do pow(dhewm3tmpres.xyz, vec3(1/gamma)) "POW result.color.y, dhewm3tmpres.y, program.env[21].w;\n" // (apparently POW only supports scalars, not whole vectors) "POW result.color.z, dhewm3tmpres.z, program.env[21].w;\n" "MOV result.color.w, dhewm3tmpres.w;\n" // alpha remains unmodified "\nEND\n\n"; // we add this block right at the end, replacing the original "END" string int fullLen = strlen( start ) + strlen( tmpres ) + strlen( extraLines ); char *outStr = ( char * )_alloca( fullLen + 1 ); // add tmpres right after OPTION line (if any) char *insertPos = R_FindArbShaderComment( start, "OPTION" ); if ( insertPos == NULL ) { // no OPTION? then just put it after the first line (usually something like "!!ARBfp1.0\n") insertPos = start; } // but we want the position *after* that line while ( *insertPos != '\0' && *insertPos != '\n' && *insertPos != '\r' ) { ++insertPos; } // skip the newline character(s) as well while ( *insertPos == '\n' || *insertPos == '\r' ) { ++insertPos; } // copy text up to insertPos int curLen = insertPos - start; memcpy( outStr, start, curLen ); // copy tmpres ("TEMP dhewm3tmpres; # ..") memcpy( outStr + curLen, tmpres, strlen( tmpres ) ); curLen += strlen( tmpres ); // copy remaining original shader up to (excluding) "END" int remLen = end - insertPos; memcpy( outStr + curLen, insertPos, remLen ); curLen += remLen; outStr[curLen] = '\0'; // make sure it's NULL-terminated so normal string functions work // replace all existing occurrences of "result.color" with "dhewm3tmpres" for ( char *resCol = strstr( outStr, "result.color" ); resCol != NULL; resCol = strstr( resCol + 13, "result.color" ) ) { memcpy( resCol, "dhewm3tmpres", 12 ); // both strings have the same length. // if this was part of "OUTPUT bla = result.color;", replace // "OUTPUT bla" with "ALIAS bla" (so it becomes "ALIAS bla = dhewm3tmpres;") char *s = resCol - 1; // first skip whitespace before "result.color" while ( s > outStr && ( *s == ' ' || *s == '\t' ) ) { --s; } // if there's no '=' before result.color, this line can't be affected if ( *s != '=' || s <= outStr + 8 ) { continue; // go on with next "result.color" in the for-loop } --s; // we were on '=', so go to the char before and it's time to skip whitespace again while ( s > outStr && ( *s == ' ' || *s == '\t' ) ) { --s; } // now we should be at the end of "bla" (or however the variable/alias is called) if ( s <= outStr + 7 || !R_IsArbIdentifier( *s ) ) { continue; } --s; // skip all the remaining chars that are legal in identifiers while ( s > outStr && R_IsArbIdentifier( *s ) ) { --s; } // there should be at least one space/tab between "OUTPUT" and "bla" if ( s <= outStr + 6 || ( *s != ' ' && *s != '\t' ) ) { continue; } --s; // skip remaining whitespace (if any) while ( s > outStr && ( *s == ' ' || *s == '\t' ) ) { --s; } // now we should be at "OUTPUT" (specifically at its last 'T'), // if this is indeed such a case if ( s <= outStr + 5 || *s != 'T' ) { continue; } s -= 5; // skip to start of "OUTPUT", if this is indeed "OUTPUT" if ( idStr::Cmpn( s, "OUTPUT", 6 ) == 0 ) { // it really is "OUTPUT" => replace "OUTPUT" with "ALIAS " memcpy( s, "ALIAS ", 6 ); } } assert( curLen + strlen( extraLines ) <= fullLen ); // now add extraLines that calculate and set a gamma-corrected result.color // strcat() should be safe because fullLen was calculated taking all parts into account strcat( outStr, extraLines ); start = outStr; } qglBindProgramARB( progs[progIndex].target, progs[progIndex].ident ); qglGetError(); qglProgramStringARB( progs[progIndex].target, GL_PROGRAM_FORMAT_ASCII_ARB, strlen( start ), start ); err = qglGetError(); qglGetIntegerv( GL_PROGRAM_ERROR_POSITION_ARB, ( GLint * )&ofs ); if ( err == GL_INVALID_OPERATION ) { const GLubyte *str = qglGetString( GL_PROGRAM_ERROR_STRING_ARB ); common->Printf( "\nGL_PROGRAM_ERROR_STRING_ARB: %s\n", str ); if ( ofs < 0 ) { common->Printf( "GL_PROGRAM_ERROR_POSITION_ARB < 0 with error\n" ); } else if ( ofs >= ( int )strlen( start ) ) { common->Printf( "error at end of program\n" ); } else { int printOfs = Max( ofs - 20, 0 ); // DG: print some more context common->Printf( "error at %i:\n%s", ofs, start + printOfs ); } return; } if ( ofs != -1 ) { common->Printf( "\nGL_PROGRAM_ERROR_POSITION_ARB != -1 without error\n" ); return; } common->Printf( "\n" ); } in draw_arb2.cpp and /* ================== RB_SetProgramEnvironment Sets variables that can be used by all vertex programs [SteveL #3877] Note on the use of fragment program environmental variables. Parameters 0 and 1 are set here to allow conversion of screen coordinates to texture coordinates, for use when sampling _currentRender. Those same parameters 0 and 1, plus 2 and 3, are given entirely different meanings in draw_arb2.cpp while light interactions are being drawn. This function is called again before currentRender size is needed by post processing effects are done, so there's no clash. Only parameters 0..3 were in use before #3877 - and in dhewm3 also 4, for gamma in shader. Now I've used a new parameter 22 for the size of _currentDepth. It's needed throughout, including by light interactions, and its size might in theory differ from _currentRender. Parameters 23 and 24 are used by soft particles #3878. Note these can be freely reused by different draw calls. ================== */ void RB_SetProgramEnvironment( bool isPostProcess ) { float parm[4]; int pot; if ( !glConfig.ARBVertexProgramAvailable ) { return; } #if 0 // screen power of two correction factor, one pixel in so we don't get a bilerp // of an uncopied pixel int w = backEnd.viewDef->viewport.x2 - backEnd.viewDef->viewport.x1 + 1; pot = globalImages->currentRenderImage->uploadWidth; if ( w == pot ) { parm[0] = 1.0; } else { parm[0] = ( float )( w - 1 ) / pot; } int h = backEnd.viewDef->viewport.y2 - backEnd.viewDef->viewport.y1 + 1; pot = globalImages->currentRenderImage->uploadHeight; if ( h == pot ) { parm[1] = 1.0; } else { parm[1] = ( float )( h - 1 ) / pot; } parm[2] = 0; parm[3] = 1; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 0, parm ); #else // screen power of two correction factor, assuming the copy to _currentRender // also copied an extra row and column for the bilerp int w = backEnd.viewDef->viewport.x2 - backEnd.viewDef->viewport.x1 + 1; pot = globalImages->currentRenderImage->uploadWidth; parm[0] = ( float )w / pot; int h = backEnd.viewDef->viewport.y2 - backEnd.viewDef->viewport.y1 + 1; pot = globalImages->currentRenderImage->uploadHeight; parm[1] = ( float )h / pot; parm[2] = 0; parm[3] = 1; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 0, parm ); #endif qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, parm ); // window coord to 0.0 to 1.0 conversion parm[0] = 1.0 / w; parm[1] = 1.0 / h; parm[2] = 0; parm[3] = 1; qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 1, parm ); // DG: brightness and gamma in shader as program.env[4]. TODO: shader gamma for GLSL if ( r_gammaInShader.GetBool() ) { // program.env[4].xyz are all r_brightness, program.env[4].w is 1.0/r_gamma if ( !isPostProcess ) { parm[0] = parm[1] = parm[2] = r_brightness.GetFloat(); parm[3] = 1.0f / r_gamma.GetFloat(); // 1.0f / gamma so the shader doesn't have to do this calculation } else { // don't apply gamma/brightness in postprocess passes to avoid applying them twice // (setting them to 1.0 makes them no-ops) parm[0] = parm[1] = parm[2] = parm[3] = 1.0f; } qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, PP_GAMMA_BRIGHTNESS, parm ); } // #3877: Allow shaders to access depth buffer. // Two useful ratios are packed into this parm: [0] and [1] hold the x,y multipliers you need to map a screen // coordinate (fragment position) to the depth image: those are simply the reciprocal of the depth // image size, which has been rounded up to a power of two. Slots [3] and [4] hold the ratio of the depth image // size to the current render image size. These sizes can differ if the game crops the render viewport temporarily // during post-processing effects. The depth render is smaller during the effect too, but the depth image doesn't // need to be downsized, whereas the current render image does get downsized when it's captured by the game after // the skybox render pass. The ratio is needed to map between the two render images. parm[0] = 1.0f / globalImages->currentDepthImage->uploadWidth; parm[1] = 1.0f / globalImages->currentDepthImage->uploadHeight; parm[2] = static_cast<float>( globalImages->currentRenderImage->uploadWidth ) / globalImages->currentDepthImage->uploadWidth; parm[3] = static_cast<float>( globalImages->currentRenderImage->uploadHeight ) / globalImages->currentDepthImage->uploadHeight; qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, PP_CURDEPTH_RECIPR, parm ); // // set eye position in global space // parm[0] = backEnd.viewDef->renderView.vieworg[0]; parm[1] = backEnd.viewDef->renderView.vieworg[1]; parm[2] = backEnd.viewDef->renderView.vieworg[2]; parm[3] = 1.0; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 1, parm ); } in draw_common.cpp. note the TODO sections. biggest problem right now is probably in R_LoadARBProgram which is not used by the GLSL backend at all since it only handles the interaction shaders atm. it does have its own version of this function but it is lobotomized and only used if for some reason a user wants to use an external shader.it is here in draw_arb2.cpp -> /* ================== GL_GetGLSLFromFile ================== */ static GLchar *GL_GetGLSLFromFile( GLchar *name ) { idStr fullPath = "glprogs130/"; fullPath += name; GLchar *fileBuffer; GLchar *buffer; if ( !glConfig.isInitialized ) { return NULL; } fileSystem->ReadFile( fullPath.c_str(), reinterpret_cast<void **>( &fileBuffer ), NULL ); if ( !fileBuffer ) { common->Printf( "%s: File not found, using internal shaders\n", fullPath.c_str() ); return NULL; } // copy to stack memory (do not use _alloca here!!! it breaks loading the shaders) buffer = reinterpret_cast<GLchar *>( Mem_Alloc( strlen( fileBuffer ) + 1 ) ); strcpy( buffer, fileBuffer ); fileSystem->FreeFile( fileBuffer ); common->Printf( "%s: loaded\n", fullPath.c_str() ); return buffer; } as you can see it is somewhat more empty than the arb shaders version, but would probably suffice for the same thing.
-
No, Rust would not change much. In this specific case, the game would still crash. The only difference is that it would crash immediately on out-of-bounds access, while in C++ there is a chance it will not crash immediately but drag for a bit longer, or even not crash at all. Rust promises no undefined behavior, but does not protect you against crashes, memory leaks, and logical errors. It is a good idea when your software is attacked by hackers. TDM is full of insecure stuff so there is no reason to bother. I think wasm interpreter runs in a sandbox, like any embedded language. So if you want to do networking, threads, and other stuff that invokes syscalls, you won't be able to do it inside such a sandbox by default. Maybe something extra can be enabled, or you can always manually provide access to any functions inside the sandbox. But generally speaking, this will not work as a way to provide arbitrary native extensions. Speaking of addons, I think the minimalistic approach would be to allow doom scripts declare and call functions from DLLs. Something like "plugin(myaddon) float add(float a, float b);". Then dynamically load myaddon.dll/so and invoke the exported symbol with name "add", and let it handle the arguments using a bit of C++ code that supports ordinary script events. This is what e.g. C# supports: very simple to understand and implement. No need for namespaces and other additions to scripting engine.
- 25 replies
-
- 1
-
-
I can say for sure that this is a marvelous technical achievement A geek inside me cries about how cool it is But I don't see much point in having Rust scripts in TDM, and here are some reasons for this. Note: my experience in Rust is just a few days writing basic data structures + some reading. 1) Generally speaking, high-performance scripts are not needed. Some time ago there was an idea that we can probably JIT-compile doom scripts. While I would happily work on it just because it sounds so cool, the serious response was "Let's discuss it after someone shows doom scripts which are too slow". And the examples never followed. If one wants to write some complicated code, he'd better do it in the engine. 2) If we wanted to have native scripts, it should better be C/C++ and not Rust. And the reason is of course: staying in the same ecosystem as much as possible. It seems to me that fast builds and debugging are very important to gamedev, and that's where Visual Studio really shines (while it is definitely an awful Windows-specific monster in many other regards). I bet C/C++ modules are much easier to hook into the same debugger than Rust. And I've heard rumors that scripts in C++ is what e.g. Doom 2016 does. The engine will not go anywhere, and it's in C++, so adding C++ modules will not increase knowledge requirements, while adding Rust modules will. Rust even looks completely different from C++, while e.g. Doom script looks like C++. And writing code in Rust is often much harder. And another build system + package manager (yeah, Rust ones are certainly much simpler and better, but it is still adding more, since C++ will remain). Everyone knows that C++ today is very complicated... but luckily Doom 3 engine is written in not-very-modern C++. As the result, we have at least several people who does not seem to be C++ programmers but still managed to commit hefty pieces of C++ code to the engine. Doing that in Rust would certainly be harder. 3) If we simply need safe scripts in powerful language, better compile C++ to wasm and run in wasm interpreter. Seriously, today WebAssembly allows us to compile any C++ using clang into bytecode, which can be interpreted with pretty minimalistic interpreter! And this approach is perfecly safe, unlike running native C++ or Rust. And if we are eager to do it faster, we can find interpreter with JIT-compiler as well. Perhaps we can invent some automatic translation of script events interface into wasm, and it will run pretty well... maybe even some remote debugger will work (at least Lua debuggers do work with embedded Lua, so I expect some wasm interpreter can do the same). However, after looking though the code, it seems to me that this is not about scripts in Rust, it is about addons in Rust. So it is supposed to do something like what gamex86.dll did in the original Doom 3 but on a more compact and isolated scale. You can write a DLL module which will be loaded dynamically and you can then call its functions from Doom script. Is it correct? Because if this is true, then it is most likely not something we can support in terms of backwards compatibility. It seems that it allows hooks into most C++ virtual functions, which are definitely not something that will not break.
- 25 replies
-
- 1
-
-
I don't. It already is "memory-safe" because a group of highly skilled developers have been doing that for over a decade. Quotes from my posts above: Note the bold, which was in the original text. To be again absolutely clear - the reason I picked Rust is not that I (or anyone else) has a reservation about the quality of the C++ code the team is writing, it is (a) a curiosity project and (b) because my assumption - right or wrong - is that people, especially the core team who are responsible for making sure [supported addon workflows and] new code does not break TDM, would not want to support DLLs because it is impossible to trust that the code of third-parties does not corrupt memory (accidentally, malicious code being a separate problem). The idea is that giving people a starter pack that auto-checks things the team are unlikely to trust is not a bad thing. I also put a list of limitations directly after that, very explicitly stating "Rust is safe" is a bad assumption. Rust is not a solution, it may not be sufficient, but I'm more than happy to do a C++ version, if that is not an issue in the first place - those changes work just as well for C++, Rust or Fortran for that very reason, and nobody wants TDM to start using Rust in core, that would be nonsensical when there is an solid, stable, well-known codebase in C++ and skilled C++ devs working on it. That aside, the OOP paradigm for C++ is probably (I suspect) better for game engine programming, but I'm not going to fight any Rust gamedevs over that. The point of this is that perhaps not all features are things that could or should be in the core, and that modularity could allow more experimentation without the related risks and workload adding them to the central codebase - happy to be corrected, but I doubt bringing new core engine features is just about developing them, it's also deciding when it's worth baking all new code, and functionality, right into the main repo to be maintained for ever more for every release and every user. Similarly, but I would appreciate if you responded to the points, and the caveats, I made, rather than stating something clearly unreasonable to suggest is a bad idea. It misleads others too. For the record, I have no issue about doing a C++ SDK instead, but I'm not sure that's the issue - we could just "script" in C++ if that wasn't a concern at all.
- 25 replies
-
Hi folks, and thanks so much to the devs & mappers for such a great game. After playing a bunch over Christmas week after many years gap, I got curious about how it all went together, and decided to learn by picking a challenge - specifically, when I looked at scripting, I wondered how hard it would be to add library calls, for functionality that would never be in core, in a not-completely-hacky-way. Attached is an example of a few rough scripts - one which runs a pluggable webserver, one which logs anything you pick up to a webpage, one which does text-to-speech and has a Phi2 LLM chatbot ("Borland, the angry archery instructor"). The last is gimmicky, and takes 20-90s to generate responses on my i7 CPU while TDM runs, but if you really wanted something like this, you could host it and just do API calls from the process. The Piper text-to-speech is much more potentially useful IMO. Thanks to snatcher whose Forward Lantern and Smart Objects mods helped me pull example scripts together. I had a few other ideas in mind, like custom AI path-finding algorithms that could not be fitted into scripts, math/data algorithms, statistical models, or video generation/processing, etc. but really interested if anyone has ideas for use-cases. TL;DR: the upshot was a proof-of-concept, where PK4s can load new DLLs at runtime, scripts can call them within and across PK4 using "header files", and TDM scripting was patched with some syntax to support discovery and making matching calls, with proper script-compile-time checking. Why? Mostly curiosity, but also because I wanted to see what would happen if scripts could use text-to-speech and dynamically-defined sound shaders. I also could see that simply hard-coding it into a fork would not be very constructive or enlightening, so tried to pick a paradigm that fits (mostly) with what is there. In short, I added a Library idClass (that definitely needs work) that will instantiate a child Library for each PK4-defined external lib, each holding an eventCallbacks function table of callbacks defined in the .so file. This almost follows the idClass::ProcessEventArgsPtr flow normally. As such, the so/DLL extensions mostly behave as sys event calls in scripting. Critically, while I have tried to limit function reference jumps and var copies to almost the same count as the comparable sys event calls, this is not intended for performance critical code - more things like text-to-speech that use third-party libraries and are slow enough to need their own (OS) thread. Why Rust? While I have coded for many years, I am not a gamedev or modder, so I am learning as I go on the subject in general - my assumption was that this is not already a supported approach due to stability and security. It seems clear that you could mod TDM in C++ by loading a DLL alongside and reaching into the vtable, and pulling strings, or do something like https://github.com/dhewm/dhewm3-sdk/ . However, while you can certainly kill a game with a script, it seems harder to compile something that will do bad things with pointers or accidentally shove a gigabyte of data into a string, corrupt disks, run bitcoin miners, etc. and if you want to do this in a modular way to load a bunch of such mods then that doesn't seem so great. So, I thought "what provides a lot of flexibility, but some protection against subtle memory bugs", and decided that a very basic Rust SDK would make it easy to define a library extension as something like: #[therustymod_lib(daemon=true)] mod mod_web_browser { use crate::http::launch; async fn __run() { print!("Launching rocket...\n"); launch().await } fn init_mod_web_browser() -> bool { log::add_to_log("init".to_string(), MODULE_NAME.to_string()).is_ok() } fn register_module(name: *const c_char, author: *const c_char, tags: *const c_char, link: *const c_char, description: *const c_char) -> c_int { ... and then Rust macros can handle mapping return types to ReturnFloat(...) calls, etc. at compile-time rather than having to add layers of function call indirection. Ironically, I did not take it as far as building in the unsafe wrapping/unwrapping of C/C++ types via the macro, so the addon-writer person then has to do write unsafe calls to take *const c_char to string and v.v.. However, once that's done, the events can then call out to methods on a singleton and do actual work in safe Rust. While these functions correspond to dynamically-generated TDM events, I do not let the idClass get explicitly leaked to Rust to avoid overexposing the C++ side, so they are class methods in the vtable only to fool the compiler and not break Callback.cpp. For the examples in Rust, I was moving fast to do a PoC, so they are not idiomatic Rust and there is little error handling, but like a script, when it fails, it fails explicitly, rather than (normally) in subtle user-defined C++ buffer overflow ways. Having an always-running async executor (tokio) lets actual computation get shipped off fast to a real system thread, and the TDM event calls return immediately, with the caller able to poll for results by calling a second Rust TDM event from an idThread. As an example of a (synchronous) Rust call in a script: extern mod_web_browser { void init_mod_web_browser(); boolean do_log_to_web_browser(int module_num, string log_line); int register_module(string name, string author, string tags, string link, string description); void register_page(int module_num, bytes page); void update_status(int module_num, string status_data); } void mod_grab_log_init() { boolean grabbed_check = false; entity grabbed_entity = $null_entity; float web_module_id = mod_web_browser::register_module( "mod_grab_log", "philtweir based on snatcher's work", "Event,Grab", "https://github.com/philtweir/therustymod/", "Logs to web every time the player grabs something." ); On the verifiability point, both as there are transpiled TDM headers and to mandate source code checkability, the SDK is AGPL. What state is it in? The code goes from early-stage but kinda (hopefully) logical - e.g. what's in my TDM fork - through to basic, what's in the SDK - through to rough - what's in the first couple examples - through to hacky - what's in the fun stretch-goal example, with an AI chatbot talking on a dynamically-loaded sound shader. (see below) The important bit is the first, the TDM approach, but I did not see much point in refining it too far without feedback or a proper demonstration of what this could enable. Note that the TDM approach does not assume Rust, I wanted that as a baseline neutral thing - it passes out a short set of allowed callbacks according to a .h file, so language than can produce dynamically-linkable objects should be able to hook in. What functionality would be essential but is missing? support for anything other than Linux x86 (but I use TDM's dlsym wrappers so should not be a huge issue, if the type sizes, etc. match up) ability to conditionally call an external library function (the dependencies can be loaded out of order and used from any script, but now every referenced callback needs to be in place with matching signatures by the time the main load sequence finishes or it will complain) packaging a .so+DLL into the PK4, with verification of source and checksum tidying up the Rust SDK to be less brittle and (optionally) transparently manage pre-Rustified input/output types some way of semantic-versioning the headers and (easily) maintaining backwards compatibility in the external libraries right now, a dedicated .script file has to be written to define the interface for each .so/DLL - this could be dynamic via an autogenerated SDK callback to avoid mistakes maintaining any non-disposable state in the library seems like an inherently bad idea, but perhaps Rust-side Save/Restore hooks any way to pass entities from a script, although I'm skeptical that this is desirable at all One of the most obvious architectural issues is that I added a bytes type (for uncopied char* pointers) in the scripting to be useful - not for the script to interact with directly but so, for instance, a lib can pass back a Decl definition (for example) that can be held in a variable until the script calls a subsequent (sys) event call to parse it straight from memory. That breaks a bunch of assumptions about event arguments, I think, and likely save/restore. Keen for suggestions - making indexed entries in a global event arg pointer lookup table, say, that the script can safely pass about? Adding CreateNewDeclFromMemory to the exposed ABI instead? While I know that there is no network play at the moment, I also saw somebody had experimented and did not want to make that harder, so also conscious that would need thought about. One maybe interesting idea for a two-player stealth mode could be a player-capturable companion to take across the map, like a capture-the-AI-flag, and pluggable libs might help with adding statistical models for logic and behaviour more easily than scripts, so I can see ways dynamic libraries and multiplayer would be complementary if the technical friction could be resolved. Why am I telling anybody? I know this would not remotely be mergeable, and everyone has bigger priorities, but I did wonder if the general direction was sensible. Then I thought, "hey, maybe I can get feedback from the core team if this concept is even desirable and, if so, see how long that journey would be". And here I am. [EDITED: for some reason I said "speech-to-text" instead of "text-to-speech" everywhere the first time, although tbh I thought both would be interesting]
- 25 replies
-
- 3
-
-
[2.13] Interaction groups in materials: new behavior
stgatilov replied to stgatilov's topic in TDM Editors Guild
Moved this topic from development forums, since it covers a potentially important behavior change in 2.13. Luckily, its important is countered by the rarity of such complicated materials. I hope that this change has not broken existing materials. And even if it had broken any, we will be able to fix them manually... -
Would that mean using WAV might give you better performance? Edit: Just wondering. It's probably not a good enough reason to use it.
-
A small light of player lantern nearby needs more shadow resolution than parallel light for distant objects. Unfortunately, it is very hard to select shadow map resolution automatically (unless you are going to do something like virtual shadow maps). There is some logic in TDM, but the only thing it does successfully is dropping resolution for small distant lights. It still boils down to general resolution selected and some internal hardcoded values. On the other hand, it is possible to allow mappers to specify resolution on per-light basis. Maybe not in these exact terms (like 1024 or 2048) but more like "detailed", "medium", "low" hints. The engines allow this today. One reason why we don't have it yet is that current shadow maps implementation certainly does not feel like final, and future changes will most likely break all such careful tweaks.
-
And for some pointers, read the following topic: https://forums.thedarkmod.com/index.php?/topic/22533-tdm-for-diii4a-support-topic/
-
TDM 2.13 is ready for beta test This is how to get beta versions: Upgrade from any version (fast): 1 - Start tdm_installer in darkmod folder. 2 - On the first screen, check "Get custom version" and click "Next". Choose the first name in beta/2.13 list, should look like "beta213-NN". 3 - Click on "Refresh" button to ensure that it is not going to download too much stuff. 4 - Continue installing with "Next". Fresh install (slow): 1 - Create darkmod folder anywhere you like. 2 - Download the TDM Installer from downloads section of the website. Extract tdm_installer executable from the downloaded ZIP and place it into your darkmod folder. 3 - Start tdm_installer (in case of Linux, first edit file permissions to allow executing it). 4 - On the first screen, check "Get custom version" and click "Next". Choose the first name in beta/2.13 list, should look like "beta213-NN". 5 - Continue installing with "Next". In general, upgrade is recommended over fresh install. If you don't want to lose your current TDM installation, then you can copy the whole TDM directory and upgrade the copy. This way you can have both 2.13 beta and 2.12 at the same time. At the end of installation, tdm_installer resets your config by renaming darkmod.cfg to darkmod_{datatime}.cfg. This is a recommended procedure on upgrade, otherwise you are likely to have issues due to old config. If you need your old config for some reason, you can always find it in darkmod folder. Notes 1 - Please try to be specific when reporting a problem. What you were doing, where you were when the problem occurred, can you reproduce it, etc. This wiki article provides many suggestions for good bug reports. 2 - Make sure to check every mission for update just before playing it. We expect to apply small tweaks to missions during this beta phase. 3 - This effort is to find out if we broke anything in TDM with our 2.13 changes, if a new 2.13 feature isn't working correctly. We won't be trying to fix bugs that have been around for a long time. Instead, we will create an issue in bugtracker (if not yet present), to fix it after beta. 4 - If you find something wrong, it would be helpful if you report whether the issue happens in 2.12 too. By the way, you can easily get 2.12 version: just copy your darkmod folder and run tdm_installer on the copy, selecting "release212" on the custom version screen. Thank you for testing !
-
Seasons Contest Entry : Winter Harvest by Shadowhide
wesp5 replied to Shadowhide's topic in Fan Missions
About the corpse, I dropped it into the sarcophagus with half of it sticking out which didn't look dignified at all. About the orb, sadly this seems to be caused by my patch. After I removed it, this worked. Bug searching time... About the book, this is inconsistent with the orb handling and using it also makes it glow green. Should be fixed! I know that there was some kind of fallout with Bikerdude here, but although most of his missions are great, some could need a few minor updates, e.g. in this one I also found a lot of floating trees! Is there a way to get into contact with him? P.S.: I replayed using noclip and my patch doesn't seem to be the reason for the orb issue after all. Some particles went missing, but the teleport happened now... -
TDM Modpack v4.6 This update makes the TDM Modpack compatible with the development version of TDM 2.13, in case you want to test or use 2.13 now or during the upcoming beta phase and miss some mods... This new version of the Modpack remains, of course, compatible with 2.12. Let's take this opportunity to introduce a new mod: FORWARD LANTERN MOD I think this image explains very well what this mod is about: The implementation of the player lantern light has always bothered me. And while I understand the idea behind it, and despite its long radius, it feels annoying and frustrating for some reason. I started thinking about it and I realized the problem had to do with how light is distributed on screen: most of the times all I want is to focus on what's right ahead of me but the light forces me to focus on the sides. These two images should make my point very clear: ~ CURRENT ~ ~ NEW ~ On the technical side it is worth noting I moved the origin of the light from the hip to the bottom of the spine. I did this because in some situations the light would clip through objects and disappear but now that the light is well buried into the body clipping is impossible. Let us know your opinion about this mod and the player lantern in general! This mod is available for all missions except Flakebridge Monastery, Hazard pay, Moongate Ruckus, Snowed Inn, Vota 1, 2 & 3., which come with their own tweaks to the lantern. --------------------------------------------------- THE LOOP SKILL GET ITS FIRST UPDATE In the initial release I explained I was very conservative with the approach and in order to avoid potential issues you shouldn't be too close to walls or objects when using the Loop. Well, you will be glad to know a workaround has been found and the Loop is as responsive as it gets now and you can use it almost anywhere. Almost? I still haven't figured out how to force the player to crouch and the exception remains when there isn't enough vertical space. I hope someday the developers allow mappers and modders to force the player to crouch so that this mod can be fully realized and mappers can start their missions in vents or tight spaces. - Whoops, I'm outta here! You will probably notice that the Loop feels like it takes a little longer now: I detected that when jumping to some locations the engine needs some lead time to render the new environment. The jump essentially takes the same time as before but I added an extra second right after the jump to allow the engine to render everything properly. --------------------------------------------------- FINAL REORGANIZATION Starting now each skill is presented in its own slot and the Shadowmark Tool has been removed from Core Essentials and it is presented separately as well: Such a nice set of Mods we have! I hope you have fun with this new version of the TDM Modpack! The download can be found in the opening post. Here is the full changelog: • v4.6 New release - Minor changes to make it compatible with the upcoming TDM 2.13. - FORWARD LANTERN: Initial release. - SHADOW MARK: Now in its own mod slot. - SKILL UPGRADE: Skills now presented in their own mod slots. - SKILL LOOP: More responsive. Update to allow the engine render the world timely. - SHOCK MINE: Description added to the shop. - CORE ESSENTIALS - MISC: Decreased brightness of the Objectives and the Inventory. - CORE ESSENTIALS - MISC: Decreased brightness of stock newspapers. - CORE ESSENTIALS - MISC: Removed two anticlimactic player dying sounds. Cheers!
-
Fan Mission: Requiem by Gelo "Moonbo" Fleisher (2013/10/6)
datiswous replied to Moonbo's topic in Fan Missions
I just tried this mission again (in 2.12 and 2.13), but found the performance to be awfully bad and whatever I change in my video settings (no difference between high or low settings), it doesn't help at all. Also my framerate seems to be fine, but moving the view around it looks bad. I don't have this at all with other newer missions. Opening the console does give a large list of console warnings, so maybe there's a reason there. Edit: Maybe it's cpu related. Still, I'm a little surprised considering how old the mission is. -
Experimental support of parallax mapping in 2.13
peter_spy replied to lowenz's topic in The Dark Mod
Ah, it's the Aniso x16 issue. I forgot to turn it down to x8. That's an issue going way back, by the way: sometimes when you look at a surface at acute angles, you get GPU load spikes, without any visible reason. I never had problems with other games going all the way to AF x16. Wonder what's with that in TDM. Anyway, I tested it with uncap FPS and VSync off too. I've never heard my GPU fans being this loud Default material: Geometry: POM: -
Those hand animations are very nice to see, especially the compass animation. Can you make animation for holding / showing the map, like in sea of thieves? (Also asked for in https://forums.thedarkmod.com/index.php?/topic/21038-lets-talk-about-minimap-support/#findComment-463678
-
Can't find the entity for some reason . The code below compares the current icon to see if it's equal to the one that is used when the lantern is on, and it works. However, it's dependent on the spawnarg key being "inv_icon_on", which is what is used in the default tdm_playertools_lantern.def file, so not an ideal solution. Is this something that FMs usually modify? if ($player1.getCurInvIcon() == $player1.getCurInvItemEntity().getKey("inv_icon_on")) { sys.println("ON"); } else { sys.println("OFF"); } EDIT: Nevermind, I figured out how to access the "lanternLight" entity. Works great.
-
Okidoki, I finally bit the bullet & went with a Recoil 17 from PC Specialist Specs I went for are Chassis & Display: Recoil Series: 17" Matte QHD+ 240Hz sRGB 100% LED Widescreen (2560x1600) Processor (CPU): Intel® Core™ i9 24 Core Processor 14900HX (5.8GHz Turbo) Memory (RAM): 32GB PCS PRO SODIMM DDR5 4800MHz (1 x 32GB) Graphics Card: NVIDIA® GeForce® RTX 4080 - 12.0GB GDDR6 Video RAM - DirectX® 12.1 External DVD/BLU-RAY Drive 1TB PCS PCIe M.2 SSD (3500 MB/R, 3200 MB/W) Operating System: NO OPERATING SYSTEM REQUIRED The case seems to be made of metal not plastic & there's an optional water cooling unit, which I didn't get For the OS I disabled fast boot & secure boot, loaded Zorin 17.2, used the entire disk, without any installation issues Zorin is a Ubuntu fork so Ubuntu & it's other forks shouldn't have an issue if anyone else gets one of these The only minor issue is the keyboard backlight isn't recognized by default, but the forums are full of info on sorting that out, not that I'm too bothered I've installed TDM & it runs beautifully I also copied my thief 1 & 2 installations from my desktop, I had to uncomment "d3d_disp_sw_cc" in cam_ext.cfg to get the gamma processing working but they run happily too The fans switch on when booting & switch off again after a few seconds, the machine isn't stressed enough to turn them on running TDM so far - this is not a challenge btw On the whole, I'm extremely pleased So thanks for all the advice
-
Actually I found when you pick up a second potion, it stays zero. When you use the one potion with the zero count, you have no potions anymore. So basically you get 1, but it shows 0. It stays 1 (shows 0) even when you get more potions. This does mean this build kind of breaks gameplay (no offence, I know the risks of playing on dev builds). What was the reason for changing code in the stack system?
-
Ok but it crashes to desktop, so I'm not sure if it was properly tested. (although I have no clue what the reason is)
-
I started typing this on Discord but will move it here. I'd simply love for this to be added to vanilla TDM: The change is a wonderful Christmas gift for the next release. To make this more likely I wish to offer a few points and observations from my perspective, anyone feel free to add to this or correct if I'm wrong. For the record I'm not an official TDM developer though I spend most of my days modding / mapping or playing every FM as it comes out. First of all TDM has a culture of being careful with retroactive changes: If anything modifies the experience too much (especially for existing missions) we usually want it to be an option. I suggest designing your patch with a main menu setting; I'd add it to either "Settings - Gameplay - General" or "Settings - Video - General" whichever makes more sense. I propose giving it the following 4 options: Disabled: Never show POV hands even for weapons. If someone wants this for any reason we're also giving players an option to disable them entirely. Weapons: Only Blackjack / Sword / Arrows. Current functionality and can remain default. Weapons & Items: Also see your hands holding any selected item (lantern, compass, key, readable, etc) but hide them otherwise. I'm in favor of this being the new default if no one finds it too disruptive. Always: Show even the empty hands when nothing is selected, in which case you see the mantling / leaning movements. Likely too disruptive to default but I see myself using even that. I was excited to hear you plan to add them for individual items, that's something I've been dreaming to see years ago but lost hope we ever would. When it comes to items we'll likely need special animations for the default ones as they have specific purposes. Here's a list of the the important items... especially at this stage I don't want to suggest too much and overwhelm, I'll only add essential ones the player is almost always guaranteed to use: Lantern: Hand dangling the item down. Only the skin needs to change between lit and unlit lantern, all light entities have skins and it's easy to setup. This will look awesome with glow and fireflies Spyglass: Hand brings it toward the face when using and back down when unusing. Compass: Hand facing up holds the item in its palm. Wonder if the handheld compass can actually work and point in the right direction, wouldn't bother with it now but since we got it working for the inventory icon this should be easy to do later and will look really neat Lockpick & Keys: Poke the item toward the crosshair to look like picking the lock or inserting the key. It doesn't need to go toward the keyhole or anything, seems perfectly fine the way this was done in DeusEx where you'd see the hand flailing it in front of the door wherever you pointed. Health Potion: Move the hand toward the face and back down to appear like quickly drinking. Careful not to bring it too close to the camera as the minimum clip distance may cut off the mesh and let you see into it. Everything else: Generic hold animation with a basic jolt when using. This should fit most things: Not sure how items with different sizes will look and if we'll need different grips for each one, a book isn't held the same way as a sheet of paper, but especially right now I wouldn't worry about that. Remember that TDM allows mappers to define their own items. This includes builtin ones: You can make custom lanterns, lockpicks, spyglass, etc. The best solution seems like letting each item specify the hold & use animation it wants for the hands via spawnargs, matching the best grip and use animations the author thinks looks best. We'll also need spawnargs for the origin and rotation offset for the entity attached to the hand, each item's model is bound to appear in a different place and will need to be configured by trial and error.
-
The question then becomes: Which is the model's intended area and which is the pierced area? The existing AreaLock lets you determine using either the model's origin or the center of its bounding box. In my tests I found both may fail at detecting the intended area in a few cases. One reason is the answer to the second question: Most modules are walls, but others are corner pillars which are even more problematic. On the outside you need to line them up so the edges of two wall panels don't show up, which almost always requires pushing them inside a room. This image shows it clearly if I hide the interior walls... note how the pillar's origin also falls inside the room in this case. The wall brush is 4 units thin which is very small but what I found works; Some would ask why I don't just make the walls thicker which would technically solve the problem. The issue then is you can't get the exterior modules to line up with the interior ones, they're meant to be placed back-to-back. Only way is to make unrealistically thick walls so you skip an entire panel, meaning a wall would have 5 panels on the outside but 4 on the inside positioned midway: One panel is usually is 128 units long, for modules that offer a half-wall 64... for them to line up you need your walls to be 64 or at best 32 units thick which looks like it's over 1 / 0.5 meters, that's a lot of space to waste and only makes sense for something like a castle or giant cathedral. Even then it won't work as the door frame of an interior module needs to poke through the exterior so you don't see a gap: The modules include their own door frames and expect you to use those instead of adding your own. The interior and exterior door panels need to slide into each other perfectly like a socket in a power outlet; If the door panel of the outside isn't glued to the one of the inside just right, you'll get an empty space in which you see caulk where the door sits in the door frame. The second image also shows the problem with center detection: The door frame that must poke out belongs to the indoor panel, and it's long enough to cause its bounding box to be present outside the room more than it is inside. If I use the model's origin then it works, but for a few modules the origin of an interior panel may be flipped and lands outside. Modules with door and window frames are the only ones that need to render both inside and out, everything else should be restricted to its intended area.
-
Mission Administration Terms of Service
stgatilov replied to nbohr1more's topic in TDM Editors Guild
I think we should first decide what do we want TOS for: To protect TDM from legal issues? To protect TDM team from angry mappers in case of conflicts? To guide mission authors in their work? In my opinion TOS should only cover legal issues, and wiki articles about making/releasing missions should cover author guidance. The chance of getting malware in a mission only increases after we write this publicly. Better don't even mention it, we are completely unprotected against this case. By the way, isn't it covered by "illegal" clause? I'm not sure this is worth mentioning, but I guess @demagogue knows better. By the way, which jurisdiction defines what is legal and what is not? Isn't it enough to mention that we will remove a mission from the database if legal issues are discovered? I think this is worth mentioning simply because mappers can easily do it without any malicious intent. We already had cases of problematic assets, so better include a point on license compatibility. It is a good idea to remind every mapper that this is a serious issue. I also recall some rule like "a mission of too low quality might be rejected". In my opinion, it is enough. You will never be able to pinpoint all possible cases why you might consider a mission too bad in terms of quality. And even the specifics mentioned here already raise questions. Having such a rule is already politics. I feel it does not save us from political issues but entangles us into them. If there is a mission which contains something really nasty, it will cause outrage among the community (I believe our most of active forum members are good people). If people are angry, they will tell the mission author all they think about it. And if the author won't change his mind, he will eventually leave TDM community. Then the mission can be removed from the database, perhaps with a poll about the removal. But it sounds like an exceptional case, it is hard to predict exceptional cases in advance. This is not even terms of service, but a technical detail about submissions. The mission should be accompanied by 800 x 600 screenshots. Or we can make them ourselves if you are OK with it. This is again purely technical, and I'm not even sure why it is needed. Isn't it how TDM works? If mapper does not override loading gui file, then default one is taken from core? Is it even worth mentioning? I think we should discuss mission updates by other people in general. This is worth mentioning so that mappers don't feel deceived. The generic rule is that we don't change missions without author's consent. But it is unclear how exactly we should try to reach the author if we need his consent. PM on TDM forums? Some email address? However, sometimes I do technical changes to ensure compatibility of missions with new versions of TDM. Especially since the new missions database has made it rather easy to do. Luckily, I'm not a mapper/artist, so I never fell an urge to replace model/texture or remap something. But still, it is gray zone. On the other hand, I think the truth is: we can remove a mission from database without anyone's consent. I hope it has never happened and will not happen, but I think this is the ultimate truth, and mentioning this sad fact might cover a lot of the other points automatically. -
Mission Administration Terms of Service
demagogue replied to nbohr1more's topic in TDM Editors Guild
Yeah the reason you'd mention no illegal or infringing material is for us to be able to say we're acting in good faith, if somebody finds their IP in an FM, they can't or it's not as straightforward to want to sue us for it. In practice I think it's more of a formality. As for FMs that don't finish or have buggy elements, there are demo-like or novelty FMs that might have both of those. Somebody mentioned the Tutorial itself. I wouldn't want to discourage somebody being creative, and I'd probably word it differently. Something like FMs should be "complete" before being submitted, but leaving it technically open what "complete" means. I guess it might give some examples "... including but not necessarily in special cases ..." that it starts and ends, is not egregiously buggy, does not consistently crash, etc. But I think even here it should be an encouragement instead of a hard rule, like we encourage mappers to get their FMs beta-tested, confirm that it starts and finishes, doesn't consistently crash, before submitting, and we may ask that you work on an FM more before uploading it if it is manifestly broken "without justification" (to leave open the possibility of "broken" FMs with a justification, like a novelty FM). Some of you might remember that TTLG ran a "buggy FM" contest once where broken FMs was actually the theme, and it was an amazing contest. Some of those FMs were broken in very creating, fun, and interesting ways, and it might be good to have FMs like that sometimes. The intentionality part was important though; you could have something broken if that's part of the artistic intention, so language that could leave something like that open may be good, or again encouraging people to always betatest and avoid unintentional crashing or broken FMs, etc. Because of past experience, we might also have language to give expectations regarding possible changes after they submit. Like an alert that while we make every effort to make sure future changes to the game are backwards compatible, it's possible a change breaks an FM. Also other people may want to take assets or things from the FM for their own FM, so we should say that technically, when you submit, you agree to the license we have for our assets (I forget exactly, CC-nc something something). So under that license people can use those assets. If you don't want that, you might make a personal appeal in the readme... The other worry is when people just take big chunks of the map itself, or make their own levels in the same maps... I wonder if we could have people make explicit what they consent to having done to their maps post release, if they allow the translation file, other people to use their map work, etc. But make it clear that the map is under the CC license, which allows people to use anything from it, and they can make a personal appeal that isn't binding, but people may be moved by it. And then we might have our own internal standards what seems egregious enough not to allow, like if someone completely takes another person's map and basically tries to recycle it under their own name, or when it's a team made map and the team disagrees what happens to it. Anyway, whatever we think, it's good to have language about it here to help manage expectations about what might happen, so it's good to think about what we should say to minimize conflict later on.