/* spacemaze - Fly a space ship through a maze and collect all the items. Copyright (C) 2000, 2001, 2002 John Ericson This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 2000-03-25 High / John Ericson john.ericson@home.se */ #include <stdlib.h> #include <string.h> #include <SDL.h> #include <SDL_image.h> #include "bool.h" #include "sprite.h" #include "campaign.h" #include "level.h" #include "defines.h" #include "getfile.h" #include "loadimg.h" #include "gfx.h" #include "fpstimer.h" #include "BFont.h" #ifdef _WIN32 #include "getopt.h" #else /* UNIX */ #include <getopt.h> #endif void LoadGraphics(void); /* Different default places to search for levels */ const char *levels_dirs[6] = { "./", "./levels/", "/usr/share/games/spacemaze/levels/", "/usr/local/share/games/spacemaze/levels/", "/opt/spacemaze/levels/", NULL }; const char *campaigns_dirs[6] = { "./", "./campaigns/", NULL }; const char *gfx_dirs[3] = { "./", "./gfx/", NULL }; /* Video buffer */ SDL_Surface *screen; Uint32 videoflags = SDL_SWSURFACE|SDL_HWPALETTE; /* SDL_Surface *image_spaceship, *image_box; */ /* Structure to cache images */ typedef struct { int used; SDL_Surface *image; } image_s; image_s images[MAXIMAGES]; struct struct_ship { float fx, /* Position */ fy; int x, y; int w, /* Size */ h; float oldx, /* Old position */ oldy; float vx, /* Velocity */ vy; float gasx, /* Gas */ gasy; bool dead; bool notmoving; int color; sprite sprite; } ship; static level_s level; static campaign_s campaign; unsigned int currentlevel = 0; int borderwidth = 1; //struct struct_drawrect drawrect[MAXDRAWRECT]; /* Draw level. If level piece is bigger than the screen, no drawing will be made. */ void drawlevel(void) { int i; SDL_Rect rect; for (i = 0; i <= level.size; i++) { if (level.landscape[i].visible) { rect.x = level.landscape[i].x; rect.y = level.landscape[i].y; rect.w = level.landscape[i].w; rect.h = level.landscape[i].h; SDL_FillRect(screen, &rect, level.landscape[i].color); inputdrawrect(&rect); } } } void drawship(void) { drawsprite(&ship.sprite); } void eraseship(void) { erasesprite(&ship.sprite); } /* void drawbox(int n) { drawsprite(&level.item[n].sprite); } */ void erasebox(int n) { erasesprite(&level.item[n].sprite); } /* void drawboxes(void) { int i; for (i = 0; i <= level.numitems; i++) if (level.item[i].type == LEVEL_OBJ_BOX) if (level.item[i].visible) drawbox(i); } */ /* Check if two rectangular objects intersect. For optimal collision detection, the smallest object should be 1 and the larger 2. */ int detectcollision(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2) { if ((x1 >= x2 && x1 < (x2 + w2)) || ((x1 + w1) >= x2 && (x1 + w1) < (x2 + w2))) if ((y1 >= y2 && y1 < (y2 + h2)) || ((y1 + h1) >= y2 && (y1 + h1) < (y2 + h2))) return 1; return 0; } /* Load level */ void loadlevel(const char *file, level_s *level) { char fullfile[512]; SDL_Color colors[256]; /* Check if player has played through the whole campaign */ if (strcmp(file, "") == 0) { printf("You have played through the complete game now\n"); exit(0); } if (strcmp(file, "") != 0) { /* Look through directories for the file to load */ if (!getfile(levels_dirs, file, fullfile)) { fprintf(stderr, "Error couldn't find %s\n", file); exit(1); } else { printf("%s exists\n", fullfile); } /* Load level */ if (LoadLevel(level, fullfile) != 0) { fprintf(stderr, "Error parsing XML file %s\n", fullfile); exit(1); } /* Check level version */ if (!strcmp(level->version, "1.0") == 0) { fprintf(stderr, "Error unknown level version. This game can run version 1.0 levels\n"); exit(1); } printf("Loaded level %s name %s.\n", file, level->name); } else { fprintf(stderr, "Error no map to open, please use -m\n"); exit(1); } /* If the screen resolution differs set new videomode */ if (screen == NULL || level->sizew != screen->w || level->sizeh != screen->h) { /* Set videomode */ if ((screen = SDL_SetVideoMode(level->sizew, level->sizeh, 8, videoflags)) == NULL) { fprintf(stderr, "Couldn't initialize video mode: %s\n", SDL_GetError()); exit(1); } /* Report graphics settings */ printf("Set %dx%dx%d video mode\n", screen->w, screen->h, screen->format->BitsPerPixel); /***************/ /* Set palette */ /***************/ /* Hopefully the loading of pictures wont mess this palette up */ colors[0].r = colors[0].g = colors[0].b = 0; colors[1].r = 200;colors[1].g = 0; colors[1].b = 0; colors[2].r = 0; colors[2].g = 0; colors[2].b = 150; colors[3].r = 200;colors[3].g = 200;colors[3].b = 0; colors[4].r = 0; colors[4].g = 200;colors[4].b = 0; SDL_SetColors(screen, colors, 0, 5); /* Sets a portion of the colormap for the given 8-bit surface. Last digit is how many colours to set. */ } } /* Start level */ void initlevel() { SDL_Rect trect; char caption[255] = CAPTION; /* Set title and icon for the window */ strcat(caption, " - "); strcat(caption, level.name); SDL_WM_SetCaption(caption, NULL); /* Initialize ship */ //ship.x = &ship.sprite.x; //ship.y = &ship.sprite.y; // TODO When I assign these to xf? It crapes out the value in h // These should be assigned to fx. ship.x = level.playerstart[level.numstartpos].x; ship.y = level.playerstart[level.numstartpos].y; ship.fx = (float)ship.x; ship.fy = (float)ship.y; ship.sprite.x = ship.x; ship.sprite.y = ship.y; ship.w = ship.sprite.image->w; ship.h = ship.sprite.image->h; ship.oldx = 100; ship.oldy = 100; ship.vx = 0; ship.vy = 0; ship.gasx = (float)0.1; ship.gasy = (float)0.1; ship.dead = false; ship.notmoving = true; //ship.color = 1; /* Clear all stacked drawings */ cleardrawrect(); /* Draw borders */ // Draw boarder around the screen trect.x = 0; trect.y = 0; trect.w = screen->w; trect.h = screen->h; SDL_FillRect(screen, &trect, 4); trect.x = borderwidth; trect.y = borderwidth; trect.w = screen->w - 1 - borderwidth; trect.h = screen->h - 1 - borderwidth; SDL_FillRect(screen, &trect, 0); LoadGraphics(); drawlevel(); drawship(); /* Locks the screen */ /*if (SDL_MUSTLOCK(screen)) SDL_LockSurface(screen); */ //SDL_UpdateRect(screen, 0, 0, 0, 0); /* Unlocks the screen */ /*if (SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); */ /* Redraw */ updatescreen(); } /* Check if the ship picks up any boxes */ void shipgrabboxes(void) { int i, i2, i3; SDL_Rect rect; bool alltaken = true; /* Look thru items */ for (i = 0;i <= level.numitems;i++) /* If ship collides with an existing box */ if (level.item[i].type == LEVEL_OBJ_BOX && level.item[i].visible) if (detectcollision(level.item[i].x, level.item[i].y, level.item[i].sprite.image->w, level.item[i].sprite.image->h, ship.x, ship.y, ship.w, ship.h)) { printf("Got box number %d\n", i); level.item[i].visible = false; /* If something should be activated */ if (level.item[i].activate) /* Look thru landscape */ for (i2 = 0; i2 <= level.size; i2++) if (level.landscape[i2].activate == level.item[i].activate) { /* Change visible and crashable */ if (level.landscape[i2].visible) level.landscape[i2].visible = false; else level.landscape[i2].visible = true; if (level.landscape[i2].crashable) level.landscape[i2].crashable = false; else level.landscape[i2].crashable = true; /* If landscape is changed to unvisible redraw that area */ if (!level.landscape[i2].visible) { level.landscape[i2].visible = false; rect.x = level.landscape[i2].x; rect.y = level.landscape[i2].y; rect.w = level.landscape[i2].w; rect.h = level.landscape[i2].h; /* Erase landscape */ SDL_FillRect(screen, &rect, 0); inputdrawrect(&rect); } } /* Look if all items are taken */ for (i3 = 0; i3 <= level.numitems; i3++) if (level.item[i3].type == LEVEL_OBJ_BOX) if (level.item[i3].visible) alltaken = false; erasebox(i); /* Erase the picked up box */ drawlevel(); /* Redraw level */ /* All items are taken */ if (alltaken) { printf("Congratulations! You have picked up all the boxes\n"); loadlevel(campaign.levels[currentlevel++].filename, &level); initlevel(); } } } /* See if the ship collides with any wall */ int shipcrash(void) { int i; for (i = 0; i <= level.size; i++) if (level.landscape[i].crashable) if (detectcollision(ship.x, ship.y, ship.w, ship.h, level.landscape[i].x, level.landscape[i].y, level.landscape[i].w, level.landscape[i].h)) { printf("Ship crashed into a wall\n"); ship.dead = true; return 1 ; } return 0; } void ImageCacheInit(image_s *images) { int i; for (i = 0; i < MAXIMAGES; i++, *images++) { images->used = 0; } } SDL_Surface *loadimgdata(const char *file, bool transparent) { SDL_Surface *rtnsurface; char fullfile[512]; if (!getfile(gfx_dirs, file, fullfile)) { fprintf(stderr, "Error couldn't find %s\n", file); exit(1); } else { printf("%s exists\n", fullfile); } /* Load graphics */ rtnsurface = loadimage(fullfile, transparent); if (rtnsurface == NULL) { fprintf(stderr, "Error couldn't load %s\n", fullfile); exit(1); } return rtnsurface; } void LoadGraphics(void) { int i; // TODO level.numitems is wrong. It starts at 1, it should start at 0 for (i = 0;i <= level.numitems; i++) { switch (level.item[i].type) { case LEVEL_OBJ_BOX: if (images[LEVEL_OBJ_BOX].used == 0) { images[LEVEL_OBJ_BOX].image = loadimgdata("box.png", false); if (images[LEVEL_OBJ_BOX].image != NULL) printf("Loaded image box.png\n"); } else printf("Reused image box.png\n"); /* Assign image */ level.item[i].sprite.image = images[level.item[i].type].image; break; case LEVEL_OBJ_LANDINGPLATE: if (images[LEVEL_OBJ_LANDINGPLATE].used == 0) { images[LEVEL_OBJ_LANDINGPLATE].image = loadimgdata("landingplate.png", false); if (images[LEVEL_OBJ_LANDINGPLATE].image != NULL) printf("Loaded image landingplate.png\n"); } else printf("Reused image landingplate.png\n"); /* Assign image */ level.item[i].sprite.image = images[level.item[i].type].image; /* Transform the coordinates so they are middle lower placed */ level.item[i].x -= (int)(level.item[i].sprite.image->w / 2); level.item[i].y -= level.item[i].sprite.image->h - 1; break; } /* Increase the usage to show how much this file is needed */ if (images[level.item[i].type].used != -1) images[level.item[i].type].used++; /* Assign cordinates to sprite */ level.item[i].sprite.x = level.item[i].x; level.item[i].sprite.y = level.item[i].y; } } /* Draw text on the screen */ void DrawText(unsigned int x, unsigned int y, char *text, ...) { SDL_Rect rect; PrintString(screen, x, y, text); rect.x = x; rect.y = y; rect.w = TextWidth(text); rect.h = FontHeight(GetCurrentFont()); inputdrawrect(&rect); } void drawlandscapeobjects(void) { int i; for (i = 0;i <= level.numitems; i++) { switch (level.item[i].type) { case LEVEL_OBJ_BOX: if (!level.item[i].visible) break; case LEVEL_OBJ_LANDINGPLATE: drawsprite(&level.item[i].sprite); break; } } } /* Show how to use the program */ void usage(void) { printf("spacemaze 0.9 Copyright (C) 2000 John Ericson john.ericson@home.se\n"); printf("Usage: spacemaze [OPTIONS]...\n"); printf("\n"); printf(" -h display this help\n"); printf(" -f use fullscreen\n"); printf(" -m .. map to open\n"); printf(" -c display mouse cursor inside application window\n"); } /* Main program */ int main(int argc, char *argv[]) { SDL_Event event; Uint8 *keys; //int key; //int numkeys; bool done; int i; //int readx, // ready; int c; char leveltoload[255] = DEFAULTLEVEL; char campaigntoload[255] = DEFAULTCAMPAIGN; int drawcursor = 0; char file[512]; /* Stores complete path */ BFont_Info *Font1=NULL; /**************************************/ /* Process the command line arguments */ /**************************************/ while (1) { c = getopt(argc, argv, "fm:hck"); /* Missing argument on an option that requires a argument */ if (c == '?') exit(1); /* End of command line */ if (c == EOF) break; switch(c) { case 'f': videoflags |= SDL_FULLSCREEN; break; case 'm': strcpy(leveltoload, optarg); break; case 'h': usage(); exit(1); break; case 'c': drawcursor = 1; break; case 'k': strcpy(campaigntoload, optarg); break; default: fprintf(stderr, "Error getopt returned character code 0%o\n", c); } } /***********************/ /* Initialize hardware */ /***********************/ /* Initialize SDL */ if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "Couldn't initialize SDL: %s\n",SDL_GetError()); exit(1); } /* Initialize graphich system */ initdrawrect(MAXDRAWRECT); cleardrawrect(); /***************/ /* Load levels */ /***************/ /* Check if used has specified a specific level */ if (strcmp(leveltoload, "") == 0) { /* Load a campaign */ /* Initialize campaign */ if (strcmp(campaigntoload, "") != 0) { /* Look through directories for the file to load */ if (!getfile(campaigns_dirs, campaigntoload, file)) { fprintf(stderr, "Error couldn't find campaign %s\n", campaigntoload); exit(1); } else { printf("%s exists\n", file); } /* Load campaign */ if (LoadCampaign(&campaign, file) != 0) { fprintf(stderr, "Error parsing XML file %s\n", file); exit(1); } /* Check campaign version */ if (!strcmp(campaign.version, "1.0") == 0) { fprintf(stderr, "Error unknown campaign version. This game can run version 1.0 campaigns\n"); exit(1); } printf("Loaded campaign %s name %s.\n", campaigntoload, campaign.name); } else { fprintf(stderr, "Error no campaign to open, please use -k\n"); exit(1); } } else { /* Load a level */ /* Setup a dummy campaign for our custom level */ strcpy(campaign.name, "custom level"); campaign.numlevels = 1; strcpy(campaign.levels->filename, leveltoload); } /* Initialize level */ loadlevel(campaign.levels[currentlevel++].filename, &level); /* Initialize image cache */ ImageCacheInit((image_s *)&images); /*****************/ /* Load graphics */ /*****************/ /* Ship */ images[OBJ_SPACESHIP].image = loadimgdata("spaceship.png", false); images[OBJ_SPACESHIP].used = -1; ship.sprite.image = images[OBJ_SPACESHIP].image; //LoadGraphics(); /**************/ /* Load Fonts */ /**************/ /* Look through directories for the level to load */ if (!getfile(gfx_dirs, "topaz.png", file)) { fprintf(stderr, "Error couldn't find font topaz.png\n"); exit(1); } else { printf("%s exists\n", file); } /* Load Font */ Font1 = LoadFont(file); if (Font1 == NULL) { fprintf(stderr, "Couldnt load %s\n", file); exit(1); } /* Set the default font */ SetCurrentFont(Font1); /*************************/ /* Misc program settings */ /*************************/ SDL_ShowCursor(drawcursor); /* Don't draw mouse cursor inside application window */ /************************************/ /* Set keyboard delays for gameplay */ /************************************/ /* Set keyboard delays. Delay for when to start sending repeating KEYDOWN's and the interval between them */ SDL_EnableKeyRepeat(1000, 200); keys = SDL_GetKeyState(NULL); /*******************/ /* The game begins */ /*******************/ initlevel(); /* Main loop */ done = false; while (!done) { /* Polls for currently pending events */ if (SDL_PollEvent(&event)) { switch (event.type) { case SDL_KEYDOWN: case SDL_KEYUP: keys = SDL_GetKeyState(NULL); break; case SDL_QUIT: /* Quit request */ printf("Quit requested, quitting.\n"); exit(0); break; } } if (keys[SDLK_ESCAPE] == SDL_PRESSED) { printf("Quit requested, quitting.\n"); exit(0); } if (keys[SDLK_UP] == SDL_PRESSED) { ship.vy = ship.vy - ship.gasy; ship.notmoving = false; } if (keys[SDLK_DOWN] == SDL_PRESSED) { ship.vy = ship.vy + ship.gasy; ship.notmoving = false; } if (keys[SDLK_LEFT] == SDL_PRESSED) { ship.vx = ship.vx - ship.gasx; ship.notmoving = false; } if (keys[SDLK_RIGHT] == SDL_PRESSED) { ship.vx = ship.vx + ship.gasx; ship.notmoving = false; } waitframe(); if (ship.dead) ship.vx = ship.vy = 0; /* Save old positions */ ship.oldx = (float)ship.x; ship.oldy = (float)ship.y; /* Locks the screen */ if (SDL_MUSTLOCK(screen)) if (SDL_LockSurface(screen) < 0) continue; /* IMPORTANT! No operating system or library calls should be made here since critical system locks may be held during this time. */ /* Move the ship */ if (!ship.notmoving) { eraseship(); ship.fx += ship.vx; ship.fy += ship.vy; ship.x = (int)ship.fx; ship.y = (int)ship.fy; /* Add gravity */ ship.vy += level.gravity; /* Look if the spaceship hit any wall */ if ((ship.x + (int)ship.w) >= (screen->w - borderwidth)) { ship.vx = ship.vx * -1; ship.fx = (float)(screen->w - borderwidth - (int)ship.w); } else if (ship.x <= (borderwidth)) { ship.vx = ship.vx * -1; ship.fx = (float)borderwidth + 1; } if ((ship.y + (int)ship.h) >= (screen->h - borderwidth)) { ship.vy = ship.vy * -1; ship.fy = (float)(screen->h - borderwidth - (int)ship.h); } else if (ship.y <= (borderwidth)) { ship.vy = ship.vy * -1; ship.fy = (float)borderwidth + 1; } ship.x = (int)ship.fx; ship.y = (int)ship.fy; ship.sprite.x = ship.x; ship.sprite.y = ship.y; /* Look if the spaceship has landed on any landingplate */ for (i = 0;i <= level.numitems; i++) { if (level.item[i].type == LEVEL_OBJ_LANDINGPLATE) { if (detectcollision(ship.x, ship.y+ship.h, 0, 0, level.item[i].x, level.item[i].y, level.item[i].sprite.image->w, level.item[i].sprite.image->h) && detectcollision(ship.x+ship.w, ship.y+ship.h, 0, 0, level.item[i].x, level.item[i].y, level.item[i].sprite.image->w, level.item[i].sprite.image->h)) { /* Ship landed successfully */ ship.notmoving = true; ship.vx = 0; ship.vy = 0; ship.fy = (float)level.item[i].y - ship.h; DrawText(10, 150, "You have landed"); } } } ship.x = (int)ship.fx; ship.y = (int)ship.fy; ship.sprite.x = ship.x; ship.sprite.y = ship.y; if (shipcrash()) done = true; shipgrabboxes(); drawship(); } /* Dont let the ship get out of the screen */ /* if (ship.x < 0) ship.x = 0; if (ship.y < 0) ship.y = 0; if (ship.x > (screen->w - ship.w)) ship.x = (screen->w - ship.w); if (ship.y > (screen->h - ship.h)) ship.y = (screen->h - ship.h); */ //drawboxes(); drawlandscapeobjects(); //DrawText(10, 10, "This is a test"); /* Unlocks the screen */ if (SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); /* Redraw */ updatescreen(); } /* while */ /*******************/ /* Clear things up */ /*******************/ FreeFont(Font1); /********/ /* Quit */ /********/ return 0; /* Success */ }