Tracking units AI

Antal1987

Warlord
Joined
Sep 4, 2013
Messages
160
Location
Karelia, Russia
I've managed to find out how the game prodives an AI.
The following function use AI strategies flags and make units doing some things.

Code:
bool __thiscall class_Unit::Process_AI(class_Unit *this)
{
  class_Unit *_this; // esi@1
  int _Unit_TypeID; // ecx@11
  char _AI_Strategy; // al@11
  struct_UnitType *_Unit_Type; // ecx@11
  unsigned int _AI_Strategy2; // eax@13
  int v6; // eax@52

  _this = this;
  if ( !class_Unit::f51(this) )
  {
    if ( (1 << Civilizations[_this->Body.CivID].ID) & Global_Civ_Flags2 || !class_Unit::Check_Sacrifice(_this, 0) )
    {
      if ( _this->Body.CivID )
      {
        _Unit_TypeID = _this->Body.UnitTypeID;
        _AI_Strategy = LOBYTE(BIC_Data.UnitTypes[_Unit_TypeID].AI_Strategy);
        _Unit_Type = &BIC_Data.UnitTypes[_Unit_TypeID];
        if ( _AI_Strategy & UTAI_Offence )
        {
          class_Unit::AI_Offence(_this);
        }
        else
        {
          _AI_Strategy2 = _Unit_Type->AI_Strategy;
          if ( (_Unit_Type->AI_Strategy >> UTAIV_Defence) & 1 )
          {
            class_Unit::AI_Defence(_this);
          }
          else
          {
            if ( (_AI_Strategy2 >> UTAIV_Artillery) & 1 )
            {
              class_Unit::AI_Artillery(_this);
            }
            else
            {
              if ( (_AI_Strategy2 >> UTAIV_Explore) & 1 )
              {
                class_Unit::AI_Explore(_this);
              }
              else
              {
                if ( (_AI_Strategy2 >> UTAIV_Army) & 1 )
                {
                  class_Unit::AI_Army(_this);
                }
                else
                {
                  if ( (_AI_Strategy2 >> UTAIV_Cruise_Missile) & 1 )
                  {
                    class_Unit::AI_Cruise_Missile(_this);
                  }
                  else
                  {
                    if ( (_AI_Strategy2 >> UTAIV_Air_Bombard) & 1 )
                    {
                      class_Unit::AI_Air_Bombard(_this);
                    }
                    else
                    {
                      if ( (_AI_Strategy2 >> UTAIV_Air_Defence) & 1 )
                      {
                        class_Unit::AI_Air_Defence(_this);
                      }
                      else
                      {
                        if ( BYTE1(_AI_Strategy2) & 1 )// its 8 = UTAIV_Naval_Power
                        {
                          class_Unit::AI_Naval_Power(_this);
                        }
                        else
                        {
                          if ( (_AI_Strategy2 >> UTAIV_Air_Transport) & 1 )
                          {
                            class_Unit::AI_Air_Transport(_this);
                          }
                          else
                          {
                            if ( (_AI_Strategy2 >> UTAIV_Naval_Transport) & 1 )
                            {
                              class_Unit::AI_Naval_Transport(_this);
                            }
                            else
                            {
                              if ( (_AI_Strategy2 >> UTAIV_Naval_Carrier) & 1 )
                              {
                                class_Unit::AI_Naval_Carrier(_this);
                              }
                              else
                              {
                                if ( (_AI_Strategy2 >> UTAIV_Terraform) & 1 )
                                {
                                  class_Unit::AI_Terraform(_this);
                                }
                                else
                                {
                                  if ( (_AI_Strategy2 >> UTAIV_Settle) & 1 )
                                  {
                                    class_Unit::AI_Settle(_this);
                                  }
                                  else
                                  {
                                    if ( (_AI_Strategy2 >> UTAIV_Leader) & 1 )
                                    {
                                      class_Unit::AI_Leader(_this);
                                    }
                                    else
                                    {
                                      if ( (_AI_Strategy2 >> UTAIV_Tactical_Nuke) & 1 )
                                      {
                                        class_Unit::AI_Tactical_Nuke(_this);
                                      }
                                      else
                                      {
                                        if ( BYTE2(_Unit_Type->AI_Strategy) & 1 )// 0x10 = UTAIV_ICBM
                                        {
                                          class_Unit::AI_ICBM(_this);
                                        }
                                        else
                                        {
                                          if ( (_AI_Strategy2 >> UTAIV_Naval_Missile_Transport) & 1 )
                                          {
                                            class_Unit::AI_Naval_Missile_Transport(_this);
                                          }
                                          else
                                          {
                                            if ( (_AI_Strategy2 >> UTAIV_Flag_Unit) & 1 )
                                            {
                                              class_Unit::f52_Process_field_1A8_and_IDLS(_this, -1);
                                              class_Unit::Set_Unit_State(_this, 1);
                                            }
                                            else
                                            {
                                              if ( (_AI_Strategy2 >> UTAIV_King) & 1 )
                                                class_Unit::AI_King(_this);
                                              else
                                                class_Unit::Skip_Turn(_this);
                                            }
                                          }
                                        }
                                      }
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
      else
      {
        class_Unit::f53(_this);
      }
    }
    else
    {
      if ( !class_Unit::Check_Sacrifice(_this, 0) )
        class_Unit::Skip_Turn(_this);
      if ( f_Get_City_by_XY(_this->Body.X, _this->Body.Y) )
        class_Unit::Sacrifice(_this);
      else
        class_Unit::f55_Set_Unit_State(_this, UnitState_1D);
    }
  }
  v6 = class_Unit::Get_Default_Hit_Points(_this) - _this->Body.Damage;
  return v6 < 0 || v6 <= 9999 && !v6;
}

This code calls 19 single-referenced AI functions (one per AI flag except Flag_Unit). Each of these functions either set some unit state or does some action, like moving or attacking.

Unit state values affect on it's behaviour. Depending on the value a Unit can terraform a tile or exploring the land.

There is a function which process unit states and does appropriate action:
Code:
bool __thiscall class_Unit::m23_Process_Unit_State(class_Unit *this)
{
  class_Unit *v1; // esi@1
  signed int v2; // ebx@1
  int _Unit_State; // eax@1
  int v4; // edi@7
  int v5; // eax@7
  int v6; // edi@10
  int v7; // eax@10
  int v8; // eax@33
  char v9; // dl@45
  int v10; // edi@57
  int v11; // edi@62
  int v12; // eax@69
  bool result; // al@70

  v1 = this;
  v2 = 0;
  _Unit_State = this->Body.UnitState;
  if ( _Unit_State >= UnitState_Build_Mines && _Unit_State < UnitState_Intercept )
  {
    class_Unit::Do_Work(this, _Unit_State - 2);
    goto LABEL_69;
  }
  if ( _Unit_State == UnitState_Go_To )
  {
    if ( f_Is_Net_Game() && (1 << Civilizations[v1->Body.CivID].ID) & Global_Civ_Flags2 )
    {
      v4 = v1->Body.Moves;
      v5 = class_Unit::Get_Complete_Moves(v1);
      if ( f_BoundValue(v5 - v4, 0, 9999) )
      {
        do
        {
          if ( v1->Body.UnitState != UnitState_Go_To )
            break;
          if ( v2 >= 256 )
            goto LABEL_12;
          class_Unit::Do_Go_To_Movement(v1);
          v6 = v1->Body.Moves;
          ++v2;
          v7 = class_Unit::Get_Complete_Moves(v1);
        }
        while ( f_BoundValue(v7 - v6, 0, 9999) );
        if ( v2 < 256 )
          goto LABEL_69;
LABEL_12:
        v1->Body.Moves = class_Unit::Get_Complete_Moves(v1);
      }
    }
    else
    {
      class_Unit::Do_Go_To_Movement(v1);
    }
  }
  else
  {
    if ( _Unit_State == UnitState_Road_To_Tile || _Unit_State == UnitState_Railroad_To_Tile )
    {
      class_Unit::Do_Build_Road_To_Tile(this);
    }
    else
    {
      if ( _Unit_State == UnitState_Build_Colony )
      {
        class_Unit::Do_Auto_Build_Colony(this);
        goto LABEL_69;
      }
      if ( _Unit_State == UnitState_Auto_Irrigate )
      {
        class_Unit::Do_Auto_Irrigate(this);
        goto LABEL_69;
      }
      if ( _Unit_State == UnitState_Build_Trade_Routes )
      {
        class_Unit::Do_Build_Trade_Routes(this);
        goto LABEL_69;
      }
      if ( _Unit_State == UnitState_Auto_Clear_Forest )
      {
        class_Unit::Do_Auto_Clear_Forest(this);
        goto LABEL_69;
      }
      if ( _Unit_State == UnitState_Auto_Clear_Swamp )
      {
        class_Unit::Do_Auto_Clear_Swamp(this);
        goto LABEL_69;
      }
      if ( _Unit_State == UnitState_Auto_Clear_Pollution )
      {
        class_Unit::Do_Auto_Clear_Pollution(this);
        goto LABEL_69;
      }
      if ( _Unit_State == UnitState_Auto_Save_City_Tiles )
      {
        class_Unit::Do_Auto_Save_City_Tiles(this);
        goto LABEL_69;
      }
      if ( _Unit_State == UnitState_Explore )
      {
        if ( f_Is_Net_Game() && (1 << Civilizations[v1->Body.CivID].ID) & Global_Civ_Flags2 )
        {
          while ( 1 )
          {
            v8 = class_Unit::Get_Complete_Moves(v1) - v1->Body.Moves;
            if ( v8 < 0 )
              break;
            if ( v8 <= 9999 && !v8 )
              break;
            if ( v1->Body.UnitState != UnitState_Explore )
              break;
            class_Unit::f27(v1);
          }
        }
        else
        {
          class_Unit::f27(v1);
        }
      }
      else
      {
        if ( _Unit_State == UnitState_1B )
        {
          class_Unit::f54_Unit_State_1B(this);
          goto LABEL_69;
        }
        if ( _Unit_State == UnitState_1C )
        {
          if ( (this->vtable->m25)()
            || (class_Unit::Check_Ability(v1, UTA_Hidden_Nationality)
             || (BIC_Data.UnitTypes[v1->Body.UnitTypeID].AI_Strategy >> UTAIV_Settle) & 1 ? (v9 = 0) : (v9 = 1),
                !class_Leader::f24(&Civilizations[v1->Body.CivID], v1->Body.X, v1->Body.Y, 1, v9, 0, 1, 0, 81, 0)) )
            class_Unit::Set_Unit_State(v1, UnitState_Fortifying);
          else
            class_Unit::f83_Unit_State_1C_1D(v1);
        }
        else
        {
          if ( _Unit_State == UnitState_1D )
          {
            class_Unit::f83_Unit_State_1C_1D(this);
            goto LABEL_69;
          }
          if ( _Unit_State == UnitState_1E )
          {
            class_Unit::f84_Unit_State_1E(this);
            goto LABEL_69;
          }
          if ( _Unit_State == UnitState_Auto_Bombard )
          {
            class_Unit::f85_Auto_Bombard(this);
            goto LABEL_69;
          }
          if ( _Unit_State == UnitState_Auto_Air_Bombard )
          {
            v10 = f_Get_Tile_OccupantID(this->Body.field_1B0[5], this->Body.field_1B0[6], -1, 1);
            if ( class_Unit::f86_Check_Auto_Air_Bombard(v1, v1->Body.field_1B0[5], v1->Body.field_1B0[6])
              && (v10 == -1 || Civilizations[v1->Body.CivID].At_War[v10]) )
            {
              class_Unit::Bombard(v1, v1->Body.field_1B0[5], v1->Body.field_1B0[6]);
              goto LABEL_69;
            }
          }
          else
          {
            if ( _Unit_State == UnitState_21 )
            {
              v11 = f_Get_Tile_OccupantID(this->Body.field_1B0[5], this->Body.field_1B0[6], -1, 1);
              if ( class_Unit::f87_Check_State_21(v1, v1->Body.field_1B0[5], v1->Body.field_1B0[6])
                && (v11 == -1 || Civilizations[v1->Body.CivID].At_War[v11]) )
              {
                class_Unit::f23_Bombard_Tile(v1, v1->Body.field_1B0[5], v1->Body.field_1B0[6]);
                goto LABEL_69;
              }
            }
            else
            {
              if ( _Unit_State != UnitState_22 )
                goto LABEL_69;
            }
          }
          class_Unit::Set_Unit_State(v1, 0);
        }
      }
    }
  }
LABEL_69:
  v12 = class_Unit::Get_Default_Hit_Points(v1) - v1->Body.Damage;
  if ( v12 >= 0 )
  {
    if ( v12 > 9999 )
      v12 = 9999;
    result = v12 == 0;
  }
  else
  {
    result = 1;
  }
  return result;
}

Now I've created logging subsystem in my patch framework to trace unit actions (it's not only for units: actually, cities and civilizations are available to be traced too). Download

The patch framework creates "C3CPF.log" and writes log messages there.

Now I have some unknown unit state types. So I'm looking for some volunteers. The task is pretty simple. Log contains info about unit states used to be changed by different unit actions including AI actions. And there are some unrecognized state types. I need more information about which unit types are getting these states and when.

Play just as usual, use different scenarios, involve unit types with different AI strategy flags. My PF will create "C3CPF.log" file with content like this:
Code:
2014-06-11 09:22:58	[Unit AI]	Unit #221 (Worker) of Civ #3 (Inca) at [23, 47]; Action: AI_Terraform
2014-06-11 09:22:58	[Unit State]	Unit #221 (Worker) of Civ #3 (Inca) at [23, 47] changed state from 0 (Normal) to 29 (State_29)
2014-06-11 09:22:58	[Unit State]	Unit #221 (Worker) of Civ #3 (Inca) at [23, 47] changed state from 29 (State_29) to 1 (Fortifying)
The sample shows that one of unrecognized state types (State_29) is set after AI_Terraform action is run. Why? I don't know.

But the more information we collect the more we can inderstand about AI. That could be very useful in fixing submarine bug and other AI misbehavoiurs.

It also can be useful in scenarios tests.

Logging can be turned off. I've created an INI file to store some settings. For now it has only 2 of them:
Code:
[main]
log=true
log_time=true

Important thing is that now you can use different settings for the game
 
There is some explanation about using logging subsystem. Let's return to submarine bug.

Here is the origin situation:


Galeon (Inca) has coordinates: [28, 70]. It's not fortified
Submarine (Russia) has coordinates: [29, 71]. It's fortified.

After I skip turn, Inca declares war, and the Galeon attacks the Submarine.

Here is the log:
Code:
2014-06-11 09:56:29	[Unit AI]	Unit #6 (Galeon) of Civ #3 (Inca) at [28, 70]; Action: AI_Naval_Transport
2014-06-11 09:56:29	[Unit State]	Unit #6 (Galeon) of Civ #3 (Inca) at [28, 70] changed state from 0 (Normal) to 26 (Explore)
2014-06-11 09:56:29	[Move Unit]	Unit #6 (Galeon) of Civ #3 (Inca) moving from [28, 70] to [29, 71]
2014-06-11 09:56:30	[Unit Attack Tile]	Unit #6 (Galeon) of Civ #3 (Inca) attacking tile from [28, 70] to [29, 71]
2014-06-11 10:17:47	[Unit Loses]	Unit #6 (Galeon) of Civ #3 (Inca) at [28, 70] was defeated by Unit #9 (Submarine) of Civ #1 (Russia) at [29, 71]
2014-06-11 09:56:35	[Delete Unit]	Unit #6 (Galeon) of Civ #3 (Inca) destroyed by Civ #1 (Russia) at [28, 70]

Now I know the game takes decision to move the Galeon through the Submarine (It thinks the tile [29, 71] is empty because of invisibility) while processing AI_Naval_Transport action and it uses "Explore" state.
 
Ah ok, and using this information you can later change the unit behaviour?

Not only change, extend too! We can even design new AI posibilities. I've already rewritten the only method that controls AI actions. So it can be extended with new AI flags and Unit State types. It's very promisingly.

I'm also trying to understand cities and civilizations tactics. It's actions are also documentable or rewritable.
 
Incoming smarter AI that can finally use artillery to ruin your day :devil:. Also I wonder if this can be used to make land mines and sea mines useable by the AI to defend certain spots instead of how they mindlessly use them or not use them at all right now. Do you think the code could be altered for a unit to detonate like a cruise missile when its being attacked, instead of dying like if it was a defender? Causing x amount of damage to a unit when it explodes while defending? It would make land mines or mine based concepts work very nice. (Basically a suicide unit combining cruise+defending instead of the current Cruise+Bombard, or even Cruise+Attack sounds nice like the cruise missiles in civ 2)

Oh and by the way im not asking for any of that lol. Im just using my imagination because im curious to know how far this can go, and trying to get a clear understanding about the logging system. Is any of that possible?
 
Oh and by the way im not asking for any of that lol. Im just using my imagination because im curious to know how far this can go, and trying to get a clear understanding about the logging system. Is any of that possible?

I think many options are possible... Logging system is designed to give us hidden information about game construction so we could test any possible or even impossible scenario.
 
For things like the terraform there are some scenarios / mods that give worker functions to other types of units. In RFRE the Legio Imperatoria can do some of the worker and settler tasks - including building roads, clearing forest, and founding settlements - while also being an effective combat unit. Even though that unit is only available to the player, this still might be a good choice for comparison since there are also standard workers (Servus) and settlers (Tribus Migrans) available to the AI.

Is the patch cross-platform (work with the Mac version of C3)?
 
Maybe Quintullus could adapt some of his fixes to work on the Mac. I just wish that he could change the code for the Resources to allow for more strategic and luxury resources.
 
Back
Top Bottom