Strategy game, Battle system, statistics (Post #23, Strategy #14)

At our last post we went on to the creation of a basic AI system, and sequenced it to attack if another unit is on sight. For this post we'll implement the battle initiation to our player, add some more visuals, fix some bugs and start working on other windows like statistics.

➤ Additions on UI

Since we add something in almost every post of this series, let's do some more visual additions here too. I noticed we don't have a city size indicator on our map, I added one on the UI where we'll display over it the size and name. Add a bit of transparency on it so it looks a bit faded.

Changed the code inside the city drawing loop to the following

            For Y = 0 To MaxFactions
                For X = 0 To MaxCitiesPF
                    If Faction(Y).City(X).Enabled = 1 Then
                        sRect = New Rectangle(625, 281, 96, 16)
                        dRect = New Rectangle(GridWorld(Faction(Y).City(X).TileX, Faction(Y).City(X).TileY).SX + OffSetX - 44 * ZoomVar, GridWorld(Faction(Y).City(X).TileX, Faction(Y).City(X).TileY).SY + OffSetY - 30 * ZoomVar, 140 * ZoomVar, 20 * ZoomVar)
                        DrawTexturedQuad(sRect, dRect, 1120, 720, 3, 1.0F, 1, 1, 1, 0.7)
                        Dim CityPopulation As Integer = (51 + (Faction(Y).City(X).Population \ 200))                         TexY = CityPopulation \ 17
                        TexX = CityPopulation - TexY * 17
                        DrawText((CityPopulation - TexY * 17) + 1, GridWorld(Faction(Y).City(X).TileX, Faction(Y).City(X).TileY).SX + OffSetX - 38 * ZoomVar, GridWorld(Faction(Y).City(X).TileX, Faction(Y).City(X).TileY).SY + OffSetY - 24 * ZoomVar, 1.2 * ZoomVar, 1, FactionColors(Y).R, FactionColors(Y).G, FactionColors(Y).B, 1)
                        DrawText(Faction(Y).City(X).Name, GridWorld(Faction(Y).City(X).TileX, Faction(Y).City(X).TileY).SX + OffSetX - 12 * ZoomVar, GridWorld(Faction(Y).City(X).TileX, Faction(Y).City(X).TileY).SY + OffSetY - 24 * ZoomVar, 1.2 * ZoomVar, 1, FactionColors(Y).R, FactionColors(Y).G, FactionColors(Y).B, 1)
                        If TexX <= 6 Then
                            sRect = New Rectangle(TexX * 64, TexY * 40, 64, 40)
                        Else
                            sRect = New Rectangle(6 * 64, TexY * 40, 64, 40)
                        End If
                        dRect = New Rectangle(GridWorld(Faction(Y).City(X).TileX, Faction(Y).City(X).TileY).SX + OffSetX, GridWorld(Faction(Y).City(X).TileX, Faction(Y).City(X).TileY).SY + OffSetY - 8 * ZoomVar, 64 * ZoomVar, 40 * ZoomVar)
                        DrawTexturedQuad(sRect, dRect, 1088, 160, 0, 1.0F, 1, 1, 1, 1)
                    End If
                Next
            Next

You'll also notice that I changed the population division for size to 100 instead of 20000 which was way too much.

Now that there is a placeholder for our city size and name, let's also display our turn number and date on our main UI side bar.

Just add the following (inside our UI drawing code), which checks which turn we are on, and translates it to a date.

Dim GameTimeToDate As String
            If GameTime < 200 Then
                GameTimeToDate = (200 - GameTime) * 10 & " BC"
            Else
                GameTimeToDate = (GameTime) * 10 & " AD"
            End If
            DrawText(GameTimeToDate, 844 * (ScreenWidth / UISizeW), 150 * (ScreenHeight / UISizeH), 1.0, 1, 0.8, 0.8, 0.8, 1)
            DrawText(GameTime, 912 * (ScreenWidth / UISizeW), 150 * (ScreenHeight / UISizeH), 1.0, 1, 0.8, 0.8, 0.8, 1)

You should see now on the top of our side bar right under the minimap these values.

Now in order to make our factions more recognizable on the map, I created separate outlined background sheets for units with different colors based on our faction colors and changed the unit outline code to draw based on their indexes. Code and result below. (Also changed the factioncolors sprite index to 10)

Changed this (Inside drawing units code)

DrawTexturedQuad(sRect, dRect, 1472, 448, 5, 1, 1 - FactionColors(Y).R, 1 - FactionColors(Y).G, 1 - FactionColors(Y).B, 0.7) 

To this

DrawTexturedQuad(sRect, dRect, 1472, 448, 5 + Y, 1, 1, 1, 1, 0.75)


Now we have units and cities easily recognizable by color.

➤Fixing unit selection

Now, to fix some things first of which was that after enemy faction finished turn, you had to press space button to select your unit. (Add the following and the end of END_TURN_STEPS code.

If UnitSelected < MaxUnitsPF Then
                    If Faction(0).Unit(UnitSelected + 1).ID <> 0 Then
                        UnitSelected += 1
                    Else
                        UnitSelected = 0
                    End If
                Else
                    UnitSelected = 0
                End If

On pressing space button there was a bug that caused to choose over maximum units, so the above code should be the one that is in button click event too.

➤ Player battle initiation

 Added a declaration to hold a state where when mouse hovers over an available moving tile, that tile has an enemy unit on it.

Public UnitSelectedEnemyHover As Integer 'Direction Mouse Hovers over has an enemy unit

Change the code inside "MOUSE_HOVER_UNIT" to the following

Dim HoveringOver As Int16 = 0
            Dim EnemyHover As Int16 = 10
            For Q = 0 To 7
                If GridPX = Faction(FactionID).Unit(ID).TileToM(Q).X And GridPY = Faction(FactionID).Unit(ID).TileToM(Q).Y And Faction(FactionID).Unit(ID).TileToMA(Q) = True Then
                    UnitSelectedMouseHover = Q
                    HoveringOver = 1
                    For Y = 1 To MaxFactions
                        For X = 0 To MaxUnitsPF
                            If Faction(Y).Unit(X).TileX = GridPX And Faction(Y).Unit(X).TileY = GridPY  And Faction(Y).Unit(X).ID <> 0 Then
                                EnemyHover = Q

                                TargetF = Y
                                TargetU = X
                            End If
                        Next
                    Next
                    Exit For
                End If
                EnemyHover = 10
            Next
            UnitSelectedEnemyHover = EnemyHover
            If HoveringOver = 0 Then
                UnitSelectedMouseHover = 10
            End If

Where the addition is that when we check available tiles to move we also check if there is a unit there and hold the available tile index.

Inside the Z loop we have on our drawing units code add the following

If UnitSelectedEnemyHover = Z Then
                                            sRect = New Rectangle(1216, 392, 64, 56) 'Y * 56
                                            DrawTexturedQuad(sRect, dRect, 1472, 448, 5 + Y, 1.1, 1, 1, 1, 1) 'GridWorld(Faction(Y).Unit(X).TileX, Faction(Y).Unit(X).TIleY).Z
                                            DrawTexturedQuad(sRect, dRect, 1472, 448, 1, 1.1, 1, 1, 1, 1) 'GridWorld(Faction(Y).Unit(X).TileX, Faction(Y).Unit(X).TIleY).Z
                                        End If

To draw the battle initiation icon.

Now to initiate a battle, we'll go to mouse click.

ElseIf Func = "MOUSE_MOVE_UNIT" Then
            If UnitSelectedMouseHover <> 10 And Faction(FactionID).Unit(UnitSelected).OnTheMove = 0 And Faction(FactionID).Unit(UnitSelected).TurnPoints > 0 And Faction(FactionID).Unit(UnitSelected).ID <> 0 And UnitSelectedEnemyHover = 10 Then
                Faction(FactionID).Unit(UnitSelected).TargetX = GridWorld(Faction(FactionID).Unit(UnitSelected).TileToM(UnitSelectedMouseHover).X, Faction(FactionID).Unit(UnitSelected).TileToM(UnitSelectedMouseHover).Y).SX
                Faction(FactionID).Unit(UnitSelected).TargetY = GridWorld(Faction(FactionID).Unit(UnitSelected).TileToM(UnitSelectedMouseHover).X, Faction(FactionID).Unit(UnitSelected).TileToM(UnitSelectedMouseHover).Y).SY
                Faction(FactionID).Unit(UnitSelected).OnTheMove = 1
                Faction(FactionID).Unit(UnitSelected).TileToMC = UnitSelectedMouseHover
                Faction(FactionID).Unit(UnitSelected).TurnPoints -= 1
            ElseIf UnitSelectedMouseHover <> 10 And Faction(FactionID).Unit(UnitSelected).OnTheMove = 0 And Faction(FactionID).Unit(UnitSelected).TurnPoints > 0 And Faction(FactionID).Unit(UnitSelected).ID <> 0 And UnitSelectedEnemyHover <> 10 Then
                If Faction(FactionID).Unit(UnitSelected).TurnPoints > 0 Then
                    ApplyDamage(FactionID, UnitSelected, TargetF, TargetU)
                    Faction(FactionID).Unit(UnitSelected).TurnPoints -= 1
                End If
            End If


Now that we can initiate a battle by clicking on that enemy unit tile, let's go back to our rendering code where we draw the damage visuals, and we remove the attack damage from the health and add the following to remove the unit if it takes too much damage.

If Faction(Y).Unit(X).Health <= 0 Then
                            Faction(Y).Unit(X).ID = 0
                        End If

While doing so I found a bug in which the enemy units where going after dead units so I modified the enemy code a bit inside Get_Unit_Fov sub

If Faction(I).Unit(Q).TileX = X And Faction(I).Unit(Q).TileY = Y And I <> FactionID Then

Changed to

If Faction(I).Unit(Q).TileX = X And Faction(I).Unit(Q).TileY = Y And I <> FactionID And Faction(I).Unit(Q).ID <> 0 Then

In order for enemy units to not chase units that had their ID revert back to 0

Inside enemyfactions sub this was also changed since it made enemy units to do multiple damage

ApplyDamage(I, Q, TargetF, TargetU)
                                        CounterT = 1

to

ApplyDamage(I, Q, TargetF, TargetU)
                                        CounterT = 1
                                        Faction(I).Unit(Q).TurnPoints -= 1

 ➤ Adding some more abilities to units

We are going to add some abilities to our worker units in particular and to all our units an ability to fortify so their defense capability goes up.

We'll need to add a couple of buttons on our sidebar.


Inside GameButtons("CREATE") add the following buttons

SButton(91).Text = "Next Unit"
            SButton(91).Enabled = 1
            SButton(91).X = 825 : SButton(91).Y = 467
            SButton(91).Width = 15 : SButton(91).Height = 15

            SButton(92).Text = "Fortify"
            SButton(92).Enabled = 1
            SButton(92).X = 841 : SButton(92).Y = 467
            SButton(92).Width = 15 : SButton(92).Height = 15

 Inside GameButtons("CLICK")

ElseIf SButtonHover = 91 Then 'Add under this, the code we have when we press space (the one we used on fixing unit selection above on this post)

Now for fortifying. Add this declaration inside our unitstruct which will hold a fortified state

Dim Fortified As Int16

Inside our Unit_F("MOUSE_MOVE_UNIT") add the following to both cases of our clause

Faction(FactionID).Unit(UnitSelected).Fortified = 0 'If the unit moves or attacks, it loses it's fortified ability

Now moving to our button click add the following code

ElseIf SButtonHover = 92 Then 'Fortifying unit
                If Faction(0).Unit(UnitSelected).TurnPoints > 0 Then 'If selected unit has at least 1 turn point left
                    Faction(0).Unit(UnitSelected).TurnPoints -= 1
                    Faction(0).Unit(UnitSelected).Fortified = 2
                    'Then select next unit
                    If UnitSelected < MaxUnitsPF Then
                        If Faction(0).Unit(UnitSelected + 1).ID <> 0 Then
                            UnitSelected += 1
                        Else
                            UnitSelected = 0
                        End If
                    Else
                        UnitSelected = 0
                    End If
                End If

So if a unit has turn points it will fortify lose one turn point and the selection will move to the next available unit.

Now let's go to our rendering code where we have the damage applying and modify it as seen below

Faction(Y).Unit(X).Health -= Faction(Y).Unit(X).DamageApl

to the following

Faction(Y).Unit(X).Health -= Faction(Y).Unit(X).DamageApl / Faction(Y).Unit(X).Fortified

Now if a unit is fortified, it will get half the damage applied. Let's add some visual addition so the player knows if the unit is fortified. I just added the following code after the unit health and turn point bar and a small cube in the sprite sheet.

'Fortify
                        If Faction(Y).Unit(X).Fortified = 0 Then
                            sRect = New Rectangle(858, 297, 7, 6)
                        Else
                            sRect = New Rectangle(858, 303, 7, 6)
                        End If
                        dRect = New Rectangle(Faction(Y).Unit(X).X + OffSetX + 51 * ZoomVar, Faction(Y).Unit(X).Y + OffSetY - 34 * ZoomVar, 8 * ZoomVar, 8 * ZoomVar)
                        DrawTexturedQuad(sRect, dRect, 1120, 720, 3, 1.0F, 1, 1, 1, 1)

 

Now let's do an addition to our normal ability button, the one we click to create a city

Went over to ParseMap sub which we used to create a map and generate resources on it based on tiles and added the following clause

ElseIf func = "NEWIDOVER" Then
            For X = 0 To MaxGridX
                For Y = 0 To MaxGridY
                    If GridWorld(X, Y).IDOver = 30 Or GridWorld(X, Y).IDOver = 49 Or GridWorld(X, Y).IDOver = 50 Then
                        GridWorld(X, Y).IDResource = 4
                    End If
                Next
            Next

Now when we click Sbutton 1 (the one we use to create a city if the unit is a settler)

ElseIf faction(0).unit(UnitSelected).id = 2 Then 'If that unit is a worker
                    If GridWorld(Faction(0).Unit(UnitSelected).TileX, Faction(0).Unit(UnitSelected).TileY).ID = 4 Or GridWorld(Faction(0).Unit(UnitSelected).TileX, Faction(0).Unit(UnitSelected).TileY).ID = 5 Then
                        GridWorld(Faction(0).Unit(UnitSelected).TileX, Faction(0).Unit(UnitSelected).TileY).IDOver = 50
                        ParseMap("NEWIDOVER")
                        Faction(0).Unit(UnitSelected).ID = 0
                    ElseIf GridWorld(Faction(0).Unit(UnitSelected).TileX, Faction(0).Unit(UnitSelected).TileY).ID = 7 Or GridWorld(Faction(0).Unit(UnitSelected).TileX, Faction(0).Unit(UnitSelected).TileY).ID = 8 Then
                        GridWorld(Faction(0).Unit(UnitSelected).TileX, Faction(0).Unit(UnitSelected).TileY).IDOver = 30
                        ParseMap("NEWIDOVER")
                        Faction(0).Unit(UnitSelected).ID = 0
                    ElseIf GridWorld(Faction(0).Unit(UnitSelected).TileX, Faction(0).Unit(UnitSelected).TileY).ID = 15 Then
                        GridWorld(Faction(0).Unit(UnitSelected).TileX, Faction(0).Unit(UnitSelected).TileY).IDOver = 49
                        ParseMap("NEWIDOVER")
                        Faction(0).Unit(UnitSelected).ID = 0
                    Else

                    End If


Now you can change the resources around your cities. Let's add this to our enemy worker units too now.

Inside our EnemyFactions sub added a clause just for unit id = 2

ElseIf faction(i).unit(q).id = 2 Then
                        RndN = Int(Rnd() * 10)
                        UnitSelected = Q
                        CounterT = 100
                        Do Until CounterT = 0
                            If RndN <= 7 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

                                If GridWorld(Faction(I).Unit(Q).TileX, Faction(I).Unit(Q).TileY).ID = 4 Or GridWorld(Faction(I).Unit(Q).TileX, Faction(I).Unit(Q).TileY).ID = 5 Then
                                    GridWorld(Faction(I).Unit(Q).TileX, Faction(I).Unit(Q).TileY).IDOver = 50
                                    ParseMap("NEWIDOVER")
                                    Faction(I).Unit(Q).ID = 0
                                ElseIf GridWorld(Faction(I).Unit(Q).TileX, Faction(I).Unit(Q).TileY).ID = 7 Or GridWorld(Faction(i).Unit(Q).TileX, Faction(i).Unit(Q).TileY).ID = 8 Then
                                    GridWorld(Faction(I).Unit(Q).TileX, Faction(I).Unit(Q).TileY).IDOver = 30
                                    ParseMap("NEWIDOVER")
                                    Faction(I).Unit(Q).ID = 0
                                ElseIf GridWorld(Faction(I).Unit(Q).TileX, Faction(I).Unit(Q).TileY).ID = 15 Then
                                    GridWorld(Faction(I).Unit(Q).TileX, Faction(I).Unit(Q).TileY).IDOver = 49
                                    ParseMap("NEWIDOVER")
                                    Faction(I).Unit(Q).ID = 0
                                Else

                                End If
                                Exit Do
                            End If
                            CounterT -= 1
                        Loop

Another addition we'll do in the AI code is to make enemy settlers search for unclaimed territory before building a city

So under this clause inside our AI code If Faction(I).Unit(Q).ID < 2 Then 'Settler and inside our loop inside this Else 'Create a city

If GridWorld(Faction(I).Unit(UnitSelected).TileX, Faction(I).Unit(UnitSelected).TileY).LAND <> I Then 'If the unit is not over it's own territory

➤ Adding a statistics screen

In this post we'll add a simple statistics screen, in which we will be able to see which faction is more powerful on the current turn.

I added a small statistics window to the sprite sheet with 4 placeholder for values (Units, Territory, Research, Population).

 So let's start programming our statistics window!

On GameButtons("CREATE"), we create our button

SButton(93).Text = "Statistics"
            SButton(93).Enabled = 1
            SButton(93).X = 846 : SButton(93).Y = 487
            SButton(93).Width = 15 : SButton(93).Height = 15

SButton(94).Text = "Statistics"
            SButton(94).Enabled = 1
            SButton(94).X = 720 : SButton(94).Y = 508
            SButton(94).Width = 93 : SButton(94).Height = 18
            SButton(94).UIState = 3

On GameButtons("CLICK"), we create the link to our click button event

ElseIf SButtonHover = 93 Then 'If the mouse is statistics button
                SUIWindow = 3

 ElseIf SButtonHover = 94 Then 'If the mouse is over close statistics button
                If SUIWindow = 3 Then
                    SUIWindow = 0
                End If

 Inside drawcalls where we draw our UI

ElseIf SUIWindow = 3 Then 'STATISTICS WINDOW

                'Statistics window
                sRect = New Rectangle(857, 325, 249, 136)
                dRect = New Rectangle(ScreenWidth - 140 * (ScreenWidth / UISizeW) - sRect.Width * (ScreenWidth / UISizeW), (ScreenHeight - (sRect.Height + 5) * (ScreenHeight / UISizeH)), sRect.Width * (ScreenWidth / UISizeW), sRect.Height * (ScreenHeight / UISizeH))
                DrawTexturedQuad(sRect, dRect, 1120, 720, 3, 1.0F, 1, 1, 1, 1)
                Dim BarWidth(MaxFactions, 3) As Point
                Dim TotalBarWidth(3) As Integer
                Dim TotalCount As Integer
                Dim ScrBarWidth(MaxFactions, 3) As Integer

                For Y = 0 To MaxFactions

                    'Count Units
                    TotalCount = 0
                    For X = 0 To MaxUnitsPF
                        If Faction(Y).Unit(X).ID <> 0 Then
                            TotalCount += 1
                        End If
                    Next
                    BarWidth(Y, 0).Y = TotalCount

                    'Count Territory
                    BarWidth(Y, 1).Y = Faction(Y).LandOwned(0) + Faction(Y).LandOwned(1) + Faction(Y).LandOwned(2) + Faction(Y).LandOwned(3)
                    'Count Research
                    For X = 0 To 28
                        If Lib_Research_S(Y, X).Researched = 0 Then
                            BarWidth(Y, 2).Y += 1
                        End If
                    Next

                    'Count Population
                    BarWidth(Y, 3).Y = Faction(Y).STLibrary.BasicA(3)

                    TotalBarWidth(0) += BarWidth(Y, 0).Y
                    TotalBarWidth(1) += BarWidth(Y, 1).Y
                    TotalBarWidth(2) += BarWidth(Y, 2).Y
                    TotalBarWidth(3) += BarWidth(Y, 3).Y
                Next

                For X = 0 To 3
                    For Y = 0 To MaxFactions

                        If TotalBarWidth(X) <> 0 Then
                            dRect = New Rectangle(651 * (ScreenWidth / UISizeW), (426 + X * 20 + Y * (14 / MaxFactions)) * (ScreenHeight / UISizeH), ((BarWidth(Y, X).Y * 161) / TotalBarWidth(X)) * (ScreenWidth / UISizeW), (14 / MaxFactions) * (ScreenHeight / UISizeH))
                            DrawQuad(dRect.X, dRect.Y, dRect.Width, dRect.Height, 1.0F, 0.45F, FactionColors(Y).R, FactionColors(Y).G, FactionColors(Y).B)
                        End If

                    Next
                Next

And the result can be seen on the following image.

What we did is to simply get the values for these 4 properties, and translate them to a width appropriate to our statistics window.



Comments