From c52a18bb7ebb75a365d182290fbf7c602aed08a2 Mon Sep 17 00:00:00 2001 From: Zack Middleton Date: Thu, 16 May 2019 22:23:06 -0500 Subject: [PATCH] mint-arena: Example for player model damage skins Load up to 10 shaders for each mesh from .skin files. Use first shader at full health and later shaders as health decreases. If there are only two shaders, then first is for health 51+ and second is for 0 to 50. Put baseq3/doom-damage-skin.pk3dir in your baseq3 directory, use 'model doom/damage' and cg_forceModel 1 so bots use it too. Based on unused code I wrote for Turtle Arena (removed in TA 0.7). --- .../models/players/doom/head_damage.skin | 3 + .../models/players/doom/lower_damage.skin | 2 + .../models/players/doom/upper_damage.skin | 6 + .../scripts/doom-damage-skin.shader | 158 ++++++++++++++++++ code/cgame/cg_draw.c | 20 ++- code/cgame/cg_ents.c | 4 +- code/cgame/cg_local.h | 12 +- code/cgame/cg_players.c | 79 ++++++--- code/game/bg_misc.c | 17 ++ code/game/bg_public.h | 3 +- code/q3_ui/ui_players.c | 2 +- code/ui/ui_players.c | 2 +- 12 files changed, 276 insertions(+), 32 deletions(-) create mode 100644 baseq3/doom-damage-skin.pk3dir/models/players/doom/head_damage.skin create mode 100644 baseq3/doom-damage-skin.pk3dir/models/players/doom/lower_damage.skin create mode 100644 baseq3/doom-damage-skin.pk3dir/models/players/doom/upper_damage.skin create mode 100644 baseq3/doom-damage-skin.pk3dir/scripts/doom-damage-skin.shader diff --git a/baseq3/doom-damage-skin.pk3dir/models/players/doom/head_damage.skin b/baseq3/doom-damage-skin.pk3dir/models/players/doom/head_damage.skin new file mode 100644 index 000000000..a017d1675 --- /dev/null +++ b/baseq3/doom-damage-skin.pk3dir/models/players/doom/head_damage.skin @@ -0,0 +1,3 @@ +h_helmet,models/players/doom/doom01,models/players/doom/doom02,models/players/doom/doom03,models/players/doom/doom04,models/players/doom/doom05,models/players/doom/doom06,models/players/doom/doom07,models/players/doom/doom08,models/players/doom/doom09,models/players/doom/doom10 +h_visor,models/players/doom/doom_f00 +tag_head, diff --git a/baseq3/doom-damage-skin.pk3dir/models/players/doom/lower_damage.skin b/baseq3/doom-damage-skin.pk3dir/models/players/doom/lower_damage.skin new file mode 100644 index 000000000..55822ec1e --- /dev/null +++ b/baseq3/doom-damage-skin.pk3dir/models/players/doom/lower_damage.skin @@ -0,0 +1,2 @@ +l_legs,models/players/doom/doom01,models/players/doom/doom02,models/players/doom/doom03,models/players/doom/doom04,models/players/doom/doom05,models/players/doom/doom06,models/players/doom/doom07,models/players/doom/doom08,models/players/doom/doom09,models/players/doom/doom10 +tag_torso, diff --git a/baseq3/doom-damage-skin.pk3dir/models/players/doom/upper_damage.skin b/baseq3/doom-damage-skin.pk3dir/models/players/doom/upper_damage.skin new file mode 100644 index 000000000..981ce9eb3 --- /dev/null +++ b/baseq3/doom-damage-skin.pk3dir/models/players/doom/upper_damage.skin @@ -0,0 +1,6 @@ +u_torso,models/players/doom/doom01,models/players/doom/doom02,models/players/doom/doom03,models/players/doom/doom04,models/players/doom/doom05,models/players/doom/doom06,models/players/doom/doom07,models/players/doom/doom08,models/players/doom/doom09,models/players/doom/doom10 +tag_head, +tag_torso, +tag_weapon, +u_larm,models/players/doom/doom01,models/players/doom/doom02,models/players/doom/doom03,models/players/doom/doom04,models/players/doom/doom05,models/players/doom/doom06,models/players/doom/doom07,models/players/doom/doom08,models/players/doom/doom09,models/players/doom/doom10 +u_rarm,models/players/doom/doom01,models/players/doom/doom02,models/players/doom/doom03,models/players/doom/doom04,models/players/doom/doom05,models/players/doom/doom06,models/players/doom/doom07,models/players/doom/doom08,models/players/doom/doom09,models/players/doom/doom10 diff --git a/baseq3/doom-damage-skin.pk3dir/scripts/doom-damage-skin.shader b/baseq3/doom-damage-skin.pk3dir/scripts/doom-damage-skin.shader new file mode 100644 index 000000000..246c4be8b --- /dev/null +++ b/baseq3/doom-damage-skin.pk3dir/scripts/doom-damage-skin.shader @@ -0,0 +1,158 @@ +// Doom damage skins + +// Single shader damage skin (entity alpha) +// mesh,models/players/doom/doom00 + +models/players/doom/doom00 +{ + { + map models/players/doom/doom.tga + rgbGen lightingDiffuse + } + { + map models/players/doom/doom_statue.tga + alphaGen entity + blendFunc blend + } +} + +models/players/doom/doom_f00 +{ + { + map models/players/doom/doom_f.tga + rgbGen lightingDiffuse + } + { + map models/players/doom/doom_f_statue.tga + alphaGen entity + blendFunc blend + } +} + +// Multi-shader damage skin: +// mesh,models/players/doom/doom01,models/players/doom/doom02,models/players/doom/doom03,models/players/doom/doom04,models/players/doom/doom05,models/players/doom/doom06,models/players/doom/doom07,models/players/doom/doom08,models/players/doom/doom09,models/players/doom/doom10 + +// 91+ health +models/players/doom/doom01 +{ + { + map models/players/doom/doom.tga + rgbGen lightingDiffuse + } +} + +// 81 - 90 health +models/players/doom/doom02 +{ + { + map models/players/doom/doom.tga + rgbGen lightingDiffuse + } + { + map models/players/doom/red.tga + alphaGen const 0.3 + blendFunc blend + } +} + +// 71 - 80 health +models/players/doom/doom03 +{ + { + map models/players/doom/doom.tga + rgbGen lightingDiffuse + } + { + map models/players/doom/red.tga + alphaGen const 0.4 + blendFunc blend + } +} + +// 61 - 70 health +models/players/doom/doom04 +{ + { + map models/players/doom/doom.tga + rgbGen lightingDiffuse + } + { + map models/players/doom/red.tga + alphaGen const 0.6 + blendFunc blend + } +} + +// 51 - 60 health +models/players/doom/doom05 +{ + { + map models/players/doom/doom.tga + rgbGen lightingDiffuse + } + { + map models/players/doom/red.tga + alphaGen const 0.8 + blendFunc blend + } +} + +// 41 - 50 health +models/players/doom/doom06 +{ + { + map models/players/doom/red.tga + rgbGen lightingDiffuse + } +} + +// 31 - 40 health +models/players/doom/doom07 +{ + { + map models/players/doom/red.tga + rgbGen lightingDiffuse + } + { + map models/players/doom/doom_statue.tga + alphaGen const 0.25 + blendFunc blend + } +} + +// 21 - 30 health +models/players/doom/doom08 +{ + { + map models/players/doom/red.tga + rgbGen lightingDiffuse + } + { + map models/players/doom/doom_statue.tga + alphaGen const 0.5 + blendFunc blend + } +} + +// 11 - 20 health +models/players/doom/doom09 +{ + { + map models/players/doom/red.tga + rgbGen lightingDiffuse + } + { + map models/players/doom/doom_statue.tga + alphaGen const 0.75 + blendFunc blend + } +} + +// 0 - 10 health +models/players/doom/doom10 +{ + { + map models/players/doom/doom_statue.tga + rgbGen lightingDiffuse + } +} diff --git a/code/cgame/cg_draw.c b/code/cgame/cg_draw.c index 740444cc0..82cb4daef 100644 --- a/code/cgame/cg_draw.c +++ b/code/cgame/cg_draw.c @@ -102,7 +102,7 @@ CG_Draw3DModelEx ================ */ -void CG_Draw3DModelEx( float x, float y, float w, float h, qhandle_t model, cgSkin_t *skin, vec3_t origin, vec3_t angles, const byte *rgba ) { +void CG_Draw3DModelEx( float x, float y, float w, float h, qhandle_t model, cgSkin_t *skin, vec3_t origin, vec3_t angles, const byte *rgba, entityState_t *state ) { refdef_t refdef; refEntity_t ent; @@ -118,7 +118,7 @@ void CG_Draw3DModelEx( float x, float y, float w, float h, qhandle_t model, cgSk AnglesToAxis( angles, ent.axis ); VectorCopy( origin, ent.origin ); ent.hModel = model; - ent.customSkin = CG_AddSkinToFrame( skin ); + ent.customSkin = CG_AddSkinToFrame( skin, state ); ent.renderfx = RF_NOSHADOW; // no stencil shadows if ( rgba ) { @@ -151,7 +151,7 @@ CG_Draw3DModel ================ */ void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, cgSkin_t *skin, vec3_t origin, vec3_t angles ) { - CG_Draw3DModelEx( x, y, w, h, model, skin, origin, angles, NULL ); + CG_Draw3DModelEx( x, y, w, h, model, skin, origin, angles, NULL, NULL ); } /* @@ -167,6 +167,8 @@ void CG_DrawHead( float x, float y, float w, float h, int playerNum, vec3_t head float len; vec3_t origin; vec3_t mins, maxs; + entityState_t *entityState; + byte color[4]; pi = &cgs.playerinfo[ playerNum ]; @@ -190,7 +192,15 @@ void CG_DrawHead( float x, float y, float w, float h, int playerNum, vec3_t head // allow per-model tweaking VectorAdd( origin, pi->headOffset, origin ); - CG_Draw3DModelEx( x, y, w, h, pi->headModel, &pi->modelSkin, origin, headAngles, pi->c1RGBA ); + // TODO: Use prediction entity state for local players? + entityState = &cg_entities[playerNum].currentState; + + color[0] = pi->c1RGBA[0]; + color[1] = pi->c1RGBA[1]; + color[2] = pi->c1RGBA[2]; + color[3] = entityState->skinFraction * 255; + + CG_Draw3DModelEx( x, y, w, h, pi->headModel, &pi->modelSkin, origin, headAngles, color, entityState ); } else if ( cg_drawIcons.integer ) { CG_DrawPic( x, y, w, h, pi->modelIcon ); } @@ -2999,7 +3009,7 @@ void CG_DrawMiscGamemodels( void ) { } ent.hModel = cgs.miscGameModels[i].model; - ent.customSkin = CG_AddSkinToFrame( &cgs.miscGameModels[i].skin ); + ent.customSkin = CG_AddSkinToFrame( &cgs.miscGameModels[i].skin, NULL ); trap_R_AddRefEntityToScene( &ent ); drawn++; diff --git a/code/cgame/cg_ents.c b/code/cgame/cg_ents.c index 5a02e58e1..7a7537def 100644 --- a/code/cgame/cg_ents.c +++ b/code/cgame/cg_ents.c @@ -1120,11 +1120,11 @@ static void CG_TeamBase( centity_t *cent ) { if ( cent->currentState.modelindex == TEAM_RED ) { model.hModel = cgs.media.harvesterModel; - model.customSkin = CG_AddSkinToFrame( &cgs.media.harvesterRedSkin ); + model.customSkin = CG_AddSkinToFrame( &cgs.media.harvesterRedSkin, ¢->currentState ); } else if ( cent->currentState.modelindex == TEAM_BLUE ) { model.hModel = cgs.media.harvesterModel; - model.customSkin = CG_AddSkinToFrame( &cgs.media.harvesterBlueSkin ); + model.customSkin = CG_AddSkinToFrame( &cgs.media.harvesterBlueSkin, ¢->currentState ); } else { model.hModel = cgs.media.harvesterNeutralModel; diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index f5a04937d..adc7ca100 100644 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -203,9 +203,15 @@ typedef struct { // skin surfaces array shouldn't be dynamically allocated because players reuse the same skin structure when changing models #define MAX_CG_SKIN_SURFACES 100 +#define MAX_CG_SKIN_SURFACE_SHADERS 10 typedef struct { - int numSurfaces; - qhandle_t surfaces[MAX_CG_SKIN_SURFACES]; + qhandle_t surfaces[MAX_CG_SKIN_SURFACE_SHADERS]; // allocated skin surfaces (mesh name + shader) + int numShaders; +} cgSkinMesh_t; + +typedef struct { + int numMeshes; + cgSkinMesh_t meshes[MAX_CG_SKIN_SURFACES]; } cgSkin_t; //================================================= @@ -1713,7 +1719,7 @@ int CG_Text_Height( const char *text, float scale, int limit ); void CG_Player( centity_t *cent ); void CG_ResetPlayerEntity( centity_t *cent ); void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state ); -qhandle_t CG_AddSkinToFrame( const cgSkin_t *skin ); +qhandle_t CG_AddSkinToFrame( const cgSkin_t *skin, entityState_t *state ); qboolean CG_RegisterSkin( const char *name, cgSkin_t *skin, qboolean append ); void CG_NewPlayerInfo( int playerNum ); sfxHandle_t CG_CustomSound( int playerNum, const char *soundName ); diff --git a/code/cgame/cg_players.c b/code/cgame/cg_players.c index 6b276fc83..50af9bdba 100644 --- a/code/cgame/cg_players.c +++ b/code/cgame/cg_players.c @@ -526,12 +526,30 @@ static qboolean CG_FindPlayerHeadFile( char *filename, int length, playerInfo_t CG_AddSkinToFrame ========================== */ -qhandle_t CG_AddSkinToFrame( const cgSkin_t *skin ) { - if ( !skin || !skin->numSurfaces ) { +qhandle_t CG_AddSkinToFrame( const cgSkin_t *skin, entityState_t *state ) { + qhandle_t surfaces[MAX_CG_SKIN_SURFACES]; + int i, index; + float skinFraction; + + if ( !skin || !skin->numMeshes ) { return 0; } - return trap_R_AddSkinToFrame( skin->numSurfaces, skin->surfaces ); + skinFraction = state ? state->skinFraction : 0.0f; + + for ( i = 0; i < skin->numMeshes; i++ ) { + if ( skinFraction >= 1.0f ) { + index = skin->meshes[i].numShaders-1; + } else if ( skinFraction <= 0.0f ) { + index = 0; + } else { // > 0 && < 1 + index = skinFraction * skin->meshes[i].numShaders; + } + + surfaces[i] = skin->meshes[i].surfaces[index]; + } + + return trap_R_AddSkinToFrame( skin->numMeshes, surfaces ); } /* @@ -567,11 +585,11 @@ qboolean CG_RegisterSkin( const char *name, cgSkin_t *skin, qboolean append ) { } if ( !append ) { - skin->numSurfaces = 0; + skin->numMeshes = 0; } - initialSurfaces = skin->numSurfaces; - totalSurfaces = skin->numSurfaces; + initialSurfaces = skin->numMeshes; + totalSurfaces = skin->numMeshes; // load the file len = trap_FS_FOpenFile( name, &f, FS_READ ); @@ -614,20 +632,35 @@ qboolean CG_RegisterSkin( const char *name, cgSkin_t *skin, qboolean append ) { continue; } - // parse the shader name - token = COM_ParseExt2( &text_p, qfalse, ',' ); - Q_strncpyz( shaderName, token, sizeof( shaderName ) ); + if ( skin->numMeshes < MAX_CG_SKIN_SURFACES ) { + int numShaders; + + for ( numShaders = 0; numShaders < MAX_CG_SKIN_SURFACE_SHADERS; numShaders++ ) { + if ( *text_p == ',' ) { + text_p++; + } - if ( skin->numSurfaces < MAX_CG_SKIN_SURFACES ) { - hShader = trap_R_RegisterShaderEx( shaderName, LIGHTMAP_NONE, qtrue ); + // parse the shader name + token = COM_ParseExt2( &text_p, qfalse, ',' ); + Q_strncpyz( shaderName, token, sizeof( shaderName ) ); - // for compatibility with quake3 skins, don't render missing shaders listed in skins - if ( !hShader ) { - hShader = cgs.media.nodrawShader; + if ( !token[0] ) { + // End of line + break; + } + + hShader = trap_R_RegisterShaderEx( shaderName, LIGHTMAP_NONE, qtrue ); + + // for compatibility with quake3 skins, don't render missing shaders listed in skins + if ( !hShader ) { + hShader = cgs.media.nodrawShader; + } + + skin->meshes[skin->numMeshes].surfaces[numShaders] = trap_R_AllocSkinSurface( surfName, hShader ); } - skin->surfaces[skin->numSurfaces] = trap_R_AllocSkinSurface( surfName, hShader ); - skin->numSurfaces++; + skin->meshes[skin->numMeshes].numShaders = numShaders; + skin->numMeshes++; } totalSurfaces++; @@ -639,7 +672,7 @@ qboolean CG_RegisterSkin( const char *name, cgSkin_t *skin, qboolean append ) { } // failed to load surfaces - if ( !skin->numSurfaces ) { + if ( !skin->numMeshes ) { return qfalse; } @@ -1955,7 +1988,7 @@ static void CG_PlayerFlag( centity_t *cent, const cgSkin_t *skin, refEntity_t *t // show the flag model memset( &flag, 0, sizeof(flag) ); flag.hModel = cgs.media.flagFlapModel; - flag.customSkin = CG_AddSkinToFrame( skin ); + flag.customSkin = CG_AddSkinToFrame( skin, ¢->currentState ); VectorCopy( torso->lightingOrigin, flag.lightingOrigin ); flag.shadowPlane = torso->shadowPlane; flag.renderfx = torso->renderfx; @@ -2701,7 +2734,7 @@ void CG_Player( centity_t *cent ) { // add the legs // legs.hModel = pi->legsModel; - legs.customSkin = CG_AddSkinToFrame( &pi->modelSkin ); + legs.customSkin = CG_AddSkinToFrame( &pi->modelSkin, ¢->currentState ); VectorCopy( cent->lerpOrigin, legs.origin ); @@ -2712,6 +2745,8 @@ void CG_Player( centity_t *cent ) { Byte4Copy( pi->c1RGBA, legs.shaderRGBA ); + legs.shaderRGBA[3] = cent->currentState.skinFraction * 255; + CG_AddRefEntityWithPowerups( &legs, ¢->currentState ); // if the model failed, allow the default nullmodel to be displayed @@ -2738,8 +2773,12 @@ void CG_Player( centity_t *cent ) { Byte4Copy( pi->c1RGBA, torso.shaderRGBA ); + torso.shaderRGBA[3] = cent->currentState.skinFraction * 255; + CG_AddRefEntityWithPowerups( &torso, ¢->currentState ); + torso.shaderRGBA[3] = 255; // leave powerup entity alpha alone + // add the talk baloon or disconnect icon CG_PlayerSprites( cent, &torso ); @@ -2969,6 +3008,8 @@ void CG_Player( centity_t *cent ) { Byte4Copy( pi->c1RGBA, head.shaderRGBA ); + head.shaderRGBA[3] = cent->currentState.skinFraction * 255; + CG_AddRefEntityWithPowerups( &head, ¢->currentState ); CG_AddBreathPuffs( cent, &head ); diff --git a/code/game/bg_misc.c b/code/game/bg_misc.c index 1e3d3b2b1..9f4deac6f 100644 --- a/code/game/bg_misc.c +++ b/code/game/bg_misc.c @@ -909,6 +909,7 @@ vmNetField_t bg_entityStateFields[] = { NETF(origin[0]), 0 }, { NETF(origin[1]), 0 }, { NETF(origin[2]), 0 }, +{ NETF(skinFraction), 0 }, { NETF(contents), 32 }, { NETF(collisionType), 16 }, { NETF(mins[0]), 0 }, @@ -1739,6 +1740,14 @@ void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean SnapVector( s->mins ); SnapVector( s->maxs ); } + + if ( ps->stats[STAT_HEALTH] <= 0 ) { + s->skinFraction = 1.0f; + } else if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] ) { + s->skinFraction = 0.0f; + } else { + s->skinFraction = 1.0f - ( (float)ps->stats[STAT_HEALTH] / (float)ps->stats[STAT_MAX_HEALTH] ); + } } /* @@ -1828,6 +1837,14 @@ void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s SnapVector( s->mins ); SnapVector( s->maxs ); } + + if ( ps->stats[STAT_HEALTH] <= 0 ) { + s->skinFraction = 1.0f; + } else if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] ) { + s->skinFraction = 0.0f; + } else { + s->skinFraction = 1.0f - ( (float)ps->stats[STAT_HEALTH] / (float)ps->stats[STAT_MAX_HEALTH] ); + } } /* diff --git a/code/game/bg_public.h b/code/game/bg_public.h index eb065304b..359c63984 100644 --- a/code/game/bg_public.h +++ b/code/game/bg_public.h @@ -50,7 +50,7 @@ Suite 120, Rockville, Maryland 20850 USA. // because games can change separately from the main system protocol, we need a // second protocol that must match between game and cgame -#define GAME_PROTOCOL MODDIR "-4" +#define GAME_PROTOCOL MODDIR "damageskins-4" // used for switching fs_game #ifndef BASEQ3 @@ -276,6 +276,7 @@ typedef struct entityState_s { int legsAnim; // mask off ANIM_TOGGLEBIT int torsoAnim; // mask off ANIM_TOGGLEBIT int tokens; // harvester skulls + float skinFraction; // 0 = full health, 1 = dead } entityState_t; diff --git a/code/q3_ui/ui_players.c b/code/q3_ui/ui_players.c index b1b0b5d0e..0b09dd98a 100644 --- a/code/q3_ui/ui_players.c +++ b/code/q3_ui/ui_players.c @@ -821,7 +821,7 @@ void UI_DrawPlayer( float x, float y, float w, float h, uiPlayerInfo_t *pi, int // add the legs // legs.hModel = pi->legsModel; - legs.customSkin = CG_AddSkinToFrame( &pi->modelSkin ); + legs.customSkin = CG_AddSkinToFrame( &pi->modelSkin, NULL ); VectorCopy( origin, legs.origin ); diff --git a/code/ui/ui_players.c b/code/ui/ui_players.c index aa85e3347..b18783cf6 100644 --- a/code/ui/ui_players.c +++ b/code/ui/ui_players.c @@ -826,7 +826,7 @@ void UI_DrawPlayer( float x, float y, float w, float h, uiPlayerInfo_t *pi, int // add the legs // legs.hModel = pi->legsModel; - legs.customSkin = CG_AddSkinToFrame( &pi->modelSkin ); + legs.customSkin = CG_AddSkinToFrame( &pi->modelSkin, NULL ); VectorCopy( origin, legs.origin );