Automatic layering system

From VbGORE Visual Basic Online RPG Engine

This was originally part of an upgrade (Version 1.1.0 to be specific), but was omitted due to the complications it would bring to the typical user, and the amount that vbGORE would have to be altered to make it work (such as the help, GUIs, etc).

Contents

[edit] Pros and Cons

Pros:

  • Automatic layering - The layers are decided based on the Y axis along with the height of the Grh. This prevents having to break up certain graphics (like trees) into "behind" and "front" parts.
  • Faster mapping - Because you don't have to worry about the layers as much, you will be able to map a lot faster.
  • Less Grhs - There will be less Grhs because you won't have to break them into multiple parts.

Cons:

  • Automatic layering - This is also a con, since you have to hope the automatic layering gives you what you want. If it does not, you have to do some cheap hacks to get it to work.
  • Remake maps - Since the maps work completely different, you will have to remake them all.

[edit] How it works

Here is a copy-and-paste from the original entry in the upgrade guide:

"Added a new rendering system. How this system works is instead of what is drawn being decided on what layer the Grh is on, there are three tile types. These types are always below (layer 1 and 2), mixed (layer 3 to 5) and always above (layer 6). The always below is used for the ground tiles. Mixed is used for anything that has depth - trees, buildings, chairs, etc. Layer 6 is used for special cases, like birds or clouds, though it is not a very common type to have to use, which is why there is only one layer for it. The mixed layer works by sorting on a pseudo-Z axis calculated by the Y + Height of a Grh. This algorithm is also applied to particle effects (ie bless), Grh effects (ie Spike Field), characters (NPCs and PCs both), blood splatters (from when you hit a NPC / get hit) and projectiles (ie Ninja Star). Because of this new system, previously made maps will not function correctly. Though, you no longer have to split up your trees and have part of it on layer 2/3 and the other part on layer 4/5 just so you can walk in front of it and stand behind it, as the engine will do this for you. This also fixes a lot of issues with putting the shadows on these Grhs, since they are now just one Grh, so they cast just one shadow. For those who are worried about performance, there was no FPS drop when tested against ~500 Grhs on layer 3-5, so performance is not a concern."

[edit] Adding guide

Follow this guide just like you would any other upgrade guide, since this is how it was written (which is why it is in 3 parts). Start with the last on the end of the page, and move up to the first.

[edit] Fix - Animations

Open GameClient.vbp.

In module TileEngine, add:

Public Function Engine_GetFrameFromGrh(Grh As Grh) As Long
'************************************************************
'Get a Grh and return the frame (useful for animations)
'************************************************************
Dim i As Long
 
    'Check for a valid GrhIndex
    If Grh.GrhIndex < 1 Then Exit Function
    If Grh.GrhIndex > NumGrhs Then Exit Function
 
    'Round down on the frame counter
    i = Int(Grh.FrameCounter)
 
    'Check for a frame count overflow
    If i > GrhData(Grh.GrhIndex).NumFrames Then Exit Function
 
    'Return the Grh index
    Engine_GetFrameFromGrh = GrhData(Grh.GrhIndex).Frames(Int(Grh.FrameCounter))
 
End Function

Find:

                            Engine_AddToRenderList_Grh RenderList(), RenderListSize, RenderListIndex, BloodList(j).Grh.GrhIndex, X, Y, _
                                Y + GrhData(BloodList(j).Grh.GrhIndex).pixelHeight, -1, -1, -1, -1, 0, 1, 0

Replace with:

                            Engine_AddToRenderList_Grh RenderList(), RenderListSize, RenderListIndex, Engine_GetFrameFromGrh(BloodList(j).Grh), X, Y, _
                                Y + GrhData(BloodList(j).Grh.GrhIndex).pixelHeight, -1, -1, -1, -1, 0, 1, 0

Find:

                                Engine_AddToRenderList_Grh RenderList(), RenderListSize, RenderListIndex, OBJList(j).Grh.GrhIndex, X, Y, _
                                    Y + GrhData(OBJList(j).Grh.GrhIndex).pixelHeight, .Light(1), .Light(2), .Light(3), .Light(4), 0, 1, 1

Replace with:

                                Engine_AddToRenderList_Grh RenderList(), RenderListSize, RenderListIndex, Engine_GetFrameFromGrh(OBJList(j).Grh), X, Y, _
                                    Y + GrhData(OBJList(j).Grh.GrhIndex).pixelHeight, .Light(1), .Light(2), .Light(3), .Light(4), 0, 1, 1

