View Single Post
Author Message
blodia
Veteran Member
Join Date: Sep 2009
Location: UK
Old 03-30-2012 , 15:01   [SNIPPET][CSS]custom viewmodels without flickers and without disabling prediction
Reply With Quote #1

PHP Code:
#include <sourcemod>
#include <sdktools>
#include <sdkhooks>

#pragma semicolon 1

#define EF_NODRAW 32

new CustomModel1;
new 
CustomModel2;

new 
bool:SpawnCheck[MAXPLAYERS+1];
new 
ClientVM[MAXPLAYERS+1][2];
new 
bool:IsCustom[MAXPLAYERS+1];

public 
OnPluginStart()
{
    
HookEvent("player_death"Event_PlayerDeath);
    
HookEvent("player_spawn"Event_PlayerSpawn);
    
    for (new 
client 1client <= MaxClientsclient++) 
    { 
        if (
IsClientInGame(client)) 
        {
            
SDKHook(clientSDKHook_PostThinkPostOnPostThinkPost);
            
            
//find both of the clients viewmodels
            
ClientVM[client][0] = GetEntPropEnt(clientProp_Send"m_hViewModel");
            
            new 
PVM = -1;
            while ((
PVM FindEntityByClassname(PVM"predicted_viewmodel")) != -1)
            {
                if (
GetEntPropEnt(PVMProp_Send"m_hOwner") == client)
                {
                    if (
GetEntProp(PVMProp_Send"m_nViewModelIndex") == 1)
                    {
                        
ClientVM[client][1] = PVM;
                        break;
                    }
                }
            }
        } 
    }
}

public 
OnMapStart()
{
    
CustomModel1 PrecacheModel("models/Weapons/v_smg_p90.mdl");
    
CustomModel2 PrecacheModel("models/Weapons/v_smg_tmp.mdl");
}

public 
OnClientPutInServer(client)
{
    
SDKHook(clientSDKHook_PostThinkPostOnPostThinkPost);
}

public 
OnEntityCreated(entity, const String:classname[])
{
    if (
StrEqual(classname"predicted_viewmodel"false))
    {
        
SDKHook(entitySDKHook_SpawnOnEntitySpawned);
    }
}

//find both of the clients viewmodels
public OnEntitySpawned(entity)
{
    new 
Owner GetEntPropEnt(entityProp_Send"m_hOwner");
    if ((
Owner 0) && (Owner <= MaxClients))
    {
        if (
GetEntProp(entityProp_Send"m_nViewModelIndex") == 0)
        {
            
ClientVM[Owner][0] = entity;
        }
        else if (
GetEntProp(entityProp_Send"m_nViewModelIndex") == 1)
        {
            
ClientVM[Owner][1] = entity;
        }
    }
}

public 
OnPostThinkPost(client)
{
    static 
OldWeapon[MAXPLAYERS 1];
    static 
OldSequence[MAXPLAYERS 1];
    static 
Float:OldCycle[MAXPLAYERS 1];
    
    
decl String:ClassName[30];
    new 
WeaponIndex;
    
    
//handle spectators
    
if (!IsPlayerAlive(client))
    {
        new 
spec GetEntPropEnt(clientProp_Send"m_hObserverTarget");
        if (
spec != -1)
        {
            
WeaponIndex GetEntPropEnt(specProp_Send"m_hActiveWeapon");
            
GetEdictClassname(WeaponIndexClassNamesizeof(ClassName));
            if (
StrEqual("weapon_ump45"ClassNamefalse))
            {
                
SetEntProp(ClientVM[client][1], Prop_Send"m_nModelIndex"CustomModel1);
            }
            else if (
StrEqual("weapon_mp5navy"ClassNamefalse))
            {
                
SetEntProp(ClientVM[client][1], Prop_Send"m_nModelIndex"CustomModel2);
            }
        }
        
        return;
    }
    
    
WeaponIndex GetEntPropEnt(clientProp_Send"m_hActiveWeapon");
    new 
Sequence GetEntProp(ClientVM[client][0], Prop_Send"m_nSequence");
    new 
Float:Cycle GetEntPropFloat(ClientVM[client][0], Prop_Data"m_flCycle");
    
    if (
WeaponIndex <= 0)
    {
        new 
EntEffects GetEntProp(ClientVM[client][1], Prop_Send"m_fEffects");
        
EntEffects |= EF_NODRAW;
        
SetEntProp(ClientVM[client][1], Prop_Send"m_fEffects"EntEffects);
        
        
IsCustom[client] = false;
            
        
OldWeapon[client] = WeaponIndex;
        
OldSequence[client] = Sequence;
        
OldCycle[client] = Cycle;
        
        return;
    }
    
    
//just stuck the weapon switching in here aswell instead of a separate hook
    
if (WeaponIndex != OldWeapon[client])
    {
        
GetEdictClassname(WeaponIndexClassNamesizeof(ClassName));
        if (
StrEqual("weapon_ump45"ClassNamefalse))
        {
            
//hide viewmodel
            
new EntEffects GetEntProp(ClientVM[client][0], Prop_Send"m_fEffects");
            
EntEffects |= EF_NODRAW;
            
SetEntProp(ClientVM[client][0], Prop_Send"m_fEffects"EntEffects);
            
//unhide unused viewmodel
            
EntEffects GetEntProp(ClientVM[client][1], Prop_Send"m_fEffects");
            
EntEffects &= ~EF_NODRAW;
            
SetEntProp(ClientVM[client][1], Prop_Send"m_fEffects"EntEffects);
            
            
//set model and copy over props from viewmodel to used viewmodel
            
SetEntProp(ClientVM[client][1], Prop_Send"m_nModelIndex"CustomModel1);
            
SetEntPropEnt(ClientVM[client][1], Prop_Send"m_hWeapon"GetEntPropEnt(ClientVM[client][0], Prop_Send"m_hWeapon"));
            
            
SetEntProp(ClientVM[client][1], Prop_Send"m_nSequence"GetEntProp(ClientVM[client][0], Prop_Send"m_nSequence"));
            
SetEntPropFloat(ClientVM[client][1], Prop_Send"m_flPlaybackRate"GetEntPropFloat(ClientVM[client][0], Prop_Send"m_flPlaybackRate"));
            
            
IsCustom[client] = true;
        }
        else if (
StrEqual("weapon_mp5navy"ClassNamefalse))
        {
            new 
EntEffects GetEntProp(ClientVM[client][0], Prop_Send"m_fEffects");
            
EntEffects |= EF_NODRAW;
            
SetEntProp(ClientVM[client][0], Prop_Send"m_fEffects"EntEffects);
            
            
EntEffects GetEntProp(ClientVM[client][1], Prop_Send"m_fEffects");
            
EntEffects &= ~EF_NODRAW;
            
SetEntProp(ClientVM[client][1], Prop_Send"m_fEffects"EntEffects);
            
            
SetEntProp(ClientVM[client][1], Prop_Send"m_nModelIndex"CustomModel2);
            
SetEntPropEnt(ClientVM[client][1], Prop_Send"m_hWeapon"GetEntPropEnt(ClientVM[client][0], Prop_Send"m_hWeapon"));
            
            
SetEntProp(ClientVM[client][1], Prop_Send"m_nSequence"GetEntProp(ClientVM[client][0], Prop_Send"m_nSequence"));
            
SetEntPropFloat(ClientVM[client][1], Prop_Send"m_flPlaybackRate"GetEntPropFloat(ClientVM[client][0], Prop_Send"m_flPlaybackRate"));
            
            
IsCustom[client] = true;
        }
        else
        {
            
//hide unused viewmodel if the current weapon isn't using it
            
new EntEffects GetEntProp(ClientVM[client][1], Prop_Send"m_fEffects");
            
EntEffects |= EF_NODRAW;
            
SetEntProp(ClientVM[client][1], Prop_Send"m_fEffects"EntEffects);
            
            
IsCustom[client] = false;
        }
    }
    else
    {
        if (
IsCustom[client])
        {
            
//copy the animation stuff from the viewmodel to the used one every frame
            
SetEntProp(ClientVM[client][1], Prop_Send"m_nSequence"GetEntProp(ClientVM[client][0], Prop_Send"m_nSequence"));
            
SetEntPropFloat(ClientVM[client][1], Prop_Send"m_flPlaybackRate"GetEntPropFloat(ClientVM[client][0], Prop_Send"m_flPlaybackRate"));
            
            if ((
Cycle OldCycle[client]) && (Sequence == OldSequence[client]))
            {
                
SetEntProp(ClientVM[client][1], Prop_Send"m_nSequence"0);
            }
        }
    }
    
//hide viewmodel a frame after spawning
    
if (SpawnCheck[client])
    {
        
SpawnCheck[client] = false;
        if (
IsCustom[client])
        {
            new 
EntEffects GetEntProp(ClientVM[client][0], Prop_Send"m_fEffects");
            
EntEffects |= EF_NODRAW;
            
SetEntProp(ClientVM[client][0], Prop_Send"m_fEffects"EntEffects);
        }
    }
    
    
OldWeapon[client] = WeaponIndex;
    
OldSequence[client] = Sequence;
    
OldCycle[client] = Cycle;
}
//hide viewmodel on death
public Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
{
    new 
UserId GetEventInt(event"userid");
    new 
client GetClientOfUserId(UserId);
    
    new 
EntEffects GetEntProp(ClientVM[client][1], Prop_Send"m_fEffects");
    
EntEffects |= EF_NODRAW;
    
SetEntProp(ClientVM[client][1], Prop_Send"m_fEffects"EntEffects);
}

