;----------------------------------------------------------------------------;
; Does A* pathfinding for rockraiders and vehicles
;
; Copyright 2015 Ruben De Smet
;
; Redistribution and use in source and binary forms, with or without
; modification, are permitted provided that the following conditions are
; met:
; 
;     (1) Redistributions of source code must retain the above copyright
;         notice, this list of conditions and the following disclaimer. 
; 
;     (2) Redistributions in binary form must reproduce the above copyright
;         notice, this list of conditions and the following disclaimer in
;         the documentation and/or other materials provided with the
;         distribution.  
;         
;     (3) The name of the author may not be used to
;         endorse or promote products derived from this software without
;         specific prior written permission.
; 
; THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
; DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
; INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
; (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
; HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
; STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
; IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
; POSSIBILITY OF SUCH DAMAGE.
;
;----------------------------------------------------------------------------;

IDEAL
P386
MODEL FLAT, C
ASSUME cs:_TEXT,ds:FLAT,es:FLAT,fs:FLAT,gs:FLAT

INCLUDE "ASTAR.INC"
INCLUDE "READLVL.INC"
INCLUDE "DEBUG.INC"

STRUC TPriorityField
    heuristic dd ?
    distance dd ?
    x db ?
    y db ?
    fromx db ?
    fromy db ?
ENDS

STRUC TField
    distance dd ?
    x db ?
    y db ?
ENDS

CODESEG 

PROC getPath
        USES ecx
        ARG @@tgtx:dword, \
            @@tgty:dword \
        RETURNS eax, ebx ; eax contains x, ebx contains y

        call getLevelWidth
        imul eax, [@@tgty]
        add eax, [@@tgtx]
        imul eax, SIZE TField
        add eax, offset backtraceGraph        
        mov ecx, eax

        xor eax, eax
        xor ebx, ebx

        mov al, [(TField ptr ecx).x]
        mov bl, [(TField ptr ecx).y]

        ret
ENDP getPath

PROC findPath
        ; eax will contain a 1 when a path has been found
        ;                    0 otherwise.
        ARG @@srcx:dword, \
            @@srcy:dword, \
            @@tgtx:dword, \
            @@tgty:dword, \
            @@type:dword \
            RETURNS eax

        ; Check whether the target field is "allowed" for
        ; the selected vehicle or rock raider
        call getField, [@@tgtx], [@@tgty]
        mov al, [byte ptr eax]
        and eax, 0FFh

        add eax, offset actionTable
        mov eax, [eax]
        and eax, [@@type]               ; TODO: for now, rock raider is hard coded
        jnz @canGoToTarget

        mov eax, 0
        ret
@canGoToTarget:

        call cleanData
        mov eax, [@@type]
        mov [currentType], eax

        mov eax, [@@srcx]
        mov [currentOpen.x], al
        mov eax, [@@srcy]
        mov [currentOpen.y], al

        call distance, [@@srcx], [@@srcy], [@@tgtx], [@@tgty] 
        ; eax <- distance
        call addOpen, [@@srcx], [@@srcy], eax, 0

@openListNotEmpty:
        call popOpen
        cmp eax, 0
        je @openListEmpty

        call addToMap

        call addClosed

        mov eax, [@@tgtx]
        cmp [currentOpen.x], al
        jne @nextOpen
        mov eax, [@@tgty]
        cmp [currentOpen.y], al
        jne @nextOpen

        jmp @routeFound

        @nextOpen:
        call addNeighbours, [@@tgtx], [@@tgty]

        jmp @openListNotEmpty

@openListEmpty:
        mov eax, 0
        ret

@routeFound:
        mov eax, 1
        ret
ENDP findPath

PROC addToMap
        USES eax, ecx

        call getLevelWidth
        xor ecx, ecx
        mov cl, [currentOpen.y]
        imul eax, ecx
        mov cl, [currentOpen.x]
        add eax, ecx
        imul eax, SIZE TField
        add eax, offset backtraceGraph

        mov ecx, [currentOpen.distance]
        cmp [(TField ptr eax).distance], ecx
        jbe @dontAdd

        mov [(TField ptr eax).distance], ecx
        mov cl, [currentOpen.fromx]
        mov [(TField ptr eax).x], cl
        mov cl, [currentOpen.fromy]
        mov [(TField ptr eax).y], cl

@dontAdd:
        ret
ENDP addToMap

; Is closed checks whether the field considered is "closed" for being added to the open list.
; So, it also checks whether we can go on the selected field.
PROC isClosed
        USES ebx, ecx, edx
        ARG @@x:dword, \
            @@y:dword RETURNS eax

        ; Check bounds first:

        call getLevelWidth
        cmp [@@x], eax
        ja notWithinBounds ; ja considers -1 > 10

        call getLevelHeight
        cmp [@@y], eax
        ja notWithinBounds

        ; Check whether this field is "allowed" for
        ; the selected vehicle or rock raider
        call getField, [@@x], [@@y]
        mov al, [byte ptr eax]
        and eax, 0FFh

        add eax, offset actionTable
        mov eax, [eax]
        and eax, [currentType]               ; TODO: for now, rock raider is hard coded
        jnz @canGoHere


        inc eax ; mov eax, 1
        ret

@canGoHere:
        
        ; Getting here means the field is okay to walk/fly/whatever on

        xor ecx, ecx
        mov cx, [closedlistSize]
        cmp cx, 0 ; If empty, return 0
        jne @closedNotEmpty

        mov eax, 0
        ret

@closedNotEmpty:
        mov ebx, offset closedlist

@loopClosed:
        mov edx, [@@x]
        cmp [(TField ptr ebx).x], dl
        jne @nextClosed
        mov edx, [@@y]
        cmp [(TField ptr ebx).y], dl
        jne @nextClosed

        ; If reached here, yep, contained in closed list
        mov eax, 1
        ret

    @nextClosed:
        add ebx, SIZE TField
        dec ecx
        jnz @loopClosed

        mov eax, 0
        ret

notWithinBounds:
        mov eax, 1
        ret
ENDP isClosed

PROC addNeighbours
        USES eax, ebx, ecx, edx
        ARG @@tgtx:dword, \
            @@tgty:dword
        ; Push all neighbours of currentOpen on openList

        xor ebx, ebx
        xor ecx, ecx
        
        mov bl, [currentOpen.x]
        mov cl, [currentOpen.y]
        mov edx, [currentOpen.distance]
        inc edx ; Next distance is one more.

        ; Up
        dec ecx
        call isClosed, ebx, ecx
        cmp eax, 0
        jne @noUp
        call distance, ebx, ecx, [@@tgtx], [@@tgty]
        add eax, edx
        call addOpen, ebx, ecx, eax, edx
        @noUp:
        inc ecx

        ; Right
        inc ebx
        call isClosed, ebx, ecx
        cmp eax, 0
        jne @noRight
        call distance, ebx, ecx, [@@tgtx], [@@tgty]
        add eax, edx
        call addOpen, ebx, ecx, eax, edx
        @noRight:
        dec ebx

        ; Left
        dec ebx
        call isClosed, ebx, ecx
        cmp eax, 0
        jne @noLeft
        call distance, ebx, ecx, [@@tgtx], [@@tgty]
        add eax, edx
        call addOpen, ebx, ecx, eax, edx
        @noLeft:
        inc ebx

        ; Down
        inc ecx
        call isClosed, ebx, ecx
        cmp eax, 0
        jne @noDown
        call distance, ebx, ecx, [@@tgtx], [@@tgty]
        add eax, edx
        call addOpen, ebx, ecx, eax, edx
        @noDown:
        dec ecx

        ret
ENDP addNeighbours

PROC popOpen
        ARG RETURNS eax
        USES ebx, ecx, edx, esi, edi
        ; eax contains the smallest current heuristic
        ; ebx contains the index of that field

        cmp [openlistSize], 0           ; If empty, return 0
        jne @goForth

        mov eax, 0
        ret

@goForth:

        mov eax, 0FFFFFFFFh             ; Longest distance possible in 32 bits.
        xor ebx, ebx
        xor ecx, ecx                    ; ecx contains the current index

@searchFurther:
        mov edx, ecx
        imul edx, SIZE TPriorityField
        cmp [(TPriorityField ptr (openlist + edx)).heuristic], eax
        ja @notBetter
        ; Better guess found, put right values in eax and ebx
        mov eax, [(TPriorityField ptr (openlist + edx)).heuristic]
        mov ebx, ecx

@notBetter:

        inc ecx
        cmp cx, [openlistSize]
        jne @searchFurther

        ; By now, we have found the right item to pop from the priorityqueue.

        ; Move the correct item in currentOpen
        mov ecx, SIZE TPriorityField
        mov esi, ebx
        imul esi, ecx
        add esi, offset openlist

        mov edi, offset currentOpen
        rep movsb

        ; Now make the remove the thing from the vector

        xor ecx, ecx
        mov cx, [openlistSize]
        sub ecx, ebx
        dec ecx
        imul ecx, SIZE TPriorityField
        mov edi, esi
        sub edi, SIZE TPriorityField
        rep movsb

        dec [openlistSize]
        mov eax, 1
        ret
ENDP popOpen

PROC addClosed
        USES eax, ebx

        xor ebx, ebx
        xor eax, eax

        mov bx, [closedlistSize]
        imul ebx, SIZE TField        
        add ebx, offset closedlist ; ebx contains the target TField

        mov al, [currentOpen.x]
        mov [(TField ptr ebx).x], al
        mov al, [currentOpen.y]
        mov [(TField ptr ebx).y], al
        mov eax, [currentOpen.distance]
        mov [(TField ptr ebx).distance], eax

        inc [closedlistSize]
        cmp [closedlistSize], CLOSED_LIST_SIZE_MAX
        jne @noProblemWithClosedVector

        xor eax, eax
        mov ax, [closedlistSize]
        call crash, offset closedOutOfMemory, eax

@noProblemWithClosedVector:
        ret
ENDP addClosed

PROC addOpen
        USES eax, ebx
        ARG @@x:dword, \
            @@y:dword, \
            @@priority:dword, \
            @@distance:dword

        xor eax, eax
        mov ax, [openlistSize]
        imul eax, SIZE TPriorityField
        add eax, offset openlist

        mov ebx, [@@x]
        mov [(TPriorityField ptr eax).x], bl
        mov ebx, [@@y]
        mov [(TPriorityField ptr eax).y], bl

        mov bl, [currentOpen.x]
        mov [(TPriorityField ptr eax).fromx], bl
        mov bl, [currentOpen.y]
        mov [(TPriorityField ptr eax).fromy], bl

        mov ebx, [@@priority]
        mov [(TPriorityField ptr eax).heuristic], ebx
        mov ebx, [@@distance]
        mov [(TPriorityField ptr eax).distance], ebx

        inc [openlistSize]
        cmp [openlistSize], OPEN_LIST_SIZE_MAX
        jne @noProblem

        xor eax, eax
        mov ax, [openlistSize]
        call crash, offset openOutOfMemory, eax

@noProblem:
        ret
ENDP

PROC distance
        USES ebx
        ARG @@srcx:dword, \
            @@srcy:dword, \
            @@tgtx:dword, \
            @@tgty:dword \
        RETURNS eax

        mov eax, [@@srcx]
        sub eax, [@@tgtx]

        jns @noSignChangex
        neg eax

        @noSignChangex:

        mov ebx, [@@srcy]
        sub ebx, [@@tgty]

        jns @noSignChangey
        neg ebx

        @noSignChangey:
        add eax, ebx
        ret
ENDP distance

PROC cleanData
        USES eax, ecx
        mov [openlistSize], 0
        mov [closedlistSize], 0

        mov [currentOpen.x], -1
        mov [currentOpen.y], -1
        mov [currentOpen.distance], 0

        call getLevelWidth
        mov ecx, eax
        call getLevelHeight
        imul ecx, eax

        mov eax, offset backtraceGraph
@fieldIter:
        mov [(TField ptr eax).distance], 0ffffffffh ; Set to approximately +inf
        mov [(TField ptr eax).x], 0
        mov [(TField ptr eax).y], 0
        add eax, SIZE TField
        dec ecx
        jnz @fieldIter

        ret
ENDP cleanData

DATASEG

openOutOfMemory   db "Out of openlistSize memory. Hi dev: Please increase$"
closedOutOfMemory db "Out of closedlistSize memory. Hi dev: Please increase$"

; power | discover | walking | sailing | flying
actionTable db  00001101b, \ ;EMPTY
                00001101b, \ ;RUBBLE
                00000000b, \ ;GRAVEL
                00000000b, \ ;LOOSE ROCK
                00000000b, \ ;HARD ROCK
                00000000b, \ ;MASSIVE ROCK
                00000000b, \ ;KRISTAL SOURCE
                00000000b, \ ;OREROCK
                00001011b, \ ;WATER
                00001001b, \ ;LAVA
                00001101b, \ ;SNAIL HOLE
                00001101b, \ ;EROSION
                00011101b, \ ;POWER PATH
                00011101b, \ ;BUILDING POWER PATH
                00011000b  \ ;BUILDING

UDATASEG

currentType      dd ?
currentOpen      TPriorityField ?

openlist         TPriorityField OPEN_LIST_SIZE_MAX dup(?)
openlistSize     dw ?
closedlist       TField CLOSED_LIST_SIZE_MAX       dup(?)
closedlistSize   dw ?
backtraceGraph   TField MAX_LEVEL_SIZE             dup(?)

END
