Skip to content

Capítulo 1.9: Casting y Reflexión

Inicio | << Anterior: Memory Management | Casting & Reflection | Siguiente: Enums & Preprocessor >>


Goal: Master safe type casting, runtime type checks, and Enforce Script's reflection API for dynamic property access.


Tabla de Contenidos


Why Casting Matters

DayZ's entity hierarchy is deep. Most engine APIs return a generic base type (Object, Man, Class), but you need a specific type (PlayerBase, ItemBase, CarScript) to access specialized methods. Casting converts a base reference into a derived reference — safely.

Class (root)
  └─ Object
       └─ Entity
            └─ EntityAI
                 ├─ InventoryItem → ItemBase
                 ├─ DayZCreatureAI
                 │    ├─ DayZInfected
                 │    └─ DayZAnimal
                 └─ Man
                      └─ DayZPlayer → PlayerBase

Calling a method that doesn't exist on the base type causes a runtime crash — there is no compiler error because Enforce Script resolves virtual calls at runtime.


Class.CastTo — Safe Downcasting

Class.CastTo is the preferred casting method in DayZ. It is a static method that writes the result to an out parameter and returns bool.

c
// Signature:
// static bool Class.CastTo(out Class target, Class source)

Object obj = GetSomeObject();
PlayerBase player;

if (Class.CastTo(player, obj))
{
    // Cast succeeded — player is valid
    string name = player.GetIdentity().GetName();
    Print("Found player: " + name);
}
else
{
    // Cast failed — obj is not a PlayerBase
    // player is null here
}

Why preferred:

  • Returns false on failure instead of crashing
  • The out parameter is set to null on failure — safe to check
  • Works across the entire class hierarchy (not just Object)

Pattern: Cast-and-Continue

In loops, use cast failure to skip irrelevant objects:

c
array<Object> nearObjects = new array<Object>;
array<CargoBase> proxyCargos = new array<CargoBase>;
GetGame().GetObjectsAtPosition(pos, 50.0, nearObjects, proxyCargos);

foreach (Object obj : nearObjects)
{
    EntityAI entity;
    if (!Class.CastTo(entity, obj))
        continue;  // Skip non-EntityAI objects (buildings, terrain, etc.)

    // Now safe to call EntityAI methods
    if (entity.IsAlive())
    {
        Print(entity.GetType() + " is alive at " + entity.GetPosition().ToString());
    }
}

Type.Cast — Alternative Casting

Every class has a static Cast method that returns the cast result directly (or null on failure).

c
// Syntax: TargetType.Cast(source)

Object obj = GetSomeObject();
PlayerBase player = PlayerBase.Cast(obj);

if (player)
{
    player.DoSomething();
}

This is a one-liner that combines cast and assignment, but you must still null-check the result.

Casting Primitives and Params

Type.Cast is also used with Param classes (used heavily in RPCs and events):

c
override void OnEvent(EventType eventTypeId, Param params)
{
    if (eventTypeId == ClientReadyEventTypeID)
    {
        Param2<PlayerIdentity, Man> readyParams = Param2<PlayerIdentity, Man>.Cast(params);
        if (readyParams)
        {
            PlayerIdentity identity = readyParams.param1;
            Man player = readyParams.param2;
        }
    }
}

CastTo vs Type.Cast — When to Use Which

CaracterísticaClass.CastToType.Cast
Return typeboolTarget type or null
Null on failureYes (out param set to null)Yes (returns null)
Best forif-blocks with branching logicOne-liner assignments
Used in DayZ vanillaEverywhereEverywhere
Works with non-ObjectYes (any Class)Yes (any Class)

Rule of thumb: Use Class.CastTo when you branch on success/failure. Use Type.Cast when you just need the typed reference and will null-check later.

c
// CastTo — branch on result
PlayerBase player;
if (Class.CastTo(player, obj))
{
    // handle player
}

// Type.Cast — assign and check later
PlayerBase player = PlayerBase.Cast(obj);
if (!player) return;

obj.IsInherited — Runtime Type Checking

IsInherited checks if an object is an instance of a given type without performing a cast. It takes a typename argument.

c
Object obj = GetSomeObject();

if (obj.IsInherited(PlayerBase))
{
    Print("This is a player!");
}

