Bobot_ai.c (2)


/*-------------------------------------------------------------------------------------
pour la prise de décision "intelligente"
BOBOT_AI_Choix
choix de la prochaine action du bobot
à partir des données récoltées lors de la détection
-------------------------------------------------------------------------------------*/
void BOBOT_AI_Choix(gentity_t *bobot,Sbobot *bobotAI,usercmd_t *ucmd)
{    int            i,priority,bestAction=INVALID,priorityMax = -1;
    action_t    *currentAction,*act;

    /*    déterminons l'objectif à long terme du bot ainsi que la priorité de cet objectif (la place dans actions[GO_TO_LR_GOAL])
        peut-être qu'il faudrait inclure ça dans le pop, pour le faire plus souvent. */

    if (bobotAI->state == BOT_STATE_POSITION)
    {    BOBOT_AI_Determine_LR_Goal(bobot);
        BOBOT_AI_popInCurrentAction(bobot,bobotAI);                                        // on dépile, l'action au sommet de la pile devient l'action courante
    }
    BOBOT_AI_cleanActions(bobotAI->actions);
    BOBOT_FUZZY_evaluateMediumRangeObjectives(bobot,bobotAI);                            // évaluons les objectifs à moyen terme du bot
    currentAction = &bobotAI->currentAction;                                            // étudions l'action en cours...
    if(currentAction->type != INVALID)
    {    if(currentAction->timeOut < level.time)                                            // commençons par vérifier que l'action en cours n'a pas atteint sa limite de validité
        {    if (PrintDebug)
            { Fuzzy_Printf("BOBOT_AI_Choix : Action %s TIMED OUT\n",BOBOT_AI_actionToString(currentAction->type)); }
            BOBOT_AI_popInCurrentAction(bobot,bobotAI);                                    // si l'action a commencé depuis trop longtemps, on l'annule
        }
        else if(currentAction->timeOutWhenHidden > 0 && currentAction->timeOutWhenHidden < level.time)
        {    if (PrintDebug)
            { Fuzzy_Printf("BOBOT_AI_Choix : Action %s TIMED OUT (cible perdue de vue)\n",
                           BOBOT_AI_actionToString(currentAction->type));
            }
            BOBOT_AI_popInCurrentAction(bobot,bobotAI);                                    // pareil si la cible de l'action est cachée depuis trop longtemps
        }

        /*    regardons si l'action en cours se fait sur une cible (un pack par ex.) visible.
            Si elle est visible, pas de problème. Mais si elle est invisible, l'action ne sera
            pas présente dans le tableau actions[], et donc un pack momentanément caché sera oublié.
            action déclenchable du même type que l'action courante */

        act = &bobotAI->actions[currentAction->type];

        /*    si elle est cachée, et bien sûr que la cible n'est pas le bot lui même */
        if(bobotAI->currentAction.ent != bobot && !BOBOT_AI_currentTargetVisible(bobot,bobotAI))
        {    if(act->priority < currentAction->priority)                                    // si on n'a pas une cible plus prioritaire du même type...
            {    act->type = currentAction->type;                                        // alors on force le bot a conserver l'action...
                act->node = currentAction->node;                                        // en cours avec la même priorité
                act->ent = currentAction->ent;
                act->priority = currentAction->priority;
                VectorCopy(currentAction->lastKnownPos,act->lastKnownPos);
            }
            if(currentAction->timeOutWhenHidden < 0)                                    // De plus, si la cible n'était pas déjà cachée...
            {                                                                            // on marque le moment où elle a commencé à être cachée
                currentAction->timeOutWhenHidden = level.time + BOBOT_AI_actionToTimeOfValidityWhenHidden(currentAction->type);
            }
        }
        else                                                                            // si on la voit
        { currentAction->timeOutWhenHidden = -1; }                                        // elle n'est plus cachée
    }

    /*    choisissons maintenant l'action à priorité maximale */
    for(i=0;i<NB_COMMON_OBJECTIVES;i++)
    {    act = &bobotAI->actions[i];
        priority = act->priority;
        if(priority == INVALID)
        { continue; }
        if(priority > priorityMax)
        {    bestAction = i;
            priorityMax = priority;
        }
    }
    // pour debug
    /*BOBOT_DEBUG_showStack(bobotAI);
    BOBOT_DEBUG_showActions(bobot,bobotAI);
    BOBOT_DEBUG_showState(bobotAI);*/

    /*    si l'action prioritaire n'est pas l'action en cours */
    if(bestAction == INVALID)
    { return; }
    if(BOBOT_AI_actionCompare(currentAction,&bobotAI->actions[bestAction]))
    {    if(currentAction->type != INVALID && currentAction->type != GO_TO_LR_GOAL)        // si une action est en cours
        { BOBOT_AI_pushCurrentAction(bobot,bobotAI); }                                    // on l'interrompt, et on la place dans la pile
        BOBOT_AI_startNewAction(bobotAI,bestAction);                                    // nouvelle action en cours : la plus prioritaire
    }
    /*    par contre si l'action prioritaire est la même que l'action courante, alors on met à jour la dernière position connue
        de la cible de l'action    pour que, si l'action se retrouve dans la pile (puis au sommet),on puisse calculer sa priorité
        en fonction de cette position (si la cible n'est pas visible et n'est pas un node, bien sûr) */
    else
    { VectorCopy(bobotAI->actions[bestAction].lastKnownPos,currentAction->lastKnownPos); }
    bobotAI->goal_node = currentAction->node;                                            // par sécurité pour l'instant mais à terme...
    bobotAI->goalentity = currentAction->ent;                                            // ça serait bien de virer goal_node et goalentity
    bobotAI->state = BOBOT_AI_actionToState(bobotAI,currentAction->type);
}

