0
\$\begingroup\$

I want to create a mechanism that could read text script, for example some kind of custom script such as ".skill" or ".item", which maybe contain some sort of simple script like

.item
Item.Name = "Strength Gauntlet";
Character.STR += 20;

..

.skill
Skill.Name = "Poison Attack";
Skill.Description = "Steal HP and inflict poison";
Player.HP += 200;
Enemy.HP -= 200;
Enemy.Status += Status.POISON;

It may be different from this, but just want to give some idea of what I desire. However, I do not know how to dynamically parse these things and translate it into working script. For example, in battle scenerio, I should make my game read one of this ".skill" file and apply it to the current enemy, or the current player. How would I do this? Should I go for String parsing? It is like a script engine, but I prefer C# than creating new language, so how would I parse custom files into appropiate status commands?

Another problem is, I have also created a command engine which would read String input and parse it into action such as "MOVE (1,2)" would move character to tile (1,2). Each command belong to separate class, and provide generic parsing method that should be implemented by hand. This is for the reason of custom number/type of arguments per each command. However, I think this is not well designed, because I desire it to automatically parse the parameters according to specify list of types.

For example, MOVE command in "MOVE 1 2" would automatically parse the parameters into int, int and put it into X and Y. Now, this form can change, and we should be able to manually specify all type of formats. Any suggestion to this problem? Should I change from string parsing to some hardcode methods/classes?

\$\endgroup\$
3
  • \$\begingroup\$ Too much info! Concentrate on one problem at time. As for the first part, it's very unefficient to read files at runtime, you should store these structures in memory. What you are trying to achive particularly? \$\endgroup\$
    – pabdulin
    Commented Jun 7, 2012 at 6:29
  • \$\begingroup\$ You can do with this with Ruby via eval. The same idea will work for your language of choice. \$\endgroup\$
    – ashes999
    Commented Jun 7, 2012 at 8:09
  • \$\begingroup\$ @PetrAbdulin I should say that I put up anything that I could've think of, because I have never done script engine before. My objective is to search for a way to model instance of items and skills, which may have various, distinct effect on enemy/player. Another thing is, I would want to know how to appropiately create a command-based engine, so that I can issue my character using this interface. It would be effective for both battle actions (such as I could issue ATTACK animation on specific tile) and later ingame events. \$\endgroup\$
    – user1542
    Commented Jun 7, 2012 at 8:18

4 Answers 4

2
\$\begingroup\$

As far, as I understand your problem, you are approaching it from opposite direction.

Ultimately you will need to integrate all those values (I don't see script here, only values) into your game logic (that's where "script" part will actually be). So, the main problem here is not how to read or store it in file, but how it will be implemented in game logic. Create game logic (engine) which will be flexible enough for you, and then the task to code it in some sort of file is easy.

\$\endgroup\$
1
\$\begingroup\$

In terms of storing code in a text file, I personally like compile-time error checking. You'll lose that (if you have it -- Ruby, anyone?) by doing this.

If you're still keen, look up dynamic compilation. You can read the text file, compile it at runtime, and execute it.

\$\endgroup\$
0
\$\begingroup\$

I suggest you to embed an interpreted language into your game engine. This is a quite proven strategy, the main reason you should not write your custom "script parsing procedures" is that you will never get enough. You will add functionalities to your script engine over and over, until it will start to look like any general purpose interpreted language out of there (but more ugly).

Good choices are: Lua, Javascript, Python (maybe Ruby), Guile and probably many others.

The best way (in my experience) to manage this integration is to export a Façade that both simplifies your script manipulation duties and let you to decide what/how expose of your game engine functionalities.

In practice your game engine adds the instance of the Facade to the interpreter context and ask such interpeter to evaluate a script file; that script will find an istance of that Facade in the global context when executed.

A good strategy is to let your game engine to delegate to a script set (found in specific folders) the execution when something happens (onInit, onBattleBegins, onTick, onProgramCrash ect). This is how every -base plugin system works for every program usefull enough to deserve a plugin system for customization.

If you are good enough, your game engine has some sort of event management system. In this case you can write a generic extension of your reactor that looks for scripts as event handlers.

