In this post we'll delve a bit into creating a simple AI for our enemy factions, it won't be a challenging one and it will need lots of updating and modifying afterwards depending on what you want to achieve, and how hard you want it to be. We will also make some other additions to our code for some visuals we haven't used yet.
➤ Unit selection information
We have on our UI side bar a place that was created to hold information for selected unit that we haven't implemented into our code yet. In the image below you can see where the values will be displayed.
Since we have created the unit selection system and these values are assigned when a unit is created through our unit library, we'll just display this information on our UI.
Add the following code on our UI render, (I added this after 'main UI overlap part)
'Unit description
DrawText(Lib_Construct_S(Faction(0).Unit(UnitSelected).ID).Name, 868 *
(ScreenWidth / UISizeW), 368 * (ScreenHeight / UISizeH), 1.1, 1, 0.8,
0.8, 0.8, 1) 'Unit name
If Faction(0).Unit(UnitSelected).ID < 18 Then
DrawText("Ground", 868 * (ScreenWidth / UISizeW), 388 * (ScreenHeight / UISizeH), 1.1, 1, 0.8, 0.8, 0.8, 1) 'Unit type
ElseIf Faction(0).Unit(UnitSelected).ID < 18 Then
DrawText("Air", 868 * (ScreenWidth / UISizeW), 388 * (ScreenHeight / UISizeH), 1.1, 1, 0.8, 0.8, 0.8, 1) 'Unit type
ElseIf Faction(0).Unit(UnitSelected).ID < 21 Then
DrawText("Sea", 868 * (ScreenWidth / UISizeW), 388 * (ScreenHeight / UISizeH), 1.1, 1, 0.8, 0.8, 0.8, 1) 'Unit type
End If
DrawText("Hit points : " & Faction(0).Unit(UnitSelected).Health,
835 * (ScreenWidth / UISizeW), 408 * (ScreenHeight / UISizeH), 1.1, 1,
0.8, 0.8, 0.8, 1) 'Unit hit points
DrawText("Att/Def : " & Faction(0).Unit(UnitSelected).Attack &
"/" & Faction(0).Unit(UnitSelected).Defence, 835 * (ScreenWidth /
UISizeW), 428 * (ScreenHeight / UISizeH), 1.1, 1, 0.8, 0.8, 0.8, 1) 'Unit attack and defence
DrawText("Turn points : " &
Faction(0).Unit(UnitSelected).TurnPoints, 835 * (ScreenWidth / UISizeW),
448 * (ScreenHeight / UISizeH), 1.1, 1, 0.8, 0.8, 0.8, 1) 'Unit attack and defence
'Drawing unit image
sRect = New Rectangle(Faction(0).Unit(UnitSelected).ID * 64, 0, 64, 56) 'Y * 56
dRect = New Rectangle(828 * (ScreenWidth / UISizeW), 364 *
(ScreenHeight / UISizeH), 35 * (ScreenWidth / UISizeW), 35 *
(ScreenHeight / UISizeH))
DrawTexturedQuad(sRect, dRect, 1472, 448, 1, 1, 1, 1, 1, 1)
By adding this part of code you should see the result as shown on the photo below. When pressing space or whichever button you have assigned to switch between units the information on the UI will update.
➤ Adding research and construction libraries to all enemy factions
In order to add the other factions, we need to change some of our code that we created before, since the libraries where made with no index for enemy factions and they were created and handled, only for the player.
First we need to use the find and replace code and change this
Lib_Research_S( to Lib_Research_S(0,
and replace on the declaration of the library the 0 with maxfactions
Public Lib_Research_S(MaxFactions, 28) As Library_Research_S
With this simple change we add an extra index that will hold the research data for all factions.
Let's create an extra loop on library creation and replace the 0 in there.
Our sub was like this : Public Sub Game_Library_Construct(ByVal Func As String, ByVal X As Integer, ByVal FactionID As Integer), we just had not used the factionID value till now.
So let's change inside that sub Lib_Research_S(0, with Lib_Research_S(FactionID,
After that we'll search all calls to this sub which are the following and change these calls.
Inside FACTION_F > END_TURN_STEPS change it to the following
Game_Library_Construct("REFRESH_RESEARCH", Faction(I).CurResearchID, I)
Inside Scene_Build replace it with the following loop
Dim I As Int16
For I = 0 To MaxFactions
Game_Library_Construct("LOAD", 0, I)
Next
Now the research library initializes and gets refreshed for all factions. We should repeat this process for our construction libraries now too. After that we should be able to start working on the logic algorithm which our enemies will use.
For the construction library we follow the same process.
Where Lib_Construct_S( we replace it with Lib_Construct_S(0,
Our declaration of Lib_Construct should be changed to hold maxfactions instead of 0
Public Lib_Construct_S(MaxFactions, 36) As Library_Constructions_S
Inside our Game_Library_Construct sub we replace
Lib_Construct_S(0 with Lib_Construct_S(FactionID
➤ Adding movement functions to enemy factions
Now that we added the libraries to all factions, we will add an index inside Unit_F for our movement so we can use the sub for enemies.
The unit_F sub will now be like this : Public Sub Unit_F(ByVal Func As String, ByVal ID As Integer, ByVal FactionID As Integer)
And we replace inside it
Faction(0) with Faction(FactionID)
Now go through the error list through wherever Unit_F is called and add a ,0) at the end of the call. Like the example below (including the references)
Unit_F("MOUSE_HOVER_UNIT", UnitSelected, 0)
Will need to add it on these calls
GLMouseMove (Change it to ,0)
GLMouseDown (Change it to ,0)
Faction_F (Change it to ,0)
Unit_M (change it to ,Y)
Create_Unit (change it to ,FactionID)
The above is the reason why we should add references at the start of our code or make our code in a way it can be easily manipulated based on what we want to do in the future. When the code gets spaghetti everything gets harder to change.
➤ Creating a rudimentary AI
Creating a proper AI is not an easy process, but in any case we need to have a base for it. So we'll create a chart which will represent the basic process of AI action flow. When this is made you can add more clauses and results, that they lead to. Here we'll work the AI like the image below. Simple research and building assigning, moving around and if there is an enemy sighted, attack. Doesn't make for a hard opponent but at least it's an opponent.
Change the Mousedown event to the following, cause it will bug out in the process and move your units too while enemy is playing.
Public Sub GLMouseDown(ByVal Btn As MouseEventArgs)
If TurnChange = False Then
If Btn.Button = MouseButtons.Left Then
If SButtonHover <> 3 Then Unit_F("MOUSE_MOVE_UNIT", UnitSelected, 0)
GameButtons("CLICK")
End If
End If
End Sub
Now let's move on to implementing our new code.
First add these declarations inside our unit struct
Dim DamageSeq As Int16 'This is the sequence for the damage animation
Dim DamageApl As Single 'This is how much damage will be applied
We'll add the following subs to our code. And I'll explain over each one what they are.
The following sub is just to apply damage. We will hold the damage on the unit that receives it.
Public Sub ApplyDamage(ByVal FactionID As Integer, ByVal UnitID As Integer, ByVal TargetFaction As Integer, ByVal TargetUnit As Integer)
Faction(TargetFaction).Unit(TargetUnit).DamageApl = Faction(FactionID).Unit(UnitID).Attack
Faction(TargetFaction).Unit(TargetUnit).DamageSeq = 20
End Sub
The next sub is our actual logical sequence to move units, build and research that our enemy factions will follow. It's based on the above image with our chart.
Public FactionResearch(MaxFactions) As Integer
Public Sub EnemyFactions()
Dim I, Q, L As Int16
Dim RndN As Integer
Dim RndTile As Integer
'Dim MoveLoop As Integer
Dim CounterT As Integer = 100
Dim RndConstructArray(25) As Int16
Dim ConstructArrayMax As Int16
'(A) /////////////////////Loop through factions/////////////////////////////////////
For I = 1 To MaxFactions
'(C)+(D) Assign available constructions inside a random array
ConstructArrayMax = 0
For Q = 1 To 36
If Lib_Construct_S(I, Q).Unlocked = 1 Then
ConstructArrayMax += 1
RndConstructArray(ConstructArrayMax) = Q
End If
Next
'(B) /////////////////////Assign Research if there is none/////////////////////////////////////
If Faction(I).CurResearchID = 0 Then 'If there is no research at the moment
For Q = 0 To 28 'Loop through the research library and pick the first available
If Lib_Research_S(I, Q).Researched = 0 And Lib_Research_S(I, Q).Available = 1 Then
Faction(I).CurResearchID = Q
Faction(I).CurResearchTurns = Int(Lib_Research_S(I, Q).Turns - Lib_Research_S(I, Q).Turns * (Faction(I).STLibrary.BasicB(1) + Faction(0).STLibrary.BasicB(4) + Faction(0).STLibrary.BasicB(7)) / 100)
Exit For
End If
Next
Else 'If there is a research do nothing
End If
'(C)+(D) /////////////////////Assign construction to cities//////////////////////////////////////
For Q = 0 To MaxCitiesPF 'Loop through city indices
If Faction(I).City(Q).Enabled = 1 Then 'There is a city built on this index
If Faction(I).City(Q).ConstructionID = 0 Then 'The city is not building anything
RndN = Int(Rnd() * ConstructArrayMax) + 1 'Get a random value
Faction(I).City(Q).ConstructionID = RndConstructArray(RndN) 'Pick from the available constructions array random value to build
Faction(I).City(Q).ConstructTurns = Int(Lib_Construct_S(I, RndConstructArray(RndN)).Turns - Lib_Construct_S(I, RndConstructArray(RndN)).Turns * (Faction(I).STLibrary.BasicB(6) + Faction(I).STLibrary.BasicB(4)) / 100)
'MsgBox(RndN & " , " & " max = " & ConstructArrayMax & " /" & RndConstructArray(0) & RndConstructArray(1) & RndConstructArray(2))
End If
End If
Next
'(E) /////////////////////Loop through units/////////////////////////////////////
For Q = 0 To MaxUnitsPF
If Faction(I).Unit(Q).ID < 1 Then
'No unit
Else 'There is a unit
If Faction(I).Unit(Q).ID < 2 Then 'Settler
RndN = Int(Rnd() * 10)
UnitSelected = Q
CounterT = 100
Do Until CounterT = 0
If RndN <= 8 Then
Unit_F("MOVE_CALCULATE", Q, I) 'Calculate available tiles
RndTile = Int(Rnd() * 8)
Do Until Faction(I).Unit(Q).TileToMA(RndTile) = True
RndTile = Int(Rnd() * 8)
Loop
UnitSelectedMouseHover = RndTile
Unit_F("MOUSE_MOVE_UNIT", Q, I) 'Move unit
Else 'Create a city
For L = 0 To MaxCitiesPF
If Faction(I).City(L).Enabled = 0 Then
City_M("CREATE", L, I, Faction(I).Unit(UnitSelected).TileX, Faction(I).Unit(UnitSelected).TileY)
Exit For
End If
Next
Exit Do
End If
CounterT -= 1
Loop
ElseIf Faction(I).Unit(Q).ID >= 2 Then
Unit_FOV_Check = 0
Get_Unit_FOV(I, Q)
UnitSelected = Q
CounterT = 100
If Unit_FOV_Check = 0 Then
Do Until CounterT = 0
Unit_F("MOVE_CALCULATE", Q, I) 'Calculate available tiles
RndTile = Int(Rnd() * 8)
Do Until Faction(I).Unit(Q).TileToMA(RndTile) = True
RndTile = Int(Rnd() * 8)
Loop
UnitSelectedMouseHover = RndTile
Unit_F("MOUSE_MOVE_UNIT", Q, I) 'Move unit
CounterT -= 1
Loop
ElseIf Unit_FOV_Check = 1 Then
Do Until CounterT = 0
Unit_F("MOVE_CALCULATE", Q, I) 'Calculate available tiles
If Unit_FOV_Direction < 8 Then
UnitSelectedMouseHover = Unit_FOV_Direction
Unit_F("MOUSE_MOVE_UNIT", Q, I) 'Move unit
Else 'Unit_FOV_Direction = 8
If Faction(I).Unit(Q).TurnPoints > 0 Then
ApplyDamage(I, Q, TargetF, TargetU)
CounterT = 1
End If
End If
CounterT -= 1
Loop
End If
End If
End If
Next
Next
End Sub
The next sub is to check tiles, available positions to move and checking for enemies that AI controlled units will follow.
Public Unit_FOV_Check As Int16
Public Unit_FOV_Direction As Int16
Public TempEnemyL As Int16
Public TargetF, TargetU As Int16
Private Sub Get_Unit_FOV(ByVal FactionID As Integer, ByVal UnitID As Integer)
Dim X, Y As Integer
Dim I, Q, L As Integer
Dim Xto, Yto As Int16
For Y = (Faction(FactionID).Unit(UnitID).TileY - 2) To (Faction(FactionID).Unit(UnitID).TileY + 2)
For X = (Faction(FactionID).Unit(UnitID).TileX - 2) To (Faction(FactionID).Unit(UnitID).TileX + 2)
If X >= 0 And X <= MaxGridX And Y >= 0 And Y <= MaxGridY Then
For I = 0 To MaxFactions
For Q = 0 To MaxUnitsPF
If Faction(I).Unit(Q).TileX = X And Faction(I).Unit(Q).TileY = Y And I <> FactionID Then
Unit_FOV_Check = 1
Xto = Faction(I).Unit(Q).TileX - Faction(FactionID).Unit(UnitID).TileX
Yto = Faction(I).Unit(Q).TileY - Faction(FactionID).Unit(UnitID).TileY
If Xto > 0 And Yto = 0 Then
Unit_FOV_Direction = 0
ElseIf Xto > 0 And Yto > 0 Then
Unit_FOV_Direction = 1
ElseIf Xto = 0 And Yto > 0 Then
Unit_FOV_Direction = 2
ElseIf Xto < 0 And Yto > 0 Then
Unit_FOV_Direction = 3
ElseIf Xto < 0 And Yto = 0 Then
Unit_FOV_Direction = 4
ElseIf Xto < 0 And Yto < 0 Then
Unit_FOV_Direction = 5
ElseIf Xto = 0 And Yto < 0 Then
Unit_FOV_Direction = 6
ElseIf Xto > 0 And Yto < 0 Then
Unit_FOV_Direction = 7
End If
For L = 0 To 7
If Faction(I).Unit(Q).TileX = Faction(FactionID).Unit(UnitID).TileToM(L).X And Faction(I).Unit(Q).TileY = Faction(FactionID).Unit(UnitID).TileToM(L).Y Then
Unit_FOV_Direction = 8
TempEnemyL = L
TargetF = I
TargetU = Q
Exit For
End If
Next
End If
Next
Next
End If
Next
Next
End Sub
That's it for a basic AI, now when you run it you'll figure out that it has lots of problems that need to be fixed. Problems like the enemy faction constructs too many settler units or sea units are built even though the city is not next to water. The base is there for the logical sequence our enemy will use.
The last bit of code for the damage to show we'll add the following under our unit drawing part of the code.
'Damage
If Faction(Y).Unit(X).DamageSeq > 0 And Faction(Y).Unit(X).DamageApl <> 0 Then
Faction(Y).Unit(X).DamageSeq -= 1
sRect = New Rectangle(560 + (5 - Faction(Y).Unit(X).DamageSeq \ 4) * 49, 461, 49, 52)
dRect = New Rectangle(Faction(Y).Unit(X).X + OffSetX + 4, Faction(Y).Unit(X).Y + OffSetY - 22 * ZoomVar, 49 * ZoomVar, 52 * ZoomVar)
DrawTexturedQuad(sRect, dRect, 1020, 548, 3, 1.0F, 1, 1, 1, 1)
ElseIf Faction(Y).Unit(X).DamageSeq <= 0 And Faction(Y).Unit(X).DamageApl <> 0 Then
Faction(Y).Unit(X).Health -= Faction(Y).Unit(X).DamageApl
Faction(Y).Unit(X).DamageApl = 0
End If
We now should have enemy factions that build move and fight. On our next post we'll add the battle system to our player system too, since by now only enemy factions can initiate a battle.
Comments
Post a Comment