/*-----------------------------------------------------------------------------
BOBOT_AI_Action : le bobot agit en fonction des décisions qu'il a prises
-------------------------------------------------------------------------------*/
extern void BOBOT_NeedBackup(gentity_t *bobot,usercmd_t *ucmd);
extern void Bobot_ResponseChat(gentity_t *bobot);

void BOBOT_AI_Action(gentity_t *bobot,Sbobot *bobotAI,usercmd_t *ucmd)
{    weapon_t        weapon ;
    int                r,inmyteam,TempsUse,i;
    det_client_t    *teammates = bobotAI->teammates;
    gentity_t        *teammate;
    weapon_t        weapon1  = bobot->client->sess.playerWeapon;
    weapon_t        weapon2  = bobot->client->sess.playerWeapon2;
    vec3_t             dir,angles;
    vec_t            dist;
   
    char            MessTank[10][20]={"LetsGo","FireInTheHole","WhereTo",
                                      "CoverMe","Negative","Sorry","EnemyWeak",
                                      "TakingFire","Incoming","cheer"};
   
    inmyteam = (bobotAI->myteam == TEAM_ALLIES ? NODE_TEAM_ALLIES : NODE_TEAM_AXIS);
    weapon = bobot->client->ps.weapon;

    /*     ACTIONS PRINCIPALES DU BOT (à moyen ou long terme) */
    switch(bobotAI->state)
    {    case BOT_STATE_GETFLAG:
        case BOT_STATE_MOVE:
            BOBOT_Move(bobot,ucmd);
            break;
        case BOT_STATE_TOUT_DROIT:
            BOBOT_BasicMove(bobot,ucmd);
            break;
        case BOT_STATE_SPECIAL_MOVE:
            BOBOT_SpecialMove(bobot,ucmd);
            break;
        case BOT_STATE_CONSTRUCT:
            BOBOT_ENGINEER_Construct(bobot,ucmd);
            break;
        case BOT_STATE_DYNAMITE:
            BOBOT_ENGINEER_PlantDynamite(bobot,ucmd);
            break;
        case BOT_STATE_ARMING_DYNA:
            BOBOT_ENGINEER_ArmDynamite(bobot,ucmd,qtrue);
            break;
        case BOT_STATE_DEFUSE:
            BOBOT_ENGINEER_ArmDynamite(bobot,ucmd,qfalse);
            break;
        case BOT_STATE_REVIVE:
            BOBOT_MEDIC_Revive(bobot,ucmd);
            break;
        case BOT_STATE_GIVE_HEALTH:
            BOBOT_MEDIC_GiveHealth(bobot,ucmd);
            break;
        case BOT_STATE_GIVE_AMMO:
            BOBOT_FIELDOPS_GiveAmmo(bobot,ucmd);
            break;
        case BOT_STATE_TAKE_PACK:
            BOBOT_COMMON_TakePack(bobot,ucmd);
            break;
        case BOT_STATE_DEFEND_POSITION:
            if(bobotAI->skill>=3)
                BOBOT_COMMON_defendPosition(bobot,ucmd);
            break;
        case BOT_STATE_CHASE_ENEMY:
            BOBOT_COMMON_chaseEnemy(bobot,ucmd);
            break;
        case BOT_STATE_BACKUP_TEAMMATE:
            BOBOT_COMMON_backupTeammate(bobot,ucmd);
            break;
        case BOT_STATE_FOLLOW_TEAMMATE:
            BOBOT_COMMON_FollowMe(bobot,ucmd);
            break;
        case BOT_STATE_PROTECT_FROM_ENEMY:
            BOBOT_COMMON_protectFromEnemy(bobot,ucmd);
            break;
        case BOT_STATE_PROTECT_FROM_ENEMY_WHILE_RELOADING:
            BOBOT_COMMON_protectFromEnemy(bobot,ucmd);
            break;
        case BOT_STATE_MINE:
            BOBOT_ENGINEER_PlantMine(bobot,ucmd);
            break;
        case BOT_STATE_BACKUP:
            BOBOT_NeedBackup(bobot,ucmd);
            break;
        case BOT_STATE_ARMING_MINE:
            BOBOT_ENGINEER_ArmMine(bobot,ucmd,qtrue);
            break;
        case BOT_STATE_DEGUISE:
            BOBOT_Covert_Deguise(bobot,ucmd);
            break;
        default:
            bobotAI->state = BOT_STATE_POSITION;
            break;
    }
   
    /*     ACTIONS DES BRAS (et de la tête) DU BOT (alouette) */
    switch(bobotAI->armsAction.type)
    {    case BOBOT_FIRE:
            BOBOT_COMMON_Attack(bobot,ucmd );
        break;
        case BOBOT_LOOK_AROUND:
            if(BOBOT_heavy_weapon(weapon1))                        // armes lourdes, prendre le flingue dans les déplacements
            {    ucmd->weapon=weapon2;    }
            else if(weapon1==WP_CARBINE || weapon1==WP_KAR98)    // ingénieurs, mettre une grenade au fusil
            {    if( bobot->client->ps.weapon==WP_CARBINE)
                {    if ((bobot->client->ps.ammo[BG_FindAmmoForWeapon(WP_M7)]>0) &&
                        (BOBOT_CLASS_WeaponCharged(bobot->client,WP_M7)))
                    {    ucmd->weapon=WP_M7;        }
                }
                if( bobot->client->ps.weapon==WP_KAR98)
                {    if ((bobot->client->ps.ammo[BG_FindAmmoForWeapon(WP_GPG40)]>0) &&
                    (BOBOT_CLASS_WeaponCharged(bobot->client,WP_GPG40)))
                    {    ucmd->weapon=WP_GPG40;    }
                }
            }
            else
            { BOBOT_COMMON_TakeBestWeapon(bobot,bobotAI->myteam,ucmd); }

            if((bobotAI->state==BOT_STATE_TOUT_DROIT) || (bobotAI->state==BOT_STATE_SPECIAL_MOVE))
            {    BOBOT_Look_With_NN(bobot,ucmd); }
            else
            {    if(bot_lookaround.integer || g_SniperWar.integer)
                {    BOBOT_Look(bobot,ucmd);    }
                else
                {    BOBOT_Look_With_NN(bobot,ucmd); }
            }
            if(!g_SniperWar.integer)
            {    if ((bobot->client->ps.ammoclip[BG_FindClipForWeapon(weapon)] < 0.5
                    * ammoTableMP[BG_FindAmmoForWeapon(weapon)].maxclip) 
                    && bobot->client->ps.ammo[BG_FindAmmoForWeapon(weapon)])        // Bot CAN reload !
                        ucmd->wbuttons |= WBUTTON_RELOAD;
            }
        break;
        case BOBOT_OBEY_TO_ACTION:                                                    // rien, il laisse faire l'action
            if(BOBOT_heavy_weapon(weapon1))
            { ucmd->weapon=weapon2;    }
        break;
    }
   
    // Ne pas aller sur un node MG42|blocable déjà pris (bots et players)
    if(bobotAI->actions->node!=INVALID)
    {    if (nodes[bobotAI->actions->node].type & NODE_HEAVY_MACHINGUN)
        {    for(i=0; i < BOBOT_DETECT_MAX_TEAMMATES; i++)
            {    teammate = teammates[i].ent;
                if( teammate == NULL || teammate->client == NULL || !teammate->inuse)
                { continue; }
                if(teammate->client->ps.pm_type & PM_DEAD)
                { continue; }
                if(teammate->client->ps.pm_flags & PMF_LIMBO)
                { continue; }
                if((teammate!=bobot) && (teammate->client->ps.eFlags & (EF_MG42_ACTIVE|EF_AAGUN_ACTIVE)))
                {    if(teammate->current_node == bobotAI->actions->node)
                    {    BOBOT_AI_endOfCurrentAction(bobotAI);
                        break;
                    }
                }
            }
        }
        else if (nodes[bobotAI->actions->node].type & NODE_BLOCKABLE)
        {    for(i=0; i < BOBOT_DETECT_MAX_TEAMMATES; i++)
            {    teammate = teammates[i].ent;
                if( teammate == NULL || teammate->client == NULL || !teammate->inuse)
                { continue; }
                if(teammate->client->ps.pm_type != PM_DEAD)
                { continue; }
                if(teammate->client->ps.pm_flags & PMF_LIMBO)
                { continue; }
                if(teammate!=bobot)
                {    if(teammate->current_node == bobotAI->actions->node)
                    {    BOBOT_AI_endOfCurrentAction(bobotAI);
                        break;
                    }
                }
            }
        }
    }

    if((level.time>=bobot->ChatTime) && (bobot->vchat_id>0))
    {    Bobot_ResponseChat(bobot);
        bobot->vchat_id=-1;
        bobot->destclient=-1;
        bobot->ChatTime=0;
    }

    /* Si un bot reste + de NODE_TIME (3 minutes) sur un node pendant un move c'est qu'il est coincé, alors auto kill */
    if(bot_TimeInactivity.integer>0)
    {    if((bobot->current_node != INVALID) && (bobot->current_node != bobotAI->StuckedNode))
        {    bobotAI->TimeOnStuckNode = level.time;
            bobotAI->StuckedNode=bobot->current_node;
        }
        if(    (bobot->current_node == bobotAI->StuckedNode) && (bobotAI->TimeOnStuckNode!=0) &&
            (level.time > (bobotAI->TimeOnStuckNode+(bot_TimeInactivity.integer*1000))))
        {    if(    (bobotAI->state!=BOT_STATE_CONSTRUCT) && (bobotAI->state!=BOT_STATE_DYNAMITE) &&
                (bobotAI->state!=BOT_STATE_ARMING_DYNA) && (bobotAI->state!=BOT_STATE_DEFUSE) &&
                (bobotAI->state!=BOT_STATE_MINE) && (bobotAI->state!=BOT_STATE_ARMING_MINE))
            {    G_Say( bobot, NULL, SAY_TEAM, "Shit i'm yet stuck, ok i kill myself!");
                BOBOT_AI_initBot(bobotAI);
                Cmd_Kill_f(bobot);
            }
        }
    }
    /* PAV: Le bot arrête de tirer sur son teammate */
    if (bobotAI->HitByTeammate>=3)
    {    if(level.time > bobotAI->DelayFireOnTeammate)
        {    bobotAI->HitByTeammate=0;    }
    }

    /* bobot prone but not in run or walk*/
    if (nodes[bobot->current_node].type & NODE_PRONE)
    {    if (bobotAI->prone == qfalse)
        {    ucmd->wbuttons |= WBUTTON_PRONE;
            bobotAI->prone = qtrue;
        }
    }
    else if(bobotAI->prone == qtrue)
    {    ucmd->wbuttons |= WBUTTON_PRONE;
        bobotAI->prone = qfalse;
    }

    if (nodes[bobot->current_node].type & NODE_TAKE)                                // Pav: Le type node_take actionne le bouton d'activation
    { bobotAI->passeporte=qtrue; }
    if (bobotAI->passeporte)                                                        //Joc le bobot ouvre les portes
    {ucmd->buttons |= (rand() % 2) * BUTTON_ACTIVATE; }

    /*    pav: les bot montent dans le tank ou se sevent des MGnest.
        Si le bot a une pince à la main c'est qu'il répare le
        (tank/MG42) endomagé, il ne peut s'en servir. */

    if (nodes[bobot->current_node].type & NODE_HEAVY_MACHINGUN && (NodePris[bobot->current_node]==0))
    {    if(bobot->client->ps.weapon != WP_PLIERS)                                    // Il n'a pas de pince, l'entité n'est pas endomagée.
        {    if (bobotAI->DansTank == qfalse)                                        // Il n'a pas encore utilisé cette entité durant sa session de vie.
            {    ucmd->buttons |= (rand() % 2) * BUTTON_ACTIVATE;                    // Il monte dans le tank ou prend le MG42
                if(bobot->client->ps.eFlags & (EF_MG42_ACTIVE | EF_MOUNTEDTANK))    // S'il utilise l'entité
                {    bobotAI->DansTank = qtrue;                                         // le flag passe à vrai.
                    r=RandInt(0,9);
                    G_Voice( bobot, NULL, SAY_TEAM, MessTank[r], qtrue);
                    NodePris[bobot->current_node]=1;                                // Le node est utilisé
                    TempsUse=RandInt(1,3)*MGNEST_TIME;
                    bobotAI->TimeUseObject = level.time + TempsUse;                    // temps d'utilisation du node : entre MGNEST_TIME et MGNEST_TIME*3
                    bobotAI->TimeNode = bobotAI->TimeUseObject + 5000;                // 5 secondes avant reset
                }
            }
        }
        else
        {    bobotAI->DansTank = qfalse;
            if (nodes[bobot->current_node].type & NODE_TANK)
            {G_Voice( bobot, NULL, SAY_TEAM, "repairvehicle", qtrue);}                // L'ingénieur répare le véhicule
        }
    }

    /*    pav: Si le bot utilise un tank/mg42, pendant plus de MGNEST_TIME, il en sort */
    if((bobotAI->DansTank == qtrue) && (level.time > bobotAI->TimeUseObject))
    {    NodePris[bobot->current_node]=0;                                            // Le node est libéré
        ucmd->buttons |= BUTTON_ACTIVATE;                                            // le bot sort du MG42/tank
        if(level.time > bobotAI->TimeNode)                                            // reset au bout de 5 secondes.
        { bobotAI->DansTank=qfalse;    }
    }
       
    /*    pav: fixer un bot sur un node (train, bateau...) */
    if ((nodes[bobot->current_node].type & NODE_BLOCKABLE) && (bobotAI->actions->node==bobot->current_node)
        //&& (nodes[bobot->current_node].type & (inmyteam | NODE_ALLTEAM))
        && (NodePris[bobot->current_node]==0))
    {    if(bobotAI->Bloked != qtrue)
        {    bobotAI->Bloked = qtrue;
            bobotAI->Ducked = RandInt(0,1);
            TempsUse=RandInt(1,3)*MGNEST_TIME;
            bobotAI->TimeUseObject = level.time + TempsUse;                            // temps d'utilisation du node : entre MGNEST_TIME et MGNEST_TIME*3
            if(g_SniperWar.integer)
                bobotAI->TimeNode = bobotAI->TimeUseObject + 2000;                    // 2 secondes avant reset en sniper war suffit
            else
                bobotAI->TimeNode = bobotAI->TimeUseObject + 5000;                    // 5 secondes avant reset
            //G_Say( bobot, NULL, SAY_ALL, va("je suis sur le node %d Mon but %d!",bobot->current_node, bobotAI->actions->node));
        }
    }
    if (bobotAI->Bloked == qtrue)
    {    if(level.time > bobotAI->TimeUseObject)
        {    bobotAI->Bloked = qfalse;    }
        else
        {    bobotAI->BlokedNode = bobot->current_node;
            NodePris[bobotAI->BlokedNode]=1;
            ucmd->forwardmove = 0;
            ucmd->rightmove    = 0;
            if (bobotAI->Ducked==1)
                ucmd->upmove = -48;
            else
                ucmd->upmove = 0;
            //Si le bot se retouve éloigné de son point de visée, il s'y repositionne
            VectorSubtract (nodes[bobot->current_node].origin, bobot->client->ps.origin, dir);
            dist = VectorLength(dir);
            if (dist > nodes[bobot->current_node].radius)
            {    VectorCopy(nodes[bobot->current_node].origin,bobot->client->ps.origin );
                BOBOT_Move(bobot,ucmd);
            }
            //Sniper war on prend le weapon scope
            if(g_SniperWar.integer)
            {    if(weapon1==WP_GARAND)
                {    if (bobot->client->ps.ammoclip[BG_FindClipForWeapon(WP_GARAND)]>0)
                        ucmd->weapon = WP_GARAND_SCOPE;
                    else
                        BOBOT_COMMON_TakeBestWeapon(bobot,bobotAI->myteam,ucmd);
                }
                else if(weapon1==WP_K43)
                {    if (bobot->client->ps.ammoclip[BG_FindClipForWeapon(WP_K43)]>0)
                        ucmd->weapon = WP_K43_SCOPE;
                    else
                        BOBOT_COMMON_TakeBestWeapon(bobot,bobotAI->myteam,ucmd);
                }
            }
        }
    }
    else if (bobotAI->BlokedNode != INVALID)
    {    if(level.time > bobotAI->TimeNode)                                            // reset au bout de 5 secondes.
        {    NodePris[bobotAI->BlokedNode]=0;
            bobotAI->BlokedNode = INVALID;
        }
    }

    /*    pav: Forcer le jump pour certains cas compliqués. */
    if (nodes[bobot->current_node].type & NODE_JUMP)
    {    ucmd->upmove = 127;    }

    /*    pav: Bobot Duck sur une node duck */
    if (nodes[bobot->current_node].type & NODE_DUCK)
    {    ucmd->upmove = -48;    }
       
    /*     pav: On appelle un ingénieur si on est sur un node ou il en faut un.*/
    if (bobot->client->sess.playerType != PC_ENGINEER)
    {    if(random()<0.008)
        {    if((nodes[bobot->current_node].type & NODE_CONSTRUCTION) && (nodes[bobot->current_node].type & inmyteam))
            { G_Voice( bobot, NULL, SAY_TEAM, "NeedEngineer", qtrue); }
            if((nodes[bobot->current_node].type & NODE_DYNAMITE) && !(nodes[bobot->current_node].type & inmyteam))
            {    G_Voice( bobot, NULL, SAY_TEAM, "DestroyConstruction", qtrue); }
        }
    }
}