One last thing, consider to write all your game engine using an interpeted language. I can hear you from here "what about interpreted slowness?" This is a quite false problem: every interpreted languages allows extension at different level: to extend is easier than to embed.

An higher level language let you to focus to the matters of your application: when the things start to go slow you can profile your modules to see the bottlenecks. Once you have a bottleneck you can try to fix that or to reimplement that using a lower level language.

\$\endgroup\$
0
\$\begingroup\$

I am actually working on my own turn based tile game and have begun working out the ability system just recently. I am using the FlatRedBall game engine, which gives me a lot of good free functionality to support this sort of thing. There is still work that needs to be done, but here is the gist:

I have 2 main classes to handle abilities: BasicAbility and AbilityEffect. The numbers and names, etc for the abilities and effects will be stored in CSV format on disk, because FlatRedBall has some amazing code generation support for CSV files, which makes loading the data into C# objects trivial for me. A sample CSV entry for an ability might look like:

Poison Dart, new AbilityEffect(100, 0, 0, 0, 0, 1)
,new AbilityEffect(10, 0, 0, 0, 0, 5)

I could just as easily do these values as separate columns, but doing the above makes FlatRedBall hydrate the objects for me and link them to the ability dictionary. When I put a player type into a CSV file, I would simply add a list of ability names to the tacticalEntities.csv file as a column for the soldier for instance.

In short, you have data you need to load and not a scripting need necessarily.

BTW, I also wrote an EffectManager class to handle linking AbilityEffects to Entities in the game. It applies the effect values to the Entity objects over time based on the ticks.

Here are the classes:

public class BasicAbility : IAbility
{
    public BasicAbility(ICollection<IAbilityEffect> effects)
    {
        Effects = effects;
    }

    public void execute(TacticalEntity source, ICollection<TacticalEntity> destination)
    {
        foreach (TacticalEntity entity in destination)
        {
            EffectManager.AddEffectsToEntity(entity, Effects);
        }
    }

    public ICollection<IAbilityEffect> Effects
    {
        get;
        private set;
    }
}

public class AbilityEffect : IAbilityEffect
{
    public AbilityEffect(int healthPerTick, int speedEffect, int defenseEffect, float aggroRadiusEffect, int strengthEffect, int totalticks)
    {
        HealthEffectPerTick = healthPerTick;
        SpeedEffectWhileActive = speedEffect;
        DefenseEffectWhileActive = defenseEffect;
        AggroRadiusEffectWhileActive = aggroRadiusEffect;
        StrengthEffectWhileActive = strengthEffect;
        TotalTicks = totalticks;
    }

    public AbilityEffect(TacticalEntity source, TacticalEntity affectedEntity, IAbilityEffect that)
        : this(that.HealthEffectPerTick, that.SpeedEffectWhileActive, that.DefenseEffectWhileActive, that.AggroRadiusEffectWhileActive, that.StrengthEffectWhileActive, that.TotalTicks)
    {
        AffectedEntity = affectedEntity;
        SourceEntity = source;
    }

    public int HealthEffectPerTick
    {
        protected set;
        get;
    }

    public int SpeedEffectWhileActive
    {
        protected set;
        get;
    }

    public int DefenseEffectWhileActive
    {
        protected set;
        get;
    }

    public float AggroRadiusEffectWhileActive
    {
        protected set;
        get;
    }

    public int StrengthEffectWhileActive
    {
        protected set;
        get;
    }

    private int _TotalTicks;
    public int TotalTicks
    {
        get
        {
            return _TotalTicks;
        }
        private set
        {
            _TotalTicks = value;
            TicksRemaining = value;
        }
    }

    public int TicksRemaining
    {
        get;
        private set;
    }

    public bool Active
    {
        get
        {
            return TicksRemaining > 0;
        }
    }

    public TacticalEntity AffectedEntity
    {
        get;
        private set;
    }

