Veteran Member
Join Date: Sep 2009
Location: UK
|
03-30-2012
, 15:01
[SNIPPET][CSS]custom viewmodels without flickers and without disabling prediction
|
#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 = 1; client <= MaxClients; client++) { if (IsClientInGame(client)) { SDKHook(client, SDKHook_PostThinkPost, OnPostThinkPost); //find both of the clients viewmodels ClientVM[client][0] = GetEntPropEnt(client, Prop_Send, "m_hViewModel"); new PVM = -1; while ((PVM = FindEntityByClassname(PVM, "predicted_viewmodel")) != -1) { if (GetEntPropEnt(PVM, Prop_Send, "m_hOwner") == client) { if (GetEntProp(PVM, Prop_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(client, SDKHook_PostThinkPost, OnPostThinkPost); }
public OnEntityCreated(entity, const String:classname[]) { if (StrEqual(classname, "predicted_viewmodel", false)) { SDKHook(entity, SDKHook_Spawn, OnEntitySpawned); } }
//find both of the clients viewmodels public OnEntitySpawned(entity) { new Owner = GetEntPropEnt(entity, Prop_Send, "m_hOwner"); if ((Owner > 0) && (Owner <= MaxClients)) { if (GetEntProp(entity, Prop_Send, "m_nViewModelIndex") == 0) { ClientVM[Owner][0] = entity; } else if (GetEntProp(entity, Prop_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(client, Prop_Send, "m_hObserverTarget"); if (spec != -1) { WeaponIndex = GetEntPropEnt(spec, Prop_Send, "m_hActiveWeapon"); GetEdictClassname(WeaponIndex, ClassName, sizeof(ClassName)); if (StrEqual("weapon_ump45", ClassName, false)) { SetEntProp(ClientVM[client][1], Prop_Send, "m_nModelIndex", CustomModel1); } else if (StrEqual("weapon_mp5navy", ClassName, false)) { SetEntProp(ClientVM[client][1], Prop_Send, "m_nModelIndex", CustomModel2); } } return; } WeaponIndex = GetEntPropEnt(client, Prop_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(WeaponIndex, ClassName, sizeof(ClassName)); if (StrEqual("weapon_ump45", ClassName, false)) { //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", ClassName, false)) { 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.
|
|