if (obj.IsInherited(DayZInfected))
{
    Print("This is a zombie!");
}

if (obj.IsInherited(CarScript))
{
    Print("This is a vehicle!");
}

IsInherited returns true for the exact type and any parent types in the hierarchy. A PlayerBase object returns true for IsInherited(Man), IsInherited(EntityAI), IsInherited(Object), etc.


obj.IsKindOf — String-Based Type Checking

IsKindOf does the same check but with a string class name. Useful when you have the type name as data (e.g., from config files).

c
Object obj = GetSomeObject();

if (obj.IsKindOf("ItemBase"))
{
    Print("This is an item");
}

if (obj.IsKindOf("DayZAnimal"))
{
    Print("This is an animal");
}

Importante: IsKindOf checks the full inheritance chain, just like IsInherited. A Mag_STANAG_30Rnd returns true for IsKindOf("Magazine_Base"), IsKindOf("InventoryItem"), IsKindOf("EntityAI"), etc.

IsInherited vs IsKindOf

CaracterísticaIsInherited(typename)IsKindOf(string)
ArgumentCompile-time typeString name
SpeedFaster (type comparison)Slower (string lookup)
Use whenYou know the type at compile timeType comes from data/config

obj.Type — Get Runtime Type

Type() returns the typename of an object's actual runtime class — not the declared variable type.

c
Object obj = GetSomeObject();
typename t = obj.Type();

Print(t.ToString());  // e.g., "PlayerBase", "AK101", "LandRover"

Use this for logging, debugging, or comparing types dynamically:

c
void ProcessEntity(EntityAI entity)
{
    typename t = entity.Type();
    Print("Processing entity of type: " + t.ToString());

    if (t == PlayerBase)
    {
        Print("It's a player");
    }
}

typename — Storing Type References

typename is a first-class type in Enforce Script. You can store it in variables, pass it as parameters, and compare it.

c
// Declare a typename variable
typename playerType = PlayerBase;
typename vehicleType = CarScript;

// Compare
typename objType = obj.Type();
if (objType == playerType)
{
    Print("Match!");
}

// Use in collections
array<typename> allowedTypes = new array<typename>;
allowedTypes.Insert(PlayerBase);
allowedTypes.Insert(DayZInfected);
allowedTypes.Insert(DayZAnimal);

// Check membership
foreach (typename t : allowedTypes)
{
    if (obj.IsInherited(t))
    {
        Print("Object matches allowed type: " + t.ToString());
        break;
    }
}

Creating Instances from typename

You can create objects from a typename at runtime:

c
typename t = PlayerBase;
Class instance = t.Spawn();  // Creates a new instance

// Or use the string-based approach:
Class instance2 = GetGame().CreateObjectEx("AK101", pos, ECE_PLACE_ON_SURFACE);

Nota: typename.Spawn() only works for classes with a parameterless constructor. For DayZ entities, use GetGame().CreateObject() or CreateObjectEx().


Reflection API

Enforce Script provides basic reflection — the ability to inspect and modify an object's properties at runtime without knowing its type at compile time.

Inspecting Variables

Every object's Type() returns a typename that exposes variable metadata:

c
void InspectObject(Class obj)
{
    typename t = obj.Type();

    int varCount = t.GetVariableCount();
    Print("Class: " + t.ToString() + " has " + varCount.ToString() + " variables");

    for (int i = 0; i < varCount; i++)
    {
        string varName = t.GetVariableName(i);
        typename varType = t.GetVariableType(i);

        Print("  [" + i.ToString() + "] " + varName + " : " + varType.ToString());
    }
}

Available reflection methods on typename:

MétodoRetornaDescripción
GetVariableCount()intNumber of member variables
GetVariableName(int index)stringVariable name at index
GetVariableType(int index)typenameVariable type at index
ToString()stringClass name as string

EnScript.GetClassVar / SetClassVar

EnScript.GetClassVar and EnScript.SetClassVar let you read/write member variables by name at runtime. This is Enforce Script's equivalent of dynamic property access.

c
// Signature:
// static void EnScript.GetClassVar(Class instance, string varName, int index, out T value)
// static bool EnScript.SetClassVar(Class instance, string varName, int index, T value)
// 'index' is the array element index — use 0 for non-array fields.