    public void ApplyConstantEffects()
    {
        AffectedEntity.strength += StrengthEffectWhileActive;
        AffectedEntity.defense += DefenseEffectWhileActive;
        AffectedEntity.speed += SpeedEffectWhileActive;
        AffectedEntity.aggroCircle.Radius += AggroRadiusEffectWhileActive;
    }

    public void RemoveConstantEffects()
    {
        AffectedEntity.strength -= StrengthEffectWhileActive;
        AffectedEntity.defense -= DefenseEffectWhileActive;
        AffectedEntity.speed -= DefenseEffectWhileActive;
        AffectedEntity.aggroCircle.Radius -= AggroRadiusEffectWhileActive;
    }

    public void ApplyEffectTick()
    {
        if (Active)
        {
            AffectedEntity.health += HealthEffectPerTick;
            --TicksRemaining;
        }
    }


    public IAbilityEffect Clone(TacticalEntity source, TacticalEntity entity)
    {
        return new AbilityEffect(SourceEntity, entity, this);
    }


    public TacticalEntity SourceEntity
    {
        get;
        private set;
    }
}

public static class EffectManager
{
    private static Dictionary<TacticalEntity, ICollection<IAbilityEffect>> entityEffects = new Dictionary<TacticalEntity, ICollection<IAbilityEffect>>(20);
    private static double lasttick = TimeManager.CurrentTime;

    public static void AddEffectsToEntity(TacticalEntity entity, ICollection<IAbilityEffect> effects)
    {
        if (!entityEffects.ContainsKey(entity))
        {
            entityEffects.Add(entity, new List<IAbilityEffect>(effects.Count));
        }
        foreach (IAbilityEffect effect in effects)
        {
            AddEffectToEntity(entity, effect);
        }
    }

    public static void AddEffectToEntity(TacticalEntity source, TacticalEntity entity, IAbilityEffect effect)
    {
        IAbilityEffect newEffect = effect.Clone(source, entity);
        ICollection<IAbilityEffect> effects;
        if (!entityEffects.ContainsKey(entity))
        {
            effects = new List<IAbilityEffect>(1);
            entityEffects.Add(entity, effects);
        }
        else
        {
            effects = entityEffects[entity];
        }
        effects.Add(newEffect);
    }

    public static void AddEffectToEntity(TacticalEntity entity, IAbilityEffect effect)
    {
        AddEffectToEntity(null, entity, effect);
    }

    public static void Activity()
    {
        if ((TimeManager.CurrentTime - lasttick) > 1.0)
        {
            lasttick = TimeManager.CurrentTime;
            foreach (KeyValuePair<TacticalEntity, ICollection<IAbilityEffect>> pair in entityEffects)
            {
                TickAndRemoveInactiveEffects(pair);
            }
        }
    }

    private static void TickAndRemoveInactiveEffects(KeyValuePair<TacticalEntity, ICollection<IAbilityEffect>> pair)
    {
        List<IAbilityEffect> effectsToRemove = null;
        foreach (IAbilityEffect effect in pair.Value)
        {
            effect.ApplyEffectTick();
            if (!effect.Active)
            {
                if (effectsToRemove == null)
                {
                    effectsToRemove = new List<IAbilityEffect>();
                }
                effectsToRemove.Add(effect);
            }
        }
        if (effectsToRemove != null)
        {
            foreach (IAbilityEffect effect in effectsToRemove)
            {
                pair.Value.Remove(effect);
            }
        }
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ Welome to GDSE! This answer would be excellent if the OP was using FlatRedBall, but it doesn't seem like it from the question. (He tagged it XNA.) Please consider editing your answer so that it lays out a general approach that would work in any framework. Perhaps you could briefly explain the functions of your two classes and how they work together, and edit your code example at the end into some concise pseudocode? \$\endgroup\$
    – Wackidev
    Commented Jun 15, 2012 at 15:53
  • \$\begingroup\$ While I happened to mention FlatRedBall in my post, none of the code I pasted actually dealt with any FRB types. TacticalEntity in this case is the object that represents an entity on screen for me that has health, speed, etc, and has abilities which apply AbilityEffects to other TacticalEntity objects. \$\endgroup\$ Commented Aug 30, 2013 at 2:59

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .