• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Development] Custom Trigger System

Status
Not open for further replies.
Level 24
Joined
Aug 1, 2013
Messages
4,657
Hi all.
I am making a game but as I will have numberless spells, actions, etc
I thought that I could create a system that is similar to the trigger system of WC3.

I got some stuff but I am not sure about everything.
(Code is written in Java... which is not the language of the game though.)

The Globals are very easy as they are just variables that are accessable by every class:
Code:
public class Constants
{
    public static int udg_TempInteger;
    public static int udg_TempInteger2 = 10;
    public static int udg_TempInteger3;
    public static double udg_TempDouble;
    public static double udg_TempDouble2 = 26.78;
    public static double udg_TempDouble3;
    public static String udg_String;
    public static String udg_String2 = "2000";
    public static String udg_String3;
    public static String myString1;
}

I think that this class is pretty much the parent of the triggers:
Code:
public abstract class Trigger
{
    public static ArrayList<ArrayList<Trigger>> TRIGGER_LIST;
    public static final int EVENT_UNIT_CREATED = 1;
    public static final int EVENT_UNIT_KILLED = 2;
    public static final int EVENT_ABILITY_CAST = 3;
    public static final int EVENT_UNIT_WALKED = 4;
    
    //This method is called when the game starts to initialize triggers.
    public static void init()
    {
        TRIGGER_LIST = new ArrayList<>();
        for (int i = 1; i < 5; i++)
        {
            TRIGGER_LIST.add(new ArrayList<Trigger>());
        }
    }
    //This method is called when an event is triggered.
    public static void call(int event)
    {
        for (Trigger t : TRIGGER_LIST.get(event))
        {
            t.conditionalExecute();
        }
    }
    
    //Simple constructor that registers the trigger in the proper events
    public Trigger(int[] event)
    {
        for (int i : event)
        {
            TRIGGER_LIST.get(i).add(this);
        }
    }
    
    //These 3 methods are obvious.
    public final void conditionalExecute()
    {
        if (condition())
        {
            execute();
        }
    }
    
    public abstract boolean condition();
    
    public abstract void execute();
}

Triggers are simply called like this.
The concept of the events is just like that.
Code:
public class Unit
{
    public Unit()
    {
        Trigger.call(Trigger.EVENT_UNIT_CREATED);
    }
}

This class should represent a specific trigger just like we have our triggers.
The events are unit created and unit killed (dies).
The condition is always true (AKA no conditions).
The execute will not be a problem either.
Code:
public class TriggerTest extends Trigger
{
    public TriggerTest()
    {
        super(new int[]
        {
            Trigger.EVENT_UNIT_CREATED,
            Trigger.EVENT_UNIT_KILLED
        });
    }
    
    @Override
    public boolean condition()
    {
        return true;
    }

    @Override
    public void execute()
    {
        System.out.println("TriggerTest executed!");
        System.out.println("Unit is either created or killed.");
    }
}