Find:

                Engine_AddToRenderList_Grh RenderList(), RenderListSize, RenderListIndex, MapData(.TileX, .TileY).Graphic(Layer).GrhIndex, _
                    .PixelPosX + PixelOffsetX, .PixelPosY + PixelOffsetY, .PixelPosY + PixelOffsetY + _
                    GrhData(MapData(.TileX, .TileY).Graphic(Layer).GrhIndex).pixelHeight, MapData(.TileX, .TileY).Light(1), _
                    MapData(.TileX, .TileY).Light(2), MapData(.TileX, .TileY).Light(3), MapData(.TileX, .TileY).Light(4), _
                    0, 0, MapData(.TileX, .TileY).Shadow(Layer)

Replace with:

                Engine_AddToRenderList_Grh RenderList(), RenderListSize, RenderListIndex, Engine_GetFrameFromGrh(MapData(.TileX, .TileY).Graphic(Layer)), _
                    .PixelPosX + PixelOffsetX, .PixelPosY + PixelOffsetY, .PixelPosY + PixelOffsetY + _
                    GrhData(MapData(.TileX, .TileY).Graphic(Layer).GrhIndex).pixelHeight, MapData(.TileX, .TileY).Light(1), _
                    MapData(.TileX, .TileY).Light(2), MapData(.TileX, .TileY).Light(3), MapData(.TileX, .TileY).Light(4), _
                    0, 0, MapData(.TileX, .TileY).Shadow(Layer)

Find:

                        Engine_AddToRenderList_Grh RenderList(), RenderListSize, RenderListIndex, GrhData(EffectList(j).Grh.GrhIndex).Frames(Int(EffectList(j).Grh.FrameCounter)), X, Y, _
                            Y + GrhData(EffectList(j).Grh.GrhIndex).pixelHeight, -1, -1, -1, -1, EffectList(j).Angle, 0, 1

Replace with:

                        Engine_AddToRenderList_Grh RenderList(), RenderListSize, RenderListIndex, Engine_GetFrameFromGrh(EffectList(j).Grh), X, Y, _
                            Y + GrhData(EffectList(j).Grh.GrhIndex).pixelHeight, -1, -1, -1, -1, EffectList(j).Angle, 0, 1

Find:

                            Engine_AddToRenderList_Grh RenderList(), RenderListSize, RenderListIndex, ProjectileList(j).Grh.GrhIndex, _
                                X, Y, Y + GrhData(ProjectileList(j).Grh.GrhIndex).pixelHeight, -1, -1, -1, -1, ProjectileList(j).Rotate, 0, 1

Replace with:

                            Engine_AddToRenderList_Grh RenderList(), RenderListSize, RenderListIndex, Engine_GetFrameFromGrh(ProjectileList(j).Grh), _
                                X, Y, Y + GrhData(ProjectileList(j).Grh.GrhIndex).pixelHeight, -1, -1, -1, -1, ProjectileList(j).Rotate, 0, 1

Open EditorMap.vbp.

In module TileEngine, add:

Public Function Engine_GetFrameFromGrh(Grh As Grh) As Long
'************************************************************
'Get a Grh and return the frame (useful for animations)
'************************************************************
Dim i As Long
 
    'Check for a valid GrhIndex
    If Grh.GrhIndex < 1 Then Exit Function
    If Grh.GrhIndex > NumGrhs Then Exit Function
 
    'Round down on the frame counter
    i = Int(Grh.FrameCounter)
 
    'Check for a frame count overflow
    If i > GrhData(Grh.GrhIndex).NumFrames Then Exit Function
 
    'Return the Grh index
    Engine_GetFrameFromGrh = GrhData(Grh.GrhIndex).Frames(Int(Grh.FrameCounter))
 
End Function

Find:

    '************** Layer 3 to 5 **************
    For Layer = 3 To 5
        LightOffset = ((Layer - 1) * 4) + 1
        For j = 1 To TileLayer(Layer).NumTiles
            With TileLayer(Layer).Tile(j)
                Engine_UpdateGrh MapData(.TileX, .TileY).Graphic(Layer), True
                Engine_AddToRenderList_Grh RenderList(), RenderListSize, RenderListIndex, MapData(.TileX, .TileY).Graphic(Layer).GrhIndex, _
                    .PixelPosX + PixelOffsetX, .PixelPosY + PixelOffsetY, .PixelPosY + PixelOffsetY + _
                    GrhData(MapData(.TileX, .TileY).Graphic(Layer).GrhIndex).pixelHeight, MapData(.TileX, .TileY).Light(1), _
                    MapData(.TileX, .TileY).Light(2), MapData(.TileX, .TileY).Light(3), MapData(.TileX, .TileY).Light(4), _
                    0, 0, MapData(.TileX, .TileY).Shadow(Layer)
            End With
        Next j
    Next Layer

