--[ ======================================== Standard AIs ======================================= ]
--The functions perform default AI behaviors, such as moving between locations or spotting the player.
-- Individual AIs either use these or variants.
--For speed reasons, the index of the calling entity is *not* range checked.

--[ ======================================= Detect Player ======================================= ]
--Attempt to spot the player. Sets the state machine variable gzTextVar.sSpottedPlayerAt to the
-- location the player was spotted, or "Null" if the player was not spotted.
fnStandardAI_SpotPlayer = function(i)

    --[Setup]
    --Reset.
    gzTextVar.sSpottedPlayerAt = "Null"
    
    --Room index.
    local iPlayerRoomIndex = fnGetRoomIndex(gzTextVar.gzPlayer.sLocation)
    if(iPlayerRoomIndex < 1 or iPlayerRoomIndex >= gzTextVar.iRoomsTotal) then
        return false
    end
    
    --Get positions of the entity vs. the player.
    local fEntityX, fEntityY, fEntityZ = fnGetRoomPosition(gzTextVar.zEntities[i].sLocation)
    local fPlayerX, fPlayerY, fPlayerZ = fnGetRoomPosition(gzTextVar.gzPlayer.sLocation)

    --[Distance Checker]
    --Too far away? Don't spot the player.
    if(fEntityZ ~= fPlayerZ or fEntityX > fPlayerX + 4 or fEntityX < fPlayerX - 4 or fEntityY > fPlayerY + 4 or fEntityY < fPlayerY - 4) then
        return false
    end

    --[Room Checker]
    --Certain rooms render the player totally invisible. This is for story reasons.
    if(gzTextVar.zRoomList[iPlayerRoomIndex].bEnemiesCannotSee == true) then return false end

    --[Minimap Marker]
    --Run the spotter routine. This is the mark visibility routine, running with code 2 to indicate enemies are checking LOS.
    gzTextVar.bSpottedPlayer = false
    fnRunVisibility(gzTextVar.zEntities[i].sLocation, 2)

    --Can't see the player. Stop here.
    if(gzTextVar.bSpottedPlayer == false) then return false end

    --Can see the player. Store where the player was, return true indicating we can see them.
    gzTextVar.sSpottedPlayerAt = gzTextVar.gzPlayer.sLocation
    if(true) then return true end
end

--[ ======================================= Door Handling ======================================= ]
--If the door handling variable is a direction without a P after it, then we need to close a door
--  behind us. A simple "P" is ignored.
--Note that the P doesn't mean anything, I just wanted to use a letter that isn't NSWE.
--Closing a door in this manner burns a turn, unless the player was recently spotted in which case
--  the door closes without costing a turn. Get the human!
--Returns whether or not a door was closed this turn.
fnStandardAI_HandleDoor = function(i)
    
    local bClosedDoorThisTurn = false
    if(gzTextVar.zEntities[i].sLastDoorDir ~= "P") then
        
        --A two-letter long string will be ignored.
        local iLen = string.len(gzTextVar.zEntities[i].sLastDoorDir)
        if(iLen == 1) then
            
            --Room index stuff.
            local iCurRoom = fnGetRoomIndex(gzTextVar.zEntities[i].sLocation)
            
            --Get the direction.
            if(gzTextVar.zEntities[i].sLastDoorDir == "N") then
                
                --Setup.
                local sPrvRoom = gzTextVar.zRoomList[iCurRoom].sMoveN
                local iPrvRoom = gzTextVar.zRoomList[iCurRoom].iMoveN
                
                --Error check.
                if(gzTextVar.zRoomList[iCurRoom].sDoorN ~= nil and iPrvRoom >= 1 and iPrvRoom <= gzTextVar.iRoomsTotal) then
                    
                    --Get the string past the first letter.
                    local sDoorState = string.sub(gzTextVar.zRoomList[iCurRoom].sDoorN, 1, 1)
                    local sLockType = string.sub(gzTextVar.zRoomList[iCurRoom].sDoorN, 2)
                    
                    --Is the door open?
                    if(sDoorState == "O") then
                        LM_ExecuteScript(gzTextVar.sRootPath .. "Functions/Modify Door State.lua", gzTextVar.zEntities[i].sLocation, "C" .. sLockType, "N", ciModifyVisibility, 0)
                    end
                end
            
            elseif(gzTextVar.zEntities[i].sLastDoorDir == "E") then
                
                --Setup.
                local sPrvRoom = gzTextVar.zRoomList[iCurRoom].sMoveE
                local iPrvRoom = gzTextVar.zRoomList[iCurRoom].iMoveE
                
                --Error check.
                if(gzTextVar.zRoomList[iCurRoom].sDoorE ~= nil and iPrvRoom >= 1 and iPrvRoom <= gzTextVar.iRoomsTotal) then
                    
                    --Get the string past the first letter.
                    local sDoorState = string.sub(gzTextVar.zRoomList[iCurRoom].sDoorE, 1, 1)
                    local sLockType = string.sub(gzTextVar.zRoomList[iCurRoom].sDoorE, 2)
                    
                    --Is the door open?
                    if(sDoorState == "O") then
                        LM_ExecuteScript(gzTextVar.sRootPath .. "Functions/Modify Door State.lua", gzTextVar.zEntities[i].sLocation, "C" .. sLockType, "E", ciModifyVisibility, 0)
                    end
                end
            
            elseif(gzTextVar.zEntities[i].sLastDoorDir == "S") then
                
                --Setup.
                local sPrvRoom = gzTextVar.zRoomList[iCurRoom].sMoveS
                local iPrvRoom = gzTextVar.zRoomList[iCurRoom].iMoveS
                
                --Error check.
                if(gzTextVar.zRoomList[iCurRoom].sDoorS ~= nil and iPrvRoom >= 1 and iPrvRoom <= gzTextVar.iRoomsTotal) then
                    
                    --Get the string past the first letter.
                    local sDoorState = string.sub(gzTextVar.zRoomList[iCurRoom].sDoorS, 1, 1)
                    local sLockType = string.sub(gzTextVar.zRoomList[iCurRoom].sDoorS, 2)
                    
                    --Is the door open?
                    if(sDoorState == "O") then
                        LM_ExecuteScript(gzTextVar.sRootPath .. "Functions/Modify Door State.lua", gzTextVar.zEntities[i].sLocation, "C" .. sLockType, "S", ciModifyVisibility, 0)
                    end
                end
            
            elseif(gzTextVar.zEntities[i].sLastDoorDir == "W") then
                
                --Setup.
                local sPrvRoom = gzTextVar.zRoomList[iCurRoom].sMoveW
                local iPrvRoom = gzTextVar.zRoomList[iCurRoom].iMoveW
                
                --Error check.
                if(gzTextVar.zRoomList[iCurRoom].sDoorW ~= nil and iPrvRoom >= 1 and iPrvRoom <= gzTextVar.iRoomsTotal) then
                    
                    --Get the string past the first letter.
                    local sDoorState = string.sub(gzTextVar.zRoomList[iCurRoom].sDoorW, 1, 1)
                    local sLockType = string.sub(gzTextVar.zRoomList[iCurRoom].sDoorW, 2)
                    
                    --Is the door open?
                    if(sDoorState == "O") then
                        LM_ExecuteScript(gzTextVar.sRootPath .. "Functions/Modify Door State.lua", gzTextVar.zEntities[i].sLocation, "C" .. sLockType, "W", ciModifyVisibility, 0)
                    end
                end
            
            end
            
            --We no longer need to close anything.
            gzTextVar.zEntities[i].sLastDoorDir = "P"
            
            --Closing a door burns a turn.
            bClosedDoorThisTurn = true
        end
    end
    
    --Pass back the result.
    return bClosedDoorThisTurn
end

--[ ================================ Player Spotted and Chasing ================================= ]
--Called to check if the player was spotted and is being chased, or if the enemy is patrolling back
-- to where they started a chase.
--Returns true if the AI update needs to stop here.
fnStandardAI_SpotChasePlayer = function(i, bCanSeePlayer, fnCatchPlayerFunc)
    
    --Positions.
    local fEntityX, fEntityY, fEntityZ = fnGetRoomPosition(gzTextVar.zEntities[i].sLocation)
    local fPlayerX, fPlayerY, fPlayerZ = fnGetRoomPosition(gzTextVar.gzPlayer.sLocation)
    
    --If the player and enemy are directly adjacent (not Z-levels) then this flag gets set to true. It allows the
    -- enemy to open the door and move in one turn, to prevent the player from slamming the door on them.
    gzTextVar.bOpenAndMoveInOneTurn = false
    if(fEntityZ == fPlayerZ) then
        if((fPlayerX == fEntityX - 1 and fPlayerY == fEntityY) or (fPlayerX == fEntityX + 1 and fPlayerY == fEntityY)) then
            gzTextVar.bOpenAndMoveInOneTurn = true
        elseif((fPlayerY == fEntityY - 1 and fPlayerX == fEntityX) or (fPlayerY == fEntityY + 1 and fPlayerX == fEntityX)) then
            gzTextVar.bOpenAndMoveInOneTurn = true
        end
    end

    --If we lost sight of the player, roll to see if we forget where they were.
    if(bCanSeePlayer == false) then
        local iRoll = LM_GetRandomNumber(1, 100)
        if(iRoll < gzTextVar.zEntities[i].iChanceToForget) then
            gzTextVar.zEntities[i].sLastSawPlayer = "Nowhere"
        end
    end

    --AI is moving to the location where it last saw the player.
    if(gzTextVar.zEntities[i].sLastSawPlayer ~= "Nowhere") then
        
        --Can we see the player? Update chasing to the new destination.
        if(bCanSeePlayer == true) then
            gzTextVar.zEntities[i].sLastSawPlayer = gzTextVar.gzPlayer.sLocation
        end
        
        --Move the doll towards the target.
        LM_ExecuteScript(gzTextVar.sRootPath .. "AIs/Standard Move To Destination.lua", i, gzTextVar.zEntities[i].sLastSawPlayer)
        
        --Entity emits sound.
        local iDestIndex = fnGetRoomIndex(gzTextVar.zEntities[i].sLocation)
        if(iDestIndex ~= -1) then
            gzTextVar.zRoomList[iDestIndex].iLastHeardSound = gzTextVar.iWorldTurns
        end
        
        --If we are at the location where we last saw the player, clear the last-spotted location.
        if(gzTextVar.zEntities[i].sLastSawPlayer == gzTextVar.zEntities[i].sLocation) then
            gzTextVar.zEntities[i].sLastSawPlayer = "Nowhere"
        end
        
        --If this is the player's location, attack them!
        if(gzTextVar.zEntities[i].sLocation == gzTextVar.gzPlayer.sLocation) then
            fnCatchPlayerFunc(i)
        end
        
        --We handled the movement.
        return true

    --We are not chasing the player, but we are patrolling back to where we started.
    elseif(gzTextVar.zEntities[i].sFirstSawPlayer ~= "Nowhere") then
        
        --If we spotted the player, resume the chase.
        if(bCanSeePlayer == true) then
        
            --Store the location where we spotted the player.
            gzTextVar.zEntities[i].sLastSawPlayer = gzTextVar.gzPlayer.sLocation
        
            --Move the doll towards the target.
            LM_ExecuteScript(gzTextVar.sRootPath .. "AIs/Standard Move To Destination.lua", i, gzTextVar.zEntities[i].sLastSawPlayer)
        
            --Doll emits sound.
            local iDestIndex = fnGetRoomIndex(gzTextVar.zEntities[i].sLocation)
            if(iDestIndex ~= -1) then
                gzTextVar.zRoomList[iDestIndex].iLastHeardSound = gzTextVar.iWorldTurns
            end
            
            --Catch the player, attack them!
            if(gzTextVar.zEntities[i].sLocation == gzTextVar.gzPlayer.sLocation) then
                fnCatchPlayerFunc(i)
            end
            
        --Can't see the player. Resume moving back to the starting location.
        else
            
            --Flags.
            local bResumedPatrol = false
            
            --If we don't even have a patrol list:
            if(gzTextVar.zEntities[i].saPatrolList == nil) then
            
            --If we do have a patrol list:
            else
                --If we're currently on a tile that's on our patrol route, just advance to that patrol position.
                local q = 1
                while(gzTextVar.zEntities[i].saPatrolList[q] ~= nil) do
                    
                    --Target room.
                    local sTargetRoom = gzTextVar.zEntities[i].saPatrolList[q+1]
                    if(sTargetRoom == nil) then sTargetRoom = gzTextVar.zEntities[i].saPatrolList[1] end
                    
                    --Run the simulation from each patrol point to the next.
                    local iTargetIndex = fnGetRoomIndex(sTargetRoom)
                    local sCurrentRoomName = gzTextVar.zEntities[i].saPatrolList[q]
                    local iCurrentRoomIndex = fnGetRoomIndex(sCurrentRoomName)
                    while(sCurrentRoomName ~= sTargetRoom) do
                        
                        --Get the first letter of matching movement.
                        local sPathInstructions = gzTextCon.zRoomList[iTargetIndex].zPathListing[iCurrentRoomIndex]
                        if(sPathInstructions == "" or sPathInstructions == nil) then break end
                        local sFirstLetter = string.sub(sPathInstructions, 1, 1)
                        
                        --Get the next room.
                        local sDestination = "Null"
                        if(sFirstLetter == "N") then
                            sDestination = gzTextVar.zRoomList[iCurrentRoomIndex].sMoveN
                        elseif(sFirstLetter == "S") then
                            sDestination = gzTextVar.zRoomList[iCurrentRoomIndex].sMoveS
                        elseif(sFirstLetter == "E") then
                            sDestination = gzTextVar.zRoomList[iCurrentRoomIndex].sMoveE
                        elseif(sFirstLetter == "W") then
                            sDestination = gzTextVar.zRoomList[iCurrentRoomIndex].sMoveW
                        elseif(sFirstLetter == "U") then
                            sDestination = gzTextVar.zRoomList[iCurrentRoomIndex].sMoveU
                        elseif(sFirstLetter == "D") then
                            sDestination = gzTextVar.zRoomList[iCurrentRoomIndex].sMoveD
                        end
                        
                        --If the room matches the current room, we're on a patrol path. Break out.
                        if(sDestination == gzTextVar.zEntities[i].sLocation) then
                            bResumedPatrol = true
                            break
                        end
                        
                        --Otherwise, advance.
                        sCurrentRoomName = sDestination
                        iCurrentRoomIndex = fnGetRoomIndex(sCurrentRoomName)
                        
                    end
                    
                    --If resuming patrol, break out.
                    if(bResumedPatrol) then break end
                    
                    --Next.
                    q = q + 1
                end
            end
            
            --Resumed patrol. Clear flags.
            if(bResumedPatrol) then
                gzTextVar.zEntities[i].sFirstSawPlayer = "Nowhere"
                gzTextVar.zEntities[i].sLastSawPlayer = "Nowhere"
            
            --Not on a patrol tile, so patrol back to where we were.
            else
            
                --Move the doll towards the target.
                LM_ExecuteScript(gzTextVar.sRootPath .. "AIs/Standard Move To Destination.lua", i, gzTextVar.zEntities[i].sFirstSawPlayer)
            
                --Doll emits sound.
                local iDestIndex = fnGetRoomIndex(gzTextVar.zEntities[i].sLocation)
                if(iDestIndex ~= -1) then
                    gzTextVar.zRoomList[iDestIndex].iLastHeardSound = gzTextVar.iWorldTurns
                end
                
                --If we reach the destination, stop the move-back routine.
                if(gzTextVar.zEntities[i].sFirstSawPlayer == gzTextVar.zEntities[i].sLocation) then
                    
                    --Reset flags.
                    gzTextVar.zEntities[i].sFirstSawPlayer = "Nowhere"
                    gzTextVar.zEntities[i].sLastSawPlayer = "Nowhere"
                end
            
                --Catch the player, attack them!
                if(gzTextVar.zEntities[i].sLocation == gzTextVar.gzPlayer.sLocation) then
                    fnCatchPlayerFunc(i)
                end
            end
        end
            
        --We handled the movement.
        return true
        
    --Doll is neither chasing nor returning to the start of a chase.
    else
        
        --If we can see the player, initialize the chase.
        if(bCanSeePlayer == true) then
            
            --If we just opened a door, close the door before pursuit begins. If LOS gets broken, stop chasing.
            if(gzTextVar.zEntities[i].sLastDoorDir ~= "P") then
                
                --A two-letter long string means we have opened the door but not moved through it yet. Note that the directions
                -- will be reversed. An "NP" case means we opened the door to the south.
                local iLen = string.len(gzTextVar.zEntities[i].sLastDoorDir)
                if(iLen == 2) then
                    
                    --Room index stuff.
                    local iCurRoom = fnGetRoomIndex(gzTextVar.zEntities[i].sLocation)
                    
                    --Get the direction.
                    if(gzTextVar.zEntities[i].sLastDoorDir == "SP") then
                        
                        --Setup.
                        local sPrvRoom = gzTextVar.zRoomList[iCurRoom].sMoveN
                        local iPrvRoom = gzTextVar.zRoomList[iCurRoom].iMoveN
                        
                        --Error check.
                        if(gzTextVar.zRoomList[iCurRoom].sDoorN ~= nil and iPrvRoom >= 1 and iPrvRoom <= gzTextVar.iRoomsTotal) then
                            
                            --Get the string past the first letter.
                            local sDoorState = string.sub(gzTextVar.zRoomList[iCurRoom].sDoorN, 1, 1)
                            local sLockType = string.sub(gzTextVar.zRoomList[iCurRoom].sDoorN, 2)
                            
                            --Is the door open?
                            if(sDoorState == "O" and sLockType ~= " Normal") then
                                LM_ExecuteScript(gzTextVar.sRootPath .. "Functions/Modify Door State.lua", gzTextVar.zEntities[i].sLocation, "C" .. sLockType, "N", ciModifyVisibility, 0)
                            end
                        end
                    
                    elseif(gzTextVar.zEntities[i].sLastDoorDir == "WP") then
                        
                        --Setup.
                        local sPrvRoom = gzTextVar.zRoomList[iCurRoom].sMoveE
                        local iPrvRoom = gzTextVar.zRoomList[iCurRoom].iMoveE
                        
                        --Error check.
                        if(gzTextVar.zRoomList[iCurRoom].sDoorE ~= nil and iPrvRoom >= 1 and iPrvRoom <= gzTextVar.iRoomsTotal) then
                            
                            --Get the string past the first letter.
                            local sDoorState = string.sub(gzTextVar.zRoomList[iCurRoom].sDoorE, 1, 1)
                            local sLockType = string.sub(gzTextVar.zRoomList[iCurRoom].sDoorE, 2)
                            
                            --Is the door open?
                            if(sDoorState == "O" and sLockType ~= " Normal") then
                                LM_ExecuteScript(gzTextVar.sRootPath .. "Functions/Modify Door State.lua", gzTextVar.zEntities[i].sLocation, "C" .. sLockType, "E", ciModifyVisibility, 0)
                            end
                        end
                    
                    elseif(gzTextVar.zEntities[i].sLastDoorDir == "NP") then
                        
                        --Setup.
                        local sPrvRoom = gzTextVar.zRoomList[iCurRoom].sMoveS
                        local iPrvRoom = gzTextVar.zRoomList[iCurRoom].iMoveS
                        
                        --Error check.
                        if(gzTextVar.zRoomList[iCurRoom].sDoorS ~= nil and iPrvRoom >= 1 and iPrvRoom <= gzTextVar.iRoomsTotal) then
                            
                            --Get the string past the first letter.
                            local sDoorState = string.sub(gzTextVar.zRoomList[iCurRoom].sDoorS, 1, 1)
                            local sLockType = string.sub(gzTextVar.zRoomList[iCurRoom].sDoorS, 2)
                            
                            --Is the door open?
                            if(sDoorState == "O" and sLockType ~= " Normal") then
                                LM_ExecuteScript(gzTextVar.sRootPath .. "Functions/Modify Door State.lua", gzTextVar.zEntities[i].sLocation, "C" .. sLockType, "S", ciModifyVisibility, 0)
                            end
                        end
                    
                    elseif(gzTextVar.zEntities[i].sLastDoorDir == "EP") then
                        
                        --Setup.
                        local sPrvRoom = gzTextVar.zRoomList[iCurRoom].sMoveW
                        local iPrvRoom = gzTextVar.zRoomList[iCurRoom].iMoveW
                        
                        --Error check.
                        if(gzTextVar.zRoomList[iCurRoom].sDoorW ~= nil and iPrvRoom >= 1 and iPrvRoom <= gzTextVar.iRoomsTotal) then
                            
                            --Get the string past the first letter.
                            local sDoorState = string.sub(gzTextVar.zRoomList[iCurRoom].sDoorW, 1, 1)
                            local sLockType = string.sub(gzTextVar.zRoomList[iCurRoom].sDoorW, 2)
                            
                            --Is the door open?
                            if(sDoorState == "O" and sLockType ~= " Normal") then
                                LM_ExecuteScript(gzTextVar.sRootPath .. "Functions/Modify Door State.lua", gzTextVar.zEntities[i].sLocation, "C" .. sLockType, "W", ciModifyVisibility, 0)
                            end
                        end
                    
                    end
                    
                    --We no longer need to close anything.
                    gzTextVar.zEntities[i].sLastDoorDir = "P"
                    
                end
            end

            --Store both the player's location and where we started the chase. Dolls path back to where they started.
            gzTextVar.zEntities[i].sFirstSawPlayer = gzTextVar.zEntities[i].sLocation
            gzTextVar.zEntities[i].sLastSawPlayer = gzTextVar.gzPlayer.sLocation
        
            --Move the doll towards the target.
            LM_ExecuteScript(gzTextVar.sRootPath .. "AIs/Standard Move To Destination.lua", i, gzTextVar.zEntities[i].sLastSawPlayer)
        
            --Doll emits sound.
            local iDestIndex = fnGetRoomIndex(gzTextVar.zEntities[i].sLocation)
            if(iDestIndex ~= -1) then
                gzTextVar.zRoomList[iDestIndex].iLastHeardSound = gzTextVar.iWorldTurns
            end
            
            --If this is the player's location, attack them!
            if(gzTextVar.zEntities[i].sLocation == gzTextVar.gzPlayer.sLocation) then
                fnCatchPlayerFunc(i)
            end
            
            --We handled the movement.
            return true
        
        --Can't see the player. Do nothing here, follow patrol logic.
        else
    
        end
    end
    
    --Did not stop movement. Return false.
    return false
