Team Fortress 2/Scripting/VScript Examples: Difference between revisions
(→Listening for events: Updated the demo event because eventlistener callback was fixed. (Feel free to change the example I made)) |
mNo edit summary |
||
Line 24: | Line 24: | ||
// This is a table of keyvalues, which is the same way as keyvalues that are defined in Hammer | // This is a table of keyvalues, which is the same way as keyvalues that are defined in Hammer | ||
// Key Value | // Key Value | ||
basevelocity = ply. | basevelocity = ply.EyeAngles().Forward()*250, | ||
teamnumber = ply.GetTeam(), | teamnumber = ply.GetTeam(), | ||
origin = ply.EyePosition()+ply. | origin = ply.EyePosition()+ply.EyeAngles().Forward()*32, | ||
angles = ply. | angles = ply.EyeAngles() | ||
}) | }) | ||
Line 116: | Line 116: | ||
The following code shows how to listen for the post_inventory_application event, then add uber protection for 2 seconds. | The following code shows how to listen for the post_inventory_application event, then add uber protection for 2 seconds. | ||
<source lang=cpp> | |||
// The event "post_inventory_application" is sent when a player gets a whole new set of items, aka touches a resupply locker / respawn cabinet or spawns in. | // The event "post_inventory_application" is sent when a player gets a whole new set of items, aka touches a resupply locker / respawn cabinet or spawns in. | ||
function OnGameEvent_post_inventory_application(params) | function OnGameEvent_post_inventory_application(params) | ||
{ | { | ||
local player = GetPlayerFromUserID(params.userid) | |||
// add uber protection to the player for 2 seconds. | |||
player.AddCondEx("TF_COND_INVULNERABLE_USER_BUFF", 2.0, null) | |||
} | } | ||
__CollectGameEventCallbacks(this) | |||
</source> | |||
Additionally, you can listen for events using an entity called "logic_eventlistener". | Additionally, you can listen for events using an entity called "logic_eventlistener". | ||
The following code shows how to use this entity to listen for the player_hurt event, and then apply an attribute to the victim. | The following code shows how to use this entity to listen for the player_hurt event, and then apply an attribute to the victim. | ||
<source lang=cpp> | |||
// run: | // run: | ||
// script_execute hurt_event.nut | // script_execute hurt_event.nut | ||
Line 187: | Line 178: | ||
Say(0, str, false) | Say(0, str, false) | ||
} | } | ||
</source> | |||
</source> | |||
== Setting up a boss health bar == | == Setting up a boss health bar == |
Revision as of 14:09, 24 November 2022
This page contains examples of vscripts for Team Fortress 2.
Iterating through entities
With awhile
loop and a Entities.FindByClassname() function, you can iterate through all entities of a matching classname, based on your arguments.
The first parameter of Entities.FindByClassname() is named 'previous' which accepts a script handle (of an entity inherits 'CBaseEntity' specifically), which verifies if the matching entity it finds has an entity index that's higher than the current one in the 'previous' argument. If it turns out to be not, then its ignored.
local ent = null
while( ent = Entities.FindByClassname(ent, "prop_physics") )
{
printl(ent)
}
Spawning an entity
The following code shows to spawn an entity, specifically a rocket. The rocket will be spawned in front of the first available player.
// By ZooL_Smith
local ply = Entities.FindByClassname(null, "player")
local rocket = SpawnEntityFromTable("tf_projectile_rocket",
{
// This is a table of keyvalues, which is the same way as keyvalues that are defined in Hammer
// Key Value
basevelocity = ply.EyeAngles().Forward()*250,
teamnumber = ply.GetTeam(),
origin = ply.EyePosition()+ply.EyeAngles().Forward()*32,
angles = ply.EyeAngles()
})
rocket.SetOwner(ply) // make it not collide with owner and give proper kill credits
Swapping the !activator's team
Whenever a script's code gets executed by inputs (i.e. RunScriptCode
/CallScriptFunction
on an entity with an attached entity script), the activator
and caller
variables will be set to the handles of the input's !activator and !caller respectively, allowing you to access them in code.
We can use that to easily do something to players that walk in a specific trigger, for example.
As for swapping the !activator's team, we need to do both change their team based on which team they are, and then also change the their cosmetic items' team to the their's new team.
First we need to figure on which team the !activator is. For that, we can use the GetTeam()
method on the !activator (activator.GetTeam()
) to get the number index of their team.
You can either use an if function to compare the returned value with the desired team index, or store it in a variable to use it later. The latter in this case may be better to reduce the length of the script.
For reference: 0 is Unassigned (no team yet), 1 is Spectator, 2 is Red, 3 is Blu.
To change the !activator's team, we should use the ForceChangeTeam(Int : Team index, Bool : Kill player if game is in Highlander mode + remove their dominations)
method on them via activator.ForceChangeTeam(...)
.
To change their cosmetic items' colour, we need to iterate over every tf_wearable entity, and then change its team via SetTeam(Int : Team index)
if they are worn by the !activator.
- To iterate, we need to specify a null variable (e.g.
local cosmetic = null
), and then pass it into the following while loop:
local cosmetic = null // Assign the "cosmetic" variable to null, will be assigned new values when going over cosmetic items
while (cosmetic = Entities.FindByClassname(cosmetic, "tf_wearable"))
{
// Do things to individual tf_wearables entities here, the "cosmetic" variable is set to the current tf_wearable entity we are iterating through
}
- To check if the cosmetic item belongs to the !activator, we can simply compare the value returned by the
GetOwner()
method when we use it on the tf_wearable (e.g.cosmetic.GetOwner()
) against the !activator. Example code:
if (cosmetic.GetOwner() == activator)
{
// Do things if the cosmetic's wearer is the !activator
}
- To change the cosmetic's team, we should use the
SetTeam(Int : Team index)
method on the cosmetic item entity.
Full code with comments:
function ActivatorSwapTeam() // Call this in map via RunScriptCode > ActivatorSwapTeam() or CallScriptFunction > ActivatorSwapTeam on a logic_script that has an Entity Script with this function
{
// The following snippet checks if an !activator has been specified and if they are a player
// If either question's answer is no, then don't execute the rest of the function
if (activator == null || activator.IsPlayer() == false)
{
return
}
// The following snippet compares the !activator's team number (Ranging from 0 to 3) to the ones used by Unassigned (0) and Spectator (1)
// If they match with either Unassigned or Spectator, we don't execute the rest of the function
// Used to ignore any potentional spectator !activators
if (activator.GetTeam() == 0 || activator.GetTeam() == 1)
{
return
}
// The following snippet specifies a local newTeam variable, and then we set it to a team number based off the !activator's current team number
local newTeam = 0
if (activator.GetTeam() == 2) // Checks if the !activator's team number is 2 (Red), and sets the newTeam variable to 3 (Blu)
{
newTeam = 3
} else { // If the !activator's team number is not 2 (Red), sets the newTeam variable to 2 (Blu) instead
newTeam = 2
}
// The following snippet calls the ForceChangeTeam method on the !activator
// First parameter: Team number to switch to
// Second parameter: If false, the game will reset the player's dominations and nemesises, and kill them if mp_highlander is on
activator.ForceChangeTeam(newTeam, true)
local cosmetic = null // Assign the "cosmetic" variable to null, will be assigned new values when going over cosmetic items
// The following snippet will go over every cosmetic item currently present, and will change its colours to the appropriate team if they are the !activator's
while (cosmetic = Entities.FindByClassname(cosmetic, "tf_wearable")) // Goes over every cosmetic item, executing code below
{
if (cosmetic.GetOwner() == activator) // Checks if the currently iterated cosmetic item's wearer is the !activator
{
cosmetic.SetTeam(newTeam) // Sets the team of the cosmetic item to the new team number that we stored in newTeam
}
}
}
Listening for events
Many actions in the game fire events to notify other parts of code that something has happened. For example, when a player dies or when an Engineer's building is sapped. Each event can also hold certain type of data, such as the index of an involved entity.
VScript can catch these events, parse the data and run some code when they happen. The list of available events can be found here: https://wiki.alliedmods.net/Team_Fortress_2_Events
The following code shows how to listen for the post_inventory_application event, then add uber protection for 2 seconds.
// The event "post_inventory_application" is sent when a player gets a whole new set of items, aka touches a resupply locker / respawn cabinet or spawns in.
function OnGameEvent_post_inventory_application(params)
{
local player = GetPlayerFromUserID(params.userid)
// add uber protection to the player for 2 seconds.
player.AddCondEx("TF_COND_INVULNERABLE_USER_BUFF", 2.0, null)
}
__CollectGameEventCallbacks(this)
Additionally, you can listen for events using an entity called "logic_eventlistener". The following code shows how to use this entity to listen for the player_hurt event, and then apply an attribute to the victim.
// run:
// script_execute hurt_event.nut
// Simple script that pushes players and apply an attribute when hurt
// From ZooL_Smith with love
local targetname = "event_player_hurt"
local listener = SpawnEntityFromTable("logic_eventlistener",
{
targetname = targetname,
EventName = "player_hurt",
IsEnabled = true,
FetchEventData = true,
OnEventFired = targetname+",RunScriptCode,player_hurt(),0,-1",
// vscripts = "player_hurt.nut"
})
listener.ValidateScriptScope() // Generate a VScript scope
listener.GetScriptScope().player_hurt <- function() // Define a new function
{
// "event_data" is the table that gets filled if "FetchEventData" is true
// this table will be filled with the data from the event.
// it is only available inside the entity's scope.
local attacker = GetPlayerFromUserID(event_data.attacker)
local victim = GetPlayerFromUserID(event_data.userid)
local newvel = victim.GetVelocity()
newvel.z = RandomFloat(10,10)*event_data.damageamount
newvel.x += RandomFloat(-10,10)*event_data.damageamount
newvel.y += RandomFloat(-10,10)*event_data.damageamount
// attribute name, value, time
victim.AddCustomAttribute("move speed penalty", 0.35, 1)
victim.SetVelocity(newvel)
local str = victim+" took "+event_data.damageamount+" damage"
if(attacker)
str += " from player "+attacker
// 0 is server, other numbers are players
Say(0, str, false)
}
Setting up a boss health bar
The boss bar that appears while any bosses are active (like MONOCULUS!, for example) is handled by the monster_resource
entity, which conveniently exists in the map normally.
You can do Entities.FindByClassname(null, "monster_resource")
to get the monster_resource
entity most of the time, but it'd be convenient to store it into a variable instead.
In order to modify the health bar's percentage, it'd be useful to first know about NetProps:
- NetProps are network properties of an entity, which are server-side only.
- They can be accessed and changed by the NetProps class methods.
The monster_resource
entity has a m_iBossHealthPercentageByte NetProp, which determines the percentage state of the health bar based on a byte value - its value must be between 0 and 255, where 0 is 0%, and 255 is 100%
The following code will add a health bar with 25% health after executed.
local healthBar = Entities.FindByClassname(null, "monster_resource") // Get the health bar entity.
if (healthBar && healthBar.IsValid) { // Check if the health bar entity exists and if it is valid, just in case to prevent errors or potential crashes.
// The following line will update the health bar's percentage to 25% by changing its NetProp.
// Do note that because it is a byte (0 - 255), we need to multiply our percentage by 255.
NetProps.SetPropInt(healthBar, "m_iBossHealthPercentageByte", 0.25 * 255)
}
Disabling HUD Elements
Some HUD elements can be hidden away by placing a bit value into one of these netprops:
NetProps.SetPropInt(player, "m_Local.m_iHideHUD", value )
NetProps.SetPropInt(player, "localdata.m_Local.m_iHideHUD", value )


The bit's functions are found on the TF2 Script Functions.
For example:
local HideHudValue = NetProps.GetPropInt(player, "m_Local.m_iHideHUD")
NetProps.SetPropInt(player, "m_Local.m_iHideHUD", HideHudValue | (Constants.HideHUD.HIDEHUD_CROSSHAIR|Constants.HideHUD.HIDEHUD_HEALTH|Constants.HideHUD.HIDEHUD_WEAPONSELECTION)) // adds bits
NetProps.SetPropInt(player, "m_Local.m_iHideHUD", HideHudValue & ~(Constants.HideHUD.HIDEHUD_CROSSHAIR|Constants.HideHUD.HIDEHUD_HEALTH|Constants.HideHUD.HIDEHUD_WEAPONSELECTION)) // removes bits
Line 2 would add bits to hide the crosshair, health, and disabling weapon switching from the player.
Line 3 would remove bits to show the crosshair, health, and re-enable weapon switching for the player.