Replace with:

    '************** Layer 2 to 5 **************
    For Layer = 2 To 5
        LightOffset = ((Layer - 1) * 4) + 1
        For j = 1 To TileLayer(Layer).NumTiles
            With TileLayer(Layer).Tile(j)
                Engine_UpdateGrh MapData(.TileX, .TileY).Graphic(Layer), True
                Engine_AddToRenderList_Grh RenderList(), RenderListSize, RenderListIndex, Engine_GetFrameFromGrh(MapData(.TileX, .TileY).Graphic(Layer)), _
                    .PixelPosX + PixelOffsetX, .PixelPosY + PixelOffsetY, .PixelPosY + PixelOffsetY + _
                    GrhData(MapData(.TileX, .TileY).Graphic(Layer).GrhIndex).pixelHeight, MapData(.TileX, .TileY).Light(LightOffset), _
                    MapData(.TileX, .TileY).Light(LightOffset + 1), MapData(.TileX, .TileY).Light(LightOffset + 2), MapData(.TileX, .TileY).Light(LightOffset + 3), _
                    0, 0, MapData(.TileX, .TileY).Shadow(Layer)
            End With
        Next j
    Next Layer

[edit] Fix - Effect rendering

Open GameClient.vbp.

Find:

    '************** Grh-Based (Non-Particle) Effects **************

Replace the block of code (from For j = ... to Next j) with:

    '************** Grh-Based (Non-Particle) Effects **************
    For j = 1 To LastEffect
        If EffectList(j).Grh.GrhIndex Then
            X = Engine_PixelPosX(EffectList(j).Pos.X - minX) + PixelOffsetX + TileBufferOffset
            Y = Engine_PixelPosY(EffectList(j).Pos.Y - minY) + PixelOffsetY + TileBufferOffset
            'Time ran out
            If EffectList(j).Time <> 0 And EffectList(j).Time < timeGetTime Then
                Engine_Effect_Erase j
            'Draw the effect
            ElseIf Y >= -32 And Y <= (ScreenHeight + 32) And X >= -32 And X <= (ScreenWidth + 32) Then
                Engine_UpdateGrh EffectList(j).Grh, False
                If EffectList(j).Animated = 1 Then
                    If EffectList(j).Grh.Started = 0 Then
                        Engine_Effect_Erase j
                        GoTo NextEffect
                    End If
                End If
                If EffectList(j).Grh.FrameCounter >= 1 Then
                    If Int(EffectList(j).Grh.FrameCounter) <= GrhData(EffectList(j).Grh.GrhIndex).NumFrames Then
                        Engine_AddToRenderList_Grh RenderList(), RenderListSize, RenderListIndex, GrhData(EffectList(j).Grh.GrhIndex).Frames(Int(EffectList(j).Grh.FrameCounter)), X, Y, _
                            Y + GrhData(EffectList(j).Grh.GrhIndex).pixelHeight, -1, -1, -1, -1, EffectList(j).Angle, 0, 1
                    End If
                End If
            'Update but not draw
            Else
                Engine_UpdateGrh EffectList(j).Grh, False
                If EffectList(j).Animated = 1 Then
                    If EffectList(j).Grh.Started = 0 Then Engine_Effect_Erase j
                End If
            End If
        End If
NextEffect:
    Next j

[edit] Add - New rendering system

Open \Data\Head.dat.

At the end of the file, add:

'The height is used in the calculation of the overall height of the character for finding the drawing order.
'This is simply just the height of the graphic - the Grh's height is not used because it is often not the
'exact height of the graphic. The height of the head and body are added together. If you have any other
'height contributors (such as large head-wear) I recommend you add height to that layer, too.
Height=27

Open \Data\Body.dat.

Find:

[2]

Before, add:

'The height is used in the calculation of the overall height of the character for finding the drawing order.
'This is simply just the height of the graphic - the Grh's height is not used because it is often not the
'exact height of the graphic. The height of the head and body are added together. If you have any other
'height contributors (such as large head-wear) I recommend you add height to that layer, too.
Height=34

At the end of the file, add:

Height=34

