8. Types
Every value in MSC has a type. The type determines what the value represents, what operations can be performed on it, and what methods are available. So far we have used types like Int, String, and Player without much explanation. This chapter explores how types work in detail and introduces user-defined types, which allow you to create your own.
8.1. What is a Type?
A type is a classification that defines what a value can do. When you write @define Int count = 5, you are telling MSC that count holds integer values and can be used with operations like +, -, and *. When you write @define Player p = Player("Ajdj123321"), you are telling MSC that p holds a reference to a player and can use methods like getName() and getHealth().
Types serve three purposes:
Safety: MSC can catch errors before your script runs. If you try to call
getName()on an integer, MSC knows that doesn’t make sense and reports an error.Documentation: Types make code easier to understand. Seeing
Player targettells you what kind of value to expect, even without reading the rest of the script.Organization: Complex types group related data and behavior together. A
Playerbundles a name, position, health, inventory, and dozens of methods into a single value.
8.2. Built-in Types
MSC provides two categories of built-in types: primitive types for basic values, and complex types for interacting with the Minecraft world.
8.2.1. Primitive Types
Primitive types represent simple values. They are covered in detail in Variables, but here is a quick summary:
Type |
Description |
Example Literals |
|---|---|---|
|
Text |
|
|
Whole number (-2 billion to 2 billion) |
|
|
Large whole number |
|
|
Decimal number |
|
|
Precise decimal number |
|
|
True or false |
|
Primitive types have constructors for converting between types:
# Convert a String to an Int
@define Int x = Int("42")
# Convert an Int to a String
@define String s = String(100)
# Convert an Int to a Double
@define Double d = Double(5)
@player {{x}}, {{s}}, {{d}}
42, 100, 5.0
8.2.2. Complex Types
Complex types represent objects in the Minecraft world. Unlike primitives, they are created using constructors and provide methods for interacting with their data.
Type |
Description |
|---|---|
|
A Minecraft player. Provides access to name, position, health, inventory, and more. |
|
Any Minecraft entity (mobs, armor stands, arrows, etc.). |
|
A block in the world at specific coordinates. |
|
An item stack with a type and amount. |
|
A position with decimal coordinates (x, y, z) and a world. |
|
A position with integer coordinates aligned to the block grid. |
|
A Location with yaw and pitch (direction the entity is facing). |
|
A WorldGuard region or a custom-defined area. |
|
A 3D vector of Doubles (x, y, z). |
|
A 3D vector of Ints aligned to the block grid. |
|
A 2D vector of Doubles (x, z). |
|
A 2D vector of Ints on the XZ plane. |
For a complete reference of constructors and methods for each type, see Built-in Types.
8.3. Constructors
A constructor creates a new instance of a type. Constructors are called by writing the type name followed by arguments in parentheses:
# Create a Player from their username
@define Player p = Player("Ajdj123321")
# Create a Block from coordinates and world name
@define Block b = Block(100, 64, -200, "Theta")
# Create a Location with precise decimal coordinates
@define Location loc = Location(100.5D, 64.0D, -200.5D, "Theta")
Each type has specific constructors that accept different arguments. For example, Player can be constructed from a name, a UUID, or coordinates:
# Find player by their current username
@define Player p1 = Player("Ajdj123321")
# Find player by their UUID (works even if they changed their name or are offline)
@define Player p2 = Player("e62bfa1e-f625-4ad3-9403-7e7f8e14d0f1")
# Find a player standing at specific coordinates
@define Player p3 = Player(100, 64, -200, "Theta")
The first finds a player by name, the second by UUID, and the third finds a player at specific coordinates. If no matching player exists or is online, the constructor returns null.
Constructors that might fail (like Player for an offline player or Block for an unloaded chunk) may return null. Always check for null before using the result:
@define Player target = Player("someone")
@if target != null
@player Found {{target.getName()}}!
@else
@player That player is not online.
@fi
That player is not online.
8.4. Methods
Methods are functions that belong to a type (see also Functions for standalone functions). They are called using dot notation on a value:
@define Player p = Player("Ajdj123321")
@player Name: {{p.getName()}}
@player Health: {{p.getHealth()}}
@player World: {{p.getWorld()}}
Name: Ajdj123321
Health: 20.0
World: Theta
Methods can take arguments:
@define Vector3 initial = Vector3(1.0D, 2.0D, 3.0D)
@define Vector3 other = Vector3(4.0D, 5.0D, 6.0D)
@define Double dot_product = initial.dot(other)
@player Dot product: {{dot_product}}
Dot product: 32.0
Some methods modify the object (these typically return Void):
# Heal the player to full health
@var player.setHealth(20.0D)
# Close any open inventory screen
@var player.closeInventory()
Others return a value that can be used in expressions:
@if player.getHealth() < 10.0D
@player &cYou're low on health!
@fi
Methods can be chained when each method returns a value:
@player {{" hello world ".trim().toUpperCase()}}
HELLO WORLD
8.5. Working with Built-in Types
Here are practical examples of working with the most commonly used types.
Technical Detail
A full list of all built-in types, their constructors, and methods can be found in Built-in Types. These examples cover only a subset of the available functionality for illustrative purposes.
8.5.1. Player
The Player type provides access to nearly everything about a player:
@player Name: {{player.getName()}}
@player Position: {{player.getX()}}, {{player.getY()}}, {{player.getZ()}}
@player Health: {{player.getHealth()}} / {{player.getMaxHealth()}}
@player Is sneaking? {{player.isSneaking()}}
Name: Ajdj123321
Position: 1508.5, 101.0, -6187.5
Health: 20.0 / 20.0
Is sneaking? false
You can modify player state:
# Restore full health
@var player.setHealth(20.0D)
# Give 100 experience points
@var player.giveExp(100)
# Fill hunger bar
@var player.setFoodLevel(20)
And access inventory:
@define Item hand = player.getItemInMainHand()
# Check if holding something (AIR means empty hand)
@if hand.getItemType() != "AIR"
@player You're holding {{hand.getItemType()}}
@else
@player Your hand is empty
@fi
You're holding DIAMOND_SWORD
8.5.2. Block
The Block type represents a block in the world:
@define Block b = Block(100, 64, -200, "Theta")
@player Type: {{b.getBlockType()}}
@player Light level: {{b.getLightLevel()}}
@player Is powered? {{b.isBlockPowered()}}
Type: STONE
Light level: 0
Is powered? false
Get blocks relative to another block:
# Get the block directly below this one
@define Block below = block.getRelative(0, -1, 0)
@if below.isEmpty()
@player There's nothing below!
@else
@player Below: {{below.getBlockType()}}
@fi
Below: STONE
8.5.3. Location and Position
Location represents a precise position with decimal coordinates. Position adds yaw and pitch for direction:
# Get the player's current location
@define Location loc = player.getLocation()
@player You are at {{loc.getX()}}, {{loc.getY()}}, {{loc.getZ()}}
# Get full position including where they're looking
@define Position pos = player.getPosition()
@player Facing: yaw={{pos.getYaw()}}, pitch={{pos.getPitch()}}
You are at 1508.5, 101.0, -6187.5
Facing: yaw=90.0, pitch=0.0
Convert between location types:
# Create a precise location
@define Location loc = Location(100.7D, 64.3D, 200.2D, "Theta")
# Convert to block coordinates (floors each component)
@define BlockLocation blockLoc = loc.asBlockLocation()
@player Block coordinates: {{blockLoc}}
Block coordinates: 100 64 200 Theta
Teleport a player using a Position:
# Create destination: x, y, z, yaw (90 = west), pitch (0 = level), world
@define Position destination = Position(100.0D, 64.0D, -200.0D, 90.0, 0.0, "Theta")
@var player.teleport(destination)
@player Teleported!
Teleported!
8.5.4. Region
The Region type wraps WorldGuard regions:
# Look up an existing WorldGuard region
@define Region r = Region("spawn", "Alpha")
@if r.containsPlayer(player)
@player You are in spawn!
@else
@player You are not in spawn.
@fi
You are not in spawn.
Create a temporary region for checking bounds:
# Define corners of a bounding box
@define BlockVector3 min = BlockVector3(0, 60, 0)
@define BlockVector3 max = BlockVector3(100, 80, 100)
# Create a transient region (not saved to WorldGuard)
@define Region bounds = Region(min, max, "Theta")
@if bounds.containsPlayer(player)
@player You are within the bounds!
@else
@player You are outside the bounds.
@fi
You are outside the bounds.
8.6. User-Defined Types
While built-in types cover most needs, you can create your own types to organize related data. User-defined types group fields (variables) and methods (functions) into a reusable structure.
Beginner Note
User-defined types are an advanced feature. If you’re new to MSC, focus on built-in types first before exploring this section. The rest of this chapter can be revisited when you’re more comfortable with MSC scripting and ready to create complex data structures.
Consider a map that tracks player statistics. You might have separate variables for each stat:
/variable define mymap relative Int deaths = 0
/variable define mymap relative Int jumps = 0
/variable define mymap relative Long startTime = 0L
This works, but the variables are loosely connected. A user-defined type bundles them together:
/type define mymap PlayerStats
Now PlayerStats is a type that can hold fields and methods, just like Player or Block.
8.6.1. Creating a Type
Types are created with the /type define command (all type-related commands are listed in the Command Reference):
/type define <namespace> <TypeName>
Type names must start with an uppercase letter:
/type define mymap Coordinate
/type define mymap PuzzleState
/type define mymap Timer
Once a type exists, you can add fields, methods, and constructors to it.
8.7. Fields
Fields are variables that belong to each instance of a type. They store the state of the instance.
Add a field with the /type field define command:
/type field define <namespace> <TypeName> <FieldType> <fieldName>
For example, a Coordinate type might store a position and a label:
/type field define mymap Coordinate Int x
/type field define mymap Coordinate Int y
/type field define mymap Coordinate Int z
/type field define mymap Coordinate String label
Access fields using dot notation:
@using mymap
# Create a coordinate (assuming constructor is defined)
@define Coordinate spot = Coordinate(100, 64, -200, "Treasure")
@player "{{spot.label}}" is at {{spot.x}}, {{spot.y}}, {{spot.z}}
"Treasure" is at 100, 64, -200
Modify fields the same way:
# Update the position and label
@var spot.x = 150
@var spot.label = "New Treasure"
@player Now at {{spot.x}} with label "{{spot.label}}"
Now at 150 with label "New Treasure"
Each instance has its own copy of each field. Changing one instance does not affect others:
@define Coordinate c1 = Coordinate(0, 64, 0, "A")
@define Coordinate c2 = Coordinate(100, 64, 100, "B")
# Only c1 is changed
@var c1.label = "Changed"
@player c1: {{c1.label}}, c2: {{c2.label}}
c1: Changed, c2: B
8.8. Methods
Methods are functions that operate on instances of a type. They can read and modify the instance’s fields, and they have access to the this keyword.
Define a method with the /type method define command:
/type method define <namespace> <TypeName> [ReturnType] <methodName>([parameters])
For example, a method to calculate distance from the coordinate to a player:
/type method define mymap Coordinate Double distanceTo(Player p)
Then add the method body with the script command:
/script create method mymap Coordinate distanceTo(Player)
The script might be:
# Calculate the difference in each dimension
@define Double dx = Double(this.x) - p.getX()
@define Double dy = Double(this.y) - p.getY()
@define Double dz = Double(this.z) - p.getZ()
# Return the Euclidean distance
@return math::sqrt(dx*dx + dy*dy + dz*dz)
Now the method can be called on any instance:
@define Coordinate goal = Coordinate(100, 64, -200, "Goal")
@player Distance to goal: {{goal.distanceTo(player)}} blocks
Distance to goal: 42.5 blocks
Methods without a return type perform actions:
/type method define mymap Coordinate teleportHere(Player p)
# Build a Position from the coordinate's fields
@define Position pos = Position(Double(this.x), Double(this.y), Double(this.z), 0.0, 0.0, "Theta")
# Teleport the player
@var p.teleport(pos)
@player {{p.getName()}} teleported to "{{this.label}}"!
8.9. The this Keyword
Inside a method or constructor, this refers to the current instance. Use it to access the instance’s fields and methods.
Without this, there would be no way for a method to access its own instance’s data:
@return this.x + this.y + this.z
If a parameter has the same name as a field, this disambiguates them:
@var this.x = x
@var this.y = y
In this example, x refers to the parameter, while this.x refers to the field.
8.10. Constructors
Constructors initialize new instances. Without a constructor, fields start at their default values (0 for numbers, "" for strings, false for booleans, null for complex types).
Define a constructor with the /type constructor define command:
/type constructor define <namespace> <TypeName>([parameters])
For the Coordinate type:
/type constructor define mymap Coordinate(Int x, Int y, Int z, String label)
Then add the constructor body:
/script create constructor mymap Coordinate(Int, Int, Int, String)
# Store the parameters in the instance's fields
@var this.x = x
@var this.y = y
@var this.z = z
@var this.label = label
Now you can create coordinates with initial values:
@define Coordinate spawn = Coordinate(100, 64, -200, "Spawn Point")
@player Created: {{spawn.label}} at {{spawn.x}}, {{spawn.y}}, {{spawn.z}}
Created: Spawn Point at 100, 64, -200
8.10.1. Multiple Constructors
A type can have multiple constructors with different parameters. This is called overloading:
/type constructor define mymap Coordinate(Int x, Int y, Int z, String label)
/type constructor define mymap Coordinate(BlockLocation loc, String label)
/type constructor define mymap Coordinate(String label)
Each constructor has its own body. The third might set default coordinates:
# Default to world origin
@var this.x = 0
@var this.y = 64
@var this.z = 0
@var this.label = label
MSC chooses the constructor based on the arguments provided:
# Uses (Int, Int, Int, String) constructor
@define Coordinate c1 = Coordinate(100, 64, -200, "A")
# Uses (BlockLocation, String) constructor
@define Coordinate c2 = Coordinate(block.getLocation(), "B")
# Uses (String) constructor with defaults
@define Coordinate c3 = Coordinate("C")
@player c3 is at {{c3.x}}, {{c3.y}}, {{c3.z}}
c3 is at 0, 64, 0
8.10.2. Returning from Constructors
Technical Detail
Constructors implicitly return the constructed instance. An explicit @return can be used for constructor chaining, where one constructor calls another. The return value must be the type being constructed.
A constructor can end without a @return statement. In this case, it returns the instance that was being constructed. If you need to return early or chain to another constructor, use @return:
# If no label provided, delegate to another constructor with a default
@if label == ""
@return Coordinate(x, y, z, "Unnamed")
@fi
# Otherwise initialize normally
@var this.x = x
@var this.y = y
@var this.z = z
@var this.label = label
8.11. Putting It All Together
Here is a complete example of a user-defined type for tracking puzzle progress:
# Create the type
/type define mymap PuzzleState
# Add fields to track progress
/type field define mymap PuzzleState Int currentStage
/type field define mymap PuzzleState Boolean[] stagesCompleted
/type field define mymap PuzzleState Long startTime
# Add a constructor
/type constructor define mymap PuzzleState(Int totalStages)
# Add methods for interacting with the puzzle
/type method define mymap PuzzleState completeStage(Int stageNum)
/type method define mymap PuzzleState Boolean isFinished()
/type method define mymap PuzzleState Int countCompleted()
Constructor body (initializes the state):
# Start at stage 0
@var this.currentStage = 0
# Create a list of booleans, one per stage, all false
@var this.stagesCompleted = Boolean[]
@for Int i in list::range(0, totalStages)
@var this.stagesCompleted.append(false)
@done
# Record when the puzzle was started
@var this.startTime = system::currentTimeMillis()
The completeStage method body:
# Make sure the stage number is valid
@if stageNum >= 0 && stageNum < this.stagesCompleted.length()
# Mark this stage as done
@var this.stagesCompleted[stageNum] = true
# Advance current stage if needed
@if stageNum >= this.currentStage
@var this.currentStage = stageNum + 1
@fi
@fi
The isFinished method body:
# Check if any stage is still incomplete
@for Boolean done in this.stagesCompleted
@if !done
@return false
@fi
@done
# All stages complete!
@return true
The countCompleted method body:
@define Int count = 0
@for Boolean done in this.stagesCompleted
@if done
@var count = count + 1
@fi
@done
@return count
Using the type in a script:
@using mymap
# Create a 5-stage puzzle
@define PuzzleState puzzle = PuzzleState(5)
# Player completes stages 0 and 2 (out of order is fine)
@var puzzle.completeStage(0)
@var puzzle.completeStage(2)
# Show progress
@player Progress: {{puzzle.countCompleted()}} / {{puzzle.stagesCompleted.length()}} stages
@player Finished? {{puzzle.isFinished()}}
Progress: 2 / 5 stages
Finished? false
8.12. Command Reference
Command |
Description |
|---|---|
|
Creates a new type in a namespace. |
|
Deletes a type and all its fields, methods, and constructors. |
|
Adds a field to a type. |
|
Removes a field from a type. |
|
Shows information about a field. |
|
Lists all fields on a user-defined type. |
|
Adds a method to a type. |
|
Removes a method from a type. |
|
Shows information about a method. |
|
Lists all methods on a built-in type. |
|
Lists all methods on a user-defined type. |
|
Adds a constructor to a type. |
|
Removes a constructor from a type. |
|
Lists all constructors for a built-in type. |
|
Lists all constructors for a user-defined type. |
Note that these commands can only be run by admins on the main server. You will need to use the test server to run these commands yourself, or ask an admin to set up types for you on the main server.