end

--[ ========================================= Patrolling ======================================== ]
--Basic logic for patrolling to locations.
fnStandardAI_Patrol = function(i, fnCatchPlayerFunc)

    --[New Movement]
    --If the current movement index is -1, move to the next node on the patrol list.
    if(gzTextVar.zEntities[i].iMoveIndex == -1) then
        
        --[Staying Still]
        --Can be flagged to stay still if you want.
        if(gzTextVar.zEntities[i].iStayStillTurns > 0) then
            gzTextVar.zEntities[i].iStayStillTurns = gzTextVar.zEntities[i].iStayStillTurns - 1
            return
        end
        
        --Setup.
        local sPatrolTarget = "Nowhere"
        
        --[Fixed Patrol Routes]
        --Entity patrols between a fixed set of locations.
        if(gzTextVar.zEntities[i].bMovesRandomly == false) then
        
            --Get the current node.
            local p = gzTextVar.zEntities[i].iPatrolIndex
            gzTextVar.zEntities[i].iPatrolIndex = gzTextVar.zEntities[i].iPatrolIndex + 1
            sPatrolTarget = gzTextVar.zEntities[i].saPatrolList[p]
            if(sPatrolTarget == nil) then
                sPatrolTarget = gzTextVar.zEntities[i].saPatrolList[1]
                gzTextVar.zEntities[i].iPatrolIndex = 2
            end
            if(sPatrolTarget == nil) then
                io.write("Warning: Entity " .. i .. " has no patrol paths.\n")
                return
            end
        
        --[Wandering Enemies]
        --Enemy picks a location, paths there, and then picks a new location.
        else
        
            --No valid moves, somehow.
            if(gzTextVar.iRandomPathsTotal < 1) then return end
        
            --Roll a random slot.
            local iRandomRoll = LM_GetRandomNumber(1, gzTextVar.iRandomPathsTotal)
            sPatrolTarget = gzTextVar.saValidRandomPaths[iRandomRoll]
            gzTextVar.zEntities[i].saPatrolList = {sPatrolTarget}
        
        end
        
        --Make the first move in the direction in question.
        gzTextVar.zEntities[i].iMoveIndex = 1
        
        --Run the subroutine. This will move the entity if everything went well.
        LM_ExecuteScript(gzTextVar.sRootPath .. "AIs/Standard Move To Destination.lua", i, sPatrolTarget)
            
        --Entity emits sound.
        local iDestIndex = fnGetRoomIndex(gzTextVar.zEntities[i].sLocation)
        if(iDestIndex ~= -1) then
            gzTextVar.zRoomList[iDestIndex].iLastHeardSound = gzTextVar.iWorldTurns
        end
        
        --If the destination was reached, reset the move handler.
        if(gzTextVar.zEntities[i].sLocation == sPatrolTarget) then
            gzTextVar.zEntities[i].iMoveIndex = -1
        end
                
        --Catch the player, attack them!
        if(gzTextVar.zEntities[i].sLocation == gzTextVar.gzPlayer.sLocation) then
            fnCatchPlayerFunc(i)
        end

    --Continuing a patrol path.
    else
        
        --Check if the patrol is invalid.
        if(gzTextVar.zEntities[i].saPatrolList == nil) then
            gzTextVar.zEntities[i].iMoveIndex = -1
            gzTextVar.zEntities[i].iPatrolIndex = 1
            gzTextVar.zEntities[i].saPatrolList = {"None"}
            return
        end
        
        --Variables.
        local sPatrolTarget = "Target"
        local sRoomName = gzTextVar.zEntities[i].sLocation
        local iRoomIndex = fnGetRoomIndex(sRoomName)
        
        --When moving along fixed patrols:
        if(gzTextVar.zEntities[i].bMovesRandomly == false) then
        
            --Get the destination again.
            local p = gzTextVar.zEntities[i].iPatrolIndex - 1
            sPatrolTarget = gzTextVar.zEntities[i].saPatrolList[p]
            
            --Increment the move index.
            gzTextVar.zEntities[i].iMoveIndex = gzTextVar.zEntities[i].iMoveIndex + 1
            
            --Error checks.
            if(iRoomIndex == -1 or iTargetIndex == -1) then return end
            
            --Already at destination, so reset.
            if(sRoomName == sPatrolTarget) then
                gzTextVar.zEntities[i].iMoveIndex = -1
                return
            end
        
        --When moving randomly, move towards the patrol target.
        else
            sPatrolTarget = gzTextVar.zEntities[i].saPatrolList[1]
        end
        
        --Run the subroutine. This will move the entity if everything went well.
        LM_ExecuteScript(gzTextVar.sRootPath .. "AIs/Standard Move To Destination.lua", i, sPatrolTarget)
            
        --Entity emits sound.
        local iDestIndex = fnGetRoomIndex(gzTextVar.zEntities[i].sLocation)
        if(iDestIndex ~= -1) then
            gzTextVar.zRoomList[iDestIndex].iLastHeardSound = gzTextVar.iWorldTurns
        end
        
        --If the destination was reached, reset the move handler.
        if(gzTextVar.zEntities[i].sLocation == sPatrolTarget) then
            gzTextVar.zEntities[i].iMoveIndex = -1
        end
                
        --Catch the player, attack them!
        if(gzTextVar.zEntities[i].sLocation == gzTextVar.gzPlayer.sLocation) then
            fnCatchPlayerFunc(i)
        end
    end
end