Now the problem:
How do I initialize all triggers?
Do I have to make a new object of every "trigger" in the init method of Trigger?
Or is there any good thing that I can run every class somehow?
(This should be possible on both Java and C#)

If anyone has a better concept of a trigger system then please tell me.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Take a delegate to be a function. In the case of a trigger, a delegate would have no parameters.

Take an event to be a group of delegates/events.


Assume a Unit Manager exists. This Unit Manager follows a singleton design pattern. The Unit Manager contains an event in it called UNIT_CREATED.

UnitManager.Event.UNIT_CREATED

It also has

UnitManager.Event.UNIT_DESTROYED


Every unit has

UnitManager.Event.UNIT_DESTROYED


Whenever a unit is created, it will execute UnitManager.Event.UNIT_CREATED

Whenever a unit is destroyed, it will execute its destroyed event, which runs in reverse order. The destroyed event first registers UnitManager.Event.UNIT_DESTROYED, then functions come after. This means UnitManager.EVENT.UNIT_DESTROYED runs last (like a stack).



Take a Trigger to be an event. Take a global event table that links all managers together under a set of predefined constants.

Whenever an event or a function is registered to another event, it will return a unique id that can be used to remove it.


Now take a Trigger to be something more than an event. That is to say, it maintains a list of events that it is registered to. When the Trigger is destroyed, unregister it from all events that it has been added to.

A function can't be destroyed, so there is no need to do a special thing for it.



This design is very similar to what you would do in Warcraft 3. See my Unit Indexer to see how I follow this design pattern (the upper layer anyways, I didn't have to code the data structures in Warcraft 3 after all). The data structures are trivial to code.


What happens when you do multi-threading? You must then make the event data structure synchronous. In the Programming Forum, check the Fun With Variadic Templates thread to see an implementation of Event in C++.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
So.. You're writing code for the game in a language it doesn't support?

Yes :thumbs_up:

Well there is a problem with the possibilities that I have and the game is basically made in C#. However there is a second program/thread that controls some input but that part is written in Java...
Next to that, I have all system concepts in one folder for a specific IDE and that IDE doesn't support C#.


Take a delegate to be a function. In the case of a trigger, a delegate would have no parameters.

Take an event to be a group of delegates/events.
Yea... in Java however there are no delegates. But abstract methods could do the same.


Assume a Unit Manager exists. This Unit Manager follows a singleton design pattern. The Unit Manager contains an event in it called UNIT_CREATED.

UnitManager.Event.UNIT_CREATED

It also has

UnitManager.Event.UNIT_DESTROYED


Every unit has

UnitManager.Event.UNIT_DESTROYED


Whenever a unit is created, it will execute UnitManager.Event.UNIT_CREATED

Whenever a unit is destroyed, it will execute its destroyed event, which runs in reverse order. The destroyed event first registers UnitManager.Event.UNIT_DESTROYED, then functions come after. This means UnitManager.EVENT.UNIT_DESTROYED runs last (like a stack).



Take a Trigger to be an event. Take a global event table that links all managers together under a set of predefined constants.

Whenever an event or a function is registered to another event, it will return a unique id that can be used to remove it.


Now take a Trigger to be something more than an event. That is to say, it maintains a list of events that it is registered to. When the Trigger is destroyed, unregister it from all events that it has been added to.

A function can't be destroyed, so there is no need to do a special thing for it.
That is the same as what I had... I guess.
Anyway if I am not wrong, it doesn't answer my question.
I mean you still have to define all triggers. How do you dynamically initialize them?


What happens when you do multi-threading? You must then make the event data structure synchronous. In the Programming Forum, check the Fun With Variadic Templates thread to see an implementation of Event in C++.
The point is that the game engine doesn't allow multi-threading in execution of gameplay... IMO multi-threading in execution of gameplay is dangerous, equal as I don't know of any game that has it.
I know many games that have a thread only for the drawing on the screen, a few threads for recieving data (automatically made for each request) and a couple of threads that run other (required) programs to make the game function (better). But they all have 1 thread that actually moves units, collects items, updates time, etc. If you would make that multi-threaded, you also have to check if neither of them is delayed.



(I can read Java/C# code better than text, so if you can please write something in code. ty)

EDIT:

I meant this thing:
Every trigger (class that extends Trigger.java) has to be initialized by creating a new object.
It can be done by adding
Code:
new TriggerTest();
To the init method of Trigger but that would have to be done for every trigger that I make.
Is there any way to automatically create them?
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
You have to create each one and link each constant to them.

Create them in managers and systems. In your engime, init systems and managers, then do your linking.

In C#, they can be properties and return the events directly from their sources.


Alternatively, you can iterate over compiled java objects and initialize everything that way. It'd be dynamic and automatic.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Yea that was almost exacly what I was looking for... (+Rep)
A shame that I have to keep everything in one folder though.

There was one function that I had trouble with... Cant seem to remember what one it was... Seems that it wasn't that important.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Well I want to make this game dynamic as hell.
Every essential aspect of objects is hard coded but all effects are triggered.
You can see it like all units have the ability "Channel" as all abilities.

(I have a better system for those who are interested.)
Code:
public class Trigger
{
    public static final int EVENT_UNIT_CREATED = 0;
    public static final int EVENT_UNIT_DIES = 1;
    public static final int EVENT_ABILITY_CAST = 2;
    public static final int EVENT_UNIT_WALKED = 3;
    public static final int EVENT_MAP_INITIALIZATION = 4;
    private static final int MAX_EVENTS = 5;
    
    public static ArrayList<Trigger> TRIGGERS;
    public static ArrayList<ArrayList<Trigger>> TRIGGER_LIST;
    
    public static void init()
    {
        TRIGGERS = new ArrayList<>();
        TRIGGER_LIST = new ArrayList<>();
        for (int i = 1; i < 5; i++)
        {
            TRIGGER_LIST.add(new ArrayList<Trigger>());
        }
    }
    public static void call(int event)
    {
        for (int i = 0; i < TRIGGER_LIST.get(event).size(); i++)
        {
            Trigger t = TRIGGER_LIST.get(event).get(i);
            t.eventTriggered();
            if (!(i < TRIGGER_LIST.get(event).size()))
            {
                i--;
            }
            else if (t != TRIGGER_LIST.get(event).get(i))
            {
                i--;
            }
        }
    }
    
    protected ArrayList<Condition> conditions;
    protected ArrayList<Action> actions;
    protected boolean isEnabled = true;
    protected boolean isOn = true;
    
    public Trigger()
    {
        TRIGGERS.add(this);
        conditions = new ArrayList<>();
        actions = new ArrayList<>();
    }
    
    public final void eventTriggered()
    {
        if (isEnabled && isOn)
        {
            conditionalExecute();
        }
    }
    
    public final void conditionalExecute()
    {
        if (condition())
        {
            execute();
        }
    }
    
    public boolean condition()
    {
        for (Condition c : conditions)
        {
            if (!c.checkCondition(new Object[]{}))
            {
                return false;
            }
        }
        return true;
    }
    
    public void execute()
    {
        for (Action a : actions)
        {
            a.executeAction(new Object[]{});
        }
    }
    
    ////////////////////////////////////////////////////////////////////////////
    //
    //  Standard functions
    //  
    ////////////////////////////////////////////////////////////////////////////
    
    public static Trigger createTrigger()
    {
        return new Trigger();
    }
    
    public static void destroyTrigger(Trigger trigger)
    {
        for (int i = 0; i < MAX_EVENTS; i++)
        {
            TRIGGER_LIST.get(i).remove(trigger);
        }
    }
    
    public static void addEvent(Trigger trigger, int event)
    {
        TRIGGER_LIST.get(event).add(trigger);
    }
    public void addEvent(int event)
    {
        TRIGGER_LIST.get(event).add(this);
    }
    
    public static void removeEvent(Trigger trigger, int event)
    {
        TRIGGER_LIST.get(event).remove(trigger);
    }
    public void removeEvent(int event)
    {
        TRIGGER_LIST.get(event).remove(this);
    }
    
    public static void addCondition(Trigger trigger, Condition condition)
    {
        trigger.conditions.add(condition);
    }
    public void addCondition(Condition condition)
    {
        conditions.add(condition);
    }
    
    public static void removeCondition(Trigger trigger, Condition condition)
    {
        trigger.conditions.remove(condition);
    }
    public void removeCondition(Condition condition)
    {
        conditions.remove(condition);
    }
    
    public static void addAction(Trigger trigger, Action action)
    {
        trigger.actions.add(action);
    }
    public void addAction(Action action)
    {
        actions.add(action);
    }
    
    public static void removeAction(Trigger trigger, Action action)
    {
        trigger.actions.remove(action);
    }
    public void removeAction(Action action)
    {
        actions.remove(action);
    }
    
    public static Unit createUnit(long unitTypeCode)
    {
        return UnitManager.getInstance().createUnit(unitTypeCode);
    }
    public static Unit createUnit(String unitTypeCode)
    {
        return UnitManager.getInstance().createUnit(Long.parseLong(unitTypeCode, 16));
    }
    
    ////////////////////////////////////////////////////////////////////////////
    //  
    ////////////////////////////////////////////////////////////////////////////
}
Code:
public interface Action
{
    public abstract void executeAction(Object[] parameters);
}
Code:
public interface Condition
{
    public abstract boolean checkCondition(Object[] parameters);
}
Code:
        Trigger t = Trigger.createTrigger();
        t.addEvent(Trigger.EVENT_UNIT_CREATED);
        t.addAction(new Action()
        {
            @Override
            public void executeAction(Object[] parameters)
            {
                System.out.println("A unit is created.");
            }
        });
 
Status
Not open for further replies.
Top