Livescript Objects
In this tutorial, we will learn how to create a new command that expands on the previous one. Instead of just sending ourselves a message, we’ll the command cast a spell on our currently selected target.
Getting a players current selection
We will start by presenting a naive version of this script. The function we call to get a players selection is .GetSelection()
, so we might end up with the following code:
export function Main(events: TSEvents) {
events.Player.OnCommand((player,command,found)=>{
if(command.get() == "ping")
{
// casts "Fireball" on our current selection
player.CastSpell(player.GetSelection(), 133)
}
})
}
However, if we type this code into vscode it’ll show us an error with a red line under the player.GetSelection()
expression.
The reason for this is that players don’t always have selections, and if we check the return value of GetSelection()
, we’ll see that it can be undefined.
To fix this, we can use normal typescript conventions for dealing with undefined values by checking it with an if statement:
export function Main(events: TSEvents) {
events.Player.OnCommand((player,command,found)=>{
if(command.get() == "ping")
{
const selection = player.GetSelection()
if (selection)
{
// casts "Fireball" on our current selection
player.CastSpell(selection, 133)
}
}
})
}
Sending a Message
To expand the above example, we’ll also try sending a chat message to the the target before casting the spell on them. We might end up with the following code:
export function Main(events: TSEvents) {
events.Player.OnCommand((player,command,found)=>{
if(command.get() == "ping")
{
const selection = player.GetSelection()
if (selection)
{
// casts "Fireball" on our current selection
selection.SendBroadcastMessage(`Fireball!`)
player.CastSpell(selection, 133)
}
}
})
}
However, even though we already checked the selection, vscode complains again about the selection
variable. Why is this?
Type Hierarchies
We have already learnt about the Player
object type in the previous tutorial, where we could send them a chat message using the player.SendBroadcastMessage
method.
Players are not the only types of entities that exist in the running server, but they exist in a type hierarchy along with other types of entities, such as Creatures
(non-player characters) and GameObjects
(chairs, chests, doors, flagpoles etc.).
A type hierarchy is a concept from object-oriented programming, and means that multiple object types can have common ancestors that they inherit ‘methods’, or functions from. The SendBroadcastMessage
function we call on player objects with the .
operator is one such method, and method in this context just means “function that is attached to an object”.
It is not necessary to understand all the aspects of type hierarchies to use livescripts, but it is useful to know what it means for what we can do with entities in the game.
The root object types used by all ‘object’ entities in the game is the Object
type, and it has (for our purposes) the following important child types:
Object
WorldObject
: Anything that can be spawned in the worldGameObject
: Doors, chests, buttons, chairs etcUnit
: Anything that can walk around and enter combatPlayer
: Player characters logged into the gameCreature
: Any unit that is not a player
What it means for a type to be a child type of other types is that they always get all the same methods as the parent. For example, the Unit
type has a GetName
method, which means that all players and creatures also has a GetName
method.
A more intuitive way to say this can be that “All Players are Units, but all Units are not Players, because some are Creatures instead”.
Downcasting
So we understand that Units
can be both Players
and Creatures
, and it turns out that the type value of player.GetSelection()
is a Unit
. Since Units
are not always Players
and it doesn’t make any sense to send chat messages to creatures, it doesn’t have any SendBroadcastMessage
method.
In order to send a message to the player selection, we need to cast it into a player. This is done with a special function called ToPlayer
, which returns a Player type that we can call the method on. We get the following modified code.
export function Main(events: TSEvents) {
events.Player.OnCommand((player,command,found)=>{
if(command.get() == "ping")
{
const selection = ToPlayer(player.GetSelection())
if (selection)
{
// casts "Fireball" on our current selection
selection.SendBroadcastMessage(`Fireball!`)
player.CastSpell(selection, 133)
}
}
})
}
Because a downcast like ToPlayer
might fail, the selection could be a creature, we need to do that cast before we check the variable in the if
-statement, as the ToPlayer
function will return an undefined
object if the input is undefined
or if the input is not a player.