Last active
April 22, 2018 21:58
-
-
Save nonom/9a00215ac37e0ea372d0664f6d456783 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Project: OhNoes | |
// Created: 2018-03-28 | |
// UDT | |
Type tCell | |
x as integer | |
y as integer | |
value as integer // Walkable = 0 | |
// Wall = 1 | |
// Room = 2 | |
// Waypoint = 3 | |
EndType | |
Type tLevel | |
treasures as tTreasure[MAX_TREASURES_BOXES] | |
enemies as integer | |
EndType | |
Type tTreasure | |
id as integer | |
name as string | |
points as integer | |
treasureType as integer | |
EndType | |
Type tEnemy | |
spriteId as integer | |
direction as integer | |
group as integer | |
count as integer | |
collided as integer | |
debugSprite as integer | |
x as integer | |
y as integer | |
EndType | |
Type tPlayer | |
spriteId as integer | |
direction as integer | |
x as integer | |
y as integer | |
collisionData | |
EndType | |
#constant KEY_C 67 | |
#constant keyLeft = GetRawKeyState(37) | |
#constant keyUp = GetRawKeyState(38) | |
#constant keyDown = GetRawKeyState(40) | |
#constant keyRight = GetRawKeyState(39) | |
#constant keyEsc = GetRawKeyState(27) | |
#constant MAX_TIME 30 | |
#constant MAX_LEVELS 10 | |
#constant MAX_LIFES 6 | |
#constant CELL_SIZE 16 | |
#constant GAP 4 | |
#constant MAX_TREASURES 20 | |
#constant MAX_TREASURES_BOXES 3 | |
#constant WORLD_SIZE_X 22 // Must match with the static data atm | |
#constant WORLD_SIZE_Y 14 // TODO: Retrieve dinamically | |
#constant MAX_ENEMIES = MAX_LEVELS | |
#constant STATIC_FILE = "static.txt" | |
#constant MIN_TREASURE_POINTS 10 | |
#constant MAX_TREASURE_POINTS 50 | |
#constant ENEMIES_GROUP 1 | |
#constant WALLS_GROUP 2 | |
#constant TREASURES_GROUP 3 | |
#constant WAYPOINTS_GROUP 4 | |
#constant PATH_GROUP 0 | |
#constant ENEMIES_START_X 1 | |
#constant ENEMIES_START_Y 6 | |
#constant PLAYER_START_X 1 | |
#constant PLAYER_START_Y 1 | |
#constant STARTING_ENEMIES 4 // CHANGE THIS ;) | |
// The values coming from DATA file | |
#constant WALL_CELL_TYPE_ID = 1 | |
#constant TREASURE_CELL_TYPE_ID = 2 | |
global gameStatus as integer // 0 = Stopped / 1 = Started / 2 = Ended | |
global currentPoints as integer | |
global currentTime as integer | |
global currentLevel as integer | |
global currentLifes as integer | |
global currentScene as integer | |
global currentEnemies as integer | |
global worldX as integer | |
global worldY as integer | |
global player as tPlayer | |
global Enemies as tEnemy[] | |
global staticMap as tCell[WORLD_SIZE_X, WORLD_SIZE_Y] | |
global levels as tLevel[MAX_LEVELS] | |
global Treasures as tTreasure[] | |
global textMenuId as integer | |
global collision as integer | |
global collisionData as integer | |
// show all errors | |
SetErrorMode(2) | |
// set window properties | |
SetWindowTitle( "OhNoes" ) | |
SetWindowAllowResize( 1 ) | |
SetOrientationAllowed( 1, 1, 1, 1 ) | |
SetSyncRate( 60, 0 ) | |
//SetScissor( 0,0,0,0 ) | |
SetClearColor(55, 15, 0) | |
worldX = WORLD_SIZE_X * CELL_SIZE | |
worldY = WORLD_SIZE_Y * CELL_SIZE | |
SetWindowSize( worldX, worldY , 0 ) | |
SetVirtualResolution( (WORLD_SIZE_X + 1) * CELL_SIZE, (WORLD_SIZE_Y + 1) * CELL_SIZE) | |
type tButtonType | |
img as integer //ID of image used | |
spr as integer //ID of sprite used | |
state as integer //Button state (1,2,3) | |
deleted as integer //(0: exists, 1:deleted) | |
endtype | |
global GUIButtons as tButtonType[0] | |
type tDialog | |
img as integer //ID of image used | |
spr as integer //ID of sprite used | |
buttons as tButtonType[0] //IDs of buttons | |
endtype | |
AddVirtualButton(1,0, 0, CELL_SIZE) | |
AddVirtualButton(2,CELL_SIZE * 2, CELL_SIZE, CELL_SIZE) | |
AddVirtualButton(3,CELL_SIZE * 3, CELL_SIZE, CELL_SIZE) | |
LoadMusic(1,"Maf464.mp3") | |
global x as integer | |
global y as integer | |
do | |
PrintStats() | |
CheckButtons() | |
// State Machine | |
select currentScene: | |
case 0: | |
// Background | |
LoadStaticMap() | |
DrawStaticMap() | |
currentScene = 1 | |
endcase | |
case 1: | |
GetStartMenu() | |
//TODO DrawBackgroundDemo() | |
currentScene = 2 | |
endcase | |
case 2: | |
if GetRawKeyPressed(KEY_C) | |
DeleteAllText() | |
currentScene = 3 | |
endif | |
endcase | |
case 3: | |
// Level loading | |
currentTime = MAX_TIME | |
GenerateLevel(currentLevel) | |
if not currentEnemies | |
currentEnemies = STARTING_ENEMIES | |
endif | |
SpawnEnemies() | |
SpawnPlayer() | |
currentScene = 4 | |
endcase | |
case 4: | |
UpdateLevel() | |
endcase | |
case 4: | |
if (currentLevel > MAX_LEVELS) | |
GameEnds() | |
else | |
GoNextLevel() | |
endif | |
endcase | |
endselect | |
if keyEsc // Quie the game after deleting all sprites | |
DeleteAllSprites() | |
End | |
endif | |
Sync() | |
loop | |
Function ToggleWaypoints() | |
Endfunction | |
Function CheckButtons() | |
if GetVirtualButtonPressed(1) | |
PlayMusic(1) | |
elseif GetVirtualButtonPressed(2) | |
StopMusic() | |
elseif GetVirtualButtonPressed(3) | |
ToggleWaypoints() | |
endif | |
EndFunction | |
Function UpdateLevel() | |
UpdateEnemies() | |
UpdatePlayer() | |
Endfunction | |
Function GameEnds() | |
Endfunction | |
Function GoNextLevel() | |
currentLevel = currentLevel + 1 | |
Endfunction | |
Function UpdatePlayer() | |
UpdateCollisions() | |
PlayerController() | |
//PrintSteps() | |
EndFunction | |
function UpdateCollisions() | |
//Testing | |
if GetSpriteHitGroup(ENEMIES_GROUP, GetSpriteX(player.spriteId), GetSpriteY(player.spriteId)) | |
//DeleteEnemy(enemy) | |
//currentEnemies = currentEnemies - 1 | |
currentLifes = currentLifes - 1 // Move to delete enemy insteaed | |
endif | |
endfunction | |
Function UpdateEnemies() | |
for i=0 to currentEnemies - 1 | |
UpdateEnemy(i) | |
next | |
EndFunction | |
Function SpawnEnemies() | |
for i=1 to currentEnemies | |
enemyId = CreateSprite(0) | |
startX = Floor(CELL_SIZE * ENEMIES_START_X) | |
startY = Floor(CELL_SIZE * ENEMIES_START_Y) | |
SetSpriteOffset(enemyId,CELL_SIZE / 2, CELL_SIZE / 2) | |
SetSpriteColor(enemyId, 255, 255, 0, 255) | |
SetSpriteSize(enemyId, CELL_SIZE, CELL_SIZE) | |
SetSpritePosition(enemyId, startX, startY ) // Different start points | |
//SetSpriteShape ( enemyId, 2 ) | |
SetSpriteGroup(enemyId, ENEMIES_GROUP) | |
// Creates an enemy | |
enemy as tEnemy | |
enemy.spriteId = enemyId | |
enemy.group = ENEMIES_GROUP // Todo different groups | |
enemy.count = 0 | |
enemy.collided = 0 | |
enemy.x = startX | |
enemy.y = startY | |
enemy.direction = 0 // Up (clockwise 0-3) | |
Enemies.insert(enemy) | |
next i | |
EndFunction | |
Function SpawnPlayer() | |
playerId = CreateSprite(0) | |
xp = CELL_SIZE * PLAYER_START_X | |
yp = CELL_SIZE * PLAYER_START_Y | |
SetSpriteColor(playerId, 55, 255, 0, 255) | |
SetSpriteSize(playerId, CELL_SIZE, CELL_SIZE) | |
//SetSpriteShape ( playerId, 2 ) | |
SetSpriteOffset(playerId, CELL_SIZE/2, CELL_SIZE/2) | |
//SetSpriteOffset(playerId, GetSpriteWidth(playerId)/2, GetSpriteHeight(playerId)/2) | |
SetSpritePosition(playerId, xp, yp ) | |
// Popuplate the global player variable | |
player.spriteId = playerId | |
player.x = xp | |
player.y = yp | |
player.direction = 0 // Down (clockwise 0-3) | |
EndFunction | |
Function GenerateLevel(currentLevel) | |
level as tLevel | |
level.enemies = currentLevel | |
level.treasures = GenerateRandomTreasures() | |
EndFunction | |
Function GenerateRandomTreasures() | |
//Add Treasures | |
levelTreasures As tTreasure[] | |
// Adds MAX_TREASURES treasures | |
for i=1 to MAX_TREASURES_BOXES | |
commonTreasure As tTreasure | |
commonTreasure.treasureType = 1 | |
commonTreasure.points = Random(MIN_TREASURE_POINTS, MAX_TREASURE_POINTS) | |
levelTreasures.insert(commonTreasure) | |
next i | |
// Adds a key | |
keyTreasure As tTreasure | |
keyTreasure.treasureType = 2 | |
keyTreasure.points = MAX_TREASURE_POINTS | |
levelTreasures.insert(keyTreasure) | |
// Adds a tomb | |
tombTreasure As tTreasure | |
tombTreasure.treasureType = 3 | |
tombTreasure.points = MAX_TREASURE_POINTS | |
levelTreasures.insert(tombTreasure) | |
// Adds a scroll | |
scrollTreasure As tTreasure | |
scrollTreasure.treasureType = 4 | |
scrollTreasure.points = MAX_TREASURE_POINTS | |
levelTreasures.insert(scrollTreasure) | |
// Adds an extra enemy | |
enemyTreasure As tTreasure | |
enemyTreasure.treasureType = 5 | |
enemyTreasure.points = 0 | |
levelTreasures.insert(enemyTreasure) | |
EndFunction levelTreasures | |
Function LoadStaticMap() | |
delimiter$ = "," | |
dataFile = OpenToRead(STATIC_FILE) | |
dataLine$ = ReadLine(dataFile) | |
y as integer = 0 | |
while not FileEOF(dataFile) | |
for x = 0 to WORLD_SIZE_X | |
cell as tCell | |
cell.x = x | |
cell.y = y | |
cell.value = Val(GetStringToken(dataLine$, delimiter$, x + 1)) | |
staticMap[x,y] = cell | |
next x | |
if y < WORLD_SIZE_Y then inc y else y = 0 | |
dataLine$ = ReadLine(dataFile) | |
endwhile | |
EndFunction | |
Function DrawStaticMap() | |
for i=0 to WORLD_SIZE_X | |
for j=0 to WORLD_SIZE_Y | |
spriteId = CreateSprite(0) | |
select staticMap[i,j].value | |
case 0: | |
SetSpriteColor(spriteId, 0,0,0,255) | |
endcase | |
case 1: | |
SetSpriteColor(spriteId, 255, 125, 0, 255) | |
SetSpriteGroup(spriteId, WALLS_GROUP) | |
endcase | |
case 2: | |
SetSpriteColor(spriteId, 255, 155, 0, 255) | |
SetSpriteGroup(spriteId, TREASURES_GROUP) | |
endcase | |
case 3: | |
SetSpriteColor(spriteId, 155, 155, 0, 255) | |
SetSpriteGroup(spriteId, WAYPOINTS_GROUP) | |
endcase | |
endselect | |
x = i * CELL_SIZE | |
y = j * CELL_SIZE | |
//SetSpriteShape ( spriteId, 2 ) | |
SetSpriteSize(spriteId, CELL_SIZE, CELL_SIZE) | |
SetSpriteOffset(spriteId, CELL_SIZE / 2, CELL_SIZE / 2) | |
SetSpritePosition(spriteId, x, y) | |
next j | |
next i | |
// For debug purposes | |
for i=0 to WORLD_SIZE_X | |
for j=0 to WORLD_SIZE_Y | |
textId = CreateText(Str(staticMap[i,j].value)) | |
select staticMap[i,j].value | |
case 1: | |
SetTextColor(textId, 0,255,255,255) | |
endcase | |
case 0: | |
SetTextColor(textId, 255,255,0,255) | |
endcase | |
endselect | |
x = i * CELL_SIZE | |
y = j * CELL_SIZE | |
SetTextPosition(textId, x, y) | |
SetTextSize(textId, CELL_SIZE) | |
SetTextAlignment(textId, 0) | |
next j | |
next i | |
EndFunction | |
Function PrintStats() | |
PrintC ( "SCORE : " + Str(currentPoints) + " " ) | |
PrintC ( "MEN : " + Str(currentLifes) + " " ) | |
Print ( "FPS : " + Str(Round(ScreenFPS()))) | |
print(x / CELL_SIZE) | |
print(y / CELL_SIZE) | |
//print( "Collision: " + str(GetSpriteCollision( sprite[1], sprite[0] ))) | |
//print( "Hit : " + str(GetSpriteHit(x#,y#))) | |
//Print ("PlayerX: " + Str(player.x)) | |
//Print ("PlayerY: " + Str(player.y)) | |
//Print ("PlayerDirection: " + Str(player.direction)) | |
//Print ("PlayerLifes: " + Str(currentLifes)) | |
//Print ("PlayerCollision: " + Str(collision)) | |
EndFunction | |
Function GetStartMenu() | |
textMenuId = CreateText("Press C to Continue") | |
SetTextPosition(textMenuId, (worldX / 4), worldY / 2) | |
SetTextColor(textMenuId, 0, 255, 255, 255) | |
SetTextSize(textMenuId, 18) | |
if GetRawKeyPressed(KEY_C) | |
DeleteAllText() | |
gameStatus = 1 | |
endif | |
EndFunction | |
Function UpdateEnemy(i as integer) | |
MoveEnemy(i) | |
//OnSeeCreature(enemy, player) | |
EndFunction | |
Function MoveEnemy(enemyIndex as integer) | |
// Get the enemy coordinates | |
enemyX = GetSpriteX(Enemies[enemyIndex].spriteId) | |
enemyY = GetSpriteY(Enemies[enemyIndex].spriteId) | |
// ATM we need to review the outbounds to prevent the walls | |
if enemyX < CELL_SIZE then enemyX = enemyX + CELL_SIZE | |
if enemyY < CELL_SIZE then enemyY = enemyY + CELL_SIZE | |
if enemyX > worldX then enemyX = worldX - CELL_SIZE | |
if enemyY > worldY then enemyY = worldY - CELL_SIZE | |
// In every step we need to "see" what cell type is in front of us | |
x1 = enemyX / CELL_SIZE | |
y1 = enemyY / CELL_SIZE | |
//Outbounds nullcheck | |
if x1 < 1 then x1 = 1 | |
if y1 < 1 then y1 = 1 | |
if x1 > WORLD_SIZE_X then x1 = WORLD_SIZE_X | |
if y1 > WORLD_SIZE_Y then y1 = WORLD_SIZE_Y | |
collisionData = staticMap[x1,y1].value | |
//Print ("CollisionData : " + Str(collisionData)) | |
Enemies[enemyIndex].collided = collisionData | |
if (Enemies[enemyIndex].x = (x1 * CELL_SIZE)) && (Enemies[enemyIndex].y = (y1 * CELL_SIZE)) | |
select Enemies[enemyIndex].collided | |
case 3: | |
repeat | |
if Random(0, 100) <= 33 // 33% chance to move the direction | |
Enemies[enemyIndex].direction = Random(0,3) | |
endif | |
select(Enemies[enemyIndex].direction) | |
case 0: //up | |
y1 = y1 - 1 | |
endcase | |
case 1: //right | |
x1 = x1 + 1 | |
endcase | |
case 2: //down | |
y1 = y1 + 1 | |
endcase | |
case 3: //left | |
x1 = x1 - 1 | |
endcase | |
endselect | |
//Outbounds nullchecks again to be safe | |
if x1 < 1 then x1 = 1 | |
if y1 < 1 then y1 = 1 | |
if x1 > WORLD_SIZE_X then x1 = WORLD_SIZE_X | |
if y1 > WORLD_SIZE_Y then y1 = WORLD_SIZE_Y | |
collisionData = staticMap[x1,y1].value | |
until collisionData = 0 // Ready to go | |
endcase | |
endselect | |
endif | |
//For debug purposes, uncomment the other debug lines to see it in action | |
//SetSpritePosition(Enemies[enemyIndex].debugSprite, x1 * CELL_SIZE, y1 * CELL_SIZE) | |
// Adds an step to the enemy | |
select(Enemies[enemyIndex].direction) | |
case 0: //up | |
enemyY = enemyY - 1 | |
endcase | |
case 1: //right | |
enemyX = enemyX + 1 | |
endcase | |
case 2: //down | |
enemyY = enemyY + 1 | |
endcase | |
case 3: //left | |
enemyX = enemyX - 1 | |
endcase | |
endselect | |
Enemies[enemyIndex].x = enemyX | |
Enemies[enemyIndex].y = enemyY | |
// Useless atm | |
if Enemies[enemyIndex].x < CELL_SIZE then Enemies[enemyIndex].x = Enemies[enemyIndex].x + CELL_SIZE | |
if Enemies[enemyIndex].y < CELL_SIZE then Enemies[enemyIndex].y = Enemies[enemyIndex].x + CELL_SIZE | |
if Enemies[enemyIndex].x > worldX then Enemies[enemyIndex].x = worldX - CELL_SIZE | |
if Enemies[enemyIndex].y > worldY then Enemies[enemyIndex].y = worldY - CELL_SIZE | |
// Finally after a few checs we can move our enemy | |
SetSpritePosition(Enemies[enemyIndex].spriteId, Enemies[enemyIndex].x, Enemies[enemyIndex].y) | |
//Print ("EnemyD " + Str(Enemies[enemyIndex].direction)) | |
//Print ("EnemyX " + Str(x1)) | |
//Print ("EnemyY " + Str(y1)) | |
//Print ("EnemyRealX: " + Str(enemyX)) | |
//Print ("EnemyRealY: " + Str(enemyY)) | |
EndFunction | |
function CheckCollision(player as tPlayer) | |
// Get the player coordinates | |
playerX = GetSpriteX(player.spriteId) | |
playerY = GetSpriteY(player.spriteId) | |
// ATM we need to review the outbounds to prevent the walls | |
//if playerX < 0 then playerX = CELL_SIZE | |
//if playerY < 0 then playerY = CELL_SIZE | |
//if playerX > worldX then playerX = worldX - CELL_SIZE | |
//if playerY > worldY then playerY = worldY - CELL_SIZE | |
// In every step we need to "see" what cell type is in front of us | |
x1 = Floor(playerX / CELL_SIZE) | |
y1 = Floor(playerY / CELL_SIZE) | |
//Outbounds nullcheck | |
//if x1 < 1 then x1 = 1 | |
//if y1 < 1 then y1 = 1 | |
//if x1 > WORLD_SIZE_X then x1 = WORLD_SIZE_X | |
//if y1 > WORLD_SIZE_Y then y1 = WORLD_SIZE_Y | |
select(player.direction) | |
case 0: //up | |
y1 = y1 | |
endcase | |
case 1: //right | |
x1 = x1 + 1 | |
endcase | |
case 2: //down | |
y1 = y1 + 1 | |
endcase | |
case 3: //left | |
x1 = x1 | |
endcase | |
endselect | |
//Outbounds nullchecks again to be safe | |
if x1 < 0 then x1 = 0 | |
if y1 < 0 then y1 = 0 | |
if x1 > WORLD_SIZE_X then x1 = WORLD_SIZE_X | |
if y1 > WORLD_SIZE_Y then y1 = WORLD_SIZE_Y | |
collisionData = staticMap[x1,y1].value | |
//Print ("CollisionData : " + Str(collisionData)) | |
player.collisionData = collisionData | |
remstart | |
//if (Enemies[enemyIndex].x = (x1 * CELL_SIZE)) && (Enemies[enemyIndex].y = (y1 * CELL_SIZE)) | |
if (player.x = (x1 * CELL_SIZE)) && (player.y = (y1 * CELL_SIZE)) | |
select player.collisionData | |
case 3, 0: | |
endcase | |
endselect | |
endif | |
remend | |
player.x = playerX | |
player.y = playerY | |
// Useless atm | |
//if player.x < CELL_SIZE then player.x = player.x + CELL_SIZE | |
//if player.y < CELL_SIZE then player.y = player.y + CELL_SIZE | |
//if player.x > worldX then player.x = worldX - CELL_SIZE | |
//if player.y > worldY then player.y = worldY - CELL_SIZE | |
// Finally after a few checs we can move our enemy | |
//SetSpritePosition(Enemies[enemyIndex].spriteId, Enemies[enemyIndex].x, Enemies[enemyIndex].y) | |
//Print ("PlayerD " + Str(player.direction)) | |
//Print ("PlayerX " + Str(x1)) | |
//Print ("PlayerY " + Str(y1)) | |
//Print ("PlayerRealX: " + Str(playerX)) | |
//Print ("PlayerRealY: " + Str(playerY)) | |
//Print ("CollisionData: " + Str(collisionData)) | |
//Print ("SpriteX: " + Str(GetSpriteX(player.spriteId))) | |
//Print ("SpriteY: " + Str(GetSpriteY(player.spriteId))) | |
//Print ("SpriteOffsetX: " + Str(GetSpriteXByOffset(player.spriteId))) | |
//Print ("SPriteOffsetY: " + Str(GetSpriteYByOffset(player.spriteId))) | |
//Print ("XXGetSpriteHit: " + Str(GetSpriteHit(GetSpriteX(player.spriteId), GetSpriteY(player.spriteId)))) | |
//GetSpriteCollision Group | |
fd = findDistance(player.spriteId) | |
Print ("DISTANCE: " + Str(fd)) | |
//if not GetSpriteCollision(player.spriteId, hittingSprite) // || not GetSpriteHitGroup(WALLS_GROUP, ox, oy) | |
collision = 1 | |
if collisionData = 0 or collisionData = 3 | |
//GetSpriteHit(player.x, player.y | |
collision = 0 | |
endif | |
endfunction collision | |
function PlayerController() | |
if not collision | |
SetSpritePosition(player.spriteId, player.x, player.y) | |
x=GetSpriteX( player.spriteId ) | |
y=GetSpriteY( player.spriteId ) | |
endif | |
currentx = player.x | |
currenty = player.y | |
// In this case the movomenet iis only horiz | - vert | |
if keyLeft | |
player.direction = 3 | |
if not CheckCollision(player) then player.x = player.x - 1 | |
elseif keyUp | |
player.direction = 0 | |
if not CheckCollision(player) then player.y = player.y - 1 | |
elseif keyDown | |
player.direction = 2 | |
if not CheckCollision(player) then player.y = player.y + 1 | |
elseif keyRight | |
player.direction = 1 // To paint the steps and previee the movements | |
if not CheckCollision(player) then player.x = player.x + 1 | |
endif | |
//SetSpritePosition(player.spriteId, (WORLD_SIZE_X - (player.x / CELL_SIZE)) * CELL_SIZE, (WORLD_SIZE_y - (player.y / CELL_SIZE)) * CELL_SIZE) | |
endfunction | |
function findDistance(sprite as integer) | |
gridSize = 16 | |
// Determine which grid square the sprite is in | |
gridX = Floor(GetSpriteXByOffset(sprite) / WORLD_SIZE_X) | |
gridY = Floor(GetSpriteYByOffset(sprite) / WORLD_SIZE_Y) | |
// This determines the left edge of the sprite | |
x1 = GetSpriteXByOffset(sprite) - (GetSpriteWidth(sprite)/2) | |
Print (x1) | |
// This finds the left edge of the grid tile | |
gx = gridX * gridSize | |
Print (gx) | |
// Distance of red line: | |
d = x1 - gx | |
Print (d) | |
endfunction d | |
// Theme song credits | |
// https://soundcloud.com/maf464/2006-oh-mummy-theme-maf464-remake | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment