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: Difference between revisions

From TF2 Classified Wiki
Created page with "{{stub}} category:guides"
 
mNo edit summary
 
(3 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{stub}}
{{stub}}
{{todo|Make the page more beginner friendly:
Start simple
Give plenty of examples that each go through multiple new things
Add explanations for detecting actions like attack, attack2 and reload
}}
TF2C extends TF2 and MapBase's VScripting, making anything possible in either of them mostly possible in TF2C.
VScript in TF2C uses [https://en.wikipedia.org/wiki/Squirrel_(programming_language) Squirrel] <sup>{{tooltip|Note|as of writing the actual squirrel website is inaccessible for whatever reason, so the Wikipedia page will have to do for now I suppose}}</sup>, 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.<br>
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 ==
Here's an example table that disables Medic's health regen when he's holding a weapon that has a custom, schema defined attribute.
<pre>
::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)
}
}
}
}
}
</pre>
And later we can do <code>__CollectGameEventCallbacks(AttributeTable)</code> which collects all of our functions in the table and acts on them.<br>
While there is no such thing as a "medic no heal" attribute, we can just make one up in our item schema like this:
<pre>
"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"
}
}
}
</pre>
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 weapon pack does its VScript attributes <sub>{{tooltip|Todo|should we link this?}}</sub>
For example we're going to pick apart one of them, specifically the "mod shoot self" attribute.
<pre>
::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" );
</pre>
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.<br>
<code>::ListenToGameEvent( <GameEvent>, function(args){behavior}, <String>)</code><br>
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. <code>::ListenToGameEvent( "player_shoot", function(event){printl("doing things")}, "didathing")</code> 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 =
[https://developer.valvesoftware.com/wiki/Team_Fortress_2/Docs/Scripting/Script_Functions TF2's VScript Functions]<br>
[https://developer.valvesoftware.com/wiki/Team_Fortress_2/Docs/Scripting/Game_Events TF2's Game Events (used for hooks)]<br>
[https://developer.valvesoftware.com/wiki/Mapbase/Scripting/Script_Functions MapBase's VScript Functions]<br>
[https://developer.valvesoftware.com/wiki/Team_Fortress_2/Docs/Scripting/Script_Functions MapBase's Game Events (used for hooks)]<br>
[https://developer.valvesoftware.com/wiki/Source_2013_MP/Scripting/Game_Events Source 2013's Game Events (used for hooks)]<br>


[[category:guides]]
[[category:guides]]