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

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