#include // fopen #include // see millis() #include #include #include #include /* * TICKS_PER_SEC & MSEC_PER_TICK * * The game's main loop mainly takes care of two things: drawing to the * screen and handling game logic, from user input to world gravity stuff. * The call for rendering is made every time the main loop loops, and then * uses interpolation for smooth drawing to the screen. However, the call * for logic would be preferred to be run every set amount of time. * * * The logic loop is currently implemented to run at a certain interval * that we call a 'tick'. As one may now guess, TICKS_PER_SEC defines the * amount of ticks that should be made every second. MSEC_PER_TICK then * does a simple calculation of how many milliseconds elapse per each * 'tick'. Simple math is then done in the main loop using MSEC_PER_TICK * to call the logic handler when necessary. * */ #define TICKS_PER_SEC 20 #define MSEC_PER_TICK (1000/TICKS_PER_SEC) /* * window & mainGLContext * * In order to draw using SDL and its openGL facilities SDL requires * an SDL_Window object, which spawns a window for the program to draw * to. Once the SDL_Window is initialized, an SDL_GLContext is made so * that openGL calls can be made to SDL. The game requires both of these * variables to initialize. * */ SDL_Window *window = NULL; SDL_GLContext mainGLContext = NULL; /* * bgImage contains the GLuint returned when creating a texture for the * background image. Currently there is only one background image for the * main world; this will be changed and bgImage likely removed once better * backgrounds are implemented. * */ static GLuint bgDay, bgNight, bgMtn, bgTreesFront, bgTreesMid, bgTreesFar; /* * gameRunning * * This is one of the most important variables in the program. The main * loop of the game is set to break once this variable is set to false. * The only call to modify this variable is made in src/ui.cpp, where it * is set to false if either an SDL_QUIT message is received (the user * closes the window through their window manager) or if escape is pressed. * */ bool gameRunning; float handAngle; /* * currentWorld - This is a pointer to the current world that the player * is in. Most drawing/entity handling is done through this * variable. This should only be changed when layer switch * buttons are pressed (see src/ui.cpp), or when the player * enters a Structure/Indoor World (see src/ui.cpp again). * * player - This points to a Player object, containing everything for * the player. Most calls made with currentWorld require a * Player object as an argument, and glOrtho is set based * off of the player's coordinates. This is probably the one * Entity-derived object that is not pointed to in the entity * array. * */ World *currentWorld=NULL; Player *player; /* * Tells if player is currently inside a structure. */ extern bool worldInside; /* * tickCount contains the number of ticks generated since main loop entrance. * This variable might be used anywhere. * * deltaTime is used for interpolation stuff. * * Pretty sure these variables are considered static as they might be externally * referenced somewhere. */ unsigned int tickCount = 0; unsigned int deltaTime = 0; /* * */ GLuint fragShader; GLuint shaderProgram; /* * names is used to open a file containing all possible NPC names. It is externally * referenced in src/entities.cpp for getting random names. */ FILE *names; /* * These variables are used by SDL_mixer to create sound. * horn is not currently used, although it may be set. */ Mix_Music *music; Mix_Chunk *horn; /* * loops is used for texture animation. It is believed to be passed to entity * draw functions, although it may be externally referenced instead. */ unsigned int loops = 0; // Used for texture animation /* * initEverything * * Before the main loop, things like the player, entities, and worlds should * be created. This game has not reached the point that these can be scripted * or programmed, so this function substitues for that. It is defined in * src/gameplay.cpp. * */ extern void initEverything(void); /* * mainLoop is in fact the main loop, which runs 'infinitely' (as long as gameRunning * * * * is set). Each loop updates timing values (tickCount and deltaTime), runs logic() * if MSEC_PER_TICK milliseconds have passed, and then runs render(). * * logic handles all user input and entity/world physics. * * render handles all drawing to the window, calling draw functions for everything. * */ void logic(void); void render(void); void mainLoop(void); std::string readFile(const char *filePath) { std::string content; std::ifstream fileStream(filePath, std::ios::in); if(!fileStream.is_open()) { std::cerr << "Could not read file " << filePath << ". File does not exist." << std::endl; return ""; } std::string line = ""; while(!fileStream.eof()) { std::getline(fileStream, line); content.append(line + "\n"); } fileStream.close(); return content; } /* * This offset is used as the player offset in the world drawing so * everything can be moved according to the player */ vec2 offset; /** OFFSET!!!!!!!!!!!!!!!!!!!! **/ /* * millis * * We've encountered many problems when attempting to create delays for triggering * the logic function. As a result, we decided on using the timing libraries given * by in the standard C++ library. This function simply returns the amount * of milliseconds that have passed sine the epoch. * */ #ifdef __WIN32__ #define millis() SDL_GetTicks() #else unsigned int millis(void){ std::chrono::system_clock::time_point now=std::chrono::system_clock::now(); return std::chrono::duration_cast(now.time_since_epoch()).count(); } #endif typedef enum { SUNNY = 0, DARK, RAIN } WEATHER; #define DAY_CYCLE 3000 static WEATHER weather = SUNNY; static vec2 star[100]; /******************************************************************************* * MAIN ************************************************************************ *******************************************************************************/ int main(int argc, char *argv[]){ gameRunning=false; /* * (Attempt to) Initialize SDL libraries so that we can use SDL facilities and eventually * make openGL calls. Exit if there was an error. */ if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0){ std::cout << "SDL was not able to initialize! Error: " << SDL_GetError() << std::endl; return -1; } // Run SDL_Quit when main returns atexit(SDL_Quit); /* * (Attempt to) Initialize SDL_image libraries with IMG_INIT_PNG so that we can load PNG * textures for the entities and stuff. */ if(!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)){ std::cout << "Could not init image libraries! Error: " << IMG_GetError() << std::endl; return -1; } // Run IMG_Quit when main returns atexit(IMG_Quit); /* * (Attempt to) Initialize SDL_mixer libraries for loading and playing music/sound files. * */ if(Mix_OpenAudio( 44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0){ std::cout << "SDL_mixer could not initialize! Error: " << Mix_GetError() << std::endl; } // Run Mix_Quit when main returns atexit(Mix_Quit); /* * Create a window for SDL to draw to. Most parameters are the default, except for the * following which are defined in include/common.h: * * GAME_NAME the name of the game that is displayed in the window title bar * SCREEN_WIDTH the width of the created window * SCREEN_HEIGHT the height of the created window * FULLSCREEN makes the window fullscreen * */ window = SDL_CreateWindow(GAME_NAME, SDL_WINDOWPOS_UNDEFINED, // Spawn the window at random (undefined) x and y coordinates SDL_WINDOWPOS_UNDEFINED, // SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL #ifdef FULLSCREEN | SDL_WINDOW_FULLSCREEN #endif // FULLSCREEN ); /* * Exit if the window cannot be created */ if(window==NULL){ std::cout << "The window failed to generate! SDL_Error: " << SDL_GetError() << std::endl; return -1; } /* * Create the SDL OpenGL context. Once created, we are allowed to use OpenGL functions. * Saving this context to mainGLContext does not appear to be necessary as mainGLContext * is never referenced again. * */ if((mainGLContext = SDL_GL_CreateContext(window)) == NULL){ std::cout << "The OpenGL context failed to initialize! SDL_Error: " << SDL_GetError() << std::endl; return -1; } /* * Initialize GLEW libraries, and exit if there was an error. * Not sure what they're for yet. * */ GLenum err; #ifndef __WIN32__ glewExperimental = GL_TRUE; #endif if((err=glewInit()) != GLEW_OK){ std::cout << "GLEW was not able to initialize! Error: " << glewGetErrorString(err) << std::endl; return -1; } /* * Initialize the FreeType libraries and select what font to use using functions from the ui * namespace, defined in include/ui.h and src/ui.cpp. These functions should abort with errors * if they have error. * */ ui::initFonts(); ui::setFontFace("ttf/Perfect DOS VGA 437.ttf"); // as in gamedev/ttf/ /* * Initialize the random number generator. At the moment, initRand is a macro pointing to libc's * srand, and its partner getRand points to rand. This is because having our own random number * generator may be favorable in the future, but at the moment is not implemented. * */ initRand(millis()); /* * Do some basic setup for openGL. Enable double buffering, switch to by-pixel coordinates, * setup the alpha channel for textures/transparency, and finally hide the system's mouse * cursor so that we may draw our own. * */ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); SDL_ShowCursor(SDL_DISABLE); /* * Initializes our shaders so that the game has shadows. */ #ifdef SHADERS std::cout << "Initializing shaders!" << std::endl; fragShader = glCreateShader(GL_FRAGMENT_SHADER); std::string shaderFileContents = readFile("shader.frag"); const GLchar *shaderSource = shaderFileContents.c_str(); GLint bufferln = GL_FALSE; int logLength; glShaderSource(fragShader, 1, &shaderSource, NULL); glCompileShader(fragShader); glGetShaderiv(fragShader, GL_COMPILE_STATUS, &bufferln); glGetShaderiv(fragShader, GL_INFO_LOG_LENGTH, &logLength); std::vectorfragShaderError((logLength > 1) ? logLength : 1); glGetShaderInfoLog(fragShader, logLength, NULL, &fragShaderError[0]); std::cout << &fragShaderError[0] << std::endl; if(bufferln == GL_FALSE){ std::cout << "Error compiling shader" << std::endl; } shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, fragShader); glLinkProgram(shaderProgram); glValidateProgram(shaderProgram); glGetProgramiv(shaderProgram, GL_LINK_STATUS, &bufferln); glGetProgramiv(shaderProgram, GL_INFO_LOG_LENGTH, &logLength); std::vector programError( (logLength > 1) ? logLength : 1 ); glGetProgramInfoLog(shaderProgram, logLength, NULL, &programError[0]); std::cout << &programError[0] << std::endl; #endif //SHADERS //glEnable(GL_DEPTH_TEST); //THIS DOESN'T WORK ON LINUX glEnable(GL_MULTISAMPLE); /* * Open the names file containing potential names for NPCs and store it in the names file * pointer. This will be reference by getName in src/entities.cpp when NPCs are spawned. * */ names = fopen("assets/names_en-us", "r+"); /* * Create all the worlds, entities, mobs, and the player. This function is defined in * src/gameplay.cpp * */ initEverything(); /* * Open a test background music file and sound. The background music is then played indefinitely * while the sound is never referenced again. * */ music = Mix_LoadMUS("assets/BennyHillTheme.wav"); // as in gamedev/assets/ horn = Mix_LoadWAV("assets/air-horn-club-sample_1.wav"); // Mix_VolumeMusic(15); // Set the volume Mix_PlayMusic( music, -1 ); // Play music forever /* * Load a temporary background image. */ bgDay =Texture::loadTexture("assets/bg.png" ); bgNight =Texture::loadTexture("assets/bgn.png" ); bgMtn =Texture::loadTexture("assets/bgFarMountain.png"); bgTreesFront =Texture::loadTexture("assets/bgFrontTree.png" ); bgTreesMid =Texture::loadTexture("assets/bgMidTree.png" ); bgTreesFar =Texture::loadTexture("assets/bgFarTree.png" ); /* * Load sprites used in the inventory menu. See src/inventory.cpp */ initInventorySprites(); /* * Generate coordinates for stars that are drawn at night. */ unsigned int i; for(i=0;i<100;i++){ star[i].x=getRand()%currentWorld->getTheWidth()-currentWorld->getTheWidth()/2; star[i].y=getRand()%SCREEN_HEIGHT+100; } /************************** **** GAMELOOP **** **************************/ gameRunning=true; while(gameRunning){ mainLoop(); } /************************** **** CLOSE PROGRAM **** **************************/ /* * Close the window and free resources */ fclose(names); SDL_GL_DeleteContext(mainGLContext); SDL_DestroyWindow(window); FILE *worldSave = fopen("world.dat","w"); char *worldBuf; unsigned int worldSize; worldBuf=currentWorld->save(&worldSize); fclose(worldSave); return 0; // Calls everything passed to atexit } /* * fps contains the game's current FPS, debugY contains the player's * y coordinates, updated at a certain interval. These are used in * the debug menu (see below). * */ static unsigned int fps=0; static float debugY=0; void mainLoop(void){ static unsigned int debugDiv=0; // A divisor used to update the debug menu if it's open static unsigned int prevTime = 0, // Used for timing operations currentTime = 0, // prevPrevTime= 0; // shit unsigned int i; // Used for `for` loops if(!currentTime){ // Initialize currentTime if it hasn't been currentTime=millis(); prevPrevTime=currentTime; } /* * Update timing values. This is crucial to calling logic and updating the window (basically * the entire game). */ prevTime = currentTime; currentTime = millis(); deltaTime = currentTime - prevTime; /* * Run the logic handler if MSEC_PER_TICK milliseconds have passed. */ if(prevPrevTime + MSEC_PER_TICK >= currentTime){ logic(); prevPrevTime = currentTime; } //ui::handleMouse(); /* * Update player and entity coordinates. */ currentWorld->update(player,deltaTime); /* * Update debug variables if necessary */ if(++debugDiv==20){ debugDiv=0; fps=1000/deltaTime; }else if(!(debugDiv%10)){ debugY = player->loc.y; } render(); // Call the render loop } extern bool fadeEnable; static unsigned char fadeIntensity = 0; void render(){ /* * This offset variable is what we use to move the camera and locked * objects on the screen so they always appear to be in the same relative area */ offset.x = player->loc.x + player->width/2; offset.y = SCREEN_HEIGHT/2; /* * If the camera will go off of the left or right of the screen we want to lock it so we can't * see past the world render */ if(!worldInside){ if(player->loc.x - SCREEN_WIDTH/2 < currentWorld->getTheWidth() * -0.5f) offset.x = ((currentWorld->getTheWidth() * -0.5f) + SCREEN_WIDTH / 2) + player->width / 2; if(player->loc.x + player->width + SCREEN_WIDTH/2 > currentWorld->getTheWidth() * 0.5f) offset.x = ((currentWorld->getTheWidth() * 0.5f) - SCREEN_WIDTH / 2);// + player->width / 2; } if(player->loc.y > SCREEN_HEIGHT/2) offset.y = player->loc.y + player->height; /* * These functions run everyloop to update the current stacks presets * * Matrix ---- A matrix is a blank "canvas" for the renderer to draw on, * this canvas can be rotated, scales, skewed, etc.. * * Stack ---- A stack is exactly what it sounds like, it is a stack.. A * stack is a "stack" of matrices for the renderer to draw on. * Each stack can be made up of varying amounts of matricies. * * glMatrixMode This changes our current stacks mode so the drawings below * it can take on certain traits. * * GL_PROJECTION This is the matrix mode that sets the cameras position, * GL_PROJECTION is made up of a stack with two matrices which * means we can make up to 2 seperate changes to the camera. * * GL_MODELVIEW This matrix mode is set to have the dimensions defined above * by GL_PROJECTION so the renderer can draw only what the camera * is looking at. GL_MODELVIEW has a total of 32 matrices on it's * stack, so this way we can make up to 32 matrix changes like, * scaling, rotating, translating, or flipping. * * glOrtho glOrtho sets our ortho, or our cameras resolution. This can also * be used to set the position of the camera on the x and y axis * like we have done. The glOrtho must be set while the stack is in * GL_PROJECTION mode, as this is the mode that gives the * camera properties. * * glPushMatrix This creates a "new" matrix. What it really does is pull a matrix * off the bottom of the stack and puts it on the top so the renderer * can draw on it. * * glLoadIdentity This scales the current matrix back to the origin so the * translations are seen normally on a stack. */ glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho((offset.x-SCREEN_WIDTH/2),(offset.x+SCREEN_WIDTH/2),offset.y-SCREEN_HEIGHT/2,offset.y+SCREEN_HEIGHT/2,-1,1); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glEnable(GL_STENCIL_TEST); glPushMatrix(); /* * glPushAttrib This passes attributes to the renderer so it knows what it can * render. In our case, GL_DEPTH_BUFFER_BIT allows the renderer to * draw multiple objects on top of one another without blending the * objects together; GL_LIGHING_BIT allows the renderer to use shaders * and other lighting effects to affect the scene. * * glClear This clears the new matrices using the type passed. In our case: * GL_COLOR_BUFFER_BIT allows the matrices to have color on them */ glPushAttrib( GL_DEPTH_BUFFER_BIT | GL_LIGHTING_BIT ); glClear(GL_COLOR_BUFFER_BIT); /************************** **** RENDER STUFF HERE **** **************************/ /* * Draw a temporary background image */ glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D,bgDay); safeSetColorA(255,255,255,255-worldShade*4); glBegin(GL_QUADS); glTexCoord2i(0,1);glVertex2i(-SCREEN_WIDTH*2+offset.x,0+offset.y-SCREEN_HEIGHT/2); glTexCoord2i(1,1);glVertex2i( SCREEN_WIDTH*2+offset.x,0+offset.y-SCREEN_HEIGHT/2); glTexCoord2i(1,0);glVertex2i( SCREEN_WIDTH*2+offset.x,SCREEN_HEIGHT*2+offset.y-SCREEN_HEIGHT/2); glTexCoord2i(0,0);glVertex2i(-SCREEN_WIDTH*2+offset.x,SCREEN_HEIGHT*2+offset.y-SCREEN_HEIGHT/2); glEnd(); glBindTexture(GL_TEXTURE_2D,bgNight); safeSetColorA(255,255,255,worldShade*4); glBegin(GL_QUADS); glTexCoord2i(0,1);glVertex2i(-SCREEN_WIDTH*2+offset.x,0+offset.y-SCREEN_HEIGHT/2); glTexCoord2i(1,1);glVertex2i( SCREEN_WIDTH*2+offset.x,0+offset.y-SCREEN_HEIGHT/2); glTexCoord2i(1,0);glVertex2i( SCREEN_WIDTH*2+offset.x,SCREEN_HEIGHT*2+offset.y-SCREEN_HEIGHT/2); glTexCoord2i(0,0);glVertex2i(-SCREEN_WIDTH*2+offset.x,SCREEN_HEIGHT*2+offset.y-SCREEN_HEIGHT/2); glEnd(); glDisable(GL_TEXTURE_2D); /* * Draws stars if it is an appropriate time of day for them. */ int base = 40 - (int)worldGetYBase(currentWorld); int shade = worldShade*2; if(((weather==DARK )&(tickCount%DAY_CYCLE)DAY_CYCLE*.75) ){ if(tickCount%DAY_CYCLE){ // The above if statement doesn't check for exact midnight. safeSetColorA(255,255,255,shade); for(unsigned int i=0;i<100;i++){ glRectf(star[i].x+offset.x*.9,star[i].y,star[i].x+offset.x*.9+HLINE,star[i].y+HLINE); } } } /* * Calculate the Y to start drawing the background at, and a value for how shaded the * background elements should be. */ glEnable(GL_TEXTURE_2D); /* * Draw the mountains. */ glBindTexture(GL_TEXTURE_2D, bgMtn); glBegin(GL_QUADS); safeSetColorA(150-shade,150-shade,150-shade,220); for(int i = 0; i <= currentWorld->getTheWidth()/1920; i++){ glTexCoord2i(0,1);glVertex2i((currentWorld->getTheWidth()*-0.5f)+(1920 * i)+offset.x*.85,base); glTexCoord2i(1,1);glVertex2i((currentWorld->getTheWidth()*-0.5f)+(1920 * (i+1))+offset.x*.85,base); glTexCoord2i(1,0);glVertex2i((currentWorld->getTheWidth()*-0.5f)+(1920 * (i+1))+offset.x*.85,base+1080); glTexCoord2i(0,0);glVertex2i((currentWorld->getTheWidth()*-0.5f)+(1920 * i)+offset.x*.85,base+1080); } glEnd(); /* * Draw three layers of trees. */ glBindTexture(GL_TEXTURE_2D, bgTreesFar); glBegin(GL_QUADS); safeSetColorA(100-shade,100-shade,100-shade,240); for(int i = 0; i <= currentWorld->getTheWidth()/1920; i++){ glTexCoord2i(0,1);glVertex2i((currentWorld->getTheWidth()*-0.5f)+(1920 * i)+offset.x*.6,base); glTexCoord2i(1,1);glVertex2i((currentWorld->getTheWidth()*-0.5f)+(1920 * (i+1))+offset.x*.6,base); glTexCoord2i(1,0);glVertex2i((currentWorld->getTheWidth()*-0.5f)+(1920 * (i+1))+offset.x*.6,base+1080); glTexCoord2i(0,0);glVertex2i((currentWorld->getTheWidth()*-0.5f)+(1920 * i)+offset.x*.6,base+1080); } glEnd(); glBindTexture(GL_TEXTURE_2D, bgTreesMid); glBegin(GL_QUADS); safeSetColorA(150-shade,150-shade,150-shade,250); for(int i = 0; i <= currentWorld->getTheWidth()/1920; i++){ glTexCoord2i(0,1);glVertex2i((currentWorld->getTheWidth()*-0.5f)+(1920 * i)+offset.x*.4,base); glTexCoord2i(1,1);glVertex2i((currentWorld->getTheWidth()*-0.5f)+(1920 * (i+1))+offset.x*.4,base); glTexCoord2i(1,0);glVertex2i((currentWorld->getTheWidth()*-0.5f)+(1920 * (i+1))+offset.x*.4,base+1080); glTexCoord2i(0,0);glVertex2i((currentWorld->getTheWidth()*-0.5f)+(1920 * i)+offset.x*.4,base+1080); } glEnd(); glBindTexture(GL_TEXTURE_2D, bgTreesFront); glBegin(GL_QUADS); safeSetColorA(255-shade,255-shade,255-shade,255); for(int i = 0; i <= currentWorld->getTheWidth()/1920; i++){ glTexCoord2i(0,1);glVertex2i((currentWorld->getTheWidth()*-0.5f)+(1920 * i)+offset.x*.25,base); glTexCoord2i(1,1);glVertex2i((currentWorld->getTheWidth()*-0.5f)+(1920 * (i+1))+offset.x*.25,base); glTexCoord2i(1,0);glVertex2i((currentWorld->getTheWidth()*-0.5f)+(1920 * (i+1))+offset.x*.25,base+1080); glTexCoord2i(0,0);glVertex2i((currentWorld->getTheWidth()*-0.5f)+(1920 * i)+offset.x*.25,base+1080); } glEnd(); glDisable(GL_TEXTURE_2D); /* * Call the world's draw function, drawing the player, the world, and entities. Also * draw the player's inventory if it exists. */ player->near=true; // Draw the player's name currentWorld->draw(player); /* * Apply shaders if desired. */ #ifdef SHADERS glUseProgramObjectARB(shaderProgram); glUniform2f(glGetUniformLocation(shaderProgram, "lightLocation"), 640,100); glUniform3f(glGetUniformLocation(shaderProgram, "lightColor"), 1,1,1); glUniform1f(glGetUniformLocation(shaderProgram, "lightStrength"), 100 + (1000-(shade*10))); //std::cout << 100 + (1000-(shade*10)) << std::endl; glColor4ub(0,0,0,200); glRectf(offset.x-SCREEN_WIDTH/2,0,offset.x+SCREEN_WIDTH/2,SCREEN_HEIGHT); glUseProgramObjectARB(0); #endif //SHADERS if(player->light){ handAngle = atan((ui::mouse.y - (player->loc.y + player->height/2)) / (ui::mouse.x - player->loc.x + player->width/2))*180/PI; if(ui::mouse.x < player->loc.x){ if(handAngle <= 0) handAngle+=180; if(ui::mouse.y < player->loc.y + player->height/2){ handAngle+=180; } } if(ui::mouse.x > player->loc.x && ui::mouse.y < player->loc.y+player->height/2 && handAngle <= 0) handAngle = 360+handAngle; vec2 light; int lightStr = 150; vec2 curCoord; light.x = player->loc.x + player->width; light.y = player->loc.y + player->height/2; std::vectorfray(60); unsigned int a = 0; float angle = 0; glColor3f(0.0f, 0.0f, 0.0f); for(auto &r : fray){ r.start = light; curCoord = r.start; angle = .5*a + handAngle; //for length for(int l = 0;l<=lightStr;l++){ //std::cout << a << ": " << curCoord.x << "," << curCoord.y << "\n"; if(angle == 0){ curCoord.x += HLINE; curCoord.y += 0; } if(angle == 90){ curCoord.y += HLINE; curCoord.x += 0; } if(angle == 180){ curCoord.x -= HLINE; curCoord.y += 0; } if(angle == 270){ curCoord.y -= HLINE; curCoord.x += 0; } if(angle == 360){ curCoord.x += HLINE; curCoord.y += 0; }else{ curCoord.x += float((HLINE) * cos(angle*PI/180)); curCoord.y += float((HLINE) * sin(angle*PI/180)); } for(auto &en : currentWorld->entity){ if(curCoord.x > en->loc.x && curCoord.x < en->loc.x + en->width){ if(curCoord.y > en->loc.y && curCoord .y < en->loc.y + en->height){ r.end = curCoord; l=lightStr; } } } /*if(curCoord.x > player->loc.x && curCoord.x < player->loc.x + player->width){ if(curCoord.y > player->loc.y && curCoord .y < player->loc.y + player->height){ r.end = curCoord; l=lightStr; } }*/if(l==lightStr)r.end = curCoord; }//end length glBegin(GL_LINES); glVertex2f(r.start.x,r.start.y); glVertex2f(r.end.x, r.end.y); glEnd(); //std::cout << angle << "\n"; a++; } glUseProgramObjectARB(shaderProgram); glUniform2f(glGetUniformLocation(shaderProgram, "lightLocation"), 640,300); glUniform3f(glGetUniformLocation(shaderProgram, "lightColor"), 1,1,1); glUniform1f(glGetUniformLocation(shaderProgram, "lightStrength"), 5); glColor4f(1.0f, 1.0f, 1.0f, .5f); for(auto r = 0; r < fray.size(); r++){ glBegin(GL_TRIANGLES); glVertex2f(fray[r].start.x, fray[r].start.y); glVertex2f(fray[r].end.x, fray[r].end.y); r==fray.size()-1 ? glVertex2f(fray[r].end.x, fray[r].end.y) : glVertex2f(fray[r+1].end.x, fray[r+1].end.y); glEnd(); } glUseProgramObjectARB(0); } player->inv->draw(); /* * Draw UI elements. This includes the player's health bar and the dialog box. */ ui::draw(); /* * Draw the debug overlay if it has been enabled. */ if(ui::debug){ ui::putText(offset.x-SCREEN_WIDTH/2, (offset.y+SCREEN_HEIGHT/2)-ui::fontSize, "FPS: %d\nG:%d\nRes: %ux%u\nE: %d\nPOS: (x)%+.2f\n (y)%+.2f\nTc: %u\nHA: %+.2f", fps, player->ground, SCREEN_WIDTH, // Window dimensions SCREEN_HEIGHT, // currentWorld->entity.size(),// Size of entity array player->loc.x, // The player's x coordinate debugY, // The player's y coordinate tickCount, handAngle ); if(ui::posFlag){ glBegin(GL_LINES); glColor3ub(255,0,0); glVertex2i(0,0); glVertex2i(0,SCREEN_HEIGHT); glColor3ub(255,255,255); glVertex2i(player->loc.x + player->width/2,0); glVertex2i(player->loc.x + player->width/2,SCREEN_HEIGHT); glVertex2i(-SCREEN_WIDTH/2+offset.x,player->loc.y); glVertex2i(SCREEN_WIDTH/2+offset.x, player->loc.y); glEnd(); } } /* * Draw a white triangle as a replacement for the mouse's cursor. */ glColor3ub(255,255,255); glBegin(GL_TRIANGLES); glVertex2i(ui::mouse.x ,ui::mouse.y ); glVertex2i(ui::mouse.x+HLINE*3.5,ui::mouse.y ); glVertex2i(ui::mouse.x ,ui::mouse.y-HLINE*3.5); glEnd(); /* * Here we draw a black overlay if it's been requested. */ if(fadeIntensity){ glColor4ub(0,0,0,fadeIntensity); glRectf(offset.x-SCREEN_WIDTH /2, offset.y-SCREEN_HEIGHT/2, offset.x+SCREEN_WIDTH /2, offset.y+SCREEN_HEIGHT/2); if(fadeIntensity == 255){ ui::importantText("The screen is black."); } }else if(ui::fontSize != 16) ui::setFontSize(16); /************************** **** END RENDERING **** **************************/ /* * These next two function finish the rendering * * glPopMatrix This anchors all of the matrices and blends them to a single * matrix so the renderer can draw this to the screen, since screens * are only 2 dimensions, we have to combine the matrixes to be 2d. * * SDL_GL_SwapWindow Since SDL has control over our renderer, we need to now give our * new matrix to SDL so it can pass it to the window. */ glPopMatrix(); SDL_GL_SwapWindow(window); } void logic(){ /* * NPCSelected is used to insure that only one NPC is made interactable with the mouse * if, for example, multiple entities are occupying one space. */ static bool NPCSelected = false; /* * Handle user input (keyboard & mouse). */ ui::handleEvents(); /* * Run the world's detect function. This handles the physics of the player and any entities * that exist in this world. */ currentWorld->detect(player); if(player->loc.y<.02)gameRunning=false; /* * Entity logic: This loop finds every entity that is alive and in the current world. It then * basically runs their AI functions depending on what type of entity they are. For NPCs, * click detection is done as well for NPC/player interaction. * */ for(auto &n : currentWorld->npc){ if(n->alive){ /* * Make the NPC 'wander' about the world if they're allowed to do so. * Entity->canMove is modified when a player interacts with an NPC so * that the NPC doesn't move when it talks to the player. * */ if(n->canMove) n->wander((rand() % 120 + 30)); /* * Don't bother handling the NPC if another has already been handled. */ if(NPCSelected){ n->near=false; break; } /* * Check if the NPC is under the mouse. */ if(ui::mouse.x >= n->loc.x && ui::mouse.x <= n->loc.x + n->width && ui::mouse.y >= n->loc.y && ui::mouse.y <= n->loc.y + n->width ){ /* * Check of the NPC is close enough to the player for interaction to be * considered legal. In other words, require the player to be close to * the NPC in order to interact with it. * * This uses the Pythagorean theorem to check for NPCs within a certain * radius (40 HLINEs) of the player's coordinates. * */ if(pow((n->loc.x - player->loc.x),2) + pow((n->loc.y - player->loc.y),2) <= pow(40*HLINE,2)){ /* * Set Entity->near so that this NPC's name is drawn under them, and toggle NPCSelected * so this NPC is the only one that's clickable. */ n->near=true; NPCSelected=true; /* * Check for a right click, and allow the NPC to interact with the * player if one was made. */ if(SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_RIGHT)){ if(!ui::dialogBoxExists){ n->interact(); Mix_PlayChannel( -1, horn, 0); // Audio feedback } } } /* * Hide the NPC's name if the mouse isn't on the NPC. */ }else n->near=false; } } for(auto &m : currentWorld->mob){ if(m->alive){ /* * Run the Mob's AI function. */ switch(m->subtype){ case MS_RABBIT: case MS_BIRD: m->wander((rand()%240 + 15)); // Make the mob wander :) break; } } } unsigned int i = 0; for(auto &o : currentWorld->object){ if(o->alive){ if(pow((o->loc.x - player->loc.x),2) + pow((o->loc.y - player->loc.y),2) <= pow(40*HLINE,2)){ /* * Check for a right click, and allow the Object to interact with the * player if one was made. */ if(SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_RIGHT)){ std::cout << "Picking up!\n"; o->interact(); } } } if(!(o->alive)){ currentWorld->object.erase(currentWorld->object.begin()+i); } i++; } /* * Switch between day and night (SUNNY and DARK) if necessary. */ if(!(tickCount%DAY_CYCLE)||!tickCount){ if(weather==SUNNY){ weather=DARK; }else{ weather=SUNNY; } } /* * Calculate an in-game shading value (as opposed to GLSL shading). */ worldShade=50*sin((tickCount+(DAY_CYCLE/2))/(DAY_CYCLE/PI)); /* * Transition to and from black if necessary. */ if(fadeEnable){ if(fadeIntensity < 160)fadeIntensity+=5; else if(fadeIntensity < 255)fadeIntensity+=1; }else{ if(fadeIntensity > 150)fadeIntensity-=1; else if(fadeIntensity) fadeIntensity-=5; } /* * Increment a loop counter used for animating sprites. */ loops++; tickCount++; // Used for day/night cycles NPCSelected=false; // See above }