//when a player repsawns at round start after surviving previous round the viewmodel is unhidden
public Event_PlayerSpawn(Handle:event, const String:name[], bool:dontBroadcast)
{
    new 
UserId GetEventInt(event"userid");
    new 
client GetClientOfUserId(UserId);
    
    
//use to delay hiding viewmodel a frame or it won't work
    
SpawnCheck[client] = true;

i saw some people wanted to know how to this this from here.

this snippet changes the ump45 viewmodel to the p90 and the mp5 viewmodel to the tmp. i stuck the 2nd weapon code in there last minute just so you could see how it was done, that needs recoding a bit since theres a lot of duplicate code. in css each client gets 2 viewmodel entities, one seems to be unused from what i could tell so i just use that as a proxy. the unused viewmodel isn't predicted so clients with high pings will get delays before animations are played.

the code handle spectators aswell (thanks to GoD-Tony for mentioning a way to get it to work). in the thread mentioned above there is a snippet for world models but its not worth it because the model won't change when its on the ground or on the players back, also if the players weapon changes serverside which wasn't done on the client e.g weapon drop/pickup the weapon becomes invisible.

as long as the custom model has the same number of animations as the original, all custom animations have the same names as those in the original and the custom animations are the same length as the originals things will work fine otherwise you get animation glitches and such.

this snippet(modified) may work in other games if they also give clients an unused viewmodel, tf2 uses an extra one for the spies cloak activating/deactivating hand. in the sdk code clients can only have a maximum of 2 viewmodels, it may be different for hl2 mods and games may have their own maximum defined.

if clients haven't got the maximum number of viewmodels you can create a new one using an sdk call on the player using berni's virtual offset dumper look for CreateViewModel(int). the parameter is an index for how many viewmodels the client has, "m_nViewModelIndex" on the viewmodel stores its index, if the client has one viewmodel, its index will be 0, if they have 2 one will be 0 and the other 1 like an array. so to create a 2nd viewmodel you want to use an index of 1, 2 for a 3rd etc.

Last edited by blodia; 05-03-2012 at 15:51.
blodia is offline