You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
532 lines
14 KiB
NASM
532 lines
14 KiB
NASM
org 0x8000
|
|
bits 16
|
|
|
|
;precompiler constant
|
|
%define entityArraySize 16
|
|
;Let's begin by going into graphic mode
|
|
call initGraphics
|
|
|
|
;Now let's register some custom interrupt handlers
|
|
call registerInterruptHandlers
|
|
|
|
;init map
|
|
call initMap
|
|
|
|
;Main game loop
|
|
gameLoop:
|
|
call resetBuffer ;reset screen to draw on empty canvas
|
|
|
|
;MODULAR DRAWING CODE
|
|
mov di, entityArray
|
|
add di, 2 ;skip drawing player
|
|
.nextEntity:
|
|
cmp [di], word 0
|
|
je .skip
|
|
pusha
|
|
mov cx, [player+2] ;player x to draw relative
|
|
mov dx, [player+4] ;player z to draw relative
|
|
mov di, [di]
|
|
call drawEntity
|
|
popa
|
|
.skip:
|
|
add di, 2
|
|
cmp di, entityArray+((entityArraySize-1)*2) ;confirm that di is still pointing into the entityArray
|
|
jl .nextEntity
|
|
|
|
call drawMap
|
|
|
|
; PLAYER DRAWING CODE
|
|
mov si, [player] ;get animation
|
|
mov ax, [player+6] ;get index within animation
|
|
xor dx,dx
|
|
div word [si+2] ; animation time % time of full animation
|
|
mov ax, dx
|
|
xor dx, dx
|
|
div word [si] ; (animation time % time of full animation) / time of one frame
|
|
add ax, ax ; index*2 because image address is a word
|
|
|
|
add si, 4 ;skip first two words of structure
|
|
add si, ax ;add the offset to the frame
|
|
mov si, [si] ;set the image parameter to the image referenced in the frame
|
|
|
|
mov ax, 80/2 - 9/2 - 1 ;center player image
|
|
mov bx, 50/2 - 12/2 - 1 ;center player image
|
|
call drawImage
|
|
; END OF PLAYER DRAWING CODE
|
|
|
|
call copyBufferOver ;draw frame to screen
|
|
|
|
call gameControls ;handle control logic
|
|
|
|
call synchronize ;synchronize emulator and real application through delaying
|
|
|
|
jmp gameLoop
|
|
|
|
|
|
jmp $
|
|
|
|
;di = entity cx,dx = xpos,zpos
|
|
drawEntity:
|
|
push dx
|
|
inc word [di+6]
|
|
mov ax, [di+6] ;get index within animation
|
|
mov si, [di]
|
|
xor dx,dx
|
|
div word [si+2] ; animation time % time of full animation
|
|
mov ax, dx
|
|
xor dx, dx
|
|
div word [si] ; (animation time % time of full animation) / time of one frame
|
|
add ax, ax ; index*2 because image address is a word
|
|
|
|
add si, 4 ;skip first two words of structure
|
|
add si, ax ;add the offset to the frame
|
|
mov si, [si] ;set the image parameter to the image referenced in the frame
|
|
pop dx
|
|
|
|
;mov si, word [di] ;get animation
|
|
;mov si, word [si+4] ;get first frame of animation
|
|
|
|
mov ax, word [di+2] ;get entity x
|
|
sub ax, cx ;subtract the position of the player from the x position
|
|
add ax, 80/2 - 9/2 - 1 ;relative to screen image drawing code for x position
|
|
mov bx, word [di+4] ;get entity y
|
|
sub bx, dx ;subtract the position of the player from the z position
|
|
add bx, 50/2 - 12/2 - 1 ;relative to screen image drawing code for z position
|
|
call drawImage ;draw image to buffer
|
|
ret
|
|
|
|
;di = entity, cx = new_xpos, dx = new_zpos, bp = new animation
|
|
;fixed for modular entity system
|
|
checkForCollision:
|
|
pusha ;save current state
|
|
mov si, entityArray ;set si to entityArray
|
|
.whileLoop:
|
|
mov bx, word [si] ;read entityArray entry
|
|
test bx, bx ;if entry is zero => end of array
|
|
jz .whileSkip
|
|
cmp bx, di ;if entity is equal to di => next entity to not collide with it self
|
|
jz .whileSkip
|
|
|
|
mov ax, word [bx+2] ;ax = entity x
|
|
sub ax, 8 ;subtract 8 because of hitbox
|
|
cmp ax, cx ; (entityX-8 <= playerX)
|
|
jg .whileSkip
|
|
|
|
mov ax, word [bx+2] ;ax = entity x
|
|
add ax, 8 ;add 8 because of hitbox
|
|
cmp ax, cx ; (entityX+8 > playerX)
|
|
jle .whileSkip
|
|
|
|
mov ax, word [bx+4] ;ax = entity z
|
|
sub ax, 10 ;subtract 10 because of hitbox
|
|
cmp ax, dx ; (entityZ-10 <= playerZ)
|
|
jg .whileSkip
|
|
|
|
mov ax, word [bx+4] ;ax = entity z
|
|
add ax, 9 ;subtract 9 because of hitbox
|
|
cmp ax, dx ; (entityZ+9 > playerZ)
|
|
jle .whileSkip
|
|
|
|
;if we reach this point => actual collision
|
|
;mov cx, [di+2] ;set new x pos to current x pos => no movement
|
|
;mov dx, [di+4] ;set new z pos to current z pos => no movement
|
|
|
|
mov word [si], 0
|
|
inc word [coinFound]
|
|
|
|
;ding ding count found
|
|
|
|
jmp .noMapCollision
|
|
.whileSkip:
|
|
add si, 2 ;set si to the next entry in the entityArray
|
|
cmp si, entityArray+((entityArraySize-1)*2)
|
|
jl .whileLoop
|
|
.whileEnd
|
|
|
|
pusha
|
|
mov si, cx
|
|
mov bx, dx
|
|
call collideMap
|
|
popa
|
|
jnc .noMapCollision
|
|
;if we reach this point => actual collision
|
|
mov cx, [di+2] ;set new x pos to current x pos => no movement
|
|
mov dx, [di+4] ;set new z pos to current z pos => no movement
|
|
.noMapCollision:
|
|
mov byte [canWalk], 1
|
|
mov word [di] ,bp ;update the animation in use
|
|
mov word [di+2] ,cx ;update x pos
|
|
mov word [di+4] ,dx ;update y pos
|
|
popa ;reload old register state
|
|
ret
|
|
|
|
canWalk db 0
|
|
gameControls:
|
|
mov byte [canWalk], 0
|
|
mov di, player ;select the player as the main entity for "checkForCollision"
|
|
mov al, byte [pressA]
|
|
add al, byte [pressD]
|
|
cmp al, 0
|
|
jz .nokeyad
|
|
mov cx, word [player_PosX] ;set cx to player x
|
|
mov dx, word [player_PosZ] ;set dx to player z
|
|
mov bp, [player] ;set bp to current animation
|
|
cmp byte [pressD], 1 ;try to move x+1 if 'd' is pressed and set animation accordingly, test other cases otherwise
|
|
jne .nd
|
|
inc cx
|
|
mov bp, playerImg_right
|
|
.nd:
|
|
cmp byte [pressA], 1 ;try to move x-1 if 'a' is pressed and set animation accordingly, test other cases otherwise
|
|
jne .na
|
|
dec cx
|
|
mov bp, playerImg_left
|
|
.na:
|
|
call checkForCollision ;check if player would collide on new position, if not change position to new position
|
|
.nokeyad:
|
|
mov al, byte [pressW]
|
|
add al, byte [pressS]
|
|
cmp al, 0
|
|
jz .nokeyws
|
|
mov cx, word [player_PosX] ;set cx to player x
|
|
mov dx, word [player_PosZ] ;set dx to player z
|
|
mov bp, [player] ;set bp to current animation
|
|
cmp byte [pressW], 1 ;try to move z-1 if 'w' is pressed and set animation accordingly, test other cases otherwise
|
|
jne .nw
|
|
dec dx
|
|
mov bp, playerImg_back
|
|
.nw:
|
|
cmp byte [pressS], 1 ;try to move z+1 if 's' is pressed and set animation accordingly, test other cases otherwise
|
|
jne .ns
|
|
inc dx
|
|
mov bp, playerImg_front
|
|
.ns:
|
|
call checkForCollision ;check if player would collide on new position, if not change position to new position
|
|
.nokeyws:
|
|
cmp byte [canWalk], 0
|
|
jnz .noCollision
|
|
mov word [player+6], 0 ;reset animation counter
|
|
ret
|
|
.noCollision:
|
|
inc word [player+6] ;update animation if moving
|
|
ret
|
|
|
|
;======================================== NEW STUFF ==========================================
|
|
registerInterruptHandlers:
|
|
mov [0x0024], dword keyboardINTListener ;implements keyboardListener
|
|
ret
|
|
|
|
;; NEW KEYBOARD EVENT BASED CODE
|
|
pressA db 0
|
|
pressD db 0
|
|
pressW db 0
|
|
pressS db 0
|
|
keyboardINTListener: ;interrupt handler for keyboard events
|
|
pusha
|
|
xor bx,bx ; bx = 0: signify key down event
|
|
inc bx
|
|
in al,0x60 ;get input to AX, 0x60 = ps/2 first port for keyboard
|
|
btr ax, 7 ;al now contains the key code without key pressed flag, also carry flag set if key up event
|
|
jnc .keyDown
|
|
dec bx ; bx = 1: key up event
|
|
.keyDown:
|
|
cmp al,0x1e ;a
|
|
jne .check1
|
|
mov byte [cs:pressA], bl ;use cs overwrite because we don't know where the data segment might point to
|
|
.check1:
|
|
cmp al,0x20 ;d
|
|
jne .check2
|
|
mov byte [cs:pressD], bl
|
|
.check2:
|
|
cmp al,0x11 ;w
|
|
jne .check3
|
|
mov byte [cs:pressW], bl
|
|
.check3:
|
|
cmp al,0x1f ;s
|
|
jne .check4
|
|
mov byte [cs:pressS], bl
|
|
.check4:
|
|
mov al, 20h ;20h
|
|
out 20h, al ;acknowledge the interrupt so further interrupts can be handled again
|
|
popa ;resume state to not modify something by accident
|
|
iret ;return from an interrupt routine
|
|
|
|
;using interrupts instread of the BIOS is SUUPER fast which is why we need to delay execution for at least a few ms per gametick to not be too fast
|
|
synchronize:
|
|
pusha
|
|
mov si, 20 ; si = time in ms
|
|
mov dx, si
|
|
mov cx, si
|
|
shr cx, 6
|
|
shl dx, 10
|
|
mov ah, 86h
|
|
int 15h ;cx,dx sleep time in microseconds - cx = high word, dx = low word
|
|
popa
|
|
ret
|
|
|
|
;cx, dx = xpos, zpos, si = animation
|
|
;eax == 0 => success, else failed
|
|
addEntity:
|
|
pusha
|
|
mov bx, cx
|
|
mov di, entityArray
|
|
xor ax, ax
|
|
mov cx, (entityArraySize-1)
|
|
repne scasw ; iterate through entity array until empty stop is found
|
|
sub di, 2
|
|
test ecx, ecx ; abort here if at the end of the the entity array
|
|
je .failed
|
|
sub cx, (entityArraySize-1) ; calculate index within the array by using the amount of iterated entires
|
|
neg cx
|
|
shl cx, 3
|
|
add cx, entityArrayMem
|
|
mov [di], cx
|
|
mov di, cx
|
|
mov [di], si
|
|
mov [di+2], bx ; set x position of the entity
|
|
mov [di+4], dx ; set y position of the entity
|
|
xor bx, dx ; "randomise" initial animation position
|
|
mov [di+6], bx ; set animation state
|
|
popa
|
|
xor eax, eax ; return 0 if successfully added
|
|
ret
|
|
.failed:
|
|
popa
|
|
xor eax, eax
|
|
inc eax ; return 1 if failed to find a place for the entity
|
|
ret
|
|
|
|
;di = entity cx,dx = xpos,zpos
|
|
drawBlock:
|
|
mov ax, word [player+2]
|
|
sub ax, cx
|
|
imul ax, ax
|
|
mov bx, word [player+4]
|
|
sub bx, dx
|
|
imul bx, bx
|
|
add ax, bx
|
|
cmp ax, 3000 ;calculate distance
|
|
jge .skip
|
|
|
|
mov ax, cx
|
|
mov bx, dx
|
|
sub ax, word [player+2] ;subtract the position of the player from the x position
|
|
add ax, 80/2 - 9/2 - 1 ;relative to screen image drawing code for x position
|
|
sub bx, word [player+4] ;subtract the position of the player from the z position
|
|
add bx, 50/2 - 12/2 - 1 ;relative to screen image drawing code for z position
|
|
call drawImage ;draw image to buffer
|
|
.skip:
|
|
clc
|
|
ret
|
|
|
|
;set the position of the player to x=cx, z=dx
|
|
setSpawn:
|
|
mov word [player+2], cx ; set player x
|
|
mov word [player+4], dx ; set player z
|
|
add word [player+4], 3 ; offset player z
|
|
clc
|
|
ret
|
|
|
|
;spawn the coins add set the spawn position of the player
|
|
initMap:
|
|
mov si, coinImg
|
|
mov bp, addEntity
|
|
mov ah, 'X'
|
|
call iterateMap ; iterate the map and add a coin at every 'X' on the map
|
|
call spawnPlayer ; set spawn for player
|
|
ret
|
|
|
|
;draw the map
|
|
drawMap:
|
|
mov si, boxImg_0
|
|
mov bp, drawBlock
|
|
mov ah, '0'
|
|
call iterateMap ; iterate the map and add a box at every '0' on the map
|
|
;this second iteration is pretty unefficient but only optional for some ground texture
|
|
mov si, tileImg_0
|
|
mov bp, drawBlock
|
|
mov ah, ' '
|
|
call iterateMap ; iterate the map and add a tile at every ' ' on the map
|
|
ret
|
|
|
|
; si = player X, bx = player Y
|
|
collideMap:
|
|
mov bp, blockCollison
|
|
mov ah, '0'
|
|
call iterateMap ; iterate the map and check for a collision with a '0'
|
|
ret
|
|
|
|
;set the spawn of the player to the field 'P'
|
|
spawnPlayer:
|
|
mov bp, setSpawn
|
|
mov ah, 'P'
|
|
call iterateMap ; iterate the map and set the player position to the last 'P' found on the map
|
|
ret
|
|
|
|
%define tileWidth 8
|
|
%define ASCIImapWidth 64
|
|
%define ASCIImapHeight 64
|
|
;bp = function to call, ah = search for, si = parameter for bp function
|
|
iterateMap:
|
|
mov di, ASCIImap
|
|
mov cx, 0x0 ; map start x
|
|
mov dx, 0x0 ; map start y
|
|
.next:
|
|
mov al, [di]
|
|
test al, al
|
|
je .stop ; stop when null terminator found
|
|
cmp al, ah
|
|
jne .skip ; skip if the character is not the one this iteration is searching for
|
|
push ax ; save the content of ax
|
|
call bp ; call the specified function of this iteration
|
|
pop ax
|
|
jc .term ; the carry flag determines if the specified function has found what it was searching for (and thus exits)
|
|
.skip:
|
|
inc di ; point to the next character
|
|
add cx, tileWidth ; increase x pixel position
|
|
cmp cx, ASCIImapWidth*tileWidth ; check if x position is at the end of the line
|
|
jl .next
|
|
sub dx, tileWidth ; decrease y pixel position
|
|
xor cx, cx ; reset x position
|
|
jmp .next
|
|
.stop:
|
|
clc
|
|
.term:
|
|
ret
|
|
|
|
;si = player x, bx = player z, cx = block x, dx = block z
|
|
blockCollison:
|
|
push cx
|
|
push dx
|
|
sub cx, 8 ;subtract 8 because of hitbox
|
|
cmp cx, si ; (blockX-8 <= playerX)
|
|
jg .skip
|
|
add cx, 8+8 ;add 8 because of hitbox
|
|
cmp cx, si ; (blockX+8 > playerX)
|
|
jle .skip
|
|
sub dx, 10 ;subtract 10 because of hitbox
|
|
cmp dx, bx ; (blockZ-10 <= playerZ)
|
|
jg .skip
|
|
add dx, 9+10 ;subtract 9 because of hitbox
|
|
cmp dx, bx ; (blockZ+9 > playerZ)
|
|
jle .skip
|
|
stc
|
|
jmp .end
|
|
.skip:
|
|
clc
|
|
.end:
|
|
pop dx
|
|
pop cx
|
|
ret
|
|
|
|
%include "buffer.asm"
|
|
|
|
|
|
;game value
|
|
|
|
coinFound dw 0
|
|
|
|
;entity array
|
|
|
|
entityArray:
|
|
dw player
|
|
resw entityArraySize
|
|
|
|
;player structure
|
|
player:
|
|
player_Anim dw playerImg_front ;pointer to animation
|
|
player_PosX dw 0x32 ;position of player (x)
|
|
player_PosZ dw 0x32 ;position of player (z)
|
|
player_AnimC dw 0 ;animation counter
|
|
|
|
;entity structure
|
|
box:
|
|
box_Anim dw boxImg ;pointer to animation
|
|
box_PosX dw 0x10 ;position of box (x)
|
|
box_PosZ dw 0x10 ;position of box (z)
|
|
box_AnimC dw 0 ;animation counter
|
|
|
|
;other entity structures:
|
|
entityArrayMem:
|
|
resw entityArraySize*4
|
|
|
|
;animation structure
|
|
playerImg_front:
|
|
dw 5
|
|
dw 20
|
|
dw playerImg_front_0
|
|
dw playerImg_front_1
|
|
dw playerImg_front_0
|
|
dw playerImg_front_2
|
|
dw 0
|
|
|
|
playerImg_back:
|
|
dw 5
|
|
dw 20
|
|
dw playerImg_back_0
|
|
dw playerImg_back_1
|
|
dw playerImg_back_0
|
|
dw playerImg_back_2
|
|
dw 0
|
|
|
|
playerImg_right:
|
|
dw 5
|
|
dw 20
|
|
dw playerImg_right_0
|
|
dw playerImg_right_1
|
|
dw playerImg_right_0
|
|
dw playerImg_right_2
|
|
dw 0
|
|
|
|
playerImg_left:
|
|
dw 5
|
|
dw 20
|
|
dw playerImg_left_0
|
|
dw playerImg_left_1
|
|
dw playerImg_left_0
|
|
dw playerImg_left_2
|
|
dw 0
|
|
|
|
boxImg:
|
|
dw 1 ;time per frames
|
|
dw 1 ;time of animation
|
|
dw boxImg_0 ;frames
|
|
dw 0 ;zero end frame
|
|
|
|
coinImg:
|
|
dw 5 ;time per frames
|
|
dw 20 ;time of animation
|
|
dw coin_0 ;frames
|
|
dw coin_1 ;frames
|
|
dw coin_2 ;frames
|
|
dw coin_1 ;frames
|
|
dw 0 ;zero end frame
|
|
|
|
playerImg_front_0 incbin "img/player_front_0.bin"
|
|
playerImg_front_1 incbin "img/player_front_1.bin"
|
|
playerImg_front_2 incbin "img/player_front_2.bin"
|
|
playerImg_back_0 incbin "img/player_back_0.bin"
|
|
playerImg_back_1 incbin "img/player_back_1.bin"
|
|
playerImg_back_2 incbin "img/player_back_2.bin"
|
|
playerImg_right_0 incbin "img/player_right_0.bin"
|
|
playerImg_right_1 incbin "img/player_right_1.bin"
|
|
playerImg_right_2 incbin "img/player_right_2.bin"
|
|
playerImg_left_0 incbin "img/player_left_0.bin"
|
|
playerImg_left_1 incbin "img/player_left_1.bin"
|
|
playerImg_left_2 incbin "img/player_left_2.bin"
|
|
|
|
coin_0 incbin "img/coin_0.bin"
|
|
coin_1 incbin "img/coin_1.bin"
|
|
coin_2 incbin "img/coin_2.bin"
|
|
|
|
boxImg_0 incbin "img/box.bin"
|
|
tileImg_0 incbin "img/tile.bin"
|
|
|
|
ASCIImap incbin "img/map.bin"
|
|
db 0
|
|
|
|
%assign usedMemory ($-$$)
|
|
%assign usableMemory (512*16)
|
|
%warning [usedMemory/usableMemory] Bytes used
|
|
times (512*16)-($-$$) db 0 ;kernel must have size multiple of 512 so let's pad it to the correct size
|
|
;times (512*1000)-($-$$) db 0 ;toggle this to use in bochs
|