Creep Age Affects Order of Operations


  • Culture

    This is something I've been meaning to write about for awhile since I noticed quite a long time ago.

     

    If a creep is attacked and healed on the same tick the order of how things occurs depends on the age of the creeps involved.

    The older creeps (those with the least ticksToLive) will perform their operations before the newer creeps. This means if a creep attacks something that reflects damage, like a power bank or another melee creep, then the reflected damage won't be healed the same tick if the healers are older than the attacking creep.

     

    This also means that older attack creeps are more likely to kill their target if the target only has young healers, since its damage will be processed (and potentially kill the creep) before it receives any healing.

     

    I'm not sure how this problem can be fixed as there has to be some sort of order on you guys' side, but right now it's completely dependent on id #/creep age.



  • It is not the creep age, but rather depends on the execution order of the creep actions.

    If you are looping over Game.creeps (which probably a lot of people do), you will have the insertion order which of course directly correlates to the age of your creeps. 

     Have a look at the following Game situation:

    Preconditions:

    3 creeps (owned by you*): An attacker (A), the target (T) and the healer (H). All actions executed are considered valid (correct range, etc...), and thus should return OK.

    We will look at two execution orders:

     A.attack(T);H.heal(T);

    and

    H.heal(T);A.attack(T);

    *This is a somehow simplified scenario, because in most cases attackers and healer will belong to different players, thus you cannot influence the order of the creep actions.

    Scenario 1:

    T.hits === T.hitsMax, A.attackPower < T.hits

    The result of execution order 1 will be, that A successfully attacks T and then H successfully heals T.

    Now with execution order 2 (again, assuming, that T had full health at the beginning of the tick), A will attack T, but H will not heal T (since it had full health when the "heal" was scheduled). Notice, that still, all of those action return OK!

    Scenario 2:

    Changing the preconditions to T having hits<hitsMax:

    In both cases H will heal T. Both execution orders will leave T with the same hits at the end of this tick.

    Scenario 3:

    T.hits === T.hitsMax, but A.attackPower > T.hits (= T would die next turn).

    In both cases, T will die without H healing T. In the first execution order H does not heal, because T still has full life, in the second one, T is already considered dead.

    Scenario 4:

    T.hits < T.hitsMax, A.attackPower > T.hits, but A.attackPower < T.hits + H.healingPower

    Execution order 1 has H healing T, A attack T, T survives another tick

    Execution order 2 has A attacking T, T dies, H cannot heal T

     

    The tragic thing is, that all those creep actions will return 0 (OK) which does not reflect the actual success of the actions (both, the very first and very last scenario should more precisely result in some kind of INVALID_TARGET for the heal action. The first one, because the creep has full health, the latter one because the creep is already considered dead).

     

    A possible solution for this specific attack/heal problem would be to remove specific [full HP|dead] checks during the tick and rather calculate the state of a creep at the very end of the tick, after all instructions are processed. Then if the creep has hits>hitsMax, hits will be capped at hitsMax, and if hits<0 the creep is considered dead.

    Downside of this approach would be, that you have to manually watch out for overheal/overkill, while the current approach somehow handles this for you. 

    However, this does not cope with race conditions that occur in other places like movement, resource management (harvest/repair/build/transfer,...) or certain structural actions.

     

    Personally, I think that the the current system looks fine to me, because in some cases you benefit, in others not. On average I think, benefits and drawbacks even themselves out.

    What you can do, build a Set (which per definition obeys insertion order!) and fill it with you Game.creeps weighted by a certain order you define, rather than the creep age.

     

    let myCreeps = new Set( /*insert Game.creeps ordered by your personal preference*/ );

    myCreeps.forEach(function(creep){
    /* do creep actions */
    });

     Or you could execute the creep actions in certain groups (first all workers, then attackers, after that healers,...).