Open GameClient.vbp.

Find:

Private ChatVA() As TLVERTEX

After, add:

'Render list - used to sort the list of graphics before rendering them
Private Type RenderList
    X As Long
    Y As Long
    Z As Integer    'The value used to sort by (Y + Height)
    Grh As Long
    Light(0 To 3) As Long
    Center As Byte
    Shadow As Byte
    Angle As Single
    CharIndex As Long   'If it is a character, this is its index
    ParticleEffectIndex As Byte 'If it is a particle effect, this is its index
End Type

Find:

'Bodies list
Public Type BodyData
    Walk(1 To 8) As Grh
    Attack(1 To 8) As Grh
    HeadOffset As Position
End Type

Replace with:

'Bodies list
Public Type BodyData
    Walk(1 To 8) As Grh
    Attack(1 To 8) As Grh
    HeadOffset As Position
    Height As Long
End Type

Find:

Public Type HeadData
    Head(1 To 8) As Grh
    Blink(1 To 8) As Grh
    AgrHead(1 To 8) As Grh
    AgrBlink(1 To 8) As Grh
End Type

Replace with:

'Heads list
Public Type HeadData
    Head(1 To 8) As Grh
    Blink(1 To 8) As Grh
    AgrHead(1 To 8) As Grh
    AgrBlink(1 To 8) As Grh
    Height As Long
End Type

Find:

        BodyData(LoopC).HeadOffset.Y = CLng(Var_Get(DataPath & "Body.dat", LoopC, "HeadOffsetY"))

After, add:

        BodyData(LoopC).Height = CLng(Var_Get(DataPath & "Body.dat", LoopC, "Height"))

Find:

    'Fill List
    For LoopC = 1 To NumHeads
        For i = 1 To 8
            Engine_Init_Grh HeadData(LoopC).Head(i), CLng(Var_Get(DataPath & "Head.dat", LoopC, i)), 0
            Engine_Init_Grh HeadData(LoopC).Blink(i), CLng(Var_Get(DataPath & "Head.dat", LoopC, "b" & i)), 0
            Engine_Init_Grh HeadData(LoopC).AgrHead(i), CLng(Var_Get(DataPath & "Head.dat", LoopC, "a" & i)), 0
            Engine_Init_Grh HeadData(LoopC).AgrBlink(i), CLng(Var_Get(DataPath & "Head.dat", LoopC, "ab" & i)), 0
        Next i
    Next LoopC

Replace with:

    'Fill List
    For LoopC = 1 To NumHeads
        For i = 1 To 8
            Engine_Init_Grh HeadData(LoopC).Head(i), CLng(Var_Get(DataPath & "Head.dat", LoopC, i)), 0
            Engine_Init_Grh HeadData(LoopC).Blink(i), CLng(Var_Get(DataPath & "Head.dat", LoopC, "b" & i)), 0
            Engine_Init_Grh HeadData(LoopC).AgrHead(i), CLng(Var_Get(DataPath & "Head.dat", LoopC, "a" & i)), 0
            Engine_Init_Grh HeadData(LoopC).AgrBlink(i), CLng(Var_Get(DataPath & "Head.dat", LoopC, "ab" & i)), 0
        Next i
        HeadData(LoopC).Height = CLng(Var_Get(DataPath & "Head.dat", LoopC, "Height"))
    Next LoopC

Find:

            Engine_Init_Grh HeadData(LoopC).AgrBlink(i), CLng(Var_Get(DataPath & "Head.dat", LoopC, "ab" & i)), 0

After, add:

            HeadData(LoopC).Height = CLng(Var_Get(DataPath & "Head.dat", LoopC, "Height"))

Find:

        'Grh has a delay, so just update the frame and then leave
        Engine_UpdateGrh Grh, LoopAnim

Replace with:

        'Grh has a delay, so just update the frame and then leave
        If Animate Then Engine_UpdateGrh Grh, LoopAnim

At the end of the TileEngine module, add:

Private Sub Engine_AddToRenderList_Char(ByRef RenderList() As RenderList, ByRef RenderListSize As Long, ByRef Index As Long, _
    ByVal CharIndex As Long, ByVal X As Long, ByVal Y As Long, ByVal Z As Integer)