class MyConfig
{
    int MaxSpawns = 10;
    float SpawnRadius = 100.0;
    string WelcomeMsg = "Hello!";
}

void DemoReflection()
{
    MyConfig cfg = new MyConfig();

    // Read values by name
    int maxVal;
    EnScript.GetClassVar(cfg, "MaxSpawns", 0, maxVal);
    Print("MaxSpawns = " + maxVal.ToString());  // "MaxSpawns = 10"

    float radius;
    EnScript.GetClassVar(cfg, "SpawnRadius", 0, radius);
    Print("SpawnRadius = " + radius.ToString());  // "SpawnRadius = 100"

    string msg;
    EnScript.GetClassVar(cfg, "WelcomeMsg", 0, msg);
    Print("WelcomeMsg = " + msg);  // "WelcomeMsg = Hello!"

    // Write values by name
    EnScript.SetClassVar(cfg, "MaxSpawns", 0, 50);
    EnScript.SetClassVar(cfg, "SpawnRadius", 0, 250.0);
    EnScript.SetClassVar(cfg, "WelcomeMsg", 0, "Welcome!");
}

Advertencia: GetClassVar/SetClassVar silently fail if the variable name is wrong or the type doesn't match. Always validate variable names before use.


Real-World Examples

Finding All Vehicles in the World

c
static array<CarScript> FindAllVehicles()
{
    array<CarScript> vehicles = new array<CarScript>;
    array<Object> allObjects = new array<Object>;
    array<CargoBase> proxyCargos = new array<CargoBase>;

    // Search a large area (or use mission-specific logic)
    vector center = "7500 0 7500";
    GetGame().GetObjectsAtPosition(center, 15000.0, allObjects, proxyCargos);

    foreach (Object obj : allObjects)
    {
        CarScript car;
        if (Class.CastTo(car, obj))
        {
            vehicles.Insert(car);
        }
    }

    Print("Found " + vehicles.Count().ToString() + " vehicles");
    return vehicles;
}

Safe Object Helper With Cast

This pattern is used throughout DayZ modding — a utility function that safely checks if an Object is alive by casting to EntityAI:

c
// Object.IsAlive() does NOT exist on the base Object class!
// You must cast to EntityAI first.

static bool IsObjectAlive(Object obj)
{
    if (!obj)
        return false;

    EntityAI eai;
    if (Class.CastTo(eai, obj))
    {
        return eai.IsAlive();
    }

    return false;  // Non-EntityAI objects (buildings, etc.) — treat as "not alive"
}

Reflection-Based Config System

This pattern (used in MyMod Core) builds a generic config system where fields are read/written by name, enabling admin panels to edit any config without knowing its specific class:

c
class ConfigBase
{
    // Find a member variable index by name
    protected int FindVarIndex(string fieldName)
    {
        typename t = Type();
        int count = t.GetVariableCount();
        for (int i = 0; i < count; i++)
        {
            if (t.GetVariableName(i) == fieldName)
                return i;
        }
        return -1;
    }

    // Get any field value as string
    string GetFieldValue(string fieldName)
    {
        if (FindVarIndex(fieldName) == -1)
            return "";

        int iVal;
        EnScript.GetClassVar(this, fieldName, 0, iVal);
        return iVal.ToString();
    }

    // Set any field value from string
    void SetFieldValue(string fieldName, string value)
    {
        if (FindVarIndex(fieldName) == -1)
            return;

        int iVal = value.ToInt();
        EnScript.SetClassVar(this, fieldName, 0, iVal);
    }
}

class MyModConfig : ConfigBase
{
    int MaxPlayers = 60;
    int RespawnTime = 300;
}

void AdminPanelSave(ConfigBase config, string fieldName, string newValue)
{
    // Works for ANY config subclass — no type-specific code needed
    config.SetFieldValue(fieldName, newValue);
}

Type-Safe Event Dispatch

Use typename to build a dispatcher that routes events to the correct handler:

c
class EventDispatcher
{
    protected ref map<typename, ref array<ref EventHandler>> m_Handlers;

    void EventDispatcher()
    {
        m_Handlers = new map<typename, ref array<ref EventHandler>>;
    }

    void Register(typename eventType, EventHandler handler)
    {
        if (!m_Handlers.Contains(eventType))
        {
            m_Handlers.Insert(eventType, new array<ref EventHandler>);
        }

        m_Handlers.Get(eventType).Insert(handler);
    }

    void Dispatch(EventBase event)
    {
        typename eventType = event.Type();

        array<ref EventHandler> handlers;
        if (m_Handlers.Find(eventType, handlers))
        {
            foreach (EventHandler handler : handlers)
            {
                handler.Handle(event);
            }
        }
    }
}

Mejores Prácticas

  • Always null-check after every cast -- both Class.CastTo and Type.Cast return null on failure, and using the result unchecked causes crashes.
  • Use Class.CastTo when you need to branch on success/failure; use Type.Cast for concise one-liner assignments followed by a null check.
  • Prefer IsInherited(typename) over IsKindOf(string) when the type is known at compile time -- it is faster and catches typos at compile time.
  • Cast to EntityAI before calling IsAlive() -- the base Object class does not have this method.
  • Validate variable names with GetVariableCount/GetVariableName before using EnScript.GetClassVar -- it fails silently on wrong names.

Observado en Mods Reales

Patrones confirmados estudiando código fuente de mods profesionales de DayZ.

PatrónModDetalle
Class.CastTo + continue in entity loopsCOT / ExpansionEvery loop over Object arrays uses cast-and-continue to skip non-matching types
IsKindOf for config-driven type checksExpansion MarketItem categories loaded from JSON use string-based IsKindOf because types are data
EnScript.GetClassVar/SetClassVar for admin panelsDabs FrameworkGeneric config editors read/write fields by name so one UI works for all config classes
obj.Type().ToString() for loggingVPP AdminDebug logs always include entity.Type().ToString() to identify what was processed

Teoría vs Práctica

ConceptoTeoríaRealidad
Object.IsAlive()Expect it to exist on ObjectOnly available on EntityAI and subclasses -- calling it on Object crashes
EnScript.SetClassVar returns boolShould indicate success/failureReturns false silently on wrong field name with no error message -- easy to miss
typename.Spawn()Creates any class instanceOnly works for classes with a parameterless constructor; for game entities use CreateObject

Errores Comunes

1. Forgetting to null-check after cast

c
// WRONG — crashes if obj is not a PlayerBase
PlayerBase player = PlayerBase.Cast(obj);
player.GetIdentity();  // CRASH if cast failed!

// CORRECT
PlayerBase player = PlayerBase.Cast(obj);
if (player)
{
    player.GetIdentity();
}

2. Calling IsAlive() on base Object

c
// WRONG — Object.IsAlive() does not exist
Object obj = GetSomeObject();
if (obj.IsAlive())  // Compile error or runtime crash!

// CORRECT
EntityAI eai;
if (Class.CastTo(eai, obj) && eai.IsAlive())
{
    // Safe
}

3. Using reflection with wrong variable name

c
// SILENT FAILURE — no error, just returns zero/empty
int val;
EnScript.GetClassVar(obj, "NonExistentField", 0, val);
// val is 0, no error thrown

Always validate with FindVarIndex or GetVariableCount/GetVariableName first.

4. Confusing Type() with typename literal

c
// Type() — returns the RUNTIME type of an instance
typename t = myObj.Type();  // e.g., PlayerBase

// typename literal — a compile-time type reference
typename t = PlayerBase;    // Always PlayerBase

// They are comparable
if (myObj.Type() == PlayerBase)  // true if myObj IS a PlayerBase

Resumen

OperationSintaxisRetorna
Safe downcastClass.CastTo(out target, source)bool
Inline castTargetType.Cast(source)Target or null
Type check (typename)obj.IsInherited(typename)bool
Type check (string)obj.IsKindOf("ClassName")bool
Get runtime typeobj.Type()typename
Variable countobj.Type().GetVariableCount()int
Variable nameobj.Type().GetVariableName(i)string
Variable typeobj.Type().GetVariableType(i)typename
Read propertyEnScript.GetClassVar(obj, name, 0, out val)void
Write propertyEnScript.SetClassVar(obj, name, 0, val)bool

PreviousUpNext
1.8 Memory ManagementPart 1: Enforce Script1.10 Enums & Preprocessor

Released under CC BY-SA 4.0 | Code examples under MIT License