Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Creating custom weapons - VScript

From TF2 Classified Wiki
Revision as of 14:39, 15 July 2026 by ChaoticUltimateJ (talk | contribs) (I did a lot.)

TF2C extends TF2 and MapBase's VScripting, making anything possible in either of them mostly possible in TF2C.

Vscript in TF2C uses Squirrel Note, which is also used by TF2 and MapBase for their Vscripting as well.

Hooks

A hook in modding is when you get the game to run a function, so you can perform custom behavior or replace existing behavior. We can't actually replace TF2C's code directly of course, so instead we use a tool provided to us by Valve and the TF2C team called Vscript.
Hooking can be done in two ways, a table and collector (making a table of functions and collecting them) and a direct hook to the game's events. As of writing, the writer here has no idea what the difference is.

Table and collector method

Heres an example table that disables medic's health regen when he's holding a weapon that has a custom, schema defined attribute.

::AttributeTable <- {
// when a player heals
	function OnGameEvent_player_healed(params)
	{
		if (params.patient == params.healer) // if we heal ourselves
		{
			// get ourselves
			local player = GetPlayerFromUserID(params.patient)
			
			local activeweapon = player.GetActiveWeapon()
			
			// check the weapons attributes and "disable" healing if they have a specific attribute
			if ( activeweapon.GetAttribute("medic no heal", 0) == 1 )
			{
				if (player.GetPlayerClass() == Constants.ETFClass.TF_CLASS_MEDIC)
				{
					// unheal... basically
					player.SetHealth(player.GetHealth() - params.amount)
				}
			}
		}
	}
}

And later we can do __CollectGameEventCallbacks(AttributeTable) which collects all of our functions in the table and acts on them. While there is no such thing as a "medic no heal" attribute, we can just make one up in our item schema like this:

"items_game"
{
	<...>
	"attributes"
	{
		"7000"
		{
			"name"			"medic no heal"
			"description_string"	"While Active: No longer passively heal"
			"description_format"	"value_is_additive"
			"hidden"			"0"
			"effect_type"		"negative"
		}
	}
}

It also doesn't need an attribute class either, since the Vscript only uses the attribute's name, so ideally you shouldn't have an attribute class on vscript attributes.

Direct hook method

The direct hook method is the same way the Official April Fools Mod does it's VScript attributes Todo For example we're going to pick apart one of them, specifically the "mod shoot self" attribute.

::ListenToGameEvent( "player_shoot", function( hEvent )
{
	// Get the player and throw away the event if the event isn't from the player
	local hPlayer = GetPlayerFromUserID( hEvent.userid );
	if ( !hPlayer )
	{
		return;
	}

	// Get the weapon and throw away the event if the event isn't from the weapon 
	local hWeapon = hPlayer.GetActiveWeapon()
	if ( !hWeapon || !hWeapon.GetAttribute( "mod shoot self", 0 ) )
	{
		return;
	}

	// Take damage equal to your health, plus 10
	hPlayer.TakeDamage( hPlayer.GetMaxHealth() + 10, Constants.FDmgType.DMG_PREVENT_PHYSICS_FORCE, hPlayer );

	// For the sound to not get cut off from dying we need to emit it from the ragdoll.
	local hRagdoll = NetProps.GetPropEntity( hPlayer, "m_hRagdoll" );
	if ( hRagdoll )
	{
		hRagdoll.EmitSound( "Weapon_Scatter_Gun.Single" );
	}

	// Turn the player's head around so the shots are fired behind him.
	local angEyeAngles = hPlayer.EyeAngles()

	angEyeAngles.x = -angEyeAngles.x;
	angEyeAngles.y += 180;
	if ( angEyeAngles.y > 180 )
	{
		angEyeAngles.y -= 360;
	}

	hPlayer.SnapEyeAngles( angEyeAngles );
}, "mod_shoot_self" );

We can actually easily condense the whole script into one line so it's easier to understand how the hook works and how it modifies the game behavior.
::ListenToGameEvent( <GameEvent>, function(args){behavior}, <String>)
Now that we have it all condensed down into one line, this is basically how hooking a game event works. We run a function when a game event happens, which passes a table of information through args, and runs behavior. For a much simpler example, here's a one-line piece of code that prints to the log whenever a gun is fired. ::ListenToGameEvent( "player_shoot", function(event){printl("doing things")}, "didathing") In this instance, the table of info is passed to "event", which isn't used but is still required because the game still calls the function with that table regardless, and can't just throw it out. will print "doing things" to the console whenever we shoot.

See also

TF2's VScript Functions
TF2's Game Events (used for hooks)

MapBase's VScript Functions
MapBase's Game Events (used for hooks)

Source 2013's Game Events (used for hooks)