'*****************************************************************
'Adds a character to the RenderList()
'More info: http://www.vbgore.com/GameClient.TileEngine.Engine_AddToRenderList_Char
'*****************************************************************
 
    'Increase the index
    Index = Index + 1
 
    'Increase array size if needed
    If Index > RenderListSize Then
        RenderListSize = RenderListSize + 50
        ReDim Preserve RenderList(1 To RenderListSize)
    End If
 
    'Add the components
    With RenderList(Index)
        .CharIndex = CharIndex
        .X = X
        .Y = Y
        .Z = Z
    End With
 
End Sub
 
Private Sub Engine_AddToRenderList_PE(ByRef RenderList() As RenderList, ByRef RenderListSize As Long, ByRef Index As Long, _
    ByVal ParticleEffectIndex As Long, ByVal Z As Integer)
'*****************************************************************
'Adds a Particle Effect to the RenderList()
'More info: http://www.vbgore.com/GameClient.TileEngine.Engine_AddToRenderList_PE
'*****************************************************************
 
    'Increase the index
    Index = Index + 1
 
    'Increase array size if needed
    If Index > RenderListSize Then
        RenderListSize = RenderListSize + 50
        ReDim Preserve RenderList(1 To RenderListSize)
    End If
 
    'Add the components
    With RenderList(Index)
        .ParticleEffectIndex = ParticleEffectIndex
        .Z = Z
    End With
 
End Sub
 
Private Sub Engine_AddToRenderList_Grh(ByRef RenderList() As RenderList, ByRef RenderListSize As Long, ByRef Index As Long, _
    ByVal Grh As Long, ByVal X As Long, ByVal Y As Long, ByVal Z As Integer, ByVal Light0 As Long, ByVal Light1 As Long, ByVal Light2 As Long, _
    ByVal Light3 As Long, ByVal Angle As Single, ByVal Center As Byte, ByVal Shadow As Byte)
'*****************************************************************
'Adds a Grh (such as a tile, Grh-based effect, projectile, etc) to the RenderList()
'More info: http://www.vbgore.com/GameClient.TileEngine.Engine_AddToRenderList_Grh
'*****************************************************************
 
    'Increase the index
    Index = Index + 1
 
    'Increase array size if needed
    If Index > RenderListSize Then
        RenderListSize = RenderListSize + 50
        ReDim Preserve RenderList(1 To RenderListSize)
    End If
 
    'Add the components
    With RenderList(Index)
        .Angle = Angle
        .Center = Center
        .Grh = Grh
        .Light(0) = Light0
        .Light(1) = Light1
        .Light(2) = Light2
        .Light(3) = Light3
        .Shadow = Shadow
        .X = X
        .Y = Y
        .Z = Z
    End With
 
End Sub

Find:

Sub Engine_Render_Screen(ByVal TileX As Integer, ByVal TileY As Integer, ByVal PixelOffsetX As Integer, ByVal PixelOffsetY As Integer)

Replace sub with:

