#include <world.h> #define getWidth(w) ((w->lineCount-GEN_INC)*HLINE) // Calculates the width of world 'w' #define GEN_INC 10 // Defines at what interval y values should be calculated for the array 'line'. // As explained in World(), the last few lines in the array 'line' are incorrectly calculated // or not calculated at all, so GEN_INC is also used to decrease 'lineCount' in functions like draw() // and detect(). #define GRASS_HEIGHT 4 // Defines how long the grass layer of a line should be in multiples of HLINE. #define DRAW_Y_OFFSET 50 // Defines how many pixels each layer should be offset from each other on the y axis when drawn. #define DRAW_SHADE 30 // Defines a shade increment for draw() #define INDOOR_FLOOR_HEIGHT 100 // Defines how high the base floor of an IndoorWorld should be extern std::vector<Entity *> entity; extern std::vector<Structures *> build; void safeSetColor(int r,int g,int b){ // safeSetColor() is an alternative to directly using glColor3ub() to set if(r>255)r=255; // the color for OpenGL drawing. safeSetColor() checks for values that are if(g>255)g=255; // outside the range of an unsigned character and sets them to a safer value. if(b>255)b=255; if(r<0)r=0; if(g<0)g=0; if(b<0)b=0; glColor3ub(r,g,b); } float worldGetYBase(World *w){ float base = 0; World *ptr = w; while(ptr->infront){ base+=DRAW_Y_OFFSET; ptr=ptr->infront; } return base; } World::World(void){ } void World::generate(unsigned int width){ // Generates the world and sets all variables contained in the World class. unsigned int i; float inc; /* * Calculate the world's real width. The current form of generation fails to generate * the last GEN_INC lines, so we offset those making the real real width what was passed * to this function. * * Abort if the width is invalid. * */ if((lineCount = width + GEN_INC) <= 0) abort(); /* * Allocate enough memory for the world to be stored. */ line=(struct line_t *)calloc(lineCount,sizeof(struct line_t)); /* * Set an initial y to base generation off of, as generation references previous lines. */ line[0].y=80; /* * Populate every GEN_INCth line structure. The remaining lines will be based off of these. */ for(i=GEN_INC;i<lineCount;i+=GEN_INC){ /* * Generate a y value, ensuring it stays within a reasonable range. */ line[i].y=rand() % 8 - 4 + line[i-GEN_INC].y; // Add +/- 4 to the previous line if(line[i].y < 40)line[i].y = 40; // Minimum bound else if(line[i].y > 70)line[i].y = 70; // Maximum bound } /* * Generate values for the remaining lines here. */ for(i=0;i<lineCount-GEN_INC;i++){ /* * Every GEN_INCth line calculate the slope between the current line and the one * GEN_INC lines before it. This value is then divided into an increment that is * added to lines between these two points resulting in a smooth slope. * */ if(!i||!(i%GEN_INC)){ inc=(line[i + GEN_INC].y - line[i].y) / (float)GEN_INC; }else{ /* * Add the increment to create the smooth slope. */ line[i].y=line[i - 1].y + inc; } /* * Generate a color value for the line. This will be referenced in World->draw(), * by setting an RGB value of color (red), color - 50 (green), color - 100 (blue). */ line[i].color=rand() % 20 + 100; // 100 to 120 /* * Each line has two 'blades' of grass, here we generate random heights for them. */ line[i].gh[0]=(getRand() % 16) / 3.5 + 2; // Not sure what the range resolves to here... line[i].gh[1]=(getRand() % 16) / 3.5 + 2; // line[i].gs=true; // Show the blades of grass (modified by the player) } /* * Calculate the x coordinate to start drawing this world from so that it is centered at (0,0). */ x_start=0 - getWidth(this) / 2 + GEN_INC / 2 * HLINE; /* * Nullify pointers to other worlds. */ behind = infront = toLeft = toRight = NULL; } World::~World(void){ free(line); } void World::draw(Player *p){ static float yoff=DRAW_Y_OFFSET; // Initialize stuff static int shade=0; static World *current; int i,is,ie,v_offset,cx_start; struct line_t *cline; glClearColor(.1,.3,.6,0); /* * World drawing is done recursively, meaning that this function jumps * back as many 'layers' as it can and then draws, eventually coming * back to the initial or 'root' layer. LOOP1 does the recursion back * to the furthest behind layer, modifying shade and y offsets as it * does. * */ current=this; LOOP1: if(current->behind){ /* * Add to the y offset and shade values (explained further below) * and recurse. * */ yoff+=DRAW_Y_OFFSET; shade+=DRAW_SHADE; current=current->behind; goto LOOP1; } /* * Here is where the actual world drawing begins. A goto is made to * LOOP2 once the current layer is drawn and the function shifts to * draw the next closest layer. */ LOOP2: /* * Calculate the offset in the line array that the player is (or would) * currently be on. This function then calculates reasonable values for * the 'for' loop below that draws the layer. */ v_offset=(offset.x + p->width / 2 - current->x_start) / HLINE; // is -> i start is=v_offset - (SCREEN_WIDTH / 2 / HLINE) - GEN_INC; if(is<0)is=0; // Minimum bound // ie -> i end ie=v_offset + (SCREEN_WIDTH / 2 / HLINE) + GEN_INC + HLINE; if(ie>current->lineCount)ie=current->lineCount; // Maximum bound /* * Make more direct variables for quicker referencing. */ cline =current->line; cx_start=current->x_start; /* * Invert shading if desired. */ //shade*=-1; /* * Draw structures in the current layer if we're on the one we started at. We draw * structures behind the dirt/grass so that the buildings corners don't stick out. */ if(current==this){ for(i=0;i<entity.size();i++){ if(entity[i]->inWorld==this && entity[i]->type == STRUCTURET) entity[i]->draw(); } } /* * Draw the layer up until the grass portion, which is done later. */ bool hey=false; glBegin(GL_QUADS); for(i=is;i<ie-GEN_INC;i++){ cline[i].y+=(yoff-DRAW_Y_OFFSET); // Add the y offset if(!cline[i].y){ cline[i].y+=50; hey=true; safeSetColor(cline[i].color-100+shade,cline[i].color-150+shade,cline[i].color-200+shade); }else{ safeSetColor(cline[i].color+shade,cline[i].color-50+shade,cline[i].color-100+shade); // Set the shaded dirt color } glVertex2i(cx_start+i*HLINE ,cline[i].y-GRASS_HEIGHT); glVertex2i(cx_start+i*HLINE+HLINE,cline[i].y-GRASS_HEIGHT); glVertex2i(cx_start+i*HLINE+HLINE,0); glVertex2i(cx_start+i*HLINE ,0); cline[i].y-=(yoff-DRAW_Y_OFFSET); // Restore the line's y value if(hey){ hey=false; cline[i].y=0; } } glEnd(); /* * If we're drawing the closest/last world, handle and draw the player and entities in * the world. */ if(current==this){ /* * Calculate the line that the player is on */ int ph = (p->loc.x + p->width / 2 - x_start) / HLINE; /* * If the player is on the ground, flatten the grass where the player is standing * by setting line.gs to false. */ if(p->ground){ for(i=0;i<lineCount-GEN_INC;i++){ if(i < ph + 6 && i > ph - 6 ) cline[i].gs=false; else cline[i].gs=true; } }else{ for(i=0;i<lineCount-GEN_INC;i++){ cline[i].gs=true; } } /* * Draw the player. */ p->draw(); /* * Draw non-structure entities. */ for(i=0;i<entity.size();i++){ if(entity[i]->inWorld==this && entity[i]->type != STRUCTURET) entity[i]->draw(); } }else{ /*for(i=0;i<lineCount-GEN_INC;i++){ cline[i].gs=true; }*/ } /* * Draw grass on every line. */ float cgh[2]; glBegin(GL_QUADS); for(i=is;i<ie-GEN_INC;i++){ /* * Load the current line's grass values */ if(cline[i].y)memcpy(cgh,cline[i].gh,2*sizeof(float)); else memset(cgh,0 ,2*sizeof(float)); /* * Flatten the grass if the player is standing on it. */ if(!cline[i].gs){ cgh[0]/=4; cgh[1]/=4; } /* * Actually draw the grass. */ cline[i].y+=(yoff-DRAW_Y_OFFSET); safeSetColor(shade,150+shade,shade); glVertex2i(cx_start+i*HLINE ,cline[i].y+cgh[0]); glVertex2i(cx_start+i*HLINE+HLINE/2,cline[i].y+cgh[0]); glVertex2i(cx_start+i*HLINE+HLINE/2,cline[i].y-GRASS_HEIGHT); glVertex2i(cx_start+i*HLINE ,cline[i].y-GRASS_HEIGHT); glVertex2i(cx_start+i*HLINE+HLINE/2,cline[i].y+cgh[1]); glVertex2i(cx_start+i*HLINE+HLINE ,cline[i].y+cgh[1]); glVertex2i(cx_start+i*HLINE+HLINE ,cline[i].y-GRASS_HEIGHT); glVertex2i(cx_start+i*HLINE+HLINE/2,cline[i].y-GRASS_HEIGHT); cline[i].y-=(yoff-DRAW_Y_OFFSET); } glEnd(); /* * Restore the inverted shading if it was inverted above. */ //shade*=-1; /* * Draw platforms... */ safeSetColor(255+shade*2,0+shade,0+shade); for(i=0;i<current->platform.size();i++){ glRectf(current->platform[i].p1.x, current->platform[i].p1.y + yoff - DRAW_Y_OFFSET, current->platform[i].p2.x, current->platform[i].p2.y + yoff - DRAW_Y_OFFSET); } /* * Draw the next closest world if it exists. */ if(current->infront){ yoff -= DRAW_Y_OFFSET; shade -= DRAW_SHADE; current=current->infront; goto LOOP2; }else{ /* * If finished, reset the yoff and shade variables for the next call. */ yoff=DRAW_Y_OFFSET; shade=0; } } void World::singleDetect(Entity *e){ unsigned int i; /* * Kill any dead entities. */ if(e->alive&&e->health<=0){ e->alive=false; std::cout<<"Killing entity..."<<std::endl; return; } /* * Handle only living entities. */ if(e->alive){ /* * Calculate the line that this entity is currently standing on. */ i=(e->loc.x + e->width / 2 - x_start) / HLINE; /* * If the entity is under the world/line, pop it back to the surface. */ if(e->loc.y < line[i].y){ e->ground=true; e->vel.y=0; e->loc.y=line[i].y - .001 * deltaTime; /* * Otherwise, if the entity is above the line... */ }else if(e->loc.y > line[i].y - .002 * deltaTime){ /* * Check for any potential platform collision (i.e. landing on a platform) */ for(i=0;i<platform.size();i++){ if(((e->loc.x + e->width > platform[i].p1.x) & (e->loc.x + e->width < platform[i].p2.x)) || // Check X left bounds ((e->loc.x < platform[i].p2.x) & (e->loc.x>platform[i].p1.x))){ // Check X right bounds if(e->loc.y > platform[i].p1.y && e->loc.y < platform[i].p2.y){ // Check Y bounds /* * Check if the entity is falling onto the platform so * that it doesn't snap to it when attempting to jump * through it. * */ if(e->vel.y<=0){ e->ground=2; e->vel.y=0; e->loc.y=platform[i].p2.y; //return; // May not be necessary } } } } /* * Handle gravity. */ e->vel.y-=.001 * deltaTime; } /* * Insure that the entity doesn't fall off either edge of the world. */ if(e->loc.x<x_start){ // Left bound e->vel.x=0; e->loc.x=x_start + HLINE / 2; }else if(e->loc.x + e->width + HLINE > x_start + getWidth(this)){ // Right bound e->vel.x=0; e->loc.x=x_start + getWidth(this) - e->width - HLINE; } } } void World::detect(Player *p){ unsigned int i; /* * Handle the player. */ singleDetect(p); /* * Handle all remaining entities in this world. */ for(i=0;i<entity.size();i++){ if(entity[i]->inWorld==this){ singleDetect(entity[i]); } } } /* * The rest of these functions are explained well enough in world.h ;) */ void World::addLayer(unsigned int width){ if(behind){ behind->addLayer(width); return; } behind=new World(); behind->generate(width); behind->infront=this; } World *World::goWorldLeft(Player *p){ if(toLeft&&p->loc.x<x_start+HLINE*10){ p->loc.x=toLeft->x_start+getWidth(toLeft)-HLINE*10; p->loc.y=toLeft->line[0].y; return toLeft; } return this; } World *World::goWorldRight(Player *p){ if(toRight&&p->loc.x+p->width>x_start+getWidth(this)-HLINE*10){ p->loc.x=toRight->x_start+HLINE*10; p->loc.y=toRight->line[toRight->lineCount-GEN_INC-1].y; return toRight; } return this; } World *World::goWorldBack(Player *p){ if(behind&&p->loc.x>(int)(0-getWidth(behind)/2)&&p->loc.x<getWidth(behind)/2){ return behind; } return this; } World *World::goWorldFront(Player *p){ if(infront&&p->loc.x>(int)(0-getWidth(infront)/2)&&p->loc.x<getWidth(infront)/2){ return infront; } return this; } void World::addPlatform(float x,float y,float w,float h){ platform.push_back((Platform){{x,y},{x+w,y+h}}); } World *World::goInsideStructure(Player *p){ unsigned int i; for(i=0;i<build.size();i++){ if(build[i]->inWorld==this){ if(p->loc.x > build[i]->loc.x && p->loc.x + p->width < build[i]->loc.x + build[i]->width){ return (World *)build[i]->inside; } }else if(build[i]->inside==this){ p->loc.x=build[i]->loc.x + build[i]->width / 2 - p->width / 2; p->loc.y=build[i]->loc.y + HLINE; return (World *)build[i]->inWorld; } } return this; } void World::addHole(unsigned int start,unsigned int end){ unsigned int i; for(i=start;i<end;i++){ line[i].y=0; } } int World::getTheWidth(void){ return x_start*2; } IndoorWorld::IndoorWorld(void){ } IndoorWorld::~IndoorWorld(void){ free(line); } void IndoorWorld::generate(unsigned int width){ // Generates a flat area of width 'width' unsigned int i; // Used for 'for' loops lineCount=width+GEN_INC; // Sets line count to the desired width plus GEN_INC to remove incorrect line calculations. if(lineCount<=0)abort(); line=(struct line_t *)calloc(lineCount,sizeof(struct line_t)); // Allocate memory for the array 'line' for(i=0;i<lineCount;i++){ // Indoor areas don't have to be directly on the ground (i.e. 0)... line[i].y=INDOOR_FLOOR_HEIGHT; } behind=infront=NULL; // Set pointers to other worlds to NULL toLeft=toRight=NULL; // to avoid accidental calls to goWorld... functions x_start=0-getWidth(this)/2+GEN_INC/2*HLINE; // Calculate x_start (explained in world.h) } void IndoorWorld::draw(Player *p){ int i,ie,v_offset; v_offset=(p->loc.x-x_start)/HLINE; // Calculate the player's offset in the array 'line' using the player's location 'vec' i=v_offset-SCREEN_WIDTH/2; // um if(i<0)i=0; // If the player is past the start of that world 'i' should start at the beginning // of the world ie=v_offset+SCREEN_WIDTH/2; // Set how many lines should be drawn (the drawing for loop loops from 'i' to 'ie') if(ie>lineCount)ie=lineCount; // If the player is past the end of that world 'ie' should contain the end of that world glClearColor(.3,.1,0,0); glBegin(GL_QUADS); for(i=i;i<ie-GEN_INC;i++){ // For lines in array 'line' from 'i' to 'ie' safeSetColor(150,100,50); glVertex2i(x_start+i*HLINE ,line[i].y); // Draw the base floor glVertex2i(x_start+i*HLINE+HLINE,line[i].y); glVertex2i(x_start+i*HLINE+HLINE,line[i].y-50); glVertex2i(x_start+i*HLINE ,line[i].y-50); } glEnd(); for(i=0;i<entity.size();i++){ if(entity[i]->inWorld==this) entity[i]->draw(); } p->draw(); }