Skip to content

Sistema de Ações ​


Introdução ​

O Sistema de Ações é como o DayZ gerencia todas as interações do jogador com itens e o mundo. Toda vez que um jogador come comida, abre uma porta, faz um curativo, repara uma parede ou liga uma lanterna, o motor executa o pipeline de ações. Entender esse pipeline --- desde verificações de condição até callbacks de animação e execução no servidor --- é fundamental para criar qualquer mod de gameplay interativo.

O sistema reside principalmente em 4_World/classes/useractionscomponent/ e é construído em torno de três pilares:

  1. Classes de ação que definem o que acontece (lógica, condições, animações)
  2. Componentes de condição que controlam quando uma ação pode aparecer (distância, estado do item, tipo do alvo)
  3. Componentes de ação que controlam como a ação progride (tempo, quantidade, ciclos repetidos)

Este capítulo cobre a API completa, hierarquia de classes, ciclo de vida e padrões práticos para criar ações customizadas.


Hierarquia de Classes ​

ActionBase_Basic                         // 3_Game — shell vazio, âncora de compilação
└── ActionBase                           // 4_World — lógica central, condições, eventos
    └── AnimatedActionBase               // 4_World — callbacks de animação, OnExecute
        ├── ActionSingleUseBase          // ações instantâneas (tomar pílula, ligar lanterna)
        ├── ActionContinuousBase         // ações com barra de progresso (curativo, reparo, comer)
        └── ActionInteractBase           // interações com o mundo (abrir porta, alternar interruptor)

Diferenças Principais Entre Tipos de Ação ​

PropriedadeSingleUseContinuousInteract
Constante de categoriaAC_SINGLE_USEAC_CONTINUOUSAC_INTERACT
Tipo de entradaDefaultActionInputContinuousDefaultActionInputInteractActionInput
Barra de progressoNãoSimNão
Usa item principalSimSimNão (padrão)
Tem alvoVariaVariaSim (padrão)
Uso típicoTomar pílula, alternar lanternaCurativo, reparo, comer comidaAbrir porta, ligar gerador
Classe de callbackActionSingleUseBaseCBActionContinuousBaseCBActionInteractBaseCB

Ciclo de Vida da Ação ​

Constantes de Estado ​

A máquina de estados da ação usa estas constantes definidas em 3_Game/constants.c:

ConstanteValorSignificado
UA_NONE0Nenhuma ação em execução
UA_PROCESSING2Ação em progresso
UA_FINISHED4Ação completada com sucesso
UA_CANCEL5Ação cancelada pelo jogador
UA_INTERRUPT6Ação interrompida externamente
UA_INITIALIZE12Ação contínua inicializando
UA_ERROR24Estado de erro --- ação abortada
UA_ANIM_EVENT11Evento de execução de animação disparado
UA_IN_START17Evento de início do loop de animação
UA_IN_END18Evento de fim do loop de animação

Fluxo de Ação SingleUse ​

Fluxo de Ação Continuous ​

Fluxo de Ação Interact ​

Referência de Métodos do Ciclo de Vida ​

Esses métodos são chamados em ordem durante o tempo de vida de uma ação. Sobrescreva-os em suas ações customizadas:

MétodoChamado emPropósito
CreateConditionComponents()AmbosDefinir m_ConditionItem e m_ConditionTarget
ActionCondition()AmbosValidação customizada (distância, estado, verificações de tipo)
ActionConditionContinue()AmbosApenas contínuo: re-verificado a cada frame durante o progresso
SetupAction()AmbosInterno: constrói ActionData, reserva inventário
OnStart()AmbosAção começa (cancela posicionamento se ativo)
OnStartServer()ServidorLógica de início no lado do servidor
OnStartClient()ClienteEfeitos de início no lado do cliente
OnExecute()AmbosEvento de animação disparado --- execução principal
OnExecuteServer()ServidorLógica de execução no lado do servidor
OnExecuteClient()ClienteEfeitos de execução no lado do cliente
OnFinishProgress()AmbosApenas contínuo: um ciclo completado
OnFinishProgressServer()ServidorApenas contínuo: ciclo completo no servidor
OnFinishProgressClient()ClienteApenas contínuo: ciclo completo no cliente
OnStartAnimationLoop()AmbosApenas contínuo: loop de animação começa
OnEndAnimationLoop()AmbosApenas contínuo: loop de animação termina
OnEnd()AmbosAção finalizada (sucesso ou cancelamento)
OnEndServer()ServidorLimpeza no lado do servidor
OnEndClient()ClienteLimpeza no lado do cliente

ActionData ​

Toda ação em execução carrega uma instância de ActionData que contém o contexto de tempo de execução. Ela é passada para todo método do ciclo de vida:

c
class ActionData
{
    ref ActionBase       m_Action;          // a classe de ação sendo executada
    ItemBase             m_MainItem;        // item nas mãos do jogador (ou null)
    ActionBaseCB         m_Callback;        // handler de callback de animação
    ref CABase           m_ActionComponent;  // componente de progresso (tempo, quantidade)
    int                  m_State;           // estado atual (UA_PROCESSING, etc.)
    ref ActionTarget     m_Target;          // objeto alvo + info de acerto
    PlayerBase           m_Player;          // jogador executando a ação
    bool                 m_WasExecuted;     // true após OnExecute disparar
    bool                 m_WasActionStarted; // true após loop de ação iniciar
}

Você pode estender ActionData para dados customizados. Sobrescreva CreateActionData() na sua ação:

c
class MyCustomActionData : ActionData
{
    int m_CustomValue;
}

class MyCustomAction : ActionContinuousBase
{
    override ActionData CreateActionData()
    {
        return new MyCustomActionData;
    }

    override void OnFinishProgressServer(ActionData action_data)
    {
        MyCustomActionData data = MyCustomActionData.Cast(action_data);
        data.m_CustomValue = data.m_CustomValue + 1;
        // ... usar dados customizados
    }
}

ActionTarget ​

A classe ActionTarget representa para o que o jogador está mirando:

Arquivo: 4_World/classes/useractionscomponent/actiontargets.c

c
class ActionTarget
{
    Object GetObject();         // o objeto direto sob o cursor (ou proxy filho)
    Object GetParent();         // objeto pai (se o alvo é um proxy/anexo)
    bool   IsProxy();           // true se o alvo tem um pai
    int    GetComponentIndex(); // índice do componente de geometria (seleção nomeada)
    float  GetUtility();        // pontuação de prioridade
    vector GetCursorHitPos();   // posição exata do mundo do acerto do cursor
}

Como os Alvos São Selecionados ​

A classe ActionTargets é executada a cada frame no cliente, coletando alvos potenciais:

  1. Raycast da posição da câmera na direção da câmera (c_RayDistance)
  2. Varredura de proximidade para objetos próximos ao redor do jogador
  3. Para cada candidato, o motor chama GetActions() no objeto para encontrar ações registradas
  4. Os componentes de condição de cada ação (CCIBase.Can(), CCTBase.Can()) e ActionCondition() são testados
  5. Ações válidas são classificadas por utilidade e exibidas no HUD

Componentes de Condição ​

Toda ação tem dois componentes de condição definidos em CreateConditionComponents(). Estes são verificados antes de ActionCondition() e determinam se a ação pode aparecer no HUD do jogador.

Condições de Item (CCIBase) ​

Controla se o item na mão do jogador se qualifica para esta ação.

Arquivo: 4_World/classes/useractionscomponent/itemconditioncomponents/

ClasseComportamento
CCINoneSempre passa --- sem requisito de item
CCIDummyPassa se o item não é null (item deve existir)
CCINonRuinedPassa se o item existe E não está arruinado
CCINotPresentPassa se o item é null (mãos devem estar vazias)
CCINotRuinedAndEmptyPassa se o item existe, não arruinado e não vazio
c
// CCINone — sem item necessário, sempre true
class CCINone : CCIBase
{
    override bool Can(PlayerBase player, ItemBase item) { return true; }
    override bool CanContinue(PlayerBase player, ItemBase item) { return true; }
}

// CCINotPresent — mãos devem estar vazias
class CCINotPresent : CCIBase
{
    override bool Can(PlayerBase player, ItemBase item) { return !item; }
}

// CCINonRuined — item deve existir e não estar destruído
class CCINonRuined : CCIBase
{
    override bool Can(PlayerBase player, ItemBase item)
    {
        return (item && !item.IsDamageDestroyed());
    }
}

Condições de Alvo (CCTBase) ​

Controla se o objeto alvo (para o que o jogador está olhando) se qualifica.

Arquivo: 4_World/classes/useractionscomponent/targetconditionscomponents/

ClasseConstrutorComportamento
CCTNoneCCTNone()Sempre passa --- sem alvo necessário
CCTDummyCCTDummy()Passa se o objeto alvo existe
CCTSelfCCTSelf()Passa se o jogador existe e está vivo
CCTObjectCCTObject(float dist)Objeto alvo dentro da distância
CCTCursorCCTCursor(float dist)Posição de acerto do cursor dentro da distância
CCTNonRuinedCCTNonRuined(float dist)Alvo dentro da distância E não arruinado
CCTCursorParentCCTCursorParent(float dist)Cursor no objeto pai dentro da distância

A distância é medida tanto da posição raiz do jogador quanto da posição do bone da cabeça (a que for mais próxima). A verificação de CCTObject:

c
class CCTObject : CCTBase
{
    protected float m_MaximalActionDistanceSq;

    void CCTObject(float maximal_target_distance = UAMaxDistances.DEFAULT)
    {
        m_MaximalActionDistanceSq = maximal_target_distance * maximal_target_distance;
    }

    override bool Can(PlayerBase player, ActionTarget target)
    {
        Object targetObject = target.GetObject();
        if (!targetObject || !player)
            return false;

        vector playerHeadPos;
        MiscGameplayFunctions.GetHeadBonePos(player, playerHeadPos);

        float distanceRoot = vector.DistanceSq(targetObject.GetPosition(), player.GetPosition());
        float distanceHead = vector.DistanceSq(targetObject.GetPosition(), playerHeadPos);

        return (distanceRoot <= m_MaximalActionDistanceSq || distanceHead <= m_MaximalActionDistanceSq);
    }
}

Constantes de Distância ​

Arquivo: 4_World/classes/useractionscomponent/actions/actionconstants.c

ConstanteValor (metros)Uso típico
UAMaxDistances.SMALL1.3Interações próximas, escadas
UAMaxDistances.DEFAULT2.0Ações padrão
UAMaxDistances.REPAIR3.0Ações de reparo
UAMaxDistances.LARGE8.0Ações de área grande
UAMaxDistances.BASEBUILDING20.0Construção de base
UAMaxDistances.EXPLOSIVE_REMOTE_ACTIVATION100.0Detonação remota

Registrando Ações em Itens ​

Ações são registradas em entidades através do padrão SetActions() / AddAction() / RemoveAction(). O motor chama GetActions() em uma entidade para recuperar sua lista de ações; na primeira vez que isso acontece, InitializeActions() constrói o mapa via SetActions().

Em ItemBase (Itens de Inventário) ​

O padrão mais comum. Sobrescreva SetActions() em uma modded class:

c
modded class MyCustomItem extends ItemBase
{
    override void SetActions()
    {
        super.SetActions();          // CRÍTICO: manter todas as ações vanilla
        AddAction(MyCustomAction);   // adicionar sua ação
    }
}

Para remover uma ação vanilla e adicionar sua própria substituição:

c
modded class Bandage_Basic extends ItemBase
{
    override void SetActions()
    {
        super.SetActions();
        RemoveAction(ActionBandageTarget);       // remover vanilla
        AddAction(MyImprovedBandageAction);      // adicionar substituição
    }
}

Em BuildingBase (Construções do Mundo) ​

Construções usam o mesmo padrão, mas através de BuildingBase:

c
// Exemplo vanilla: Poço registra ações de água
class Well extends BuildingSuper
{
    override void SetActions()
    {
        super.SetActions();
        AddAction(ActionWashHandsWell);
        AddAction(ActionDrinkWellContinuous);
    }
}

Em PlayerBase (Ações do Jogador) ​

Ações no nível do jogador (beber de poças, abrir portas, etc.) são registradas em PlayerBase.SetActions(). Existem duas assinaturas:

c
// Abordagem moderna (recomendada) — usa parâmetro InputActionMap
void SetActions(out TInputActionMap InputActionMap)
{
    AddAction(ActionOpenDoors, InputActionMap);
    AddAction(ActionCloseDoors, InputActionMap);
    // ...
}

// Abordagem legada (compatibilidade retroativa) — não recomendada
void SetActions()
{
    // ...
}

O jogador também tem SetActionsRemoteTarget() para ações realizadas em um jogador por outro jogador (RCP, verificar pulso, etc.):

c
void SetActionsRemoteTarget(out TInputActionMap InputActionMap)
{
    AddAction(ActionCPR, InputActionMap);
    AddAction(ActionCheckPulseTarget, InputActionMap);
}

Como o Sistema de Registro Funciona Internamente ​

Cada tipo de entidade mantém um TInputActionMap estático (um map<typename, ref array<ActionBase_Basic>>) indexado por tipo de entrada. Quando AddAction() é chamado:

  1. O singleton da ação é obtido de ActionManagerBase.GetAction()
  2. O tipo de entrada da ação é consultado (GetInputType())
  3. A ação é inserida no array para aquele tipo de entrada
  4. Em tempo de execução, o motor consulta todas as ações para o tipo de entrada correspondente

Isso significa que ações são compartilhadas por tipo (classe), não por instância. Todos os itens da mesma classe compartilham a mesma lista de ações.


Criando uma Ação Customizada --- Passo a Passo ​

Exemplo 1: Ação Simples de Uso Único ​

Uma ação customizada que cura instantaneamente o jogador quando ele usa um item especial:

c
// Arquivo: 4_World/actions/ActionHealInstant.c

class ActionHealInstant : ActionSingleUseBase
{
    void ActionHealInstant()
    {
        m_CommandUID = DayZPlayerConstants.CMD_ACTIONMOD_EAT_PILL;
        m_CommandUIDProne = DayZPlayerConstants.CMD_ACTIONFB_EAT_PILL;
        m_Text = "#heal";  // chave de stringtable, ou texto simples: "Curar"
    }

    override void CreateConditionComponents()
    {
        m_ConditionItem = new CCINonRuined;    // item não deve estar arruinado
        m_ConditionTarget = new CCTSelf;       // auto-ação
    }

    override bool HasTarget()
    {
        return false;  // sem alvo externo necessário
    }

    override bool HasProneException()
    {
        return true;  // permitir animação diferente quando deitado
    }

    override bool ActionCondition(PlayerBase player, ActionTarget target, ItemBase item)
    {
        // Mostrar apenas se o jogador está realmente ferido
        if (player.GetHealth("GlobalHealth", "Health") >= player.GetMaxHealth("GlobalHealth", "Health"))
            return false;

        return true;
    }

    override void OnExecuteServer(ActionData action_data)
    {
        // Curar o jogador no servidor
        PlayerBase player = action_data.m_Player;
        player.SetHealth("GlobalHealth", "Health", player.GetMaxHealth("GlobalHealth", "Health"));

        // Consumir o item (reduzir quantidade em 1)
        ItemBase item = action_data.m_MainItem;
        if (item)
        {
            item.AddQuantity(-1);
        }
    }