Sub Engine_Render_Screen(ByVal TileX As Integer, ByVal TileY As Integer, ByVal PixelOffsetX As Integer, ByVal PixelOffsetY As Integer)
'************************************************************
'Draw current visible to scratch area based on TileX and TileY
'More info: http://www.vbgore.com/GameClient.TileEngine.Engine_Render_Screen
'************************************************************
Dim RenderList() As RenderList
Dim RenderListSize As Long
Dim RenderListIndex As Long
Dim FrameUseMotionBlur As Boolean   'Lets us know if this frame is using motion blur so we don't have to leave support for it on
Dim LightOffset As Long
Dim Y As Long           'Keeps track of where on map we are
Dim X As Long
Dim j As Long
Dim Angle As Single
Dim Layer As Byte
Dim pList() As Integer
Dim ValueList() As Integer
Dim TempGrh As Grh
 
    'Check for valid positions
    If UserPos.X = 0 Then Exit Sub
    If UserPos.Y = 0 Then Exit Sub
    If UserCharIndex = 0 Then Exit Sub
 
    'Check if we need to update the graphics
    If TileX <> LastTileX Or TileY <> LastTileY Then
 
        'Figure out Ends and Starts of screen
        ScreenMinY = TileY - (WindowTileHeight \ 2)
        ScreenMaxY = TileY + (WindowTileHeight \ 2)
        ScreenMinX = TileX - (WindowTileWidth \ 2)
        ScreenMaxX = TileX + (WindowTileWidth \ 2)
        minY = ScreenMinY - TileBufferSize
        maxY = ScreenMaxY + TileBufferSize
        minX = ScreenMinX - TileBufferSize
        maxX = ScreenMaxX + TileBufferSize
 
        'Update the last position
        LastTileX = TileX
        LastTileY = TileY
 
        'Re-create the tile layers
        Engine_CreateTileLayers
 
    End If
 
    'Calculate the particle offset values
    'Do NOT move this any farther down in the module or you will get "jumps" as the left/top borders on particles
    ParticleOffsetX = (Engine_PixelPosX(ScreenMinX) - PixelOffsetX)
    ParticleOffsetY = (Engine_PixelPosY(ScreenMinY) - PixelOffsetY)
 
    'Check if we have the device
    If D3DDevice.TestCooperativeLevel <> D3D_OK Then
 
        'The worst we can do at this point is avoid an error we can't fix!
        On Error Resume Next
 
        'Do a loop while device is lost
        If D3DDevice.TestCooperativeLevel = D3DERR_DEVICELOST Then Exit Sub
 
        'Clear all the textures
        LastTexture = -999
        For j = 1 To NumGrhFiles
            Set SurfaceDB(j) = Nothing
            SurfaceTimer(j) = 0
            SurfaceSize(j).X = 0
            SurfaceSize(j).Y = 0
        Next j
 
        'Clear the D3DXSprite
        If AlternateRenderDefault = 1 Or AlternateRenderMap = 1 Or AlternateRenderText = 1 Then
            SpriteBegun = 0
            Set Sprite = Nothing
            Set Sprite = D3DX.CreateSprite(D3DDevice)
        End If
 
        Set DeviceBuffer = Nothing
        Set DeviceStencil = Nothing
        Set BlurStencil = Nothing
        Set BlurTexture = Nothing
        Set BlurSurf = Nothing
 
        'Make sure the scene is ended
        D3DDevice.EndScene
 
        'Reset the device
        D3DDevice.Reset D3DWindow
 
        Set DeviceBuffer = D3DDevice.GetRenderTarget
        Set DeviceStencil = D3DDevice.GetDepthStencilSurface
        Set BlurStencil = D3DDevice.CreateDepthStencilSurface(BufferWidth, BufferHeight, D3DFMT_D16, D3DMULTISAMPLE_NONE)
        Set BlurTexture = D3DX.CreateTexture(D3DDevice, BufferWidth, BufferHeight, 0, D3DUSAGE_RENDERTARGET, DispMode.Format, D3DPOOL_DEFAULT)
        Set BlurSurf = BlurTexture.GetSurfaceLevel(0)
 
        'Reset the render states
        Engine_Init_RenderStates
 
        'Load the particle textures
        Engine_Init_ParticleEngine True
 
        On Error GoTo 0
 
    Else
 
        'We have to bypass the present the first time through here or else we get an error
        If NotFirstRender Then
 
            'Close off the last sprite
            If SpriteBegun Then
                Sprite.End
                SpriteBegun = 0
                LastTexture = -101
            End If
 
            With D3DDevice
 
                'End the rendering (scene)
                .EndScene
 
                'Flip the backbuffer to the screen
                .Present ByVal 0, ByVal 0, 0, ByVal 0
 
            End With
 
        Else
 
            'Set NotFirstRender to True so we can start displaying
            NotFirstRender = True
 
        End If
 
    End If
 
    'Check if running (turn on motion blur)
    If UseMotionBlur Then
        If UserCharIndex > 0 Then
            If CharList(UserCharIndex).Moving = 1 And CharList(UserCharIndex).Running Then
                BlurIntensity = 45
            Else
                If BlurIntensity < 255 Then
                    BlurIntensity = BlurIntensity + (ElapsedTime * 0.8)
                    If BlurIntensity > 255 Then BlurIntensity = 255
                End If
            End If
        End If
    End If
 
    'Set the motion blur if needed
    If UseMotionBlur Then
        If BlurIntensity < 255 Or ZoomLevel > 0 Then
            FrameUseMotionBlur = True
            D3DDevice.SetRenderTarget BlurSurf, BlurStencil, 0
        End If
    End If
 
    'Begin the scene
    D3DDevice.BeginScene
 
    'Clear the screen with a solid color (to prevent artifacts)
    D3DDevice.Clear 0, ByVal 0, D3DCLEAR_TARGET, 0, 1#, 0
 
    '********************************************************
    '************ Update and draw layer 1 and 2 *************
    '********************************************************
 
    'Set the alternate rendering for the map on / off
    AlternateRender = AlternateRenderMap
 
    'Loop through the lower 2 layers
    For Layer = 1 To 2
        LightOffset = ((Layer - 1) * 4) + 1
 
        'Loop through all the tiles we know we will draw for this layer
        For j = 1 To TileLayer(Layer).NumTiles
            With TileLayer(Layer).Tile(j)
                Engine_UpdateGrh MapData(.TileX, .TileY).Graphic(Layer)
 
                'Check if we have to draw with a shadow or not (slighty changes because we have to animate on the shadow, not the main render)
                If MapData(.TileX, .TileY).Shadow(Layer) = 1 Then
                    Engine_Render_Grh MapData(.TileX, .TileY).Graphic(Layer), .PixelPosX + PixelOffsetX, .PixelPosY + PixelOffsetY, 0, 1, True, ShadowColor, ShadowColor, ShadowColor, ShadowColor, 1
                    Engine_Render_Grh MapData(.TileX, .TileY).Graphic(Layer), .PixelPosX + PixelOffsetX, .PixelPosY + PixelOffsetY, 0, 0, True, MapData(.TileX, .TileY).Light(LightOffset), MapData(.TileX, .TileY).Light(LightOffset + 1), MapData(.TileX, .TileY).Light(LightOffset + 2), MapData(.TileX, .TileY).Light(LightOffset + 3)
                Else
                    Engine_Render_Grh MapData(.TileX, .TileY).Graphic(Layer), .PixelPosX + PixelOffsetX, .PixelPosY + PixelOffsetY, 0, 1, True, MapData(.TileX, .TileY).Light(LightOffset), MapData(.TileX, .TileY).Light(LightOffset + 1), MapData(.TileX, .TileY).Light(LightOffset + 2), MapData(.TileX, .TileY).Light(LightOffset + 3)
                End If
 
            End With
        Next j
 
    Next Layer
 
    'Set the alternate rendering back to what it was before
    AlternateRender = AlternateRenderDefault
 
    '********************************************************
    '********** Update and draw the ground objects **********
    '********************************************************
    For j = 1 To LastObj
        If OBJList(j).Grh.GrhIndex Then
            X = Engine_PixelPosX(OBJList(j).Pos.X - minX) + PixelOffsetX + OBJList(j).Offset.X + TileBufferOffset
            Y = Engine_PixelPosY(OBJList(j).Pos.Y - minY) + PixelOffsetY + OBJList(j).Offset.Y + TileBufferOffset
            If Y >= -32 Then
                If Y <= (ScreenHeight + 32) Then
                    If X >= -32 Then
                        If X <= (ScreenWidth + 32) Then
                            Engine_UpdateGrh OBJList(j).Grh, True
                            With MapData(OBJList(j).Pos.X, OBJList(j).Pos.Y)
                                Engine_AddToRenderList_Grh RenderList(), RenderListSize, RenderListIndex, OBJList(j).Grh.GrhIndex, X, Y, _
                                    Y + GrhData(OBJList(j).Grh.GrhIndex).pixelHeight, .Light(1), .Light(2), .Light(3), .Light(4), 0, 1, 1
                            End With
                        End If
                    End If
                End If
            End If
        End If
    Next j
 
    '********************************************************
    '****** Start storing graphics in the RenderList() ******
    '****** so it can be sorted by their Y co-ordinate ******
    '********************************************************
 
    '************** Characters **************
    For j = 1 To LastChar
        If CharList(j).Active Then
            X = Engine_PixelPosX(CharList(j).Pos.X - minX) + PixelOffsetX + TileBufferOffset
            Y = Engine_PixelPosY(CharList(j).Pos.Y - minY) + PixelOffsetY + TileBufferOffset
            If Y >= -32 And Y <= (ScreenHeight + 32) And X >= -32 And X <= (ScreenWidth + 32) Then
                'Update the NPC chat and draw the character
                Engine_NPCChat_Update j
                Engine_AddToRenderList_Char RenderList(), RenderListSize, RenderListIndex, j, X, Y, _
                    Y + CharList(j).Body.Height + CharList(j).Head.Height
            Else
                'Update just the real position
                CharList(j).RealPos.X = X + CharList(j).MoveOffset.X
                CharList(j).RealPos.Y = Y + CharList(j).MoveOffset.Y
            End If
        End If
    Next j
 
    '************** Layer 3 to 5 **************
    AlternateRender = AlternateRenderMap
    For Layer = 3 To 5
        LightOffset = ((Layer - 1) * 4) + 1
        For j = 1 To TileLayer(Layer).NumTiles
            With TileLayer(Layer).Tile(j)
                Engine_UpdateGrh MapData(.TileX, .TileY).Graphic(Layer), True
                Engine_AddToRenderList_Grh RenderList(), RenderListSize, RenderListIndex, MapData(.TileX, .TileY).Graphic(Layer).GrhIndex, _
                    .PixelPosX + PixelOffsetX, .PixelPosY + PixelOffsetY, .PixelPosY + PixelOffsetY + _
                    GrhData(MapData(.TileX, .TileY).Graphic(Layer).GrhIndex).pixelHeight, MapData(.TileX, .TileY).Light(1), _
                    MapData(.TileX, .TileY).Light(2), MapData(.TileX, .TileY).Light(3), MapData(.TileX, .TileY).Light(4), _
                    0, 0, MapData(.TileX, .TileY).Shadow(Layer)
            End With
        Next j
    Next Layer
    AlternateRender = AlternateRenderDefault
 
    '************** Grh-Based (Non-Particle) Effects **************
    For j = 1 To LastEffect
        If EffectList(j).Grh.GrhIndex Then
            X = Engine_PixelPosX(EffectList(j).Pos.X - minX) + PixelOffsetX + TileBufferOffset
            Y = Engine_PixelPosY(EffectList(j).Pos.Y - minY) + PixelOffsetY + TileBufferOffset
            'Time ran out
            If EffectList(j).Time <> 0 And EffectList(j).Time < timeGetTime Then
                Engine_Effect_Erase j
            'Draw the effect
            ElseIf Y >= -32 And Y <= (ScreenHeight + 32) And X >= -32 And X <= (ScreenWidth + 32) Then
                Engine_UpdateGrh EffectList(j).Grh, False
                If EffectList(j).Animated = 1 Then
                    If EffectList(j).Grh.Started = 0 Then Engine_Effect_Erase j
                End If
                Engine_AddToRenderList_Grh RenderList(), RenderListSize, RenderListIndex, EffectList(j).Grh.GrhIndex, X, Y, _
                    Y + GrhData(EffectList(j).Grh.GrhIndex).pixelHeight, -1, -1, -1, -1, EffectList(j).Angle, 0, 1
            'Update but not draw
            Else
                Engine_UpdateGrh EffectList(j).Grh, False
                If EffectList(j).Animated = 1 Then
                    If EffectList(j).Grh.Started = 0 Then Engine_Effect_Erase j
                End If
            End If
        End If
    Next j
 
    '************** Projectiles **************
    'Check if it is close enough to the target to remove
    For j = 1 To LastProjectile
        If ProjectileList(j).Grh.GrhIndex Then
            If Abs(ProjectileList(j).X - ProjectileList(j).tX) < 20 Then
                If Abs(ProjectileList(j).Y - ProjectileList(j).tY) < 20 Then
                    Engine_Projectile_Erase j
                End If
            End If
        End If
    Next j
 
    For j = 1 To LastProjectile
        If ProjectileList(j).Grh.GrhIndex Then
            'Update the position
            Angle = DegreeToRadian * Engine_GetAngle(ProjectileList(j).X, ProjectileList(j).Y, ProjectileList(j).tX, ProjectileList(j).tY)
            ProjectileList(j).X = ProjectileList(j).X + (Sin(Angle) * ElapsedTime * 0.63)
            ProjectileList(j).Y = ProjectileList(j).Y - (Cos(Angle) * ElapsedTime * 0.63)
            'Update the rotation
            If ProjectileList(j).RotateSpeed > 0 Then
                ProjectileList(j).Rotate = ProjectileList(j).Rotate + (ProjectileList(j).RotateSpeed * ElapsedTime * 0.01)
                Do While ProjectileList(j).Rotate > 360
                    ProjectileList(j).Rotate = ProjectileList(j).Rotate - 360
                Loop
            End If
            'Draw if within range
            X = ((-minX - 1) * 32) + ProjectileList(j).X + PixelOffsetX + TileBufferOffset
            Y = ((-minY - 1) * 32) + ProjectileList(j).Y + PixelOffsetY + TileBufferOffset
            If Y >= -32 Then
                If Y <= (ScreenHeight + 32) Then
                    If X >= -32 Then
                        If X <= (ScreenWidth + 32) Then
                            Engine_UpdateGrh ProjectileList(j).Grh, True
                            Engine_AddToRenderList_Grh RenderList(), RenderListSize, RenderListIndex, ProjectileList(j).Grh.GrhIndex, _
                                X, Y, Y + GrhData(ProjectileList(j).Grh.GrhIndex).pixelHeigh