Jump to content

mahdi

Community Members
  • Posts

    21
  • Joined

  • Last visited

Posts posted by mahdi

  1. In practical terms, are you saying that (since you "read C++ source code" and then "re-create it yourself" in C#) you are the legal owner of your C# code and you can use it for commercial purpose? Ethically unfair, but it makes legally sense...

    Well, now it's clear why you didn't release your code, I didn't understand that you were planning this and I don't like it, I was supporting your idea of using C# because I'd like to get involved and give an hand (write and release high quality C++ code is too time consuming for me at the moment). So is this thread just your "look for a sponsor"?

    By the way,

    Maybe you are taking it wrong ? Or maybe i just understood the legal stuff wrong. My port will be open source when it finished. I am not going to do illegal. I wont sell it if that's not ETHICALLY true. its just that.

    Look: It is a hobbyist port. And has potentials.

  2. Well, I don't really think that if we exclude "amateur" javascript developers (that use it to animate buttons in web pages) it is more knowledgeable than C#.

    If you want beginner and amateur developers to be involved, consider also that C# is FAR EASIER to understand and to maintain than C++ and easier to develop and to debug than javascript.

    I'm a software engineer and I'm currently working mainly with C#, but I worked several years with C/C++ and Java as well, and IMO the power and productivity of Visual Studio and C# worth the pain of migrating the entire project (we are currently migrating several components from C++ to C#)...

    BTW, I'm still too new to the project to state with certainty, I just wanted to support Mahdi's action! :-)

    To support the comment, i'd like to mention i have developed for C/C++, Java, JavaScript and C# for 10 years. Among these languages (and also including languages i didn't name them) i believe C# is easier to learn, construct a stable code-base/framework and to debug.

    However, C# is too much Microsoft-dependent and solutions with Mono/Xamarin are not mature enough to consider non-Microsoft platforms with C#. But with C#, at least we will have Windows Phone, Xbox, Windows Desktop, Windows Modern and WinRT versions of the game even without recompilation effort. It is too much i believe.

    One more point is that, this port is not going to replace JavaScript or C++. It is just a hobbyist port of the game with potential to come commercially in Microsoft Store.

  3. What conditions do the Microsoft Stores have that can't be fulfilled by the original version of the game?

    I'm just curious.

    The store requires a completely different coding style, mainly it does not support OpenGL (we have DirectX). Also coding in a C++ for WinRT is completely different from coding in old-fashion Desktop applications.

    i dream with 0ad in my xbox360. you think is possibly jump to consoles, or other massive devices?

    Absolutely the answer is Yes, we only have to develop a new user experience as Xbox does not have a mouse/touch device. The player should be able to control his nation with a GamePad. Hopefully the are some RTS titles available for Xbox and we can inspires ideas for this issue.

    I'm impressed by the amount of effort you must have put into this, but to be honest I find it highly unlikely that anyone will be able to fund ongoing maintenance of a completely disparate codebase. The approach is simply too cumbersome, though I recognize that Windows 8 is supposedly a very different beast.

    It must be possible to emulate the old Win32 API in some fashion, at least for 0 A.D. part 1. If we want to support WinRT natively for part 2, we'll likely rethink the whole engine from scratch, rather than undertaking this Herculean task of having to port each and every single little change back and forth between each platform.

    I think, after the project is separated/branched from the mother project, it could be a totally new and of course independent project. Indeed, They still can exchange ideas/improvement.

    However, even keeping the changes back and forth is so easy thanks to SVN, as i am currently applying any change from original source to the ported one.

    Thanks friends.

  4. Spending nearly 1 year to port the game from C++, JS and OpenGL to a Windows-base platform C# and DirectX, it seems i cannot continue to finish the rest of job without getting support from a sponsor.

    The latest status is this:

    - Graphics almost done, except shadows and water effects. Terrain rendering works fine (the same as original CPP version) but it could be optimized to some extend.

    - Artificial Intelligence needs tons of work. Specially on porting Bot scripts from JS.

    - User Interface has minimum progress.

    - Needs a new user-experience to let the player play the game without needing a mouse device. Only touch would be enough for Modern (Metro) UI and Windows Phone.

    - The game could be commercial, worth 6-8 US dollar (has to check license and copyright stuff).

    - The game is currently in playable states, if you do not care about enemy AI.

    If there are any candidate sponsors for the project, i would share the source code to them for their insurance (this is not a fake project).

    The main reason to port the game to C# and DirectX is to hit Microsoft Stores (Windows 8 and Windows Phone).

    I wish to get comments from you readers, even if you cannot sponsor.

  5. Here's C# code for UnitAI component, Originally created in Javascript.


    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using ZeroAD.Helpers;
    namespace ZeroAD.Components
    {
    public class CCmpUnitAI : ICmpUnitAI
    {
    public static void RegisterMessageTypes(CComponentManager mgr)
    {
    mgr.SubscribeToMessageType(ComponentTypeId.CID_UnitAI, MessageTypeId.Create);
    mgr.SubscribeToMessageType(ComponentTypeId.CID_UnitAI, MessageTypeId.Destroy);
    mgr.SubscribeToMessageType(ComponentTypeId.CID_UnitAI, MessageTypeId.OwnershipChanged);
    mgr.SubscribeToMessageType(ComponentTypeId.CID_UnitAI, MessageTypeId.DiplomacyChanged);
    mgr.SubscribeToMessageType(ComponentTypeId.CID_UnitAI, MessageTypeId.MotionChanged);
    mgr.SubscribeGloballyToMessageType(ComponentTypeId.CID_UnitAI, MessageTypeId.ConstructionFinished);
    mgr.SubscribeGloballyToMessageType(ComponentTypeId.CID_UnitAI, MessageTypeId.EntityRenamed);
    mgr.SubscribeToMessageType(ComponentTypeId.CID_UnitAI, MessageTypeId.Attacked);
    mgr.SubscribeToMessageType(ComponentTypeId.CID_UnitAI, MessageTypeId.HealthChanged);
    mgr.SubscribeToMessageType(ComponentTypeId.CID_UnitAI, MessageTypeId.RangeUpdate);
    }
    private static readonly SortedDictionary<string, UnitStance> g_Stances = new SortedDictionary<string, UnitStance>
    {
    {
    "violent", new UnitStance
    {
    targetVisibleEnemies= true,
    targetAttackersAlways= true,
    targetAttackersPassive= true,
    respondFlee= false,
    respondChase= true,
    respondChaseBeyondVision= true,
    respondStandGround= false,
    respondHoldGround= false,
    }
    },
    {
    "aggressive", new UnitStance
    {
    targetVisibleEnemies= true,
    targetAttackersAlways= false,
    targetAttackersPassive= true,
    respondFlee= false,
    respondChase= true,
    respondChaseBeyondVision= false,
    respondStandGround= false,
    respondHoldGround= false,
    }
    },
    {
    "defensive", new UnitStance
    {
    targetVisibleEnemies= true,
    targetAttackersAlways= false,
    targetAttackersPassive= true,
    respondFlee= false,
    respondChase= false,
    respondChaseBeyondVision= false,
    respondStandGround= false,
    respondHoldGround= true,
    }
    },
    {
    "passive", new UnitStance
    {
    targetVisibleEnemies= false,
    targetAttackersAlways= false,
    targetAttackersPassive= true,
    respondFlee= true,
    respondChase= false,
    respondChaseBeyondVision= false,
    respondStandGround= false,
    respondHoldGround= false,
    }
    },
    {
    "standground", new UnitStance
    {
    targetVisibleEnemies= true,
    targetAttackersAlways= false,
    targetAttackersPassive= true,
    respondFlee= false,
    respondChase= false,
    respondChaseBeyondVision= false,
    respondStandGround= true,
    respondHoldGround= false,
    }
    },
    };


    ZeroAD.Helpers.FSM UnitFsm;
    private bool isIdle;
    private bool isGarrisoned;
    private List<OrderItem> orderQueue = new List<OrderItem>();
    OrderItem order;
    private string attackType;
    private uint formationController;
    private uint? losRangeQuery;
    private uint? losHealRangeQuery;
    private uint? losGaiaRangeQuery;
    private bool resyncAnimation;
    private int? lastAttacked;
    private int? lastHealed;
    private ICmpAttack.AttackTimer attackTimers;
    private string stance;
    public string fsmNextState;
    public string fsmStateName;
    public bool fsmReenter;
    private int? timer;
    CVector3D? heldPosition;
    private string lastFormationName;
    private ICmpHeal.HealTimer healTimers;

    public CCmpUnitAI()
    {
    StateNode stateROOT = new StateNode();

    // Default event handlers:
    stateROOT.Handlers["MoveCompleted"] = new Action<dynamic>((msg) =>
    {
    // ignore spurious movement messages
    // (these can happen when stopping moving at the same time
    // as switching states)
    });
    stateROOT.Handlers["MoveStarted"] = new Action<dynamic>((msg) =>
    {
    // ignore spurious movement messages
    });
    stateROOT.Handlers["ConstructionFinished"] = new Action<dynamic>((msg) =>
    {
    // ignore uninteresting construction messages
    });
    stateROOT.Handlers["LosRangeUpdate"] = new Action<dynamic>((msg) =>
    {
    // ignore newly-seen units by default
    });
    stateROOT.Handlers["LosGaiaRangeUpdate"] = new Action<dynamic>((msg) =>
    {
    // ignore newly-seen Gaia units by default
    });
    stateROOT.Handlers["LosHealRangeUpdate"] = new Action<dynamic>((msg) =>
    {
    // ignore newly-seen injured units by default
    });
    stateROOT.Handlers["Attacked"] = new Action<dynamic>((msg) =>
    {
    // ignore attacker
    });
    stateROOT.Handlers["HealthChanged"] = new Action<dynamic>((msg) =>
    {
    // ignore
    });
    stateROOT.Handlers["EntityRenamed"] = new Action<dynamic>((msg) =>
    {
    // ignore
    });
    // Formation handlers:
    stateROOT.Handlers["FormationLeave"] = new Action<dynamic>((msg) =>
    {
    // ignore when we're not in FORMATIONMEMBER
    });
    // Called when being told to walk as part of a formation
    stateROOT.Handlers["Order.FormationWalk"] = new Action<dynamic>((msg) =>
    {
    // Let players move captured domestic animals around
    if (this.IsAnimal() && !this.IsDomestic())
    {
    this.FinishOrder();
    return;
    }
    var cmpUnitMotion = Engine.QueryInterface(this.entity, InterfaceId.UnitMotion) as ICmpUnitMotion;
    cmpUnitMotion.MoveToFormationOffset((uint)msg.data.target, (float)msg.data.x, (float)msg.data.z);
    this.SetNextState("FORMATIONMEMBER.WALKING");
    });
    // Special orders:
    // (these will be overridden by various states)
    stateROOT.Handlers["Order.LeaveFoundation"] = new Action<dynamic>((msg) =>
    {
    if (!PlayerHelper.IsOwnedByAllyOfEntity(Engine, this.entity, (uint)msg.data.target))
    {
    this.FinishOrder();
    return;
    }
    // Move a tile outside the building
    var range = 4;
    var ok = this.MoveToTargetRangeExplicit((uint)msg.data.target, range, range);
    if (ok)
    {
    // We've started walking to the given point
    this.SetNextState("INDIVIDUAL.WALKING");
    }
    else
    {
    // We are already at the target, or can't move at all
    this.FinishOrder();
    }
    });
    stateROOT.Handlers["Order.Stop"] = new Action<dynamic>((msg) =>
    {
    // We have no control over non-domestic animals.
    if (this.IsAnimal() && !this.IsDomestic())
    {
    this.FinishOrder();
    return;
    }
    // Stop moving immediately.
    this.StopMoving();
    this.FinishOrder();
    // No orders left, we're an individual now
    if (this.IsAnimal())
    {
    this.SetNextState("ANIMAL.IDLE");
    }
    else
    {
    this.SetNextState("INDIVIDUAL.IDLE");
    }
    });
    stateROOT.Handlers["Order.Walk"] = new Action<dynamic>((msg) =>
    {
    // Let players move captured domestic animals around
    if (this.IsAnimal() && !this.IsDomestic())
    {
    this.FinishOrder();
    return;
    }
    this.SetHeldPosition((float)this.order.data.x, (float)this.order.data.z);
    this.MoveToPoint((float)this.order.data.x, (float)this.order.data.z);
    if (this.IsAnimal())
    {
    this.SetNextState("ANIMAL.WALKING");
    }
    else
    {
    this.SetNextState("INDIVIDUAL.WALKING");
    }
    });
    stateROOT.Handlers["Order.WalkToTarget"] = new Action<dynamic>((msg) =>
    {
    // Let players move captured domestic animals around
    if (this.IsAnimal() && !this.IsDomestic())
    {
    this.FinishOrder();
    return;
    }
    var ok = this.MoveToTarget((uint)this.order.data.target);
    if (ok)
    {
    // We've started walking to the given point
    if (this.IsAnimal())
    {
    this.SetNextState("ANIMAL.WALKING");
    }
    else
    {
    this.SetNextState("INDIVIDUAL.WALKING");
    }
    }
    else
    {
    // We are already at the target, or can't move at all
    this.StopMoving();
    this.FinishOrder();
    }
    });
    stateROOT.Handlers["Order.Flee"] = new Action<dynamic>((msg) =>
    {
    // TODO: if we were attacked by a ranged unit, we need to flee much further away
    var ok = this.MoveToTargetRangeExplicit((uint)this.order.data.target, this.template["FleeDistance"].ToFloat(), -1);
    if (ok)
    {
    // We've started fleeing from the given target
    if (this.IsAnimal())
    {
    this.SetNextState("ANIMAL.FLEEING");
    }
    else
    {
    this.SetNextState("INDIVIDUAL.FLEEING");
    }
    }
    else
    {
    // We are already at the target, or can't move at all
    this.StopMoving();
    this.FinishOrder();
    }
    });
    stateROOT.Handlers["Order.Attack"] = new Action<dynamic>((msg) =>
    {
    // Check the target is alive
    if (!this.TargetIsAlive(this.order.data.target))
    {
    this.FinishOrder();
    return;
    }
    // Work out how to attack the given target
    var type = this.GetBestAttackAgainst(this.order.data.target);
    if (type == null)
    {
    // Oops, we can't attack at all
    this.FinishOrder();
    return;
    }
    this.attackType = type;
    // If we are already at the target, try attacking it from here
    if (this.CheckTargetRange(this.order.data.target, InterfaceId.Attack, this.attackType))
    {
    this.StopMoving();
    if (this.IsAnimal())
    {
    this.SetNextState("ANIMAL.COMBAT.ATTACKING");
    }
    else
    {
    this.SetNextState("INDIVIDUAL.COMBAT.ATTACKING");
    }
    return;
    }
    // If we can't reach the target, but are standing ground, then abandon this attack order.
    // Unless we're hunting, that's a special case where we should continue attacking our target.
    if (this.GetStance().respondStandGround && !this.order.data.force && !this.order.data.hunting)
    {
    this.FinishOrder();
    return;
    }
    // Try to move within attack range
    if (this.MoveToTargetRange(this.order.data.target, InterfaceId.Attack, this.attackType))
    {
    // We've started walking to the given point
    if (this.IsAnimal())
    {
    this.SetNextState("ANIMAL.COMBAT.APPROACHING");
    }
    else
    {
    this.SetNextState("INDIVIDUAL.COMBAT.APPROACHING");
    }
    return;
    }
    // We can't reach the target, and can't move towards it,
    // so abandon this attack order
    this.FinishOrder();
    });
    stateROOT.Handlers["Order.Heal"] = new Action<dynamic>((msg) =>
    {
    // Check the target is alive
    if (!this.TargetIsAlive(this.order.data.target))
    {
    this.FinishOrder();
    return;
    }
    // Healers can't heal themselves.
    if (this.order.data.target == this.entity)
    {
    this.FinishOrder();
    return;
    }
    // Check if the target is in range
    if (this.CheckTargetRange(this.order.data.target, InterfaceId.Heal))
    {
    this.StopMoving();
    this.SetNextState("INDIVIDUAL.HEAL.HEALING");
    return;
    }
    // If we can't reach the target, but are standing ground,
    // then abandon this heal order
    if (this.GetStance().respondStandGround && !this.order.data.force)
    {
    this.FinishOrder();
    return;
    }
    // Try to move within heal range
    if (this.MoveToTargetRange(this.order.data.target, InterfaceId.Heal))
    {
    // We've started walking to the given point
    this.SetNextState("INDIVIDUAL.HEAL.APPROACHING");
    return;
    }
    // We can't reach the target, and can't move towards it,
    // so abandon this heal order
    this.FinishOrder();
    });
    stateROOT.Handlers["Order.Gather"] = new Action<dynamic>((msg) =>
    {
    // If the target is still alive, we need to kill it first
    if (this.MustKillGatherTarget((uint)this.order.data.target) && this.CheckTargetVisible((uint)this.order.data.target))
    {
    // Make sure we can attack the target, else we'll get very stuck
    if (this.GetBestAttackAgainst((uint)this.order.data.target) == null)
    {
    // Oops, we can't attack at all - give up
    // TODO: should do something so the player knows why this failed
    this.FinishOrder();
    return;
    }
    this.PushOrderFront("Attack", new AllInOneData { target = this.order.data.target, force = false, hunting = true });
    return;
    }
    // Try to move within range
    if (this.MoveToTargetRange(this.order.data.target, InterfaceId.ResourceGatherer))
    {
    // We've started walking to the given point
    this.SetNextState("INDIVIDUAL.GATHER.APPROACHING");
    }
    else
    {
    // We are already at the target, or can't move at all,
    // so try gathering it from here.
    // TODO: need better handling of the can't-reach-target case
    this.StopMoving();
    this.SetNextStateAlwaysEntering("INDIVIDUAL.GATHER.GATHERING");
    }
    });
    stateROOT.Handlers["Order.GatherNearPosition"] = new Action<dynamic>((msg) =>
    {
    // Move the unit to the position to gather from.
    this.MoveToPoint(this.order.data.x, this.order.data.z);
    this.SetNextState("INDIVIDUAL.GATHER.WALKING");
    });
    stateROOT.Handlers["Order.ReturnResource"] = new Action<dynamic>((msg) =>
    {
    // Try to move to the dropsite
    if (this.MoveToTarget((uint)this.order.data.target))
    {
    // We've started walking to the target
    this.SetNextState("INDIVIDUAL.RETURNRESOURCE.APPROACHING");
    }
    else
    {
    // Oops, we can't reach the dropsite.
    // Maybe we should try to pick another dropsite, to find an
    // accessible one?
    // For now, just give up.
    this.StopMoving();
    this.FinishOrder();
    return;
    }
    });
    stateROOT.Handlers["Order.Trade"] = new Action<dynamic>((msg) =>
    {
    if (this.MoveToMarket(this.order.data.firstMarket))
    {
    // We've started walking to the first market
    this.SetNextState("INDIVIDUAL.TRADE.APPROACHINGFIRSTMARKET");
    }
    });
    stateROOT.Handlers["Order.Repair"] = new Action<dynamic>((msg) =>
    {
    // Try to move within range
    if (this.MoveToTargetRange(this.order.data.target, InterfaceId.Builder, null))
    {
    // We've started walking to the given point
    this.SetNextState("INDIVIDUAL.REPAIR.APPROACHING");
    }
    else
    {
    // We are already at the target, or can't move at all,
    // so try repairing it from here.
    // TODO: need better handling of the can't-reach-target case
    this.StopMoving();
    this.SetNextState("INDIVIDUAL.REPAIR.REPAIRING");
    }
    });
    stateROOT.Handlers["Order.Garrison"] = new Action<dynamic>((msg) =>
    {
    if (this.MoveToTarget(this.order.data.target))
    {
    this.SetNextState("INDIVIDUAL.GARRISON.APPROACHING");
    }
    else
    {
    // We do a range check before actually garrisoning
    this.StopMoving();
    this.SetNextState("INDIVIDUAL.GARRISON.GARRISONED");
    }
    });
    stateROOT.Handlers["Order.Cheering"] = new Action<dynamic>((msg) =>
    {
    this.SetNextState("INDIVIDUAL.CHEERING");
    });
    {
    // States for the special entity representing a group of units moving in formation:
    var stateFORMATIONCONTROLLER = stateROOT.SubStates["FORMATIONCONTROLLER"] = new StateNode();
    stateFORMATIONCONTROLLER.Handlers["Order.Walk"] = new Action<dynamic>((msg) =>
    {
    var cmpFormation = Engine.QueryInterface(this.entity, InterfaceId.Formation) as ICmpFormation;
    cmpFormation.CallMemberFunction(ai => ai.SetHeldPosition((float)msg.data.x, (float)msg.data.z));
    this.MoveToPoint(this.order.data.x, this.order.data.z);
    this.SetNextState("WALKING");
    });
    // Only used by other orders to walk there in formation
    stateFORMATIONCONTROLLER.Handlers["Order.WalkToTargetRange"] = new Action<dynamic>((msg) =>
    {
    if (this.MoveToTargetRangeExplicit(this.order.data.target, this.order.data.min, this.order.data.max))
    {
    this.SetNextState("WALKING");
    }
    else
    {
    this.FinishOrder();
    }
    });
    stateFORMATIONCONTROLLER.Handlers["Order.Stop"] = new Action<dynamic>((msg) =>
    {
    var cmpFormation = Engine.QueryInterface(this.entity, InterfaceId.Formation) as ICmpFormation;
    cmpFormation.CallMemberFunction(ai => ai.Stop(false));
    cmpFormation.Disband();
    });
    stateFORMATIONCONTROLLER.Handlers["Order.Attack"] = new Action<dynamic>((msg) =>
    {
    // TODO: we should move in formation towards the target,
    // then break up into individuals when close enough to it
    var cmpFormation = Engine.QueryInterface(this.entity, InterfaceId.Formation) as ICmpFormation;
    cmpFormation.CallMemberFunction(ai => ai.Attack((uint)msg.data.target, false));
    // TODO: we should wait until the target is killed, then
    // move on to the next queued order.
    // Don't bother now, just disband the formation immediately.
    cmpFormation.Disband();
    });
    stateFORMATIONCONTROLLER.Handlers["Order.Heal"] = new Action<dynamic>((msg) =>
    {
    // TODO: see notes in Order.Attack
    var cmpFormation = Engine.QueryInterface(this.entity, InterfaceId.Formation) as ICmpFormation;
    cmpFormation.CallMemberFunction(ai => ai.Heal((uint)msg.data.target, false));
    cmpFormation.Disband();
    });
    stateFORMATIONCONTROLLER.Handlers["Order.Repair"] = new Action<dynamic>((msg) =>
    {
    // TODO on what should we base this range?
    // Check if we are already in range, otherwise walk there
    if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10))
    {
    if (!this.TargetIsAlive(msg.data.target))
    // The building was finished or destroyed
    this.FinishOrder();
    else
    // Out of range move there in formation
    this.PushOrderFront("WalkToTargetRange", new AllInOneData { target = msg.data.target, min = 0, max = 10 });
    return;
    }
    var cmpFormation = Engine.QueryInterface(this.entity, InterfaceId.Formation) as ICmpFormation;
    // We don't want to rearrange the formation if the individual units are carrying
    // out a task and one of the members dies/leaves the formation.
    cmpFormation.SetRearrange(false);
    cmpFormation.CallMemberFunction(ai => ai.Repair((uint)msg.data.target, (bool)msg.data.autocontinue, false));
    this.SetNextState("REPAIR");
    });
    stateFORMATIONCONTROLLER.Handlers["Order.Gather"] = new Action<dynamic>((msg) =>
    {
    // TODO: see notes in Order.Attack
    // If the resource no longer exists, send a GatherNearPosition order
    var cmpFormation = Engine.QueryInterface(this.entity, InterfaceId.Formation) as ICmpFormation;
    if (this.CanGather(msg.data.target))
    {
    cmpFormation.CallMemberFunction(ai => ai.Gather((uint)msg.data.target, false));
    }
    else
    {
    cmpFormation.CallMemberFunction(ai => ai.GatherNearPosition(msg.data.lastPos.X, msg.data.lastPos.Z, msg.data.type, msg.data.template, false));
    }
    cmpFormation.Disband();
    });
    stateFORMATIONCONTROLLER.Handlers["Order.GatherNearPosition"] = new Action<dynamic>((msg) =>
    {
    // TODO: see notes in Order.Attack
    var cmpFormation = Engine.QueryInterface(this.entity, InterfaceId.Formation) as ICmpFormation;
    cmpFormation.CallMemberFunction(ai => ai.GatherNearPosition(msg.data.x, msg.data.z, msg.data.type, msg.data.template, false));
    cmpFormation.Disband();
    });
    stateFORMATIONCONTROLLER.Handlers["Order.ReturnResource"] = new Action<dynamic>((msg) =>
    {
    // TODO: see notes in Order.Attack
    var cmpFormation = Engine.QueryInterface(this.entity, InterfaceId.Formation) as ICmpFormation;
    cmpFormation.CallMemberFunction(ai => ai.ReturnResource((uint)msg.data.target, false));
    cmpFormation.Disband();
    });
    stateFORMATIONCONTROLLER.Handlers["Order.Garrison"] = new Action<dynamic>((msg) =>
    {
    // TODO: see notes in Order.Attack
    var cmpFormation = Engine.QueryInterface(this.entity, InterfaceId.Formation) as ICmpFormation;
    cmpFormation.CallMemberFunction(ai => ai.Garrison((uint)msg.data.target, false));
    cmpFormation.Disband();
    });
    stateFORMATIONCONTROLLER.SubStates["IDLE"] = new StateNode();
    {
    var stateFORMATIONCONTROLLER_WALKING = stateFORMATIONCONTROLLER.SubStates["WALKING"] = new StateNode();
    stateFORMATIONCONTROLLER_WALKING.Handlers["MoveStarted"] = new Action<dynamic>((msg) =>
    {
    var cmpFormation = Engine.QueryInterface(this.entity, InterfaceId.Formation) as ICmpFormation;
    cmpFormation.SetRearrange(true);
    cmpFormation.MoveMembersIntoFormation(true);
    });
    stateFORMATIONCONTROLLER_WALKING.Handlers["MoveCompleted"] = new Action<dynamic>((msg) =>
    {
    if (this.FinishOrder())
    return;
    // If this was the last order, attempt to disband the formation.
    var cmpFormation = Engine.QueryInterface(this.entity, InterfaceId.Formation) as ICmpFormation;
    cmpFormation.FindInPosition();
    });
    }
    {
    var stateFORMATIONCONTROLLER_REPAIR = stateFORMATIONCONTROLLER.SubStates["REPAIR"] = new StateNode();
    stateFORMATIONCONTROLLER_REPAIR.Handlers["ConstructionFinished"] = new Action<dynamic>((msg) =>
    {
    if (msg.data.entity != this.order.data.target)
    {
    return;
    }
    if (this.FinishOrder())
    {
    return;
    }
    var cmpFormation = Engine.QueryInterface(this.entity, InterfaceId.Formation) as ICmpFormation;
    cmpFormation.Disband();
    });
    }
    }
    {
    // States for entities moving as part of a formation:
    var stateFORMATIONMEMBER = stateROOT.SubStates["FORMATIONMEMBER"] = new StateNode();
    stateFORMATIONMEMBER.Handlers["FormationLeave"] = new Action<dynamic>((msg) =>
    {
    // Stop moving as soon as the formation disbands
    this.StopMoving();
    // If the controller handled an order but some members rejected it,
    // they will have no orders and be in the FORMATIONMEMBER.IDLE state.
    if (this.orderQueue.Count != 0)
    {
    // We're leaving the formation, so stop our FormationWalk order
    if (this.FinishOrder())
    {
    return;
    }
    }
    // No orders left, we're an individual now
    if (this.IsAnimal())
    {
    this.SetNextState("ANIMAL.IDLE");
    }
    else
    {
    this.SetNextState("INDIVIDUAL.IDLE");
    }
    });
    // Override the LeaveFoundation order since we're not doing
    // anything more important (and we might be stuck in the WALKING
    // state forever and need to get out of foundations in that case)
    stateFORMATIONMEMBER.Handlers["Order.LeaveFoundation"] = new Action<dynamic>((msg) =>
    {
    if (!PlayerHelper.IsOwnedByAllyOfEntity(Engine, this.entity, msg.data.target))
    {
    this.FinishOrder();
    return;
    }
    // Move a tile outside the building
    var range = 4;
    var ok = this.MoveToTargetRangeExplicit(msg.data.target, range, range);
    if (ok)
    {
    // We've started walking to the given point
    this.SetNextState("WALKINGTOPOINT");
    }
    else
    {
    // We are already at the target, or can't move at all
    this.FinishOrder();
    }
    });
    {
    var stateFORMATIONMEMBER_IDLE = stateFORMATIONMEMBER.SubStates["IDLE"] = new StateNode();
    stateFORMATIONMEMBER_IDLE.Handlers["enter"] = new Action<dynamic>((msg) => this.SelectAnimation("idle"));
    }
    {
    var stateFORMATIONMEMBER_WALKING = stateFORMATIONMEMBER.SubStates["WALKING"] = new StateNode();
    stateFORMATIONMEMBER_WALKING.Handlers["enter"] = new Action<dynamic>((msg) => this.SelectAnimation("move"));
    // Occurs when the unit has reached its destination and the controller
    // is done moving. The controller is notified, and will disband the
    // formation if all units are in formation and no orders remain.
    stateFORMATIONMEMBER_WALKING.Handlers["MoveCompleted"] = new Action<dynamic>((msg) =>
    {
    var cmpFormation = Engine.QueryInterface(this.formationController, InterfaceId.Formation) as ICmpFormation;
    cmpFormation.SetInPosition(this.entity);
    });
    }
    {
    var stateFORMATIONMEMBER_WALKINGTOPOINT = stateFORMATIONMEMBER.SubStates["WALKINGTOPOINT"] = new StateNode();
    stateFORMATIONMEMBER_WALKINGTOPOINT.Handlers["enter"] = new Action<dynamic>((msg) =>
    {
    var cmpFormation = Engine.QueryInterface(this.formationController, InterfaceId.Formation) as ICmpFormation;
    cmpFormation.UnsetInPosition(this.entity);
    this.SelectAnimation("move");
    });
    stateFORMATIONMEMBER_WALKINGTOPOINT.Handlers["MoveCompleted"] = new Action<dynamic>((msg) => { this.FinishOrder(); });
    }
    }
    {
    var stateINDIVIDUAL = stateROOT.SubStates["INDIVIDUAL"] = new StateNode();
    stateINDIVIDUAL.Handlers["enter"] = new Action<dynamic>((msg) =>
    {
    // Sanity-checking
    if (this.IsAnimal())
    {
    //error("Animal got moved into INDIVIDUAL.* state");
    }
    });
    stateINDIVIDUAL.Handlers["Attacked"] = new Action<dynamic>((msg) =>
    {
    // Respond to attack if we always target attackers, or if we target attackers
    // during passive orders (e.g. gathering/repairing are never forced)
    if (this.GetStance().targetAttackersAlways || (this.GetStance().targetAttackersPassive && (this.order == null || this.order.data == null || !this.order.data.force)))
    {
    this.RespondToTargetedEntities(new[] { (uint)msg.data.attacker });
    }
    });
    {
    var stateINDIVIDUAL_IDLE = stateINDIVIDUAL.SubStates["IDLE"] = new StateNode();
    stateINDIVIDUAL_IDLE.Handlers["enter"] = new Func<dynamic, bool>((msg) =>
    {
    // Switch back to idle animation to guarantee we won't
    // get stuck with an incorrect animation
    this.SelectAnimation("idle");
    // The GUI and AI want to know when a unit is idle, but we don't
    // want to send frequent spurious messages if the unit's only
    // idle for an instant and will quickly go off and do something else.
    // So we'll set a timer here and only report the idle event if we
    // remain idle
    this.StartTimer(1000, 0);
    // If a unit can heal and attack we first want to heal wounded units,
    // so check if we are a healer and find whether there's anybody nearby to heal.
    // (If anyone approaches later it'll be handled via LosHealRangeUpdate.)
    // If anyone in sight gets hurt that will be handled via LosHealRangeUpdate.
    if (this.IsHealer() && this.FindNewHealTargets())
    {
    return true; // (abort the FSM transition since we may have already switched state)
    }
    // If we entered the idle state we must have nothing better to do,
    // so immediately check whether there's anybody nearby to attack.
    // (If anyone approaches later, it'll be handled via LosRangeUpdate.)
    if (this.FindNewTargets())
    {
    return true; // (abort the FSM transition since we may have already switched state)
    }
    // Nobody to attack - stay in idle
    return false;
    });
    stateINDIVIDUAL_IDLE.Handlers["leave"] = new Action<dynamic>((msg) =>
    {
    var rangeMan = (ICmpRangeManager)Engine.QueryInterface(InterfaceId.RangeManager);
    rangeMan.DisableActiveQuery(this.losRangeQuery.Value);
    if (this.losHealRangeQuery != null)
    {
    rangeMan.DisableActiveQuery(this.losHealRangeQuery.Value);
    }
    this.StopTimer();
    if (this.isIdle)
    {
    this.isIdle = false;
    Engine.PostMessage(this.entity, new CMessageUnitIdleChanged { idle = this.isIdle });
    }
    });
    stateINDIVIDUAL_IDLE.Handlers["LosRangeUpdate"] = new Action<dynamic>((msg) =>
    {
    if (this.GetStance().targetVisibleEnemies)
    {
    // Start attacking one of the newly-seen enemy (if any)
    this.AttackEntitiesByPreference(msg.data.added);
    }
    });
    stateINDIVIDUAL_IDLE.Handlers["LosGaiaRangeUpdate"] = new Action<dynamic>((msg) =>
    {
    if (this.GetStance().targetVisibleEnemies)
    {
    // Start attacking one of the newly-seen enemy (if any)
    this.AttackGaiaEntitiesByPreference(msg.data.added);
    }
    });
    stateINDIVIDUAL_IDLE.Handlers["LosHealRangeUpdate"] = new Action<dynamic>((msg) =>
    {
    this.RespondToHealableEntities(msg.data.added);
    });
    stateINDIVIDUAL_IDLE.Handlers["Timer"] = new Action<dynamic>((msg) =>
    {
    if (!this.isIdle)
    {
    this.isIdle = true;
    Engine.PostMessage(this.entity, new CMessageUnitIdleChanged { idle = this.isIdle });
    }
    });
    }
    {
    var stateINDIVIDUAL_WALKING = stateINDIVIDUAL.SubStates["WALKING"] = new StateNode();
    stateINDIVIDUAL_WALKING.Handlers["enter"] = new Action<dynamic>((msg) =>
    {
    this.SelectAnimation("move");
    });
    stateINDIVIDUAL_WALKING.Handlers["MoveCompleted"] = new Action<dynamic>((msg) =>
    {
    this.FinishOrder();
    });
    }
    {
    var stateINDIVIDUAL_FLEEING = stateINDIVIDUAL.SubStates["FLEEING"] = new StateNode();
    stateINDIVIDUAL_FLEEING.Handlers["enter"] = new Action<dynamic>((msg) =>
    {
    this.PlaySound("panic");
    // Run quickly
    var speed = this.GetRunSpeed();
    this.SelectAnimation("move");
    this.SetMoveSpeed(speed);
    });
    stateINDIVIDUAL_FLEEING.Handlers["leave"] = new Action<dynamic>((msg) =>
    {
    // Reset normal speed
    this.SetMoveSpeed(this.GetWalkSpeed());
    });
    stateINDIVIDUAL_FLEEING.Handlers["MoveCompleted"] = new Action<dynamic>((msg) =>
    {
    // When we've run far enough, stop fleeing
    this.FinishOrder();
    });
    // TODO: what if we run into more enemies while fleeing?
    }
    {
    var stateINDIVIDUAL_COMBAT = stateINDIVIDUAL.SubStates["COMBAT"] = new StateNode();
    stateINDIVIDUAL_COMBAT.Handlers["Order.LeaveFoundation"] = new Func<dynamic, bool>((msg) =>
    {
    // Ignore the order as we're busy.
    //return new { discardOrder = true };
    return true;
    });
    stateINDIVIDUAL_COMBAT.Handlers["EntityRenamed"] = new Action<dynamic>((msg) =>
    {
    if (this.order.data.target == msg.entity)
    {
    this.order.data.target = msg.newentity;
    // If we're hunting, that means we have a queued gather
    // order whose target also needs to be updated.
    if (this.order.data.hunting && this.orderQueue[1] != null && this.orderQueue[1].type == "Gather")
    {
    this.orderQueue[1].data.target = msg.newentity;
    }
    }
    });
    stateINDIVIDUAL_COMBAT.Handlers["Attacked"] = new Action<dynamic>((msg) =>
    {
    // If we're already in combat mode, ignore anyone else
    // who's attacking us
    });
    {
    var stateINDIVIDUAL_COMBAT_APPROACHING = stateINDIVIDUAL_COMBAT.SubStates["APPROACHING"] = new StateNode();
    stateINDIVIDUAL_COMBAT_APPROACHING.Handlers["enter"] = new Action<dynamic>((msg) =>
    {
    // Show weapons rather than carried resources.
    this.SetGathererAnimationOverride(true);
    this.SelectAnimation("move");
    this.StartTimer(1000, 1000);
    });
    stateINDIVIDUAL_COMBAT_APPROACHING.Handlers["leave"] = new Action<dynamic>((msg) =>
    {
    // Show carried resources when walking.
    this.SetGathererAnimationOverride(false);
    this.StopTimer();
    });
    stateINDIVIDUAL_COMBAT_APPROACHING.Handlers["Timer"] = new Action<dynamic>((msg) =>
    {
    if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force, InterfaceId.Attack))
    {
    this.StopMoving();
    this.FinishOrder();
    // Return to our original position
    if (this.GetStance().respondHoldGround)
    {
    this.WalkToHeldPosition();
    }
    }
    });
    stateINDIVIDUAL_COMBAT_APPROACHING.Handlers["MoveCompleted"] = new Action<dynamic>((msg) =>
    {
    this.SetNextState("ATTACKING");
    });
    stateINDIVIDUAL_COMBAT_APPROACHING.Handlers["Attacked"] = new Action<dynamic>((msg) =>
    {
    // If we're attacked by a close enemy, we should try to defend ourself
    // but only if we're not forced to target something else
    if ((string)msg.data.type == "Melee" && (this.GetStance().targetAttackersAlways || (this.GetStance().targetAttackersPassive && !(bool)this.order.data.force)))
    {
    this.RespondToTargetedEntities(new[] { (uint)msg.data.attacker });
    }
    });
    }
    {
    var stateINDIVIDUAL_COMBAT_ATTACKING = stateINDIVIDUAL_COMBAT.SubStates["ATTACKING"] = new StateNode();
    stateINDIVIDUAL_COMBAT_ATTACKING.Handlers["enter"] = new Action<dynamic>((msg) =>
    {
    var cmpAttack = Engine.QueryInterface(this.entity, InterfaceId.Attack) as ICmpAttack;
    this.attackTimers = cmpAttack.GetTimers(this.attackType);
    // If the repeat time since the last attack hasn't elapsed,
    // delay this attack to avoid attacking too fast.
    var prepare = this.attackTimers.prepare;
    if (this.lastAttacked != null)
    {
    var cmpTimer = Engine.QueryInterface(InterfaceId.Timer) as ICmpTimer;
    var repeatLeft = this.lastAttacked.GetValueOrDefault() + this.attackTimers.repeat - cmpTimer.GetTime();
    prepare = Math.Max(prepare, repeatLeft);
    }
    this.SelectAnimation("melee", false, 1.0f, "attack");
    this.SetAnimationSync(prepare, this.attackTimers.repeat);
    this.StartTimer(prepare, this.attackTimers.repeat);
    // TODO: we should probably only bother syncing projectile attacks, not melee
    // If using a non-default prepare time, re-sync the animation when the timer runs.
    this.resyncAnimation = (prepare != this.attackTimers.prepare) ? true : false;
    this.FaceTowardsTarget((uint)this.order.data.target);
    });
    stateINDIVIDUAL_COMBAT_ATTACKING.Handlers["leave"] = new Action<dynamic>((msg) =>
    {
    this.StopTimer();
    });
    stateINDIVIDUAL_COMBAT_ATTACKING.Handlers["Timer"] = new Action<dynamic>((msg) =>
    {
    var target = (uint)this.order.data.target;
    // Check the target is still alive and attackable
    if (this.TargetIsAlive(target) && this.CanAttack(target, (bool)this.order.data.forceResponse))
    {
    // Check we can still reach the target
    if (this.CheckTargetRange(target, InterfaceId.Attack, this.attackType))
    {
    var cmpTimer = Engine.QueryInterface(InterfaceId.Timer) as ICmpTimer;
    this.lastAttacked = cmpTimer.GetTime() - (int)msg.lateness;
    this.FaceTowardsTarget(target);
    var cmpAttack = Engine.QueryInterface(this.entity, InterfaceId.Attack) as ICmpAttack;
    cmpAttack.PerformAttack(this.attackType, target);
    if (this.resyncAnimation)
    {
    this.SetAnimationSync(this.attackTimers.repeat, this.attackTimers.repeat);
    this.resyncAnimation = false;
    }
    return;
    }
    // Can't reach it - try to chase after it
    if (this.ShouldChaseTargetedEntity(target, this.order.data.force))
    {
    if (this.MoveToTargetRange(target, InterfaceId.Attack, this.attackType))
    {
    this.SetNextState("COMBAT.CHASING");
    return;
    }
    }
    }
    // Can't reach it, no longer owned by enemy, or it doesn't exist any more - give up
    if (this.FinishOrder())
    {
    return;
    }
    // See if we can switch to a new nearby enemy
    if (this.FindNewTargets())
    {
    // Attempt to immediately re-enter the timer function, to avoid wasting the attack.
    this.TimerHandler(msg.data, msg.lateness);
    {
    return;
    }
    }
    // Return to our original position
    if (this.GetStance().respondHoldGround)
    {
    this.WalkToHeldPosition();
    }
    });
    // TODO: respond to target deaths immediately, rather than waiting
    // until the next Timer event
    stateINDIVIDUAL_COMBAT_ATTACKING.Handlers["Attacked"] = new Action<dynamic>((msg) =>
    {
    if (this.order.data.target != msg.data.attacker)
    {
    // If we're attacked by a close enemy, stronger than our current target,
    // we choose to attack it, but only if we're not forced to target something else
    if (msg.data.type == "Melee" && (this.GetStance().targetAttackersAlways || (this.GetStance().targetAttackersPassive && !this.order.data.force)))
    {
    var ents = new List<uint> { (uint)this.order.data.target, (uint)msg.data.attacker };
    EntityHelper.SortEntitiesByPriority(Engine, ents);
    if (ents[0] != this.order.data.target)
    {
    this.RespondToTargetedEntities(ents.ToArray());
    }
    }
    }
    });
    }
    {
    var stateINDIVIDUAL_COMBAT_CHASING = stateINDIVIDUAL_COMBAT.SubStates["CHASING"] = new StateNode();
    stateINDIVIDUAL_COMBAT_CHASING.Handlers["enter"] = new Action<dynamic>((msg) =>
    {
    // Show weapons rather than carried resources.
    this.SetGathererAnimationOverride(true);
    this.SelectAnimation("move");
    this.StartTimer(1000, 1000);
    });
    stateINDIVIDUAL_COMBAT_CHASING.Handlers["leave"] = new Action<dynamic>((msg) =>
    {
    // Show carried resources when walking.
    this.SetGathererAnimationOverride(false);
    this.StopTimer();
    });
    stateINDIVIDUAL_COMBAT_CHASING.Handlers["Timer"] = new Action<dynamic>((msg) =>
    {
    if (this.ShouldAbandonChase((uint)this.order.data.target, (bool)this.order.data.force, InterfaceId.Attack))
    {
    this.StopMoving();
    this.FinishOrder();
    // Return to our original position
    if (this.GetStance().respondHoldGround)
    {
    this.WalkToHeldPosition();
    }
    }
    });
    stateINDIVIDUAL_COMBAT_CHASING.Handlers["MoveCompleted"] = new Action<dynamic>((msg) =>
    {
    this.SetNextState("ATTACKING");
    });
    }
    }
    {
    var stateINDIVIDUAL_GATHER = stateINDIVIDUAL.SubStates["GATHER"] = new StateNode();
    {
    var stateINDIVIDUAL_GATHER_APPROACHING = stateINDIVIDUAL_GATHER.SubStates["APPROACHING"] = new StateNode();
    stateINDIVIDUAL_GATHER_APPROACHING.Handlers["enter"] = new Action<dynamic>((msg) =>
    {
    this.SelectAnimation("move");
    });
    stateINDIVIDUAL_GATHER_APPROACHING.Handlers["MoveCompleted"] = new Action<dynamic>((msg) =>
    {
    if ((bool)msg.data.error)
    {
    // We failed to reach the target
    // Save the current order's data in case we need it later
    var oldType = this.order.data.type;
    var oldTarget = (uint)this.order.data.target;
    var oldTemplate = (string)this.order.data.template;
    // Try the next queued order if there is any
    if (this.FinishOrder())
    {
    return;
    }
    // Try to find another nearby target of the same specific type
    // Also don't switch to a different type of huntable animal
    var nearby = this.FindNearbyResource((ent, type, template) =>
    {
    return (
    ent != oldTarget
    && ((type.generic == "treasure" && oldType.generic == "treasure")
    || (type.specific == oldType.specific
    && (type.specific != "meat" || oldTemplate == template)))
    );
    });
    if (nearby != null)
    {
    this.PerformGather(nearby.Value, false, false);
    return;
    }
    // Couldn't find anything else. Just try this one again,
    // maybe we'll succeed next time
    this.PerformGather(oldTarget, false, false);
    return;
    }
    // We reached the target - start gathering from it now
    this.SetNextState("GATHERING");
    });
    }
    {
    var stateINDIVIDUAL_GATHER_WALKING = stateINDIVIDUAL_GATHER.SubStates["WALKING"] = new StateNode();
    stateINDIVIDUAL_GATHER_WALKING.Handlers["enter"] = new Action<dynamic>((msg) =>
    {
    this.SelectAnimation("move");
    });
    stateINDIVIDUAL_GATHER_WALKING.Handlers["MoveCompleted"] = new Action<dynamic>((msg) =>
    {
    var resourceType = this.order.data.type;
    var resourceTemplate = this.order.data.template;
    // Try to find another nearby target of the same specific type
    // Also don't switch to a different type of huntable animal
    var nearby = this.FindNearbyResource((ent, type, template) =>
    {
    return (
    (type.generic == "treasure" && resourceType.generic == "treasure")
    || (type.specific == resourceType.specific
    && (type.specific != "meat" || resourceTemplate == template))
    );
    });
    // If there is a nearby resource start gathering
    if (nearby != null)
    {
    this.PerformGather(nearby.Value, false, false);
    return;
    }
    // Couldn't find nearby resources, so give up
    this.FinishOrder();
    });
    }
    {
    var stateINDIVIDUAL_GATHER_GATHERING = stateINDIVIDUAL_GATHER.SubStates["GATHERING"] = new StateNode();
    stateINDIVIDUAL_GATHER_GATHERING.Handlers["enter"] = new Func<dynamic, bool>((msg) =>
    {
    var target = (uint)this.order.data.target;
    // If this order was forced, the player probably gave it, but now we've reached the target
    // switch to an unforced order (can be interrupted by attacks)
    this.order.data.force = false;
    this.order.data.autoharvest = true;
    // Calculate timing based on gather rates
    // This allows the gather rate to control how often we gather, instead of how much.
    var cmpResourceGatherer = Engine.QueryInterface(this.entity, InterfaceId.ResourceGatherer) as ICmpResourceGatherer;
    var rate = cmpResourceGatherer.GetTargetGatherRate(target);
    if (rate == 0)
    {
    // Try to find another target if the current one stopped existing
    if (Engine.QueryInterface(target, InterfaceId.Identity) == null)
    {
    // Let the Timer logic handle this
    this.StartTimer(0, null);
    return false;
    }
    // No rate, give up on gathering
    this.FinishOrder();
    return true;
    }
    // Scale timing interval based on rate, and start timer
    // The offset should be at least as long as the repeat time so we use the same value for both.
    var offset = 1000 / rate;
    var repeat = offset;
    this.StartTimer(offset, repeat);
    // We want to start the gather animation as soon as possible,
    // but only if we're actually at the target and it's still alive
    // (else it'll look like we're chopping empty air).
    // (If it's not alive, the Timer handler will deal with sending us
    // off to a different target.)
    if (this.CheckTargetRange(target, InterfaceId.ResourceGatherer))
    {
    var typename = "gather_" + this.order.data.type.specific;
    this.SelectAnimation(typename, false, 1.0f, typename);
    }
    return false;
    });
    stateINDIVIDUAL_GATHER_GATHERING.Handlers["leave"] = new Action<dynamic>((msg) =>
    {
    this.StopTimer();
    // Show the carried resource, if we've gathered anything.
    this.SetGathererAnimationOverride(false);
    });
    stateINDIVIDUAL_GATHER_GATHERING.Handlers["Timer"] = new Action<dynamic>((msg) =>
    {
    var target = this.order.data.target;
    var resourceTemplate = this.order.data.template;
    var resourceType = this.order.data.type as Name;
    // Check we can still reach and gather from the target
    if (this.CheckTargetRange(target, InterfaceId.ResourceGatherer) && this.CanGather(target))
    {
    // Gather the resources:
    var cmpResourceGatherer = Engine.QueryInterface(this.entity, InterfaceId.ResourceGatherer) as ICmpResourceGatherer;
    // Try to gather treasure
    if (cmpResourceGatherer.TryInstantGather(target))
    {
    return;
    }
    // If we've already got some resources but they're the wrong type,
    // drop them first to ensure we're only ever carrying one type
    if (cmpResourceGatherer.IsCarryingAnythingExcept(resourceType.generic))
    {
    cmpResourceGatherer.DropResources();
    }
    // Collect from the target
    var status = cmpResourceGatherer.PerformGather(target);
    // If we've collected as many resources as possible,
    // return to the nearest dropsite
    if (status.filled)
    {
    var nearby = this.FindNearestDropsite(resourceType.generic);
    if (nearby != null)
    {
    // (Keep this Gather order on the stack so we'll
    // continue gathering after returning)
    this.PushOrderFront("ReturnResource", new AllInOneData { target = nearby.Value, force = false });
    return;
    }
    // Oh no, couldn't find any drop sites. Give up on gathering.
    this.FinishOrder();
    return;
    }
    // We can gather more from this target, do so in the next timer
    if (!status.exhausted)
    {
    return;
    }
    }
    else
    {
    // Try to follow the target
    if (this.MoveToTargetRange(target, InterfaceId.ResourceGatherer))
    {
    this.SetNextState("APPROACHING");
    return;
    }
    // Can't reach the target, or it doesn't exist any more
    // We want to carry on gathering resources in the same area as
    // the old one. So try to get close to the old resource's
    // last known position
    var maxRange = 8; // get close but not too close
    if (this.order.data.lastPos != null && this.MoveToPointRange(this.order.data.lastPos.Value.X, this.order.data.lastPos.Value.Z, 0, maxRange))
    {
    this.SetNextState("APPROACHING");
    return;
    }
    }
    // We're already in range, can't get anywhere near it or the target is exhausted.
    // Give up on this order and try our next queued order
    if (this.FinishOrder())
    {
    return;
    }
    // No remaining orders - pick a useful default behaviour
    // Try to find a new resource of the same specific type near our current position:
    // Also don't switch to a different type of huntable animal
    var nearby0 = this.FindNearbyResource((ent, type, template) =>
    {
    return (
    (type.generic == "treasure" && resourceType.generic == "treasure")
    || (type.specific == resourceType.specific
    && (type.specific != "meat" || resourceTemplate == template))
    );
    });
    if (nearby0 != null)
    {
    this.PerformGather(nearby0.Value, false, false);
    return;
    }
    // Nothing else to gather - if we're carrying anything then we should
    // drop it off, and if not then we might as well head to the dropsite
    // anyway because that's a nice enough place to congregate and idle
    nearby0 = this.FindNearestDropsite(resourceType.generic);
    if (nearby0 != null)
    {
    this.PushOrderFront("ReturnResource", new AllInOneData { target = nearby0.Value, force = false });
    return;
    }
    // No dropsites - just give up
    });
    }
    }
    {
    var stateINDIVIDUAL_HEAL = stateINDIVIDUAL.SubStates["HEAL"] = new StateNode();
    {
    var stateINDIVIDUAL_HEAL_APPROACHING = stateINDIVIDUAL_HEAL.SubStates["APPROACHING"] = new StateNode();
    stateINDIVIDUAL_HEAL_APPROACHING.Handlers["enter"] = new Action<dynamic>((msg) =>
    {
    this.SelectAnimation("move");
    this.StartTimer(1000, 1000);
    });
    stateINDIVIDUAL_HEAL_APPROACHING.Handlers["leave"] = new Action<dynamic>((msg) =>
    {
    this.StopTimer();
    });
    stateINDIVIDUAL_HEAL_APPROACHING.Handlers["Timer"] = new Action<dynamic>((msg) =>
    {
    if (this.ShouldAbandonChase((uint)this.order.data.target, (bool)this.order.data.force, InterfaceId.Heal))
    {
    this.StopMoving();
    this.FinishOrder();
    // Return to our original position
    if (this.GetStance().respondHoldGround)
    {
    this.WalkToHeldPosition();
    }
    }
    });
    stateINDIVIDUAL_HEAL_APPROACHING.Handlers["MoveCompleted"] = new Action<dynamic>((msg) =>
    {
    this.SetNextState("HEALING");
    });
    }
    {
    var stateINDIVIDUAL_HEAL_HEALING = stateINDIVIDUAL_HEAL.SubStates["HEALING"] = new StateNode();
    stateINDIVIDUAL_HEAL_HEALING.Handlers["enter"] = new Action<dynamic>((msg) =>
    {
    var cmpHeal = Engine.QueryInterface(this.entity, InterfaceId.Heal) as ICmpHeal;
    this.healTimers = cmpHeal.GetTimers();
    // If the repeat time since the last heal hasn't elapsed,
    // delay the action to avoid healing too fast.
    var prepare = this.healTimers.prepare;
    if (this.lastHealed != null)
    {
    var cmpTimer = Engine.QueryInterface(InterfaceId.Timer) as ICmpTimer;
    var repeatLeft = this.lastHealed + this.healTimers.repeat - cmpTimer.GetTime();
    prepare = Math.Max(prepare, repeatLeft.Value);
    }
    this.SelectAnimation("heal", false, 1.0f, "heal");
    this.SetAnimationSync(prepare, this.healTimers.repeat);
    this.StartTimer(prepare, this.healTimers.repeat);
    // If using a non-default prepare time, re-sync the animation when the timer runs.
    this.resyncAnimation = (prepare != this.healTimers.prepare) ? true : false;
    this.FaceTowardsTarget(this.order.data.target);
    });
    stateINDIVIDUAL_HEAL_HEALING.Handlers["leave"] = new Action<dynamic>((msg) =>
    {
    this.StopTimer();
    });
    stateINDIVIDUAL_HEAL_HEALING.Handlers["Timer"] = new Action<dynamic>((msg) =>
    {
    var target = (uint)this.order.data.target;
    // Check the target is still alive and healable
    if (this.TargetIsAlive(target) && this.CanHeal(target))
    {
    // Check if we can still reach the target
    if (this.CheckTargetRange(target, InterfaceId.Heal))
    {
    var cmpTimer = Engine.QueryInterface(InterfaceId.Timer) as ICmpTimer;
    this.lastHealed = cmpTimer.GetTime() - msg.lateness;
    this.FaceTowardsTarget(target);
    var cmpHeal = Engine.QueryInterface(this.entity, InterfaceId.Heal) as ICmpHeal;
    cmpHeal.PerformHeal(target);
    if (this.resyncAnimation)
    {
    this.SetAnimationSync(this.healTimers.repeat, this.healTimers.repeat);
    this.resyncAnimation = false;
    }
    return;
    }
    // Can't reach it - try to chase after it
    if (this.ShouldChaseTargetedEntity(target, this.order.data.force))
    {
    if (this.MoveToTargetRange(target, InterfaceId.Heal))
    {
    this.SetNextState("HEAL.CHASING");
    return;
    }
    }
    }
    // Can't reach it, healed to max hp or doesn't exist any more - give up
    if (this.FinishOrder())
    {
    return;
    }
    // Heal another one
    if (this.FindNewHealTargets())
    {
    return;
    }
    // Return to our original position
    if (this.GetStance().respondHoldGround)
    {
    this.WalkToHeldPosition();
    }
    });
    }
    {
    var stateINDIVIDUAL_HEAL_CHASING = stateINDIVIDUAL_HEAL.SubStates["CHASING"] = new StateNode();
    stateINDIVIDUAL_HEAL_CHASING.Handlers["enter"] = new Action<dynamic>((msg) =>
    {
    this.SelectAnimation("move");
    this.StartTimer(1000, 1000);
    });
    stateINDIVIDUAL_HEAL_CHASING.Handlers["leave"] = new Action<dynamic>((msg) =>
    {
    this.StopTimer();
    });
    stateINDIVIDUAL_HEAL_CHASING.Handlers["Timer"] = new Action<dynamic>((msg) =>
    {
    if (this.ShouldAbandonChase((uint)this.order.data.target, (bool)this.order.data.force, InterfaceId.Heal))
    {
    this.StopMoving();
    this.FinishOrder();
    // Return to our original position
    if (this.GetStance().respondHoldGround)
    {
    this.WalkToHeldPosition();
    }
    }
    });
    stateINDIVIDUAL_HEAL_CHASING.Handlers["MoveCompleted"] = new Action<dynamic>((msg) =>
    {
    this.SetNextState("HEALING");
    });
    }
    }
    {
    var stateINDIVIDUAL_RETURNRESOURCE = stateINDIVIDUAL.SubStates["RETURNRESOURCE"] = new StateNode();
    {
    var stateINDIVIDUAL_RETURNRESOURCE_APPROACHING = stateINDIVIDUAL_RETURNRESOURCE.SubStates["APPROACHING"] = new StateNode();
    stateINDIVIDUAL_RETURNRESOURCE_APPROACHING.Handlers["enter"] = new Action<dynamic>((msg) =>
    {
    // Work out what we're carrying, in order to select an appropriate animation
    var cmpResourceGatherer = Engine.QueryInterface(this.entity, InterfaceId.ResourceGatherer) as ICmpResourceGatherer;
    var type = cmpResourceGatherer.GetLastCarriedType();
    if (type != null)
    {
    var typename = "carry_" + type.generic;
    // Special case for meat
    if (type.specific == "meat")
    {
    typename = "carry_" + type.specific;
    }
    this.SelectAnimation(typename, false, this.GetWalkSpeed());
    }
    else
    {
    // We're returning empty-handed
    this.SelectAnimation("move");
    }
    });
    stateINDIVIDUAL_RETURNRESOURCE_APPROACHING.Handlers["MoveCompleted"] = new Action<dynamic>((msg) =>
    {
    // Switch back to idle animation to guarantee we won't
    // get stuck with the carry animation after stopping moving
    this.SelectAnimation("idle");
    // Check the dropsite is in range and we can return our resource there
    // (we didn't get stopped before reaching it)
    if (this.CheckTargetRange((uint)this.order.data.target, InterfaceId.ResourceGatherer) && this.CanReturnResource((uint)this.order.data.target, true))
    {
    var cmpResourceDropsite = Engine.QueryInterface((uint)this.order.data.target, InterfaceId.ResourceDropsite) as ICmpResourceDropsite;
    if (cmpResourceDropsite != null)
    {
    // Dump any resources we can
    var dropsiteTypes = cmpResourceDropsite.GetTypes();
    var cmpResourceGatherer = Engine.QueryInterface(this.entity, InterfaceId.ResourceGatherer) as ICmpResourceGatherer;
    cmpResourceGatherer.CommitResources(dropsiteTypes);
    // Our next order should always be a Gather,
    // so just switch back to that order
    this.FinishOrder();
    return;
    }
    }
    // The dropsite was destroyed, or we couldn't reach it, or ownership changed
    // Look for a new one.
    var cmpResourceGatherer0 = Engine.QueryInterface(this.entity, InterfaceId.ResourceGatherer) as ICmpResourceGatherer;
    var genericType = cmpResourceGatherer0.GetMainCarryingType();
    var nearby = this.FindNearestDropsite(genericType);
    if (nearby != null)
    {
    this.FinishOrder();
    this.PushOrderFront("ReturnResource", new AllInOneData { target = nearby.Value, force = false });
    return;
    }
    // Oh no, couldn't find any drop sites. Give up on returning.
    this.FinishOrder();
    });
    }
    }
    {
    var stateINDIVIDUAL_TRADE = stateINDIVIDUAL.SubStates["TRADE"] = new StateNode();
    stateINDIVIDUAL_TRADE.Handlers["Attacked"] = new Action<dynamic>((msg) =>
    {
    // Ignore attack
    // TODO: Inform player
    });
    {
    var stateINDIVIDUAL_TRADE_APPROACHINGFIRSTMARKET = stateINDIVIDUAL_TRADE.SubStates["APPROACHINGFIRSTMARKET"] = new StateNode();
    stateINDIVIDUAL_TRADE_APPROACHINGFIRSTMARKET.Handlers["enter"] = new Action<dynamic>((msg) =>
    {
    this.SelectAnimation("move");
    });
    stateINDIVIDUAL_TRADE_APPROACHINGFIRSTMARKET.Handlers["MoveCompleted"] = new Action<dynamic>((msg) =>
    {
    this.PerformTradeAndMoveToNextMarket(this.order.data.firstMarket, this.order.data.secondMarket, "INDIVIDUAL.TRADE.APPROACHINGSECONDMARKET");
    });
    }
    {
    var stateINDIVIDUAL_TRADE_APPROACHINGSECONDMARKET = stateINDIVIDUAL_TRADE.SubStates["APPROACHINGSECONDMARKET"] = new StateNode();
    stateINDIVIDUAL_TRADE_APPROACHINGSECONDMARKET.Handlers["enter"] = new Action<dynamic>((msg) =>
    {
    this.SelectAnimation("move");
    });
    stateINDIVIDUAL_TRADE_APPROACHINGSECONDMARKET.Handlers["MoveCompleted"] = new Action<dynamic>((msg) =>
    {
    this.order.data.firstPass = false;
    this.PerformTradeAndMoveToNextMarket(this.order.data.secondMarket, this.order.data.firstMarket, "INDIVIDUAL.TRADE.APPROACHINGFIRSTMARKET");
    });
    }
    }
    {
    var stateINDIVIDUAL_REPAIR = stateINDIVIDUAL.SubStates["REPAIR"] = new StateNode();
    stateINDIVIDUAL_REPAIR.Handlers["ConstructionFinished"] = new Action<dynamic>((msg) =>
    {
    if (msg.data.entity != this.order.data.target)
    return; // ignore other buildings
    // Save the current order's data in case we need it later
    var oldData = this.order.data;
    // Save the current state so we can continue walking if necessary
    // FinishOrder() below will switch to IDLE if there's no order, which sets the idle animation.
    // Idle animation while moving towards finished construction looks weird (ghosty).
    var oldState = this.GetCurrentState();
    // We finished building it.
    // Switch to the next order (if any)
    if (this.FinishOrder())
    return;
    // No remaining orders - pick a useful default behaviour
    // If autocontinue explicitly disabled (e.g. by AI) then
    // do nothing automatically
    if (!oldData.autocontinue)
    return;
    // If this building was e.g. a farm of ours, the entities that recieved
    // the build command should start gathering from it
    if ((oldData.force || oldData.autoharvest) && this.CanGather(msg.data.newentity))
    {
    this.PerformGather(msg.data.newentity, true, false);
    return;
    }
    // If this building was e.g. a farmstead of ours, entities that received
    // the build command should look for nearby resources to gather
    if ((oldData.force || oldData.autoharvest) && this.CanReturnResource(msg.data.newentity, false))
    {
    var cmpResourceDropsite = Engine.QueryInterface(msg.data.newentity, InterfaceId.ResourceDropsite) as ICmpResourceDropsite;
    var types = cmpResourceDropsite.GetTypes();
    // TODO: Slightly undefined behavior here, we don't know what type of resource will be collected,
    // may cause problems for AIs (especially hunting fast animals), but avoid ugly hacks to fix that!
    var nearby = this.FindNearbyResource((ent, type, template) =>
    {
    return (types.indexOf(type.generic) != -1);
    });
    if (nearby != null)
    {
    this.PerformGather(nearby.Value, true, false);
    return;
    }
    }
    // Look for a nearby foundation to help with
    var nearbyFoundation = this.FindNearbyFoundation();
    if (nearbyFoundation != null)
    {
    this.AddOrder("Repair", new AllInOneData { target = nearbyFoundation.Value, autocontinue = (bool)oldData.autocontinue, force = false }, true);
    return;
    }
    // Unit was approaching and there's nothing to do now, so switch to walking
    if (oldState == "INDIVIDUAL.REPAIR.APPROACHING")
    {
    // We're already walking to the given point, so add this as a order.
    this.WalkToTarget(msg.data.newentity, true);
    }
    });
    {
    var stateINDIVIDUAL_REPAIR_APPROACHING = stateINDIVIDUAL_REPAIR.SubStates["APPROACHING"] = new StateNode();
    stateINDIVIDUAL_REPAIR_APPROACHING.Handlers["enter"] = new Action<dynamic>((msg) =>
    {
    this.SelectAnimation("move");
    });
    stateINDIVIDUAL_REPAIR_APPROACHING.Handlers["MoveCompleted"] = new Action<dynamic>((msg) =>
    {
    this.SetNextState("REPAIRING");
    });
    }
    {
    var stateINDIVIDUAL_REPAIR_REPAIRING = stateINDIVIDUAL_REPAIR.SubStates["REPAIRING"] = new StateNode();
    stateINDIVIDUAL_REPAIR_REPAIRING.Handlers["enter"] = new Func<dynamic, bool>((msg) =>
    {
    // If this order was forced, the player probably gave it, but now we've reached the target
    // switch to an unforced order (can be interrupted by attacks)
    if ((bool)this.order.data.force)
    {
    this.order.data.autoharvest = true;
    }
    this.order.data.force = false;
    var target = this.order.data.target;
    // Check we can still reach and repair the target
    if (!this.CheckTargetRange(target, InterfaceId.Builder) || !this.CanRepair(target))
    {
    // Can't reach it, no longer owned by ally, or it doesn't exist any more
    this.FinishOrder();
    return true;
    }
    else
    {
    var cmpFoundation = Engine.QueryInterface(target, InterfaceId.Foundation) as ICmpFoundation;
    if (cmpFoundation != null)
    {
    cmpFoundation.AddBuilder(this.entity);
    }
    }
    this.SelectAnimation("build", false, 1.0f, "build");
    this.StartTimer(1000, 1000);
    return false;
    });
    stateINDIVIDUAL_REPAIR_REPAIRING.Handlers["leave"] = new Action<dynamic>((msg) =>
    {
    this.StopTimer();
    });
    stateINDIVIDUAL_REPAIR_REPAIRING.Handlers["Timer"] = new Action<dynamic>((msg) =>
    {
    var target = (uint)this.order.data.target;
    // Check we can still reach and repair the target
    if (!this.CheckTargetRange(target, InterfaceId.Builder) || !this.CanRepair(target))
    {
    // Can't reach it, no longer owned by ally, or it doesn't exist any more
    this.FinishOrder();
    return;
    }
    var cmpBuilder = Engine.QueryInterface(this.entity, InterfaceId.Builder) as ICmpBuilder;
    cmpBuilder.PerformBuilding(target);
    });
    }
    }
    {
    var stateINDIVIDUAL_GARRISON = stateINDIVIDUAL.SubStates["GARRISON"] = new StateNode();
    {
    var stateINDIVIDUAL_GARRISON_APPROACHING = stateINDIVIDUAL_GARRISON.SubStates["APPROACHING"] = new StateNode();
    stateINDIVIDUAL_GARRISON_APPROACHING.Handlers["enter"] = new Action<dynamic>((msg) =>
    {
    this.SelectAnimation("walk", false, this.GetWalkSpeed());
    });
    stateINDIVIDUAL_GARRISON_APPROACHING.Handlers["MoveCompleted"] = new Action<dynamic>((msg) =>
    {
    this.SetNextState("GARRISONED");
    });
    stateINDIVIDUAL_GARRISON_APPROACHING.Handlers["leave"] = new Action<dynamic>((msg) =>
    {
    this.StopTimer();
    });
    }
    {
    var stateINDIVIDUAL_GARRISON_GARRISONED = stateINDIVIDUAL_GARRISON.SubStates["GARRISONED"] = new StateNode();
    stateINDIVIDUAL_GARRISON_GARRISONED.Handlers["enter"] = new Func<dynamic, bool>((msg) =>
    {
    var target = this.order.data.target;
    var cmpGarrisonHolder = Engine.QueryInterface(target, InterfaceId.GarrisonHolder) as ICmpGarrisonHolder;
    // Check that we can garrison here
    if (this.CanGarrison(target))
    {
    // Check that we're in range of the garrison target
    if (this.CheckGarrisonRange(target))
    {
    // Check that garrisoning succeeds
    if (cmpGarrisonHolder.Garrison(this.entity))
    {
    this.isGarrisoned = true;
    // Check if we are garrisoned in a dropsite
    var cmpResourceDropsite = Engine.QueryInterface(target, InterfaceId.ResourceDropsite) as ICmpResourceDropsite;
    if (cmpResourceDropsite != null)
    {
    // Dump any resources we can
    var dropsiteTypes = cmpResourceDropsite.GetTypes();
    var cmpResourceGatherer = Engine.QueryInterface(this.entity, InterfaceId.ResourceGatherer) as ICmpResourceGatherer;
    if (cmpResourceGatherer != null)
    {
    cmpResourceGatherer.CommitResources(dropsiteTypes);
    }
    }
    return false;
    }
    }
    else
    {
    // Unable to reach the target, try again
    // (or follow if it's a moving target)
    if (this.MoveToTarget(target))
    {
    this.SetNextState("APPROACHING");
    return false;
    }
    }
    }
    // Garrisoning failed for some reason, so finish the order
    this.FinishOrder();
    return true;
    });
    stateINDIVIDUAL_GARRISON_GARRISONED.Handlers["Order.Ungarrison"] = new Action<dynamic>((msg) =>
    {
    if (this.FinishOrder())
    {
    return;
    }
    });
    stateINDIVIDUAL_GARRISON_GARRISONED.Handlers["leave"] = new Action<dynamic>((msg) =>
    {
    this.isGarrisoned = false;
    });
    }
    }
    {
    var stateINDIVIDUAL_CHEERING = stateINDIVIDUAL.SubStates["CHEERING"] = new StateNode();
    stateINDIVIDUAL_CHEERING.Handlers["enter"] = new Func<dynamic, bool>((msg) =>
    {
    // Unit is invulnerable while cheering
    var cmpDamageReceiver = Engine.QueryInterface(this.entity, InterfaceId.Armour) as ICmpArmour;
    cmpDamageReceiver.SetInvulnerability(true);
    this.SelectAnimation("promotion");
    this.StartTimer(4000, 4000);
    return false;
    });
    stateINDIVIDUAL_CHEERING.Handlers["leave"] = new Action<dynamic>((msg) =>
    {
    this.StopTimer();
    var cmpDamageReceiver = Engine.QueryInterface(this.entity, InterfaceId.Armour) as ICmpArmour;
    cmpDamageReceiver.SetInvulnerability(false);
    });
    stateINDIVIDUAL_CHEERING.Handlers["Timer"] = new Action<dynamic>((msg) =>
    {
    this.FinishOrder();
    });
    }
    }
    {
    var stateANIMAL = stateROOT.SubStates["ANIMAL"] = new StateNode();
    stateANIMAL.Handlers["Attacked"] = new Action<dynamic>((msg) =>
    {
    if (this.template["NaturalBehaviour"].ToString() == "skittish" ||
    this.template["NaturalBehaviour"].ToString() == "passive")
    {
    this.Flee((uint)msg.data.attacker, false);
    }
    else if (this.IsDangerousAnimal() || this.template["NaturalBehaviour"].ToString() == "defensive")
    {
    if (this.CanAttack((uint)msg.data.attacker, false))
    {
    this.Attack((uint)msg.data.attacker, false);
    }
    }
    else if (this.template["NaturalBehaviour"].ToString() == "domestic")
    {
    // Never flee, stop what we were doing
    this.SetNextState("IDLE");
    }
    });
    stateANIMAL.Handlers["Order.LeaveFoundation"] = new Action<dynamic>((msg) =>
    {
    // Run away from the foundation
    this.MoveToTargetRangeExplicit(msg.data.target, this.template["FleeDistance"].ToFloat(), this.template["FleeDistance"].ToFloat());
    this.SetNextState("FLEEING");
    });
    {
    var stateANIMAL_IDLE = stateANIMAL.SubStates["IDLE"] = new StateNode();
    }
    {
    var stateANIMAL_ROAMING = stateANIMAL.SubStates["ROAMING"] = new StateNode();
    }
    {
    var stateANIMAL_FEEDING = stateANIMAL.SubStates["FEEDING"] = new StateNode();
    }
    {
    var stateANIMAL_FLEEING = stateANIMAL.SubStates["FLEEING"] = new StateNode();
    }
    {
    var stateANIMAL_COMBAT = stateANIMAL.SubStates["COMBAT"] = new StateNode();
    }
    {
    var stateANIMAL_WALKING = stateANIMAL.SubStates["WALKING"] = new StateNode();
    }
    }

    UnitFsm = new FSM(stateROOT);
    }
    public override void Init(CParamNode paramNode)
    {
    this.orderQueue = new List<OrderItem>(); // current order is at the front of the list
    this.order = null; // always == this.orderQueue[0]
    this.formationController = CComponentManager.INVALID_ENTITY; // entity with IID_Formation that we belong to
    this.isGarrisoned = false;
    this.isIdle = false;
    this.lastFormationName = "";
    // For preventing increased action rate due to Stop orders or target death.
    this.lastAttacked = null;
    this.lastHealed = null;
    this.SetStance(this.template["DefaultStance"].ToString());
    }
    public override void HandleMessage(CMessage msg, bool global)
    {
    //OnVisionRangeChanged(CMessage msg);
    switch (msg.GetTypeID())
    {
    case MessageTypeId.Create:
    OnCreate();
    break;
    case MessageTypeId.Destroy:
    OnDestroy();
    break;
    case MessageTypeId.OwnershipChanged:
    OnOwnershipChanged(msg as CMessageOwnershipChanged);
    break;
    case MessageTypeId.DiplomacyChanged:
    OnDiplomacyChanged(msg as CMessageDiplomacyChanged);
    break;
    case MessageTypeId.MotionChanged:
    OnMotionChanged(msg as CMessageMotionChanged);
    break;
    case MessageTypeId.ConstructionFinished:
    OnGlobalConstructionFinished(msg as CMessageConstructionFinished);
    break;
    case MessageTypeId.EntityRenamed:
    OnGlobalEntityRenamed(msg as CMessageEntityRenamed);
    break;
    case MessageTypeId.Attacked:
    OnAttacked(msg as CMessageAttacked);
    break;
    case MessageTypeId.HealthChanged:
    OnHealthChanged(msg as CMessageHealthChanged);
    break;
    case MessageTypeId.RangeUpdate:
    OnRangeUpdate(msg as CMessageRangeUpdate);
    break;
    }
    }
    public override bool IsFormationController()
    {
    return string.Equals(this.template["FormationController"], "true", StringComparison.OrdinalIgnoreCase);
    }
    public override bool IsAnimal()
    {
    return (this.template["NaturalBehaviour"].IsOk() ? true : false);
    }
    public override bool IsDangerousAnimal()
    {
    return (this.IsAnimal() && (this.template["NaturalBehaviour"].ToString().ToLower() == "violent" || this.template["NaturalBehaviour"].ToString().ToLower() == "aggressive"));
    }
    public override bool IsDomestic()
    {
    var cmpIdentity = Engine.QueryInterface(this.entity, InterfaceId.Identity) as ICmpIdentity;
    if (cmpIdentity == null)
    {
    return false;
    }
    return cmpIdentity.HasClass("Domestic");
    }
    public override bool IsHealer()
    {
    return Engine.QueryInterface(this.entity, InterfaceId.Heal) != null;
    }
    public override bool IsIdle()
    {
    return this.isIdle;
    }
    public override bool IsGarrisoned()
    {
    return this.isGarrisoned;
    }
    public override bool IsWalking()
    {
    var state = this.GetCurrentState().Split(new[] { "." }, StringSplitOptions.RemoveEmptyEntries).LastOrDefault();
    return (state == "WALKING");
    }
    public override bool CanAttackGaia()
    {
    var cmpAttack = Engine.QueryInterface(this.entity, InterfaceId.Attack);
    if (cmpAttack == null)
    {
    return false;
    }
    // Rejects Gaia (0) and INVALID_PLAYER (-1)
    var cmpOwnership = Engine.QueryInterface(this.entity, InterfaceId.Ownership) as ICmpOwnership;
    if (cmpOwnership == null || cmpOwnership.GetOwner() <= 0)
    {
    return false;
    }
    return true;
    }
    void OnCreate()
    {
    if (this.IsAnimal())
    {
    UnitFsm.Init(this, "ANIMAL.FEEDING");
    }
    else if (this.IsFormationController())
    {
    UnitFsm.Init(this, "FORMATIONCONTROLLER.IDLE");
    }
    else
    {
    UnitFsm.Init(this, "INDIVIDUAL.IDLE");
    }
    }
    void OnDiplomacyChanged(CMessageDiplomacyChanged msg)
    {
    var cmpOwnership = Engine.QueryInterface(this.entity, InterfaceId.Ownership) as ICmpOwnership;
    if (cmpOwnership != null && cmpOwnership.GetOwner() == msg.player)
    {
    this.SetupRangeQuery();
    }
    }
    void OnOwnershipChanged(CMessageOwnershipChanged msg)
    {
    this.SetupRangeQueries();
    // If the unit isn't being created or dying, clear orders and reset stance.
    if (msg.to != -1 && msg.from != -1)
    {
    this.SetStance(this.template["DefaultStance"].ToString());
    this.Stop(false);
    }
    }
    void OnDestroy()
    {
    // Clean up any timers that are now obsolete
    this.StopTimer();
    // Clean up range queries
    var rangeMan = Engine.QueryInterface(InterfaceId.RangeManager) as ICmpRangeManager;
    if (this.losRangeQuery != null)
    {
    rangeMan.DestroyActiveQuery(this.losRangeQuery.Value);
    }
    if (this.losHealRangeQuery != null)
    {
    rangeMan.DestroyActiveQuery(this.losHealRangeQuery.Value);
    }
    if (this.losGaiaRangeQuery != null)
    {
    rangeMan.DestroyActiveQuery(this.losGaiaRangeQuery.Value);
    }
    }
    void OnVisionRangeChanged(CMessage msg)
    {
    // Update range queries
    //if (this.entity == msg.entity)
    //{
    // this.SetupRangeQueries();
    //}
    }
    // Wrapper function that sets up the normal, healer, and Gaia range queries.
    public override void SetupRangeQueries()
    {
    this.SetupRangeQuery();
    if (this.IsHealer())
    this.SetupHealRangeQuery();
    if (this.CanAttackGaia() || this.losGaiaRangeQuery != null)
    this.SetupGaiaRangeQuery();
    }
    // Set up a range query for all enemy units within LOS range
    // which can be attacked.
    // This should be called whenever our ownership changes.
    public override void SetupRangeQuery()
    {
    var cmpOwnership = Engine.QueryInterface(this.entity, InterfaceId.Ownership) as ICmpOwnership;
    var owner = cmpOwnership.GetOwner();
    var rangeMan = Engine.QueryInterface(InterfaceId.RangeManager) as ICmpRangeManager;
    var playerMan = Engine.QueryInterface(InterfaceId.PlayerManager) as ICmpPlayerManager;
    if (this.losRangeQuery != null)
    {
    rangeMan.DestroyActiveQuery(this.losRangeQuery.Value);
    this.losRangeQuery = null;
    }
    var players = new List<int>();
    if (owner != -1)
    {
    // If unit not just killed, get enemy players via diplomacy
    var cmpPlayer = Engine.QueryInterface(playerMan.GetPlayerByID(owner), InterfaceId.Player) as ICmpPlayer;
    var numPlayers = playerMan.GetNumPlayers();
    for (var i = 1; i < numPlayers; ++i)
    {
    // Exclude gaia, allies, and self
    // TODO: How to handle neutral players - Special query to attack military only?
    if (cmpPlayer.IsEnemy(i))
    players.Add(i);
    }
    }
    var range = this.GetQueryRange(InterfaceId.Attack);
    this.losRangeQuery = rangeMan.CreateActiveQuery(this.entity, range.Min, range.Max, players.ToArray(), InterfaceId.Armour, rangeMan.GetEntityFlagMask("normal"));
    rangeMan.EnableActiveQuery(this.losRangeQuery.Value);
    }
    // Set up a range query for all own or ally units within LOS range
    // which can be healed.
    // This should be called whenever our ownership changes.
    public override void SetupHealRangeQuery()
    {
    var cmpOwnership = Engine.QueryInterface(this.entity, InterfaceId.Ownership) as ICmpOwnership;
    var owner = cmpOwnership.GetOwner();
    var rangeMan = Engine.QueryInterface(InterfaceId.RangeManager) as ICmpRangeManager;
    var playerMan = Engine.QueryInterface(InterfaceId.PlayerManager) as ICmpPlayerManager;
    if (this.losHealRangeQuery != null)
    {
    rangeMan.DestroyActiveQuery(this.losHealRangeQuery.Value);
    }
    var players = new List<int>();
    players.Add(owner);
    if (owner != -1)
    {
    // If unit not just killed, get ally players via diplomacy
    var cmpPlayer = Engine.QueryInterface(playerMan.GetPlayerByID(owner), InterfaceId.Player) as ICmpPlayer;
    var numPlayers = playerMan.GetNumPlayers();
    for (var i = 1; i < numPlayers; ++i)
    {
    // Exclude gaia and enemies
    if (cmpPlayer.IsAlly(i))
    {
    players.Add(i);
    }
    }
    }
    var range = this.GetQueryRange(InterfaceId.Heal);
    this.losHealRangeQuery = rangeMan.CreateActiveQuery(this.entity, range.Min, range.Max, players.ToArray(), InterfaceId.Health, rangeMan.GetEntityFlagMask("injured"));
    rangeMan.EnableActiveQuery(this.losHealRangeQuery.Value);
    }
    // Set up a range query for Gaia units within LOS range which can be attacked.
    // This should be called whenever our ownership changes.
    public override void SetupGaiaRangeQuery()
    {
    var cmpOwnership = Engine.QueryInterface(this.entity, InterfaceId.Ownership) as ICmpOwnership;
    var owner = cmpOwnership.GetOwner();
    var rangeMan = Engine.QueryInterface(InterfaceId.RangeManager) as ICmpRangeManager;
    var playerMan = Engine.QueryInterface(InterfaceId.PlayerManager) as ICmpPlayerManager;
    if (this.losGaiaRangeQuery != null)
    {
    rangeMan.DestroyActiveQuery(this.losGaiaRangeQuery.Value);
    this.losGaiaRangeQuery = null;
    }
    // Only create the query if Gaia is our enemy and we can attack.
    if (this.CanAttackGaia())
    {
    var range = this.GetQueryRange(InterfaceId.Attack);
    // This query is only interested in Gaia entities that can attack.
    this.losGaiaRangeQuery = rangeMan.CreateActiveQuery(this.entity, range.Min, range.Max, new[] { 0 }, InterfaceId.Attack, rangeMan.GetEntityFlagMask("normal"));
    rangeMan.EnableActiveQuery(this.losGaiaRangeQuery.Value);
    }
    }
    public override void SetNextState(string state)
    {
    UnitFsm.SetNextState(this, state);
    }
    public override void SetNextStateAlwaysEntering(string state)
    {
    UnitFsm.SetNextStateAlwaysEntering(this, state);
    }
    public override void DeferMessage(OrderItem msg)
    {
    UnitFsm.DeferMessage(this, msg);
    }
    public override string GetCurrentState()
    {
    return UnitFsm.GetCurrentState(this);
    }
    public override void FsmStateNameChanged(string state)
    {
    Engine.PostMessage(this.entity, new CMessageUnitAIStateChanged { to = state });
    }
    public override bool FinishOrder()
    {
    if (this.orderQueue.Count == 0)
    {
    var cmpTemplateManager = Engine.QueryInterface(InterfaceId.TemplateManager) as ICmpTemplateManager;
    var template = cmpTemplateManager.GetCurrentTemplateName(this.entity);
    throw new Exception("FinishOrder called for entity " + this.entity + " (" + template + ") when order queue is empty");
    }
    this.orderQueue.shift();
    if (this.orderQueue.Count > 0)
    {
    this.order = this.orderQueue[0];
    }
    else
    {
    this.order = null;
    }
    if (this.orderQueue.Count != 0)
    {
    var msg = new ZeroAD.Components.CCmpUnitAI.OrderItem { type = "Order." + this.order.type, data = this.order.data };
    var ret = UnitFsm.ProcessMessage(this, msg);

    Engine.PostMessage(this.entity, new CMessageUnitAIOrderDataChanged { to = this.GetOrderData() });
    // If the order was rejected then immediately take it off
    // and process the remaining queue
    if (ret != null && ret is bool && (bool)ret)
    {
    return this.FinishOrder();
    }
    // Otherwise we've successfully processed a new order
    return true;
    }
    else
    {
    this.SetNextState("IDLE");
    return false;
    }
    }
    public override void PushOrder(string type, AllInOneData data)
    {
    var order = new OrderItem { type = type, data = data };
    this.orderQueue.Add(order);
    // If we didn't already have an order, then process this new one
    if (this.orderQueue.Count == 1)
    {
    this.order = order;
    var ret = UnitFsm.ProcessMessage(this, new OrderItem { type = "Order." + this.order.type, data = this.order.data });
    Engine.PostMessage(this.entity, new CMessageUnitAIOrderDataChanged { to = this.GetOrderData() });
    // If the order was rejected then immediately take it off
    // and process the remaining queue
    if (ret != null && ret is bool && (bool)ret)
    {
    this.FinishOrder();
    }
    }
    }
    public override void PushOrderFront(string type, AllInOneData data)
    {
    var order = new OrderItem { type = type, data = data };
    // If current order is cheering then add new order after it
    if (this.order != null && this.order.type == "Cheering")
    {
    var cheeringOrder = this.orderQueue.shift();
    this.orderQueue.unshift(cheeringOrder, order);
    }
    else
    {
    this.orderQueue.unshift(order);
    this.order = order;
    var ret = UnitFsm.ProcessMessage(this, new OrderItem { type = "Order." + this.order.type, data = this.order.data });
    Engine.PostMessage(this.entity, new CMessageUnitAIOrderDataChanged { to = this.GetOrderData() });
    // If the order was rejected then immediately take it off again;
    // assume the previous active order is still valid (the short-lived
    // new order hasn't changed state or anything) so we can carry on
    // as if nothing had happened
    if (ret != null && ret is bool && (bool)ret)
    {
    this.orderQueue.shift();
    this.order = this.orderQueue[0];
    }
    }
    }
    public override void ReplaceOrder(string type, AllInOneData data)
    {
    // If current order is cheering then add new order after it
    if (this.order != null && this.order.type == "Cheering")
    {
    var order = new OrderItem
    {
    type = type,
    data = data
    };
    var cheeringOrder = this.orderQueue.shift();
    this.orderQueue = new List<OrderItem>(new[] { cheeringOrder, order });
    }
    else
    {
    this.orderQueue = new List<OrderItem>();
    this.PushOrder(type, data);
    }
    }
    public override OrderItem[] GetOrders()
    {
    return orderQueue.ToArray();
    }
    public override void AddOrders(IEnumerable<OrderItem> orders)
    {
    foreach (var order in orders)
    {
    this.PushOrder(order.type, order.data);
    }
    }
    public override List<dynamic> GetOrderData()
    {
    var orders = new List<dynamic>();
    foreach (var i in this.orderQueue)
    {
    if (!object.ReferenceEquals(i.data, null))
    {
    orders.Add(i.data);
    }
    }
    return orders;
    }
    public override void TimerHandler(dynamic data, int lateness)
    {
    // Reset the timer
    if (data.timerRepeat == null)
    {
    this.timer = null;
    }
    UnitFsm.ProcessMessage(this, new OrderItem { type = "Timer", data = data, lateness = lateness });
    }
    public override void StartTimer(float offset, float? repeat)
    {
    if (this.timer.HasValue)
    {
    // error("Called StartTimer when there's already an active timer");
    }
    var data = new { timerRepeat = repeat };
    var cmpTimer = Engine.QueryInterface(InterfaceId.Timer) as ICmpTimer;
    if (repeat == null)
    {
    this.timer = cmpTimer.SetTimeout(this.entity, InterfaceId.UnitAI, () => TimerHandler(data, 0), offset);
    }
    else
    {
    this.timer = cmpTimer.SetInterval(this.entity, InterfaceId.UnitAI, () => TimerHandler(data, 0), offset, repeat.Value);
    }
    }
    public override void StopTimer()
    {
    if (this.timer == null)
    {
    return;
    }
    var cmpTimer = Engine.QueryInterface(InterfaceId.Timer) as ICmpTimer;
    cmpTimer.CancelTimer(this.timer.Value);
    this.timer = null;
    }
    void OnMotionChanged(CMessageMotionChanged msg)
    {
    if (msg.starting && !msg.error)
    {
    UnitFsm.ProcessMessage(this, new OrderItem { type = "MoveStarted", data = msg });
    }
    else if (!msg.starting || msg.error)
    {
    UnitFsm.ProcessMessage(this, new OrderItem { type = "MoveCompleted", data = msg });
    }
    }
    void OnGlobalConstructionFinished(CMessageConstructionFinished msg)
    {
    // TODO: This is a bit inefficient since every unit listens to every
    // construction message - ideally we could scope it to only the one we're building
    UnitFsm.ProcessMessage(this, new OrderItem { type = "ConstructionFinished", data = msg });
    }
    void OnGlobalEntityRenamed(CMessageEntityRenamed msg)
    {
    UnitFsm.ProcessMessage(this, new OrderItem{ type = "EntityRenamed", entity = msg.entity, newentity = msg.newentity });
    }
    void OnAttacked(CMessageAttacked msg)
    {
    UnitFsm.ProcessMessage(this, new OrderItem { type = "Attacked", data = msg });
    }
    void OnHealthChanged(CMessageHealthChanged msg)
    {
    UnitFsm.ProcessMessage(this, new OrderItem { type = "HealthChanged", from = msg.from, to = msg.to });
    }
    void OnRangeUpdate(CMessageRangeUpdate msg)
    {
    if (msg.tag == this.losRangeQuery)
    {
    UnitFsm.ProcessMessage(this, new OrderItem { type = "LosRangeUpdate", data = msg });
    }
    else if (msg.tag == this.losGaiaRangeQuery)
    {
    UnitFsm.ProcessMessage(this, new OrderItem { type = "LosGaiaRangeUpdate", data = msg });
    }
    else if (msg.tag == this.losHealRangeQuery)
    {
    UnitFsm.ProcessMessage(this, new OrderItem { type = "LosHealRangeUpdate", data = msg });
    }
    }
    public override float GetWalkSpeed()
    {
    var cmpUnitMotion = Engine.QueryInterface(this.entity, InterfaceId.UnitMotion) as ICmpUnitMotion;
    return cmpUnitMotion.GetWalkSpeed();
    }
    public override float GetRunSpeed()
    {
    var cmpUnitMotion = Engine.QueryInterface(this.entity, InterfaceId.UnitMotion) as ICmpUnitMotion;
    return cmpUnitMotion.GetRunSpeed();
    }
    public override bool TargetIsAlive(uint ent)
    {
    var cmpHealth = Engine.QueryInterface(ent, InterfaceId.Health) as ICmpHealth;
    if (cmpHealth == null)
    {
    return false;
    }
    return (cmpHealth.GetHitpoints() != 0);
    }
    public override bool MustKillGatherTarget(uint ent)
    {
    var cmpResourceSupply = Engine.QueryInterface(ent, InterfaceId.ResourceSupply) as ICmpResourceSupply;
    if (cmpResourceSupply == null)
    {
    return false;
    }
    if (!cmpResourceSupply.GetKillBeforeGather())
    {
    return false;
    }
    return this.TargetIsAlive(ent);
    }
    public override uint? FindNearbyResource(Func<uint, Name, string, bool> filter)
    {
    var range = 64; // TODO: what's a sensible number?
    var playerMan = Engine.QueryInterface(InterfaceId.PlayerManager) as ICmpPlayerManager;
    // We accept resources owned by Gaia or any player
    var players = new List<int>(0);
    players.Add(0);
    for (var i = 1; i < playerMan.GetNumPlayers(); ++i)
    {
    players.Add(i);
    }
    var cmpTemplateManager = Engine.QueryInterface(InterfaceId.TemplateManager) as ICmpTemplateManager;
    var cmpRangeManager = Engine.QueryInterface(InterfaceId.RangeManager) as ICmpRangeManager;
    var nearby = cmpRangeManager.ExecuteQuery(this.entity, 0, range, players, InterfaceId.ResourceSupply);
    foreach (var ent in nearby)
    {
    var cmpResourceSupply = Engine.QueryInterface(ent, InterfaceId.ResourceSupply) as ICmpResourceSupply;
    var type = cmpResourceSupply.GetType();
    var amount = cmpResourceSupply.GetCurrentAmount();
    var template = cmpTemplateManager.GetCurrentTemplateName(ent);
    // Remove "resource|" prefix from template names, if present.
    if (template.IndexOf("resource|") != -1)
    {
    template = template.Substring(9);
    }
    if (amount > 0 && filter(ent, type, template))
    {
    return ent;
    }
    }
    return null;
    }
    public override uint? FindNearestDropsite(string genericType)
    {
    // Find dropsites owned by this unit's player
    var players = new List<int>();
    var cmpOwnership = Engine.QueryInterface(this.entity, InterfaceId.Ownership) as ICmpOwnership;
    if (cmpOwnership != null)
    {
    players.Add(cmpOwnership.GetOwner());
    }
    // Ships are unable to reach land dropsites and shouldn't attempt to do so.
    var excludeLand = (Engine.QueryInterface(this.entity, InterfaceId.Identity) as ICmpIdentity).HasClass("Ship");
    var rangeMan = Engine.QueryInterface(InterfaceId.RangeManager) as ICmpRangeManager;
    var nearby = rangeMan.ExecuteQuery(this.entity, 0, -1, players, InterfaceId.ResourceDropsite);
    if (excludeLand)
    {
    nearby = nearby.Where((e) =>
    {
    var iid = Engine.QueryInterface(e, InterfaceId.Identity) as ICmpIdentity;
    return iid != null && iid.HasClass("Naval");
    }).ToList();
    }
    foreach (var ent in nearby)
    {
    var cmpDropsite = Engine.QueryInterface(ent, InterfaceId.ResourceDropsite) as ICmpResourceDropsite;
    if (!cmpDropsite.AcceptsType(genericType))
    continue;
    return ent;
    }
    return null;
    }
    public override uint? FindNearbyFoundation()
    {
    var range = 64; // TODO: what's a sensible number?
    // Find buildings owned by this unit's player
    var players = new List<int>();
    var cmpOwnership = Engine.QueryInterface(this.entity, InterfaceId.Ownership) as ICmpOwnership;
    if (cmpOwnership != null)
    {
    players.Add(cmpOwnership.GetOwner());
    }
    var rangeMan = Engine.QueryInterface(InterfaceId.RangeManager) as ICmpRangeManager;
    var nearby = rangeMan.ExecuteQuery(this.entity, 0, range, players, InterfaceId.Foundation);
    foreach (var ent in nearby)
    {
    // Skip foundations that are already complete. (This matters since
    // we process the ConstructionFinished message before the foundation
    // we're working on has been deleted.)
    var cmpFoundation = Engine.QueryInterface(ent, InterfaceId.Foundation) as ICmpFoundation;
    if (cmpFoundation.IsFinished())
    {
    continue;
    }
    return ent;
    }
    return null;
    }
    public override void PlaySound(string name)
    {
    // If we're a formation controller, use the sounds from our first member
    if (this.IsFormationController())
    {
    var cmpFormation = Engine.QueryInterface(this.entity, InterfaceId.Formation) as ICmpFormation;
    var member = cmpFormation.GetPrimaryMember();
    if (member != 0)
    {
    SoundHelper.PlaySound(Engine, name, member);
    }
    }
    else
    {
    // Otherwise use our own sounds
    SoundHelper.PlaySound(Engine, name, this.entity);
    }
    }
    public override void SetGathererAnimationOverride(bool disable)
    {
    var cmpResourceGatherer = Engine.QueryInterface(this.entity, InterfaceId.ResourceGatherer) as ICmpResourceGatherer;
    if (cmpResourceGatherer == null)
    return;
    var cmpVisual = Engine.QueryInterface(this.entity, InterfaceId.Visual) as ICmpVisual;
    if (cmpVisual == null)
    return;
    // Remove the animation override, so that weapons are shown again.
    if (disable)
    {
    cmpVisual.ResetMoveAnimation("walk");
    return;
    }
    // Work out what we're carrying, in order to select an appropriate animation
    var type = cmpResourceGatherer.GetLastCarriedType();
    if (type != null)
    {
    var typename = "carry_" + type.generic;
    // Special case for meat
    if (type.specific == "meat")
    {
    typename = "carry_" + type.specific;
    }
    cmpVisual.ReplaceMoveAnimation("walk", typename);
    }
    else
    {
    cmpVisual.ResetMoveAnimation("walk");
    }
    }
    public override void SelectAnimation(string name, bool once = false, float speed = 1.0f, string sound = null)
    {
    var cmpVisual = Engine.QueryInterface(this.entity, InterfaceId.Visual) as ICmpVisual;
    if (cmpVisual == null)
    {
    return;
    }
    // Special case: the "move" animation gets turned into a special
    // movement mode that deals with speeds and walk/run automatically
    if (name == "move")
    {
    // Speed to switch from walking to running animations
    var runThreshold = (this.GetWalkSpeed() + this.GetRunSpeed()) / 2;
    cmpVisual.SelectMovementAnimation(runThreshold);
    return;
    }
    string soundgroup = null;
    if (!string.IsNullOrEmpty(sound))
    {
    var cmpSound = Engine.QueryInterface(this.entity, InterfaceId.Sound) as ICmpSound;
    if (cmpSound != null)
    {
    soundgroup = cmpSound.GetSoundGroup(sound);
    }
    }
    // Set default values if unspecified
    if (soundgroup == null)
    {
    soundgroup = "";
    }
    cmpVisual.SelectAnimation(name, once, speed, soundgroup);
    }
    public override void SetAnimationSync(int actiontime, int repeattime)
    {
    var cmpVisual = Engine.QueryInterface(this.entity, InterfaceId.Visual) as ICmpVisual;
    if (cmpVisual == null)
    {
    return;
    }
    cmpVisual.SetAnimationSyncRepeat(repeattime);
    cmpVisual.SetAnimationSyncOffset(actiontime);
    }
    public override void StopMoving()
    {
    var cmpUnitMotion = Engine.QueryInterface(this.entity, InterfaceId.UnitMotion) as ICmpUnitMotion;
    cmpUnitMotion.StopMoving();
    }
    public override bool MoveToPoint(float x, float z)
    {
    var cmpUnitMotion = Engine.QueryInterface(this.entity, InterfaceId.UnitMotion) as ICmpUnitMotion;
    return cmpUnitMotion.MoveToPointRange(x, z, 0, 0);
    }
    public override bool MoveToPointRange(float x, float z, float rangeMin, float rangeMax)
    {
    var cmpUnitMotion = Engine.QueryInterface(this.entity, InterfaceId.UnitMotion) as ICmpUnitMotion;
    return cmpUnitMotion.MoveToPointRange(x, z, rangeMin, rangeMax);
    }
    public override bool MoveToTarget(uint target)
    {
    if (!this.CheckTargetVisible(target))
    {
    return false;
    }
    var cmpUnitMotion = Engine.QueryInterface(this.entity, InterfaceId.UnitMotion) as ICmpUnitMotion;
    return cmpUnitMotion.MoveToTargetRange(target, 0, 0);
    }
    public override bool MoveToTargetRange(uint target, InterfaceId iid, string type = null)
    {
    if (!this.CheckTargetVisible(target))
    {
    return false;
    }
    var cmpRanged = Engine.QueryInterface(this.entity, iid) as IRangedComponent;
    var range = cmpRanged.GetRange(type);
    var cmpUnitMotion = Engine.QueryInterface(this.entity, InterfaceId.UnitMotion) as ICmpUnitMotion;
    return cmpUnitMotion.MoveToTargetRange(target, range.Min, range.Max);
    }
    public override bool MoveToTargetRangeExplicit(uint target, float min, float max)
    {
    if (!this.CheckTargetVisible(target))
    {
    return false;
    }
    var cmpUnitMotion = Engine.QueryInterface(this.entity, InterfaceId.UnitMotion) as ICmpUnitMotion;
    return cmpUnitMotion.MoveToTargetRange(target, min, max);
    }
    public override bool CheckTargetRange(uint target, InterfaceId iid, string type = null)
    {
    var cmpRanged = Engine.QueryInterface(this.entity, iid) as IRangedComponent;
    var range = cmpRanged.GetRange(type);
    var cmpUnitMotion = Engine.QueryInterface(this.entity, InterfaceId.UnitMotion) as ICmpUnitMotion;
    return cmpUnitMotion.IsInTargetRange(target, range.Min, range.Max);
    }
    public override bool CheckTargetRangeExplicit(uint target, float min, float max)
    {
    var cmpUnitMotion = Engine.QueryInterface(this.entity, InterfaceId.UnitMotion) as ICmpUnitMotion;
    return cmpUnitMotion.IsInTargetRange(target, min, max);
    }
    public override bool CheckGarrisonRange(uint target)
    {
    var cmpGarrisonHolder = Engine.QueryInterface(target, InterfaceId.GarrisonHolder) as ICmpGarrisonHolder;
    var range = cmpGarrisonHolder.GetLoadingRange();
    var cmpUnitMotion = Engine.QueryInterface(this.entity, InterfaceId.UnitMotion) as ICmpUnitMotion;
    return cmpUnitMotion.IsInTargetRange(target, range.Min, range.Max);
    }
    public override bool CheckTargetVisible(uint target)
    {
    var cmpOwnership = Engine.QueryInterface(this.entity, InterfaceId.Ownership) as ICmpOwnership;
    if (cmpOwnership == null)
    {
    return false;
    }
    var cmpRangeManager = Engine.QueryInterface(InterfaceId.RangeManager) as ICmpRangeManager;
    if (cmpRangeManager == null)
    {
    return false;
    }
    if (cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner(), false) == ICmpRangeManager.ELosVisibility.VIS_HIDDEN)
    {
    return false;
    }
    // Either visible directly, or visible in fog
    return true;
    }
    public override void FaceTowardsTarget(uint target)
    {
    var cmpPosition = Engine.QueryInterface(this.entity, InterfaceId.Position) as ICmpPosition;
    if (cmpPosition == null || !cmpPosition.IsInWorld())
    {
    return;
    }
    var cmpTargetPosition = Engine.QueryInterface(target, InterfaceId.Position) as ICmpPosition;
    if (cmpTargetPosition == null || !cmpTargetPosition.IsInWorld())
    {
    return;
    }
    var pos = cmpPosition.GetPosition();
    var targetpos = cmpTargetPosition.GetPosition();
    var angle = Math.Atan2(targetpos.X - pos.X, targetpos.Z - pos.Z);
    var rot = cmpPosition.GetRotation();
    var delta = (rot.Y - angle + Math.PI) % (2 * Math.PI) - Math.PI;
    if (Math.Abs(delta) > 0.2)
    {
    var cmpUnitMotion = Engine.QueryInterface(this.entity, InterfaceId.UnitMotion) as ICmpUnitMotion;
    if (cmpUnitMotion != null)
    {
    cmpUnitMotion.FaceTowardsPoint(targetpos.X, targetpos.Z);
    }
    }
    }
    public override bool CheckTargetDistanceFromHeldPosition(uint target, InterfaceId iid, string type)
    {
    var cmpRanged = Engine.QueryInterface(this.entity, iid) as IRangedComponent;
    //var range = iid != InterfaceId.Attack ? cmpRanged.GetRange() : cmpRanged.GetRange(type);
    var range = cmpRanged.GetRange(type);
    var cmpPosition = Engine.QueryInterface(target, InterfaceId.Position) as ICmpPosition;
    if (cmpPosition == null || !cmpPosition.IsInWorld())
    {
    return false;
    }
    var cmpVision = Engine.QueryInterface(this.entity, InterfaceId.Vision) as ICmpVision;
    if (cmpVision == null)
    {
    return false;
    }
    var halfvision = cmpVision.GetRange() / 2;
    var pos = cmpPosition.GetPosition();
    var dx = this.heldPosition.Value.X - pos.X;
    var dz = this.heldPosition.Value.Z - pos.Z;
    var dist = Math.Sqrt(dx * dx + dz * dz);
    return dist < halfvision + range.Max;
    }
    public override bool CheckTargetIsInVisionRange(uint target)
    {
    var cmpVision = Engine.QueryInterface(this.entity, InterfaceId.Vision) as ICmpVision;
    if (cmpVision == null)
    {
    return false;
    }
    var range = cmpVision.GetRange();
    var distance = EntityHelper.DistanceBetweenEntities(Engine, this.entity, target);
    return distance < range;
    }
    public override string GetBestAttack()
    {
    var cmpAttack = Engine.QueryInterface(this.entity, InterfaceId.Attack) as ICmpAttack;
    if (cmpAttack == null)
    {
    return null;
    }
    return cmpAttack.GetBestAttack();
    }
    public override string GetBestAttackAgainst(uint target)
    {
    var cmpAttack = Engine.QueryInterface(this.entity, InterfaceId.Attack) as ICmpAttack;
    if (cmpAttack == null)
    {
    return null;
    }
    return cmpAttack.GetBestAttackAgainst(target);
    }
    public override bool AttackVisibleEntity(IEnumerable<uint> ents, bool forceResponse)
    {
    foreach (var target in ents)
    {
    if (this.CanAttack(target, forceResponse))
    {
    this.PushOrderFront("Attack", new AllInOneData { target = target, force = false, forceResponse = forceResponse });
    return true;
    }
    }
    return false;
    }
    public override bool AttackEntityInZone(IEnumerable<uint> ents, bool forceResponse)
    {
    foreach (var target in ents)
    {
    var type = this.GetBestAttackAgainst(target);
    if (this.CanAttack(target, forceResponse) && this.CheckTargetDistanceFromHeldPosition(target, InterfaceId.Attack, type))
    {
    this.PushOrderFront("Attack", new AllInOneData { target = target, force = false, forceResponse = forceResponse });
    return true;
    }
    }
    return false;
    }
    public override bool RespondToTargetedEntities(uint[] ents)
    {
    if (ents.Length == 0)
    {
    return false;
    }
    if (this.GetStance().respondChase)
    return this.AttackVisibleEntity(ents, true);
    if (this.GetStance().respondStandGround)
    return this.AttackVisibleEntity(ents, true);
    if (this.GetStance().respondHoldGround)
    return this.AttackEntityInZone(ents, true);
    if (this.GetStance().respondFlee)
    {
    this.PushOrderFront("Flee", new AllInOneData { target = ents[0], force = false });
    return true;
    }
    return false;
    }
    public override bool RespondToHealableEntities(uint[] ents)
    {
    if (ents.Length == 0)
    {
    return false;
    }
    foreach (var ent in ents)
    {
    if (this.CanHeal(ent))
    {
    this.PushOrderFront("Heal", new AllInOneData { target = ent, force = false });
    return true;
    }
    }
    return false;
    }
    public override bool ShouldAbandonChase(uint target, bool force, InterfaceId iid)
    {
    // Forced orders shouldn't be interrupted.
    if (force)
    {
    return false;
    }
    // Stop if we're in hold-ground mode and it's too far from the holding point
    if (this.GetStance().respondHoldGround)
    {
    if (!this.CheckTargetDistanceFromHeldPosition(target, iid, this.attackType))
    {
    return true;
    }
    }
    // Stop if it's left our vision range, unless we're especially persistent
    if (!this.GetStance().respondChaseBeyondVision)
    {
    if (!this.CheckTargetIsInVisionRange(target))
    {
    return true;
    }
    }
    // (Note that CCmpUnitMotion will detect if the target is lost in FoW,
    // and will continue moving to its last seen position and then stop)
    return false;
    }
    public override bool ShouldChaseTargetedEntity(uint target, bool force)
    {
    if (this.GetStance().respondChase)
    {
    return true;
    }
    if (force)
    {
    return true;
    }
    return false;
    }
    public override void SetFormationController(uint ent)
    {
    this.formationController = ent;
    // Set obstruction group, so we can walk through members
    // of our own formation (or ourself if not in formation)
    var cmpObstruction = Engine.QueryInterface(this.entity, InterfaceId.Obstruction) as ICmpObstruction;
    if (cmpObstruction != null)
    {
    if (ent == CComponentManager.INVALID_ENTITY)
    {
    cmpObstruction.SetControlGroup(this.entity);
    }
    else
    {
    cmpObstruction.SetControlGroup(ent);
    }
    }
    // If we were removed from a formation, let the FSM switch back to INDIVIDUAL
    if (ent == CComponentManager.INVALID_ENTITY)
    {
    UnitFsm.ProcessMessage(this, new OrderItem { type = "FormationLeave" });
    }
    }
    public override uint GetFormationController()
    {
    return this.formationController;
    }
    public override void SetLastFormationName(string name)
    {
    this.lastFormationName = name;
    }
    public override string GetLastFormationName()
    {
    return this.lastFormationName;
    }
    public override float ComputeWalkingDistance()
    {
    var distance = 0.0f;
    var cmpPosition = Engine.QueryInterface(this.entity, InterfaceId.Position) as ICmpPosition;
    if (cmpPosition == null || !cmpPosition.IsInWorld())
    {
    return 0;
    }
    // Keep track of the position at the start of each order
    var pos = cmpPosition.GetPosition();
    for (var i = 0; i < this.orderQueue.Count; ++i)
    {
    var order = this.orderQueue[i];
    switch (order.type)
    {
    case "Walk":
    case "GatherNearPosition":
    // Add the distance to the target point
    var dx0 = (float)order.data.x - pos.X;
    var dz0 = (float)order.data.z - pos.Z;
    var d0 = (float)Math.Sqrt(dx0 * dx0 + dz0 * dz0);
    distance += d0;
    // Remember this as the start position for the next order
    pos = new CVector3D(order.data.x, 0, order.data.z);
    break; // and continue the loop
    case "WalkToTarget":
    case "WalkToTargetRange": // This doesn't move to the target (just into range), but a later order will.
    case "Flee":
    case "LeaveFoundation":
    case "Attack":
    case "Heal":
    case "Gather":
    case "ReturnResource":
    case "Repair":
    case "Garrison":
    {
    // Find the target unit's position
    var cmpTargetPosition = Engine.QueryInterface(order.data.target, InterfaceId.Position) as ICmpPosition;
    if (cmpTargetPosition == null || !cmpTargetPosition.IsInWorld())
    {
    return distance;
    }
    var targetPos = cmpTargetPosition.GetPosition();
    // Add the distance to the target unit
    var dx = targetPos.X - pos.X;
    var dz = targetPos.Z - pos.Z;
    var d = (float)Math.Sqrt(dx * dx + dz * dz);
    distance += d;
    // Return the total distance to the target
    return distance;
    }
    case "Stop":
    {
    return 0;
    }
    default:
    {
    //error("ComputeWalkingDistance: Unrecognised order type '" + order.type + "'");
    return distance;
    }
    }
    }
    // Return the total distance to the end of the order queue
    return distance;
    }
    public override void AddOrder(string type, dynamic data, bool queued)
    {
    if (queued)
    {
    this.PushOrder(type, data);
    }
    else
    {
    this.ReplaceOrder(type, data);
    }
    }
    public override void Walk(float x, float z, bool queued)
    {
    this.AddOrder("Walk", new AllInOneData { x = x, z = z, force = true }, queued);
    }
    /// <summary>
    /// Adds stop order to queue, forced by the player.
    /// </summary>
    public override void Stop(bool queued)
    {
    this.AddOrder("Stop", null, queued);
    }
    public override void WalkToTarget(uint target, bool queued)
    {
    this.AddOrder("WalkToTarget", new AllInOneData { target = target, force = true }, queued);
    }
    public override void LeaveFoundation(uint target)
    {
    // If we're already being told to leave a foundation, then
    // ignore this new request so we don't end up being too indecisive
    // to ever actually move anywhere
    if (this.order != null && this.order.type == "LeaveFoundation")
    {
    return;
    }
    this.PushOrderFront("LeaveFoundation", new AllInOneData { target = target, force = true });
    }
    public override void Attack(uint target, bool queued)
    {
    if (!this.CanAttack(target, false))
    {
    // We don't want to let healers walk to the target unit so they can be easily killed.
    // Instead we just let them get into healing range.
    if (this.IsHealer())
    {
    this.MoveToTargetRange(target, InterfaceId.Heal);
    }
    else
    {
    this.WalkToTarget(target, queued);
    }
    return;
    }
    this.AddOrder("Attack", new AllInOneData { target = target, force = true }, queued);
    }
    public override void Garrison(uint target, bool queued)
    {
    if (!this.CanGarrison(target))
    {
    this.WalkToTarget(target, queued);
    return;
    }
    this.AddOrder("Garrison", new AllInOneData { target = target, force = true }, queued);
    }
    public override void Ungarrison()
    {
    if (this.IsGarrisoned())
    {
    this.AddOrder("Ungarrison", null, false);
    }
    }
    /// <summary>
    /// Adds gather order to the queue, forced by the player until the target is reached .
    /// </summary>
    public override void Gather(uint target, bool queued)
    {
    this.PerformGather(target, queued, true);
    }
    public override void PerformGather(uint target, bool queued, bool force)
    {
    if (!this.CanGather(target))
    {
    this.WalkToTarget(target, queued);
    return;
    }
    // Save the resource type now, so if the resource gets destroyed
    // before we process the order then we still know what resource
    // type to look for more of
    var cmpResourceSupply = Engine.QueryInterface(target, InterfaceId.ResourceSupply) as ICmpResourceSupply;
    var type = cmpResourceSupply.GetType();
    // Also save the target entity's template, so that if it's an animal,
    // we won't go from hunting slow safe animals to dangerous fast ones
    var cmpTemplateManager = Engine.QueryInterface(InterfaceId.TemplateManager) as ICmpTemplateManager;
    var template = cmpTemplateManager.GetCurrentTemplateName(target);
    // Remove "resource|" prefix from template name, if present.
    if (template.IndexOf("resource|") != -1)
    {
    template = template.Substring(9);
    }
    // Remember the position of our target, if any, in case it disappears
    // later and we want to head to its last known position
    // (TODO: if the target moves a lot (e.g. it's an animal), maybe we
    // need to update this lastPos regularly rather than just here?)
    CVector3D? lastPos = null;
    var cmpPosition = Engine.QueryInterface(target, InterfaceId.Position) as ICmpPosition;
    if (cmpPosition != null && cmpPosition.IsInWorld())
    {
    lastPos = cmpPosition.GetPosition();
    }
    this.AddOrder("Gather", new AllInOneData { target = target, type = type, template = template, lastPos = lastPos, force = force }, queued);
    }
    public override void GatherNearPosition(float x, float z, Name type, string template, bool queued)
    {
    // Remove "resource|" prefix from template name, if present.
    if (template.IndexOf("resource|") != -1)
    {
    template = template.Substring(9);
    }
    this.AddOrder("GatherNearPosition", new AllInOneData { type = type, template = template, x = x, z = z, force = false }, queued);
    }
    public override void Heal(uint target, bool queued)
    {
    if (!this.CanHeal(target))
    {
    this.WalkToTarget(target, queued);
    return;
    }
    this.AddOrder("Heal", new AllInOneData { target = target, force = true }, queued);
    }
    public override void ReturnResource(uint target, bool queued)
    {
    if (!this.CanReturnResource(target, true))
    {
    this.WalkToTarget(target, queued);
    return;
    }
    this.AddOrder("ReturnResource", new AllInOneData { target = target, force = true }, queued);
    }
    public override void SetupTradeRoute(uint target, uint source, bool queued)
    {
    if (!this.CanTrade(target))
    {
    this.WalkToTarget(target, queued);
    return;
    }
    var cmpTrader = Engine.QueryInterface(this.entity, InterfaceId.Trader) as ICmpTrader;
    var marketsChanged = cmpTrader.SetTargetMarket(target, source);
    if (marketsChanged)
    {
    if (cmpTrader.HasBothMarkets())
    {
    this.AddOrder("Trade", new AllInOneData { firstMarket = cmpTrader.GetFirstMarket(), secondMarket = cmpTrader.GetSecondMarket(), force = false }, queued);
    }
    else
    {
    this.WalkToTarget(cmpTrader.GetFirstMarket(), queued);
    }
    }
    }
    public override bool MoveToMarket(uint targetMarket)
    {
    if (this.MoveToTarget(targetMarket))
    {
    // We've started walking to the market
    return true;
    }
    else
    {
    // We can't reach the market.
    // Give up.
    this.StopMoving();
    this.StopTrading();
    return false;
    }
    }
    public override void PerformTradeAndMoveToNextMarket(uint currentMarket, uint nextMarket, string nextFsmStateName)
    {
    if (!this.CanTrade(currentMarket))
    {
    this.StopTrading();
    return;
    }
    if (this.CheckTargetRange(currentMarket, InterfaceId.Trader))
    {
    this.PerformTrade();
    if (this.MoveToMarket(nextMarket))
    {
    // We've started walking to the next market
    this.SetNextState(nextFsmStateName);
    }
    }
    else
    {
    // If the current market is not reached try again
    this.MoveToMarket(currentMarket);
    }
    }
    public override void PerformTrade()
    {
    var cmpTrader = Engine.QueryInterface(this.entity, InterfaceId.Trader) as ICmpTrader;
    cmpTrader.PerformTrade();
    }
    public override void StopTrading()
    {
    this.FinishOrder();
    var cmpTrader = Engine.QueryInterface(this.entity, InterfaceId.Trader) as ICmpTrader;
    cmpTrader.StopTrading();
    }
    public override void Repair(uint target, bool autocontinue, bool queued)
    {
    if (!this.CanRepair(target))
    {
    this.WalkToTarget(target, queued);
    return;
    }
    this.AddOrder("Repair", new AllInOneData { target = target, autocontinue = autocontinue, force = true }, queued);
    }
    public override void Flee(uint target, bool queued)
    {
    this.AddOrder("Flee", new AllInOneData { target = target, force = false }, queued);
    }
    public override void Cheer()
    {
    this.AddOrder("Cheering", new AllInOneData { force = true }, false);
    }
    public override void SetStance(string stance)
    {
    if (g_Stances.ContainsKey(stance))
    {
    this.stance = stance;
    }
    else
    {
    //error("UnitAI: Setting to invalid stance '" + stance + "'");
    }
    }
    public override void SwitchToStance(string stance)
    {
    var cmpPosition = Engine.QueryInterface(this.entity, InterfaceId.Position) as ICmpPosition;
    if (cmpPosition == null || !cmpPosition.IsInWorld())
    {
    return;
    }
    var pos = cmpPosition.GetPosition();
    this.SetHeldPosition(pos.X, pos.Z);
    this.SetStance(stance);
    // Stop moving if switching to stand ground
    // TODO: Also stop existing orders in a sensible way
    if (stance == "standground")
    {
    this.StopMoving();
    }
    // Reset the range queries, since the range depends on stance.
    this.SetupRangeQueries();
    }

    public override bool FindNewTargets()
    {
    if (this.losRangeQuery == null)
    {
    return false;
    }
    if (!this.GetStance().targetVisibleEnemies)
    {
    return false;
    }
    var rangeMan = Engine.QueryInterface(InterfaceId.RangeManager) as ICmpRangeManager;
    if (this.AttackEntitiesByPreference(rangeMan.ResetActiveQuery(this.losRangeQuery.Value)))
    {
    return true;
    }
    // If no regular enemies were found, attempt to attack a hostile Gaia entity.
    else if (this.losGaiaRangeQuery != null)
    {
    return this.AttackGaiaEntitiesByPreference(rangeMan.ResetActiveQuery(this.losGaiaRangeQuery.Value));
    }
    return false;
    }
    public override bool FindNewHealTargets()
    {
    if (this.losHealRangeQuery == null)
    {
    return false;
    }
    var rangeMan = Engine.QueryInterface(InterfaceId.RangeManager) as ICmpRangeManager;
    var ents = rangeMan.ResetActiveQuery(this.losHealRangeQuery.Value);
    foreach (var ent in ents)
    {
    if (this.CanHeal(ent))
    {
    this.PushOrderFront("Heal", new AllInOneData { target = ent, force = false });
    return true;
    }
    }
    // We haven't found any target to heal
    return false;
    }
    public override Range GetQueryRange(InterfaceId iid)
    {
    var ret = new Range { Min = 0, Max = 0 };
    if (this.GetStance().respondStandGround)
    {
    var cmpRanged = Engine.QueryInterface(this.entity, iid) as IRangedComponent;
    if (cmpRanged == null)
    {
    return ret;
    }
    //var range = iid !== IID_Attack ? cmpRanged.GetRange() : cmpRanged.GetRange(cmpRanged.GetBestAttack());
    var range = cmpRanged.GetRange(cmpRanged.GetBestAttack());
    ret.Min = range.Min;
    ret.Max = range.Max;
    }
    else if (this.GetStance().respondChase)
    {
    var cmpVision = Engine.QueryInterface(this.entity, InterfaceId.Vision) as ICmpVision;
    if (cmpVision == null)
    {
    return ret;
    }
    var range = cmpVision.GetRange();
    ret.Max = range;
    }
    else if (this.GetStance().respondHoldGround)
    {
    var cmpRanged = Engine.QueryInterface(this.entity, iid) as IRangedComponent;
    if (cmpRanged == null)
    {
    return ret;
    }
    //var range = iid != IID_Attack ? cmpRanged.GetRange() : cmpRanged.GetRange(cmpRanged.GetBestAttack());
    var range = cmpRanged.GetRange(cmpRanged.GetBestAttack());
    var cmpVision = Engine.QueryInterface(this.entity, InterfaceId.Vision) as ICmpVision;
    if (cmpVision == null)
    {
    return ret;
    }
    var halfvision = cmpVision.GetRange() / 2;
    ret.Max = range.Max + halfvision;
    }
    // We probably have stance 'passive' and we wouldn't have a range,
    // but as it is the default for healers we need to set it to something sane.
    else if (iid == InterfaceId.Heal)
    {
    var cmpVision = Engine.QueryInterface(this.entity, InterfaceId.Vision) as ICmpVision;
    if (cmpVision == null)
    {
    return ret;
    }
    var range = cmpVision.GetRange();
    ret.Max = range;
    }
    return ret;
    }
    public override UnitStance GetStance()
    {
    return g_Stances[this.stance];
    }
    public override string GetStanceName()
    {
    return this.stance;
    }
    public override void SetMoveSpeed(float speed)
    {
    var cmpMotion = Engine.QueryInterface(this.entity, InterfaceId.UnitMotion) as ICmpUnitMotion;
    cmpMotion.SetSpeed(speed);
    }
    public override void SetHeldPosition(float x, float z)
    {
    this.heldPosition = new CVector3D(x, 0, z);
    }
    public override CVector3D? GetHeldPosition(object pos)
    {
    return this.heldPosition;
    }
    public override bool WalkToHeldPosition()
    {
    if (this.heldPosition.HasValue)
    {
    this.AddOrder("Walk", new AllInOneData { x = this.heldPosition.Value.X, z = this.heldPosition.Value.Z, force = false }, false);
    return true;
    }
    return false;
    }
    public override bool CanAttack(uint target, bool forceResponse)
    {
    // Formation controllers should always respond to commands
    // (then the individual units can make up their own minds)
    if (this.IsFormationController())
    return true;
    // Verify that we're able to respond to Attack commands
    var cmpAttack = Engine.QueryInterface(this.entity, InterfaceId.Attack) as ICmpAttack;
    if (cmpAttack == null)
    return false;
    if (!cmpAttack.CanAttack(target))
    return false;
    // Verify that the target is alive
    if (!this.TargetIsAlive(target))
    return false;
    var cmpOwnership = Engine.QueryInterface(this.entity, InterfaceId.Ownership) as ICmpOwnership;
    if (cmpOwnership == null)
    return false;
    // Verify that the target is an attackable resource supply like a domestic animal
    // or that it isn't owned by an ally of this entity's player or is responding to
    // an attack.
    var owner = cmpOwnership.GetOwner();
    if (!this.MustKillGatherTarget(target)
    && !(PlayerHelper.IsOwnedByEnemyOfPlayer(Engine, owner, target)
    || PlayerHelper.IsOwnedByNeutralOfPlayer(Engine, owner, target)
    || (forceResponse && !PlayerHelper.IsOwnedByPlayer(Engine, owner, target))))
    {
    return false;
    }
    return true;
    }
    public override bool CanGarrison(uint target)
    {
    // Formation controllers should always respond to commands
    // (then the individual units can make up their own minds)
    if (this.IsFormationController())
    {
    return true;
    }
    var cmpGarrisonHolder = Engine.QueryInterface(target, InterfaceId.GarrisonHolder) as ICmpGarrisonHolder;
    if (cmpGarrisonHolder == null)
    {
    return false;
    }
    // Verify that the target is owned by this entity's player
    var cmpOwnership = Engine.QueryInterface(this.entity, InterfaceId.Ownership) as ICmpOwnership;
    if (cmpOwnership == null || !PlayerHelper.IsOwnedByPlayer(Engine, cmpOwnership.GetOwner(), target))
    {
    return false;
    }
    // Don't let animals garrison for now
    // (If we want to support that, we'll need to change Order.Garrison so it
    // doesn't move the animal into an INVIDIDUAL.* state)
    if (this.IsAnimal())
    {
    return false;
    }
    return true;
    }
    public override bool CanGather(uint target)
    {
    // The target must be a valid resource supply.
    var cmpResourceSupply = Engine.QueryInterface(target, InterfaceId.ResourceSupply) as ICmpResourceSupply;
    if (cmpResourceSupply == null)
    {
    return false;
    }
    // Formation controllers should always respond to commands
    // (then the individual units can make up their own minds)
    if (this.IsFormationController())
    {
    return true;
    }
    // Verify that we're able to respond to Gather commands
    var cmpResourceGatherer = Engine.QueryInterface(this.entity, InterfaceId.ResourceGatherer) as ICmpResourceGatherer;
    if (cmpResourceGatherer == null)
    {
    return false;
    }
    // Verify that we can gather from this target
    if (cmpResourceGatherer.GetTargetGatherRate(target) <= 0)
    {
    return false;
    }
    // No need to verify ownership as we should be able to gather from
    // a target regardless of ownership.
    return true;
    }
    public override bool CanHeal(uint target)
    {
    // Formation controllers should always respond to commands
    // (then the individual units can make up their own minds)
    if (this.IsFormationController())
    {
    return true;
    }
    // Verify that we're able to respond to Heal commands
    var cmpHeal = Engine.QueryInterface(this.entity, InterfaceId.Heal) as ICmpHeal;
    if (cmpHeal == null)
    {
    return false;
    }
    // Verify that the target is alive
    if (!this.TargetIsAlive(target))
    {
    return false;
    }
    // Verify that the target is owned by the same player as the entity or of an ally
    var cmpOwnership = Engine.QueryInterface(this.entity, InterfaceId.Ownership) as ICmpOwnership;
    if (cmpOwnership == null || !(PlayerHelper.IsOwnedByPlayer(Engine, cmpOwnership.GetOwner(), target) || PlayerHelper.IsOwnedByAllyOfPlayer(Engine, cmpOwnership.GetOwner(), target)))
    {
    return false;
    }
    // Verify that the target is not unhealable (or at max health)
    var cmpHealth = Engine.QueryInterface(target, InterfaceId.Health) as ICmpHealth;
    if (cmpHealth == null || cmpHealth.IsUnhealable())
    {
    return false;
    }
    // Verify that the target has no unhealable class
    var cmpIdentity = Engine.QueryInterface(target, InterfaceId.Identity) as ICmpIdentity;
    if (cmpIdentity == null)
    {
    return false;
    }
    foreach (var unhealableClass in cmpHeal.GetUnhealableClasses())
    {
    if (cmpIdentity.HasClass(unhealableClass))
    {
    return false;
    }
    }
    // Verify that the target is a healable class
    var healable = false;
    foreach (var healableClass in cmpHeal.GetHealableClasses())
    {
    if (cmpIdentity.HasClass(healableClass))
    {
    healable = true;
    }
    }
    if (!healable)
    {
    return false;
    }
    return true;
    }
    public override bool CanReturnResource(uint target, bool checkCarriedResource)
    {
    // Formation controllers should always respond to commands
    // (then the individual units can make up their own minds)
    if (this.IsFormationController())
    {
    return true;
    }
    // Verify that we're able to respond to ReturnResource commands
    var cmpResourceGatherer = Engine.QueryInterface(this.entity, InterfaceId.ResourceGatherer) as ICmpResourceGatherer;
    if (cmpResourceGatherer == null)
    {
    return false;
    }
    // Verify that the target is a dropsite
    var cmpResourceDropsite = Engine.QueryInterface(target, InterfaceId.ResourceDropsite) as ICmpResourceDropsite;
    if (cmpResourceDropsite == null)
    {
    return false;
    }
    if (checkCarriedResource)
    {
    // Verify that we are carrying some resources,
    // and can return our current resource to this target
    var type = cmpResourceGatherer.GetMainCarryingType();
    if (string.IsNullOrEmpty(type) || !cmpResourceDropsite.AcceptsType(type))
    {
    return false;
    }
    }
    // Verify that the dropsite is owned by this entity's player
    var cmpOwnership = Engine.QueryInterface(this.entity, InterfaceId.Ownership) as ICmpOwnership;
    if (cmpOwnership == null || !PlayerHelper.IsOwnedByPlayer(Engine, cmpOwnership.GetOwner(), target))
    {
    return false;
    }
    return true;
    }
    public override bool CanTrade(uint target)
    {
    // Formation controllers should always respond to commands
    // (then the individual units can make up their own minds)
    if (this.IsFormationController())
    {
    return true;
    }
    // Verify that we're able to respond to Trade commands
    var cmpTrader = Engine.QueryInterface(this.entity, InterfaceId.Trader) as ICmpTrader;
    if (cmpTrader == null || !cmpTrader.CanTrade(target))
    {
    return false;
    }
    return true;
    }
    public override bool CanRepair(uint target)
    {
    // Formation controllers should always respond to commands
    // (then the individual units can make up their own minds)
    if (this.IsFormationController())
    {
    return true;
    }
    // Verify that we're able to respond to Repair (Builder) commands
    var cmpBuilder = Engine.QueryInterface(this.entity, InterfaceId.Builder) as ICmpBuilder;
    if (cmpBuilder == null)
    {
    return false;
    }
    // Verify that the target is owned by an ally of this entity's player
    var cmpOwnership = Engine.QueryInterface(this.entity, InterfaceId.Ownership) as ICmpOwnership;
    if (cmpOwnership == null || !PlayerHelper.IsOwnedByAllyOfPlayer(Engine, cmpOwnership.GetOwner(), target))
    {
    return false;
    }
    return true;
    }
    public override void MoveRandomly(float distance)
    {
    // We want to walk in a random direction, but avoid getting stuck
    // in obstacles or narrow spaces.
    // So pick a circular range from approximately our current position,
    // and move outwards to the nearest point on that circle, which will
    // lead to us avoiding obstacles and moving towards free space.
    // TODO: we probably ought to have a 'home' point, and drift towards
    // that, so we don't spread out all across the whole map
    var cmpPosition = Engine.QueryInterface(this.entity, InterfaceId.Position) as ICmpPosition;
    if (cmpPosition == null)
    {
    return;
    }
    if (!cmpPosition.IsInWorld())
    {
    return;
    }
    var pos = cmpPosition.GetPosition();
    var jitter = 0.5f;
    Random rnd = new Random();
    // Randomly adjust the range's center a bit, so we tend to prefer
    // moving in random directions (if there's nothing in the way)
    var tx = pos.X + (2 * (float)rnd.NextDouble() - 1) * jitter;
    var tz = pos.Z + (2 * (float)rnd.NextDouble() - 1) * jitter;
    var cmpMotion = Engine.QueryInterface(this.entity, InterfaceId.UnitMotion) as ICmpUnitMotion;
    cmpMotion.MoveToPointRange(tx, tz, distance, distance);
    }
    public override bool AttackEntitiesByPreference(IEnumerable<uint> ents)
    {
    var cmpAttack = Engine.QueryInterface(this.entity, InterfaceId.Attack) as ICmpAttack;
    if (cmpAttack == null)
    {
    return false;
    }
    var xx = ents.Where(v => cmpAttack.CanAttack(v)).ToList();
    xx.Sort((a, => cmpAttack.CompareEntitiesByPreference(a, );
    return this.RespondToTargetedEntities(xx.ToArray());
    }
    public override bool AttackGaiaEntitiesByPreference(IEnumerable<uint> ents)
    {
    var cmpAttack = Engine.QueryInterface(this.entity, InterfaceId.Attack) as ICmpAttack;
    if (cmpAttack == null)
    {
    return false;
    }
    Func<uint, bool> filter = (e) =>
    {
    var cmpUnitAI = Engine.QueryInterface(e, InterfaceId.UnitAI) as ICmpUnitAI;
    return (cmpUnitAI != null && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()));
    };
    var xx = ents.Where(v => cmpAttack.CanAttack(v) && filter(v)).ToList();
    xx.Sort((a, => cmpAttack.CompareEntitiesByPreference(a, );
    return this.RespondToTargetedEntities(xx.ToArray());
    }
    }
    }

    CCmpUnitAI.cs.txt

  6. Awesome work my friend.

    A few questions. The 0 A.D. development team has discussed abandoning some of the old rendering code. I wonder if this might affect your efforts?

    If your playing 0 A.D. on a tablet with a touch screen how do you do a right click command like a mouse would do?

    I'm excited to try this in the future :)

    Thanks,

    - No i have changed model rendering system to enable GPU animations and GPU instancing, though the public interface of the code is as same as 0 A.D.

    - I have not experienced an RTS on Tablet yet, but i'll have to struggle about it when i buy a Windows 8 RT tablet. Yet, tablets will have USB port (iPad dont have) so we can connect a mouse. However right clicking in a tablet would be holding your finger 2 more seconds, or tap a finger and swiftly tap another finger. I have played an RTS title on Xbox with GamePad. It was a great XP and maybe a source of inspiration.

  7. - Having SVN, it is not a very hard job to keep the source code in sync. Because the SVN shows you the exact file names changed in a particular revision and which lines of code and how it changed. Thanks to Diff. This can be done with every stable version or per revision.

    - Yes, possible. many games do this for years.

    - I don't know any better cross platform javascript interpreter for C++. But on Windows and C++.NET, you can still run C# as both compiled code and/or runtime interpreted code (Thanks to Microsoft's Roslyn). I had used Roslyn in several cases (for other projects, not 0.A.D. port), thou it runs as a script, it's still much faster than compiled C# code. Weird. Roslyn is still a CTP.

    I hope these answers help.

  8. Thou C# is slower than C++ (i say C# is 95% of C++), the main reason for getting this gain would be:

    • I used DirectX 11 Feature Level, so i did GPU-Instancing for static models/particles and GPU-Skinning for animated ones; Which saves millions of matrix multiplications done by CPU per frame. The OpenGL code is eating CPU which makes the game compatible with any computer, But please consider that most of users have graphics cards not older than 4 years, so they support modern GPU features.
    • I translated Javascript to C#, so they are running code closer to machine language. I bet Microsoft's C# compiler beats Mozilla's Javascript interpreter, called SpiderMonkey (see file mozjs185-ps-release-1.0.dll in binaries folder).

    At the moment, i am planning to debug, maintain and contribute to 0.A.D. Also i have to check legal stuff before distributing the code. So there would be no plan on moving to OpenGL/Mono; the game is already OpenGL and runs on Linux/Android.

    EDIT:

    DirectX is not faster than OpenGL, it completely depends on the feature level you ask the device. 0.A.D's opengl level is equivalent to DirectX 9.0, but my port uses DirectX 11. Migrating current opengl level to a higher one results in the same performance improvement, but less compatibility with devices. I think Android and iOS do not support advanced opengl codes.

  9. i see only the upper part of the screenshot. just for curiosity: if you disable GUI and shadows in the C++ 0AD, what is your framerate?

    The image is massive. Please refresh the page to see the complete image. Google Chrome handles such situations very well.

    Good question. I already tried to get FPS in 0.A.D. but no luck. it seems i have to check it back in a better time. maybe tomorrow.

  10. Do you have the source code somewhere for us to look at? I would be interested to see your port. (also if you distribute your port this is a legal requirement).

    I'm curious about your motivation for this, 0 A.D. is already portable, Ykkrosh successfully managed to get things running on an android phone. It sounds like a fun project to do anyway :).

    Did you use automated code translation tools to do this? Also did you port the js to C# or keep that interpreted?

    Of course. I will putting the source code somewhere online, probably on Assembla svn servers. The game can run on Windows Phone 8 though i know it already runs on Android.

    I didnt use any code translation tools, coz they suck to translate or at least i am not familiar with them. The js also translated to C#, coz Win8 Modern Apps do not allow scripts interpreted.

    Is it a full port to C# or a wrapper around the C++ somehow? A full mirror of the original code base would seem awfully cumbersome to keep in sync.

    A full port in pure C#.

  11. Hi there,

    I have been porting 0.A.D to C# and .NET Framework and DirectX since a year ago. must of the job is done and i am debugging AI and save/load.

    The big point is the compatibility with Windows 8 Modern API and pure C# code, making it totally portable and running even on Tablets and Phones. It is App Store ready.

    Since i have ported must of the code, i have a big understanding of both C++ and Javascript codes, knowing lots of todos in code. I am also a geek in graphics programming specially DirectX/HLSL, but also OpenGL/GLSL.

    I am not a good C++ writer, but a good C++ reader and totally talented in C#.

    Lets your words jump up.

×
×
  • Create New...