1
\$\begingroup\$

I'm making 2d Mobile game using JavaScript.

I have method that controls every character (Dumb AI) on battleground. It using recursion to call another action. I want to add debuff system and had little bit difficulty adding taunt (where close enemies are forced to attack specified enemy) and confuse (where enemy is attacking his ally). I want this to be instant. What I managed to do is make this happen after enemy finish his current action, but this behaviour is bad. I need somehow a way to break instantly current call, but I don't have idea how to do it as from my knowledge there isn't anything method that by reference can break JavaScript method, so I could start new one. I had some idea to use memory reference and kill it in memory, but that's where I was having some fun in my teenage years with cheat engine on some games, but this can raise other problem where the game would need access to memory and this would be bad for user and me.

What I can do achieve instant change of action?

Below my method that controls every character:

export const characterControl = async (characterData, closestEnemy, enemyCharacters, allies, characterControlId) => {
// my new attep to introduce a new variable called `characterControlId` where new method call would have new characterControlId and old one would be forced to stop.
   if(characterData.battleData.debuff && characterData.battleData.characterControlId !== characterControlId || isPaused || gamePause) {
       return;
   }

   if (checkIfBattleEnded(allies, enemyCharacters)) {
     /// code that stop all methods and game loop.
   }

   if (isBattleEnded) {
       return;
   }

   if (checkIfCharacterDied(closestEnemy)) {
          // code that find new enemy and start new action to move to him
   }
   let direction;

   switch (characterData.battleData.action) {
       case "attack":
// this handle logic to switch character Spine2d animation to attack and deal dmg to enemy
           return attackEnemy(characterData, closestEnemy, enemyCharacters, allies);
       case "move-ally":
           // testing new approach where this gonna switch Spine2d animation to run character towards his closest ally. 

           return moveCharacter(characterData, closestEnemy, enemyCharacters, allies, direction, true);
       case "attack-ally":
// new approach where this logic gonna switch Spine2d animation to attack and inflict dmg to his ally.

           return attackEnemy(characterData, closestEnemy, enemyCharacters, allies, true);
       // case "taunted":
       //     return attackEnemy(characterData, closestEnemy, enemyCharacters, allies);
       case "move":
// default approach to switch character spine2d animation to run towards closest enemy
           return moveCharacter(characterData, closestEnemy, enemyCharacters, allies, direction);
       case "skill":
// when mana is above 100% it gonna switch character animation to his skill.
           return useSkillNew(characterData, closestEnemy, enemyCharacters, allies);
       default:
// when action of character is undefined it gonna look for possibilities what he should do next. If he should run or can has enemy in range and can attack him.
           return setCharacterAction(characterData, closestEnemy, enemyCharacters, allies);
   }
}

\$\endgroup\$
3
  • \$\begingroup\$ "I had some idea to use memory reference and kill it in memory" - yes, that sounds like a terrible idea indeed. \$\endgroup\$ Commented Jan 11, 2023 at 11:32
  • \$\begingroup\$ It appears that you are using an async function here. How exactly do you use that function? Do you just call it once and then it takes care of any time delays itself? Or do you call it every frame? Are you using promises? \$\endgroup\$ Commented Jan 11, 2023 at 11:34
  • \$\begingroup\$ Yes, I call this function once for every character. I call it once and then I get delta from render library PIXI.js with this I make it stable. I use promises for finding new enemies, attacking enemy. Moving function is managed by PIXI.Ticker(). \$\endgroup\$ Commented Jan 11, 2023 at 23:04

1 Answer 1

1
\$\begingroup\$

It appears that you are trying to implement AI in form of some form of "coroutine". An asynchronous function that runs in parallel to the game and handles the AI of an entity. The problem with a coroutine-based AI is that it is not automatically aware of what else is happening in the game. So when there is some interruption condition, the coroutine is responsible for detecting that condition itself.

However, there are other patterns available for game AI that are far more adaptable, like finite-state machines or behavior trees.

Usually those patterns involve having some form of update-function for each AI actor that gets called once per game tick for each actor and checks if, given the current state of the game and of the actor, there is anything to do on this particular tick.

This can look like this (in pseudocode)

IF i am currently performing an attack THEN
    IF the current attack target is valid 
    AND i am in a state that allows me to attack THEN 
        advance the attack animation by one tick
    ELSE
        abort the attack

IF i am not currently performing an attack 
AND i am in a state that allows me to attack THEN
   find the most appropriate attack target that is valid
   IF i have an attack target THEN
      start a new attack

The function which check if an attack target is "valid" would take into account if the character is currently affected by "taunt" or "charm" and decide accordingly which attack targets are valid and which are invalid.

\$\endgroup\$
3
  • \$\begingroup\$ I do that stuff. A performer is aware of debuff or buff condition. He is also aware of the current action, his position on map, his distances to enemies and who his closest enemy is. \$\endgroup\$ Commented Jan 11, 2023 at 23:08
  • \$\begingroup\$ @JakubDobrzeniecki Then I don't understand your problem. \$\endgroup\$ Commented Jan 11, 2023 at 23:22
  • \$\begingroup\$ Thanks, for your input. A performer is aware of debuff, buff condition. He is also aware of the current action, his position on map and who his closest enemy is. Problem is until most likely he don't finish performing his attack animation he not gonna know that he is confused and should attack his ally. Forcing animation change with new function call would speed it up, but it comes with race of old function that need to be stopped and new one that should be kept. They can be distinguished by id I generate for them, but still it have soo much risk of old call interfering something \$\endgroup\$ Commented Jan 11, 2023 at 23:25

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.