/*--------------------------------------------------------------------------------------
l'IA recoit des message de l'extérieur (ici une dynamite planté près d'un objectif)
PAV : G_weapon.c (mofifié par moi, ce n'est pas ent mais tracent qu'on passe en param).
----------------------------------------------------------------------------------------*/
void BOBOT_DynamitePlanted (gentity_t *ent,qboolean planted,team_t team)
{    int            i,nodepres;
    gclient_t    *cl;
    Sbobot        *bobotAI;
    float        min,dist;
    vec3_t        diff;

    min = 999999.0;
    nodepres = -1;

    /*    Trouve le node le plus proche de la dynamite plantée */
    for (i=0 ; i< number_of_nodes ; i++)
    {    if (nodes[i].type & NODE_DYNAMITE)
        {    VectorSubtract(nodes[i].origin,ent->r.currentOrigin,diff);
            dist = VectorLength(diff);
            if (dist < min)
            {    nodepres = i;
                min=dist;
            }
        }
    }
    if (nodepres != INVALID)
    {    if(team >= 0)
        {    switch(team)
            {    case 1 :
                    G_Printf("\nAxis team dynamite planted at node %i ",nodepres);
                    break;
                case 2 :
                    G_Printf("\nAllied team dynamite planted at node %i ",nodepres);
                    break;
                default :
                    G_Printf("\nDynamite planted at node %i ",nodepres);
                    break;
            }
        }

        /*    Trouve tous les bots ingénieurs et les envoie sur la dynamite plantée. */
        for ( i=0 ; i< g_maxclients.integer ; i++ )
        {    cl = level.clients + i;
            if ( cl->pers.connected != CON_CONNECTED )
            { continue;    }
            if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) )
            { continue;    }
            if ( (team >= 0) && (cl->sess.sessionTeam != team) && (cl->sess.playerType == PC_ENGINEER))
            {    bobotAI = &bobots[cl->ps.clientNum];
                BOBOT_AI_proposeAction(bobotAI,GO_TO_LR_GOAL,nodepres,NULL,NULL,BOBOT_DEFUSE_DYNAMITE_PRIORITY);
            }
        }
    }
}
 
/*-------------------------------------End part 2 look at part 3

Créer un site gratuit avec e-monsite - Signaler un contenu illicite sur ce site