    override void OnExecuteClient(ActionData action_data)
    {
        // Opcional: reproduzir efeito no cliente, som ou notificação
    }
}

Registrar em um item:

c
// Arquivo: 4_World/entities/HealingKit.c

modded class HealingKit extends ItemBase
{
    override void SetActions()
    {
        super.SetActions();
        AddAction(ActionHealInstant);
    }
}

Exemplo 2: Ação Contínua com Barra de Progresso ​

Uma ação de reparo customizada que leva tempo e consome durabilidade do item:

c
// Arquivo: 4_World/actions/ActionRepairCustom.c

// Passo 1: Definir o callback com um componente de ação
class ActionRepairCustomCB : ActionContinuousBaseCB
{
    override void CreateActionComponent()
    {
        // CAContinuousTime(segundos) — barra de progresso única que completa uma vez
        m_ActionData.m_ActionComponent = new CAContinuousTime(UATimeSpent.DEFAULT_REPAIR_CYCLE);
    }
}

// Passo 2: Definir a ação
class ActionRepairCustom : ActionContinuousBase
{
    void ActionRepairCustom()
    {
        m_CallbackClass = ActionRepairCustomCB;
        m_CommandUID = DayZPlayerConstants.CMD_ACTIONFB_ASSEMBLE;
        m_FullBody = true;  // animação de corpo inteiro (jogador não pode se mover)
        m_StanceMask = DayZPlayerConstants.STANCEMASK_ERECT;
        m_SpecialtyWeight = UASoftSkillsWeight.ROUGH_HIGH;
        m_Text = "#repair";
    }

    override void CreateConditionComponents()
    {
        m_ConditionItem = new CCINonRuined;
        m_ConditionTarget = new CCTObject(UAMaxDistances.REPAIR);
    }

    override bool ActionCondition(PlayerBase player, ActionTarget target, ItemBase item)
    {
        Object obj = target.GetObject();
        if (!obj)
            return false;

        // Permitir apenas reparar objetos danificados (mas não arruinados)
        EntityAI entity = EntityAI.Cast(obj);
        if (!entity)
            return false;

        float health = entity.GetHealth("", "Health");
        float maxHealth = entity.GetMaxHealth("", "Health");

        // Deve estar danificado mas não arruinado
        if (health >= maxHealth || entity.IsDamageDestroyed())
            return false;

        return true;
    }

    override void OnFinishProgressServer(ActionData action_data)
    {
        // Chamado quando a barra de progresso completa
        Object target = action_data.m_Target.GetObject();
        if (target)
        {
            EntityAI entity = EntityAI.Cast(target);
            if (entity)
            {
                // Restaurar alguma vida
                float currentHealth = entity.GetHealth("", "Health");
                entity.SetHealth("", "Health", currentHealth + 25);
            }
        }

        // Danificar a ferramenta
        action_data.m_MainItem.DecreaseHealth(UADamageApplied.REPAIR, false);
    }
}

Exemplo 3: Ação Interact (Alternância de Objeto do Mundo) ​

Uma ação de interação para alternar um dispositivo customizado ligado/desligado:

c
// Arquivo: 4_World/actions/ActionToggleMyDevice.c

class ActionToggleMyDevice : ActionInteractBase
{
    void ActionToggleMyDevice()
    {
        m_CommandUID = DayZPlayerConstants.CMD_ACTIONMOD_INTERACTONCE;
        m_StanceMask = DayZPlayerConstants.STANCEMASK_CROUCH | DayZPlayerConstants.STANCEMASK_ERECT;
        m_Text = "#switch_on";
    }

    override void CreateConditionComponents()
    {
        m_ConditionItem = new CCINone;     // sem item necessário nas mãos
        m_ConditionTarget = new CCTCursor(UAMaxDistances.DEFAULT);
    }

    override bool ActionCondition(PlayerBase player, ActionTarget target, ItemBase item)
    {
        Object obj = target.GetObject();
        if (!obj)
            return false;

        // Verificar se o alvo é nosso tipo de dispositivo customizado
        MyCustomDevice device = MyCustomDevice.Cast(obj);
        if (!device)
            return false;

        // Atualizar texto de exibição baseado no estado atual
        if (device.IsActive())
            m_Text = "#switch_off";
        else
            m_Text = "#switch_on";

        return true;
    }

    override void OnExecuteServer(ActionData action_data)
    {
        MyCustomDevice device = MyCustomDevice.Cast(action_data.m_Target.GetObject());
        if (device)
        {
            if (device.IsActive())
                device.Deactivate();
            else
                device.Activate();
        }
    }
}

Registrar na construção/dispositivo:

c
class MyCustomDevice extends BuildingBase
{
    override void SetActions()
    {
        super.SetActions();
        AddAction(ActionToggleMyDevice);
    }
}

Exemplo 4: Ação com Requisito de Item Específico ​

Uma ação que requer que o jogador segure um tipo específico de ferramenta enquanto mira em um objeto específico:

c
class ActionUnlockWithKey : ActionInteractBase
{
    void ActionUnlockWithKey()
    {
        m_CommandUID = DayZPlayerConstants.CMD_ACTIONMOD_INTERACTONCE;
        m_Text = "Destrancar";
    }

    override void CreateConditionComponents()
    {
        m_ConditionItem = new CCINonRuined;   // deve segurar um item não arruinado
        m_ConditionTarget = new CCTObject(UAMaxDistances.DEFAULT);
    }

    override bool UseMainItem()
    {
        return true;  // ação requer um item na mão
    }

    override bool MainItemAlwaysInHands()
    {
        return true;  // item deve estar nas mãos, não apenas no inventário
    }

    override bool ActionCondition(PlayerBase player, ActionTarget target, ItemBase item)
    {
        // Item deve ser uma chave
        if (!item || !item.IsInherited(MyKeyItem))
            return false;

        // Alvo deve ser um contêiner trancado
        MyLockedContainer container = MyLockedContainer.Cast(target.GetObject());
        if (!container || !container.IsLocked())
            return false;

        return true;
    }

    override void OnExecuteServer(ActionData action_data)
    {
        MyLockedContainer container = MyLockedContainer.Cast(action_data.m_Target.GetObject());
        if (container)
        {
            container.Unlock();
        }
    }
}

Componentes de Ação (Controle de Progresso) ​

Componentes de ação controlam como a ação progride ao longo do tempo. Eles são criados no método CreateActionComponent() do callback.

Arquivo: 4_World/classes/useractionscomponent/actioncomponents/

Componentes Disponíveis ​

ComponenteParâmetrosComportamento
CASingleUsenenhumExecução instantânea, sem progresso
CAInteractnenhumExecução instantânea para ações de interação
CAContinuousTimefloat timeBarra de progresso, completa após time segundos
CAContinuousRepeatfloat timeCiclos repetidos, dispara OnFinishProgress a cada ciclo
CAContinuousQuantityfloat quantity_used_per_secondConsome quantidade ao longo do tempo
CAContinuousQuantityEdiblefloat quantity, float timeComo Quantity mas aplica modificadores de comida/bebida

CAContinuousTime ​

Barra de progresso única que completa uma vez:

c
class MyActionCB : ActionContinuousBaseCB
{
    override void CreateActionComponent()
    {
        // Barra de progresso de 5 segundos
        m_ActionData.m_ActionComponent = new CAContinuousTime(UATimeSpent.DEFAULT_CONSTRUCT);
    }
}

CAContinuousRepeat ​

Ciclos repetidos --- OnFinishProgressServer() é chamado cada vez que um ciclo completa, e a ação continua até o jogador soltar a tecla:

c
class MyRepeatActionCB : ActionContinuousBaseCB
{
    override void CreateActionComponent()
    {
        // Cada ciclo leva 5 segundos, repete até o jogador parar
        m_ActionData.m_ActionComponent = new CAContinuousRepeat(UATimeSpent.DEFAULT_REPAIR_CYCLE);
    }
}

Constantes de Tempo ​

Arquivo: 4_World/classes/useractionscomponent/actions/actionconstants.c

ConstanteValor (segundos)Uso
UATimeSpent.DEFAULT1.0Geral
UATimeSpent.DEFAULT_CONSTRUCT5.0Construção
UATimeSpent.DEFAULT_REPAIR_CYCLE5.0Reparo por ciclo
UATimeSpent.DEFAULT_DEPLOY5.0Implantação de itens
UATimeSpent.BANDAGE4.0Curativo
UATimeSpent.RESTRAIN10.0Amarrar
UATimeSpent.SHAVE12.75Barbear
UATimeSpent.SKIN10.0Esfolar animais
UATimeSpent.DIG_STASH10.0Cavar esconderijo

Exemplos Vanilla Anotados ​

ActionOpenDoors (Interact) ​

Arquivo: 4_World/classes/useractionscomponent/actions/interact/actionopendoors.c

c
class ActionOpenDoors : ActionInteractBase
{
    void ActionOpenDoors()
    {
        m_CommandUID  = DayZPlayerConstants.CMD_ACTIONMOD_OPENDOORFW;
        m_StanceMask  = DayZPlayerConstants.STANCEMASK_CROUCH | DayZPlayerConstants.STANCEMASK_ERECT;
        m_Text        = "#open";   // referência de stringtable
    }

    override void CreateConditionComponents()
    {
        m_ConditionItem   = new CCINone();      // sem item necessário
        m_ConditionTarget = new CCTCursor();     // cursor deve estar em algo
    }

    override bool ActionCondition(PlayerBase player, ActionTarget target, ItemBase item)
    {
        if (!target)
            return false;

        Building building;
        if (Class.CastTo(building, target.GetObject()))
        {
            int doorIndex = building.GetDoorIndex(target.GetComponentIndex());
            if (doorIndex != -1)
            {
                if (!IsInReach(player, target, UAMaxDistances.DEFAULT))
                    return false;
                return building.CanDoorBeOpened(doorIndex, true);
            }
        }
        return false;
    }

    override void OnStartServer(ActionData action_data)
    {
        super.OnStartServer(action_data);
        Building building;
        if (Class.CastTo(building, action_data.m_Target.GetObject()))
        {
            int doorIndex = building.GetDoorIndex(action_data.m_Target.GetComponentIndex());
            if (doorIndex != -1 && building.CanDoorBeOpened(doorIndex, true))
                building.OpenDoor(doorIndex);
        }
    }
}

Pontos-chave:

  • Usa OnStartServer() (não OnExecuteServer()) porque ações interact disparam imediatamente
  • GetComponentIndex() recupera qual porta o jogador está olhando
  • Verificação de distância feita manualmente com IsInReach() e também via CCTCursor

ActionTurnOnPowerGenerator (Interact) ​

Arquivo: 4_World/classes/useractionscomponent/actions/interact/actionturnonpowergenerator.c

c
class ActionTurnOnPowerGenerator : ActionInteractBase
{
    void ActionTurnOnPowerGenerator()
    {
        m_CommandUID = DayZPlayerConstants.CMD_ACTIONMOD_INTERACTONCE;
        m_Text = "#switch_on";
    }

    override bool ActionCondition(PlayerBase player, ActionTarget target, ItemBase item)
    {
        PowerGeneratorBase pg = PowerGeneratorBase.Cast(target.GetObject());
        if (pg)
        {
            return pg.HasEnergyManager()
                && pg.GetCompEM().CanSwitchOn()
                && pg.HasSparkplug()
                && pg.GetCompEM().CanWork();
        }
        return false;
    }

    override void OnExecuteServer(ActionData action_data)
    {
        ItemBase target_IB = ItemBase.Cast(action_data.m_Target.GetObject());
        if (target_IB)
        {
            target_IB.GetCompEM().SwitchOn();
            target_IB.GetCompEM().InteractBranch(target_IB);
        }
    }
}

Pontos-chave:

  • Herda o CreateConditionComponents() padrão de ActionInteractBase (CCINone + CCTObject(DEFAULT))
  • Usa OnExecuteServer() para a alternância real --- isso dispara no evento de animação
  • Múltiplas verificações de condição encadeadas em ActionCondition()

ActionEat (Continuous) ​

Arquivo: 4_World/classes/useractionscomponent/actions/continuous/actioneat.c

c
class ActionEatBigCB : ActionContinuousBaseCB
{
    override void CreateActionComponent()
    {
        m_ActionData.m_ActionComponent = new CAContinuousQuantityEdible(
            UAQuantityConsumed.EAT_BIG,   // 25 unidades consumidas por ciclo
            UATimeSpent.DEFAULT            // 1 segundo por ciclo
        );
    }
}

class ActionEatBig : ActionConsume
{
    void ActionEatBig()
    {
        m_CallbackClass = ActionEatBigCB;
        m_Text = "#eat";
    }

    override void CreateConditionComponents()
    {
        m_ConditionItem = new CCINonRuined;
        m_ConditionTarget = new CCTSelf;
    }

    override bool HasTarget()
    {
        return false;
    }
}

Pontos-chave:

  • A classe de callback controla o ritmo (CAContinuousQuantityEdible)
  • ActionConsume (o pai) gerencia toda a lógica de consumo de comida
  • HasTarget() retorna false --- comer é uma auto-ação
  • Tamanhos de porção diferentes apenas trocam a classe de callback com valores diferentes de UAQuantityConsumed

Tópicos Avançados ​

Máscaras de Condição de Ação ​

Ações podem ser restritas a estados específicos do jogador usando ActionConditionMask:

c
enum ActionConditionMask
{
    ACM_NO_EXEPTION    = 0,     // sem condições especiais
    ACM_IN_VEHICLE     = 1,     // pode usar em veículo
    ACM_ON_LADDER      = 2,     // pode usar em escada
    ACM_SWIMMING       = 4,     // pode usar nadando
    ACM_RESTRAIN       = 8,     // pode usar enquanto amarrado
    ACM_RAISED         = 16,    // pode usar com arma levantada
    ACM_ON_BACK        = 32,    // pode usar de costas
    ACM_THROWING       = 64,    // pode usar enquanto arremessa
    ACM_LEANING        = 128,   // pode usar enquanto inclina
    ACM_BROKEN_LEGS    = 256,   // pode usar com pernas quebradas
    ACM_IN_FREELOOK    = 512,   // pode usar em freelook
}

Sobrescreva os métodos correspondentes na sua ação para habilitar estes:

c
class MyVehicleAction : ActionSingleUseBase
{
    override bool CanBeUsedInVehicle()  { return true; }
    override bool CanBeUsedSwimming()   { return false; }
    override bool CanBeUsedOnLadder()   { return false; }
    override bool CanBeUsedInRestrain() { return false; }
}

Animações de Corpo Inteiro vs Aditivas ​

Ações podem ser aditivas (jogador pode andar) ou corpo inteiro (jogador fica travado no lugar):

c
class MyFullBodyAction : ActionContinuousBase
{
    void MyFullBodyAction()
    {
        m_FullBody = true;   // jogador não pode se mover durante a ação
        m_CommandUID = DayZPlayerConstants.CMD_ACTIONFB_ASSEMBLE;
        m_StanceMask = DayZPlayerConstants.STANCEMASK_ERECT;
    }
}
  • Aditiva (m_FullBody = false): Usa UIDs de comando CMD_ACTIONMOD_*. Jogador pode andar.
  • Corpo inteiro (m_FullBody = true): Usa UIDs de comando CMD_ACTIONFB_*. Jogador fica parado.

Exceção para Deitado ​

Algumas ações precisam de animações diferentes quando deitado vs em pé:

c
override bool HasProneException()
{
    return true;  // usa m_CommandUIDProne quando jogador está deitado
}

Quando HasProneException() retorna true, o motor usa m_CommandUIDProne em vez de m_CommandUID se o jogador estiver na postura deitada.

Interrupção de Ação ​

Ações podem ser interrompidas no lado do servidor através do callback:

c
override void OnFinishProgressServer(ActionData action_data)
{
    // Verificar se a ação deve ser interrompida
    if (SomeConditionFailed())
    {
        if (action_data.m_Callback)
            action_data.m_Callback.Interrupt();
        return;
    }

    // Execução normal...
}

Execução via Inventário e Quickbar ​

Ações podem ser configuradas para executar a partir da tela de inventário ou quickbar:

c
override bool CanBePerformedFromInventory()
{
    return true;   // ação aparece no menu de contexto do item no inventário
}

override bool CanBePerformedFromQuickbar()
{
    return true;   // ação pode ser acionada via quickbar
}

Travar Alvo ao Usar ​

Por padrão, ações com alvos travam o alvo para que apenas um jogador possa interagir de cada vez:

c
override bool IsLockTargetOnUse()
{
    return false;  // permitir múltiplos jogadores interagirem simultaneamente
}

Constantes de Categoria de Ação ​

Arquivo: 4_World/classes/useractionscomponent/_constants.c

ConstanteValorDescrição
AC_UNCATEGORIZED0Padrão --- não deve ser usado
AC_SINGLE_USE1Ações de uso único
AC_CONTINUOUS2Ações contínuas (barra de progresso)
AC_INTERACT3Ações de interação

Erros Comuns ​

1. Esquecendo super.SetActions() ​

Errado:

c
modded class Apple extends ItemBase
{
    override void SetActions()
    {
        // Faltando super.SetActions()!
        AddAction(MyCustomEatAction);
    }
}

Isso remove todas as ações vanilla do item. O jogador não poderá mais comer, soltar ou interagir com maçãs através de ações padrão.

Correto:

c
modded class Apple extends ItemBase
{
    override void SetActions()
    {
        super.SetActions();          // preservar ações vanilla
        AddAction(MyCustomEatAction);
    }
}

2. Colocando Lógica do Servidor em OnExecuteClient ​

Errado:

c
override void OnExecuteClient(ActionData action_data)
{
    action_data.m_Player.SetHealth("GlobalHealth", "Health", 100);  // SEM EFEITO
    action_data.m_MainItem.Delete();  // apenas no cliente, vai dessincronizar
}

Mudanças de vida e operações de inventário devem acontecer no servidor. OnExecuteClient é apenas para feedback visual (sons, efeitos de partículas, atualizações de UI).

Correto:

c
override void OnExecuteServer(ActionData action_data)
{
    action_data.m_Player.SetHealth("GlobalHealth", "Health", 100);
    action_data.m_MainItem.Delete();
}

override void OnExecuteClient(ActionData action_data)
{
    // Apenas feedback visual
}

3. Não Verificando Null em ActionCondition ​

Errado:

c
override bool ActionCondition(PlayerBase player, ActionTarget target, ItemBase item)
{
    return target.GetObject().IsInherited(MyClass);  // CRASH se target ou object for null
}

Correto:

c
override bool ActionCondition(PlayerBase player, ActionTarget target, ItemBase item)
{
    if (!target)
        return false;

    Object obj = target.GetObject();
    if (!obj)
        return false;

    return obj.IsInherited(MyClass);
}

4. Componentes de Condição Errados (Ação Nunca Aparece) ​

Problema: A ação não aparece no HUD.

Causas comuns:

  • CCIDummy requer um item na mão, mas a ação deveria funcionar com mãos vazias --- use CCINone em vez disso
  • CCTDummy requer um objeto alvo, mas a ação é auto-ação --- use CCTSelf ou CCTNone
  • Distância de CCTObject muito pequena para o tipo de alvo --- aumente o parâmetro de distância
  • HasTarget() retorna true mas não há condição de alvo válida --- adicione CCTCursor/CCTObject ou defina HasTarget() para false

5. Confundindo OnStart vs OnExecute ​

  • OnStart / OnStartServer: Chamado quando a ação começa (animação inicia). Use para preparação, reservar itens.
  • OnExecute / OnExecuteServer: Chamado quando o evento de animação dispara (o momento de "fazer"). Use para o efeito real.

Para ações interact, OnStartServer é comumente usado porque a ação é instantânea. Para ações de uso único, OnExecuteServer dispara no evento de animação. Escolha o correto baseado em quando você precisa que o efeito aconteça.

6. Ação Contínua Não Repetindo ​

Se sua ação contínua completa uma vez e para em vez de repetir, você está usando CAContinuousTime (conclusão única). Troque para CAContinuousRepeat para ciclos repetidos:

c
// Conclusão única — barra de progresso enche uma vez e a ação termina
m_ActionData.m_ActionComponent = new CAContinuousTime(5.0);

// Repetição — barra de progresso enche, dispara OnFinishProgress, reseta, continua
m_ActionData.m_ActionComponent = new CAContinuousRepeat(5.0);

7. Ação Aparece em Itens Errados ​

Lembre-se: SetActions() é chamado por tipo de classe, não por instância. Se você adiciona uma ação em uma classe pai, todos os filhos a herdam. Se você quer a ação apenas em subclasses específicas, ou:

  • Adicione apenas no SetActions() da subclasse específica
  • Adicione uma verificação de tipo em ActionCondition() como proteção

8. Esquecendo Sobrescrita de HasTarget() ​

Se sua ação é auto-ação (comer, curar, alternar item segurado), você deve sobrescrever:

c
override bool HasTarget()
{
    return false;
}

Sem isso, o motor espera um objeto alvo e pode não mostrar a ação, ou tentará sincronizar um alvo inexistente para o servidor.


Referência Rápida de Localizações de Arquivo ​

ArquivoPropósito
4_World/classes/useractionscomponent/actionbase.cActionBase --- classe central de ação
4_World/classes/useractionscomponent/animatedactionbase.cAnimatedActionBase + ActionBaseCB
4_World/classes/useractionscomponent/actions/actionsingleusebase.cActionSingleUseBase
4_World/classes/useractionscomponent/actions/actioncontinuousbase.cActionContinuousBase
4_World/classes/useractionscomponent/actions/actioninteractbase.cActionInteractBase
4_World/classes/useractionscomponent/actions/actionconstants.cUATimeSpent, UAMaxDistances, UAQuantityConsumed
4_World/classes/useractionscomponent/_constants.cAC_SINGLE_USE, AC_CONTINUOUS, AC_INTERACT
4_World/classes/useractionscomponent/actiontargets.cActionTarget, ActionTargets
4_World/classes/useractionscomponent/itemconditioncomponents/Classes CCI*
4_World/classes/useractionscomponent/targetconditionscomponents/Classes CCT*
4_World/classes/useractionscomponent/actioncomponents/Componentes de progresso CA*
3_Game/constants.cUA_NONE, UA_PROCESSING, UA_FINISHED, etc.

Resumo ​

O Sistema de Ações do DayZ segue um padrão consistente:

  1. Escolha sua classe base: ActionSingleUseBase para instantâneo, ActionContinuousBase para temporizado, ActionInteractBase para alternâncias do mundo
  2. Defina componentes de condição em CreateConditionComponents(): CCI para requisitos de item, CCT para requisitos de alvo
  3. Adicione validação customizada em ActionCondition(): verificações de tipo, estado, distância
  4. Implemente lógica do servidor em OnExecuteServer() ou OnFinishProgressServer()
  5. Registre a ação via AddAction() no SetActions() da entidade apropriada
  6. Sempre chame super.SetActions() para preservar ações vanilla

O sistema é projetado para ser modular: componentes de condição gerenciam "isso pode acontecer?", componentes de ação gerenciam "quanto tempo leva?", e suas sobrescritas gerenciam "o que isso faz?". Mantenha a lógica do servidor no servidor, feedback visual no cliente, e sempre verifique null nos seus alvos.


Boas Práticas ​

  • Sempre chame super.SetActions() ao modificar itens existentes. Omitir isso remove todas as ações vanilla (comer, soltar, inspecionar) do item, quebrando a jogabilidade central.
  • Coloque toda lógica que altera estado em OnExecuteServer ou OnFinishProgressServer. Mudanças de vida, exclusão de itens e manipulação de inventário devem rodar no lado do servidor. OnExecuteClient é apenas para feedback visual.
  • Use CCTObject com constantes de distância apropriadas. Codificar verificações de distância em ActionCondition() é frágil. Os componentes de condição integrados gerenciam controle de distância, alinhamento de cursor e verificações de estado de item de forma consistente.
  • Verifique null em todo objeto em ActionCondition(). O método é chamado frequentemente com alvos potencialmente null. Acessar .GetObject() sem proteção causa crashes difíceis de diagnosticar.
  • Prefira CAContinuousRepeat ao invés de CAContinuousTime para ações estilo reparo. Repeat dispara OnFinishProgressServer a cada ciclo e continua até o jogador soltar a tecla, o que parece mais natural para tarefas contínuas.

Compatibilidade e Impacto ​

  • Multi-Mod: Ações são registradas por tipo de classe via SetActions(). Dois mods adicionando ações diferentes ao mesmo item funcionam -- ações se acumulam. Porém, se ambos os mods sobrescrevem SetActions() sem chamar super, apenas as ações do último mod carregado sobrevivem.
  • Performance: ActionCondition() é avaliado a cada frame para cada ação candidata no alvo atual do jogador. Mantenha-o leve -- evite raycasts caros, consultas de configuração ou iterações de array dentro de verificações de condição.
  • Servidor/Cliente: O pipeline de ações é dividido: verificações de condição e exibição de UI rodam no cliente, callbacks de execução rodam no servidor. O motor gerencia a sincronização via RPCs internos. Nunca dependa de estado do lado do cliente para lógica autoritativa do jogo.

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