// // // ////////////////////////////////////////////////////////////////////////////////////// // // 3600 grids, 60 ew, 60 ns = 3600 total. Each assigned location // so grid x1 y37, would be first x-block(50ns) 37 blocks to the east. // y37 = 1850 starting variable // x1 = 0 starting variable // ////////////////////////////////////////////////////////////////////////////////////// // Personal-note, when world-map editing, all grids have to be loaded, which means loading // every single grid, this is so when the map-saves, it saves everything, not just a small chunk // of the massive map, which would = bad if it were to happen that way. // Editor on live-port will be disabled when we have builders and a dev-port for building // the new/improved world on. Though on the dev-port, we will probobly just keep the map always loaded // so that we do not have to worry about constantly loading/closing, the worldmap. // // // Additions: Adding GRID_DEBUGGING, to aid in logging of the grid system during testing phases. // Fixed localtime glitch // Added x/y stored ranges to the grid-data, for ease of grid locations. // // comment out if you don't want to debug #define GRID_DEBUGGING 1 // step 0, assigning grid_list Lexi::Listgrid_list; // global counter for memory checks int grid_max; ////////////////////////////////////////////////////////////////////////////////////// // the grid structure simply keeps track of a location array, when a segment of the // map is loaded, it gets put into the grid, and then when moving/looking a local // variable on the player points to the location of the x/y within the grid. // // This method keeps memory to an extreme minimum. // destroyed world/restore timers do not exist, plainly they will be set to a list class grid_data { public: grid_data() { extern int grid_max; extern time_t current_time; last_used = ¤t_time; grid_max++; #ifdef GRID_DEBUGGING extern void log_string(const char *, ...); log_string("[WORLDMAP]: new grid_data created. grid_max now: %d.", grid_max); #endif } ~grid_data() { extern int grid_max; grid_max--; #ifdef GRID_DEBUGGING extern void log_string(const char *, ...); log_string("[WORLDMAP]: deleted grid_data. grid_max now: %d.", grid_max); #endif } private: short location[50][50]; // a 50x50 array containing map information short grid_x; // northsouth short grid_y; // eastwest // Insanity check here short low_x_range; short high_x_range; short low_y_range; short high_y_range; // last used when? time_t last_used; // last time the system used it. // we will compare, and close after // 1 hour of non-use. // non popular zones will close every hour // while popular ones will remain opened. public: short get_location(short x, short y) { return location[x][y];} // gets the map-type void set_location(short x, short y, short type) { location[x][y] = type; } // sets the map-type short get_x() { return grid_x; } // gets the overall-x location short get_y() { return grid_y; } // gets the overall-y location void set_x(int x) { grid_x = x; } // sets the overall-x location void set_y(int y) { grid_y = y; } // sets the overall-y location void set_x_range(int low, int high) { low_x_range = low; high_x_range = high; } // sets the low/high ranges for x void set_y_range(int low, int high) { low_y_range = low; high_y_range = high; } // sets the low/high ranges for y void get_x_range(int &low, int &high) { low = &low_x_range; high = &high_x_range; } // get the ranges void get_y_range(int &low, int &high) { low = &low_y_range; high = &high_y_range; } // get the ranges time_t &get_time() { return last_used; } // returns last time the grid was accessed(roughly) void set_time() { extern time_t current_time; last_used = ¤t_time; } // sets the time the grid was used. }; ////////////////////////////////////////////////////////////////////////////////////// // lets see if our grid is loaded! bool grid_loaded(short x, short y) { // grid matching and correction // this is done so that our variables of +1/-1 work correctly. if(x > MAX_GRID) x = 0; if(y > MAX_GRID) y = 0; if(x < 0) x = MAX_GRID; if(y < 0) y = MAX_GRID; FOREACH(Lexi::List, grid_list, grid_iter) { grid_data *g = (*grid_iter); if(g->get_x() == x && g->get_y() == y) return true; } // no grid found, we aren't loaded, return false. return false; } ////////////////////////////////////////////////////////////////////////////////////// // remove a grid! void close_grid(grid_data *g) { if(InList(grid_list, g)) // are we in the list grid_list.remove_one(g); // remove us from it #ifdef GRID_DEBUGGING // why do we log it? And why do we display memory free'd, well, this is // allowing us to not only debug it, but keep track of the more frequented zones // for opening/closing, plus we want to keep track of the memory allocation, which is // why we made the grids, so its good to know. log_string("[WORLDMAP]: Grid X%d : Y%d has been closed. Memory free'd (%s)", g->get_x(), g->get_y(), size_conversion(sizeof(g))) #endif delete g; // delete the grid g = NULL; // null the variable. } ////////////////////////////////////////////////////////////////////////////////////// // close the grid void check_close(grid_data *g) { // hour not the same and the minute value matches. if(localtime(&g->get_time())->tm_hour != localtime(¤t_time)->tm_hour && localtime(&g->get_time())->tm_min > localtime(&g->get_time())->tm_min+1) { close_grid(g); // kill our grid return; } // greater then 10 minutes, we close it out. if(localtime(&g->get_time())->tm_min+10 < localtime(¤t_time)->tm_min) { close_grid(g); return; } return; } ////////////////////////////////////////////////////////////////////////////////////// // our updater, find the last time someone was within a grid. // set it, and check to see if it is time to close an open grid. // set this every 30 minutes or so. Updater(grid_update) { if(grid_list.empty()) return; #ifdef GRID_DEBUGGING log_string("[WORLDMAP]: Before: grid_update: grid_list contains %d grid(s). Approx mem used %s!", grid_list.size, (sizeof(grid_data *) * grid_list.size) ); #endif FOREACH(Lexi::List, grid_list, g_iter) { grid_data *g = (*g_iter); FOREACH(Lexi::List, wild_list, w_iter) { CHAR_DATA *c = (*w_iter); // we are in the grid! Set our time! if(c->grid_x == g->get_x() && c->grid_y == g->get_y()) { g->set_time(); } } // check to see if we have to close our grid check_close(g); } #ifdef GRID_DEBUGGING log_string("[WORLDMAP]: After: grid_update: grid_list contains %d grid(s). Approx mem used %s!", grid_list.size, (sizeof(grid_data *) * grid_list.size) ); #endif return; } ////////////////////////////////////////////////////////////////////////////////////// // we grab our grid-data, this is helpful grid_data *get_grid(short x, short y) { if(grid_list.empty()) return NULL; FOREACH(Lexi::List, grid_list, g_iter) { grid_data *g = (*g_iter); if(g->get_x() == x && g->get_y() == y) return g; } return NULL; } ////////////////////////////////////////////////////////////////////////////////////// // What grid are we within. Greatly helpful, fast to process too. // this will give us our grid-data, so we can pull any bits of information we need from // that as required. // // This will most likely be-used in the draw_map code, as the worldmap can see into other // ranges, and we will need the official grid-data's. grid_data *within_range(short real_x, short real_y) { FOREACH(Lexi::List, grid_list, g_iter) { grid_data *g = (*g_iter); int low_x, high_x; int low_y, high_y; // lets grab our ranges. carefully! g->get_x_range(&low_x, &high_x); g->get_y_range(&low_y, &high_y); // lets find out if we are within the ranges. if(real_x >= low_x && real_x < high_x) // we are within the x-range if(real_y >= low_y && real_y < high_y) // we are within the y-range return g; } ////////////////////////////////////////////////////////// // obviously, the grid wasn't found above, this is a problem // so to correct that, we know what grid we have to load // so we load it. Plain, simple. int f_x = find_grid_x(real_x); int f_y = find_grid_y(real_y); grid_data * g = load_grid(f_x, f_y); // lets return g, even if g is null, we return here, hopefully save face. return g; } ////////////////////////////////////////////////////////////////////////////////////// // Find out what the 'IN_X'/'IN_Y' values should be. // This function is used when players are 'sent' to the worldmap. void set_in_grid(CHAR_DATA *ch) { grid_data *g = within_range(ch->coordinates[COORD_X], ch->coordinates[COORD_Y]); // assume not on the map. if(!g) return; int x_low, x_high; int y_low, y_high; // lets get our value's g->get_x_range(&x_low, &x_high); g->get_y_range(&y_low, &y_high); int special_x = x_low; int special_y = y_low; int real_x = 0; int real_y = 0; ////////////////////////////////////////////////////////////////////// // Logic: 0-49 = possible locations, special_* is set to low-rating // we sift through, if the special_x match's the coord_x, then we've // found the right * location within the block. // here we go for(x = 0; x < 50; x++) { if(special_x == ch->coordinates[COORD_X]) { real_x = x; break; } special_x++; } for(y = 0; y < 50; y++) { if(special_y == ch->coordinates[COORD_Y]) { real_y = x; break; } special_y++; } // set the in_x/in_y variables ch->coordinates[IN_X] = real_x; ch->coordinates[IN_Y] = real_y; return; } ////////////////////////////////////////////////////////////////////////////////////// // LOCATING THE ACTUAL GRID: Since the world-map code is now broken into grids, but // our pre-existing linkage to the map doesn't know the grids, we have to be-able to // work with them, what means, we have to find the existing grids based on the x/y // value's, here we accomplish that, and attain our grids location ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// // here we find out what the x-grid number will be short find_grid_x(short real_x) { return real_x/50; // short won't keep the remainder } ////////////////////////////////////////////////////////////////////////////////////// // here we find out what the y-grid number will be short find_grid_y(short real_y) { return real_y/50; // short won't keep the remainder } ////////////////////////////////////////////////////////////////////////////////////// // the math on this is easy, the grid is 50x50, the map is 3000x3000 (thats 3600 grids) // we find our start location for x (up/down) and our y location (right/left). // so, grid 2 37 would load at 100 1850 on the map. // Based on the example above, ranges would be. // 100->150 ud // 1850->1900 lr // however, we do not want to overlap our loaded tiles, so, to stop this from occuring // we actualy only load to count -1., so 149, and 1899. technically its still valid :) // // The loading proceedure is fast, and simple. while it keeps track of the x/y variables, it // ultimatly creates a 'internal' set of x/y so that we do not break the overall location // within the grid. Crazy eh? // // If your math works properly with your grid alignment, you won't break any bounds // within the reading of the x/y location. if you think your not going to balance // out properly, add gdImageSX or gdImageSY checks within the loops, to ensure that // you do not exceed bounds, but we feel it isn't needed. // grid_data *load_grid(short x, short y) { int start_x = x * MAX_GRID_X; int start_y = y * MAX_GRID_Y; int end_x = start_x +50; // easy math :P stops at 49 thanks to alligators ;) int end_y = start_y +50; // easy math ;) FILE *jpgin = NULL; grid_data *g = NULL; // largest part on the memory is opening the file. // but even then, its not enough to hiccup on a modern computer. if((jpgin = fopen( WORLDMAP_FILE, "r" ) ) != NULL) { gdImagePtr im = gdImageCreateFromPng( jpgin ); // our map-loading code goes here, with the ranges inputted. g = new grid_data(); g->set_time(); // what is the overall grid x/y location (10x,37y)... g->set_x(x); g->set_y(y); // what ranges is this grid responsible for!? g->set_x_range(start_x, end_x); g->set_y_range(start_y, end_y); // on-to map loading. int internal_y = 0; // internal variable // we use set_location within the for-loop that will ensure here. for( short yx = start_y; yx < end_x; ++yx ) { int_internal_x = 0; for( short xy = start_x; xy < end_x; ++xy ) { internal_x++; int pixel = gdImageGetPixel( im, xy, yx ); short terr = get_sector_colour( im, pixel ); g->set_location(internal_x,internal_y,terr); } internal_y++; } // done like this because stranger things have happened! if(!InList(grid_list, g)) grid_list.push_front(g); else log_string("[WORLDMAP]: Grid X%d : Y%d already in the list! ODD!", g->get_x(), g->get_y()); fclose(jpgin); jpgin = NULL; gdImageDestroy( im ); // ensure the data is destroyed #ifdef GRID_DEBUGGING log_string("[WORLDMAP]: Grid X%d : Y%d has been opened. Memory Allocated (%s)", g->get_x(), g->get_y(), size_conversion(sizeof(g))) #endif } // Here we deal with a safety issue, so that we do not have any overall problems, we try // and ensuer that our maps are purged often if not being used, and when we load a new // grid, we assume that people are wandering on the map, causing the rapid opening/closing // of grids. if we have over open, then we check and see if nobody is in // the grids, and then close them if nobody is in them. if(grid_max > GRID_SAFETY) { FOREACH(Lexi::List, grid_list, grid_iter) { grid_data *g = (*grid_iter); if(localtime(&g->get_time())->tm_min+10 > localtime(¤t_time)->tm_min || (localtime(&g->get_time())->tm_hour < localtime(¤t_time)->tm_hour && localtime(&g->get_time())->tm_min < local_time(¤t_time)->tm_min)) { bool found = false; FOREACH(Lexi::List, wild_list, w_iter) { CHAR_DATA *w = (*w_iter); if(w->coordinates[GRID_X] == g->get_x() && w->coordinates[GRID_Y] == get_y()) found = true; } // was there someone within the grid? If not, lets close it! // even if the last-used time is out, we don't want to obscure // the map, so we protect ourselves, and ensure there are no outstanding issue's if(!found) { #ifdef GRID_DEBUGGING log_string("[WORLDMAP]: Grid X%d : Y%d exceeding grid safety. Will close!", g->get_x(), g->get_y())) #endif close_grid(g); } } } } return g; } ////////////////////////////////////////////////////////////////////////////////////// // movement: check coordinates for grid-loading changes. // here we go doing the hard stuff. We find out if the grid is loaded // if it isn't, we load it, since we are changing coordinates, we need // to ensure that everything is good. // set our global coordinates. coordinates[COORD_X] = temp_x; coordinates[COORD_Y] = temp_Y; // change our in_x variable appropriatly. switch(direction) { case DIR_NORTH: coordinates[IN_X]--; break; case DIR_SOUTH: coordinates[IN_X]++; break; case DIR_EAST: coordinates[IN_Y]++; break; case DIR_WEST: coordinates[IN_Y]--; break; case DIR_NORTHEAST: coordinates[IN_X]--; coordinates[IN_Y]++; break; case DIR_SOUTHWEST: coordinates[IN_X]++; coordinates[IN_Y]--; break; case DIR_NORTHWEST: coordinates[IN_X]--; coordinates[IN_Y]--; break; case DIR_SOUTHEAST: coordinates[IN_X]++; coordinates[IN_Y]++; break; } // Now we've changed our IN_X/IN_Y variables, lets see if // we are changing grids? or atleast close enough to load a new grid. // manage our local x/y coordinates if(coordinates[IN_X] == MAX_GRID_X-1) { if(!grid_loaded(coordinates[GRID_X+1], coordinates[GRID_Y])) load_grid(coordinates[GRID_X+1], coordinates[GRID_Y]); } if(coordinates[IN_Y] == MAX_GRID_Y-1) { if(!grid_loaded(coordinates[GRID_X], coordinates[GRID_Y+1])) load_grid(coordinates[GRID_X], coordinates[GRID_Y+1); } if(coordinates[IN_X] == 0) { if(!grid_loaded(coordinates[GRID_X-1], coordinates[GRID_Y])) load_grid(coordinates[GRID_X-1], coordinates[GRID_Y])); } if(coordinates[IN_Y] == 0) { if(!grid_loaded(coordinates[GRID_X], coordinates[GRID_Y-1])) load_grid(coordinates[GRID_X], coordinates[GRID_Y-1)); } // we change our standing within the world based on these coordinates // that we have manipulated, and ensure all is well. // world wrapping! // this will be changed to a switch statement so we can change more values // easily without any major issue's. such as the characters IN_X/IN_Y variables // as they change when moving grid-to-grid. Entirely based on the direction // are we moving off-the-grid! if(coordinates[IN_X] > 49 || coordinates[IN_X] < 0 || coordinates[IN_Y] > 49 || coordinates[IN_Y] < 0) { // Okay, we're exceeding the value's, so we have to correct these // because this is how we do it. So we do our grid-manipulation // to the best of our ability, change our coordinates, and re-align // our variables to match appropriatly. switch(direction) { case DIR_NORTH: coordinates[GRID_X]--; coordinates[IN_X] = 0; break; case DIR_SOUTH: coordinates[GRID_X]++; coordinates[IN_X] = 49; break; case DIR_EAST: coordinates[GRID_Y]++; coordinates[IN_Y] = 0; break; case DIR_WEST: coordinates[GRID_Y]--; coordinates[IN_Y] = 49; break; case DIR_NORTHEAST: coordinates[GRID_X]--; coordinates[GRID_Y]++; coordinates[IN_X] = 0; coordinates[IN_Y] = 0; break; case DIR_NORTHWEST: coordinates[GRID_X]--; coordinates[GRID_Y]--; coordinates[IN_X] = 0; coordinates[IN_Y] = 49; break; case DIR_SOUTHEAST: coordinates[GRID_X]++; coordinates[GRID_Y]++; coordinates[IN_X] = 49; coordinates[IN_Y] = 0; break; case DIR_SOUTHWEST: coordinates[GRID_X] +=1; coordinates[GRID_Y] -=1; coordinates[IN_X] = 0; coordinates[IN_Y] = 49; break; } } // world wrapping correction, ensuer we are in the right place // and not in an non-existant location. if(coordinates[GRID_X] > MAX_GRID-1) coordinates[GRID_X] = 0; if(coordinates[GRID_Y] > MAX_GRID-1) coordinates[GRID_Y] = 0; if(coordinates[GRID_X] < 0) coordinates[GRID_X] = MAX_GRID-1; if(coordinates[GRID_Y] < 0) coordinates[GRID_Y] = MAX_GRID-1; // everytime we move, we find our grid, there shouldn't be too many grids open // at any given time, so this should process really fast. // if it becomes an issue, then we'll just have to adjust to a minute management // of grids, so after minutes of non-use they close, apposed to hours. grid_data *g = get_grid(coordinates[GRID_X], coordinates[GRID_Y]); if(g) // we hope this isn't ever null g->set_time(); else { log_string("Character {{%s} moved in a NULL grid, safety required", IS_NPC(this) ? short_descr : name); if(IS_NPC(this)) extract_char(this, true); // NPC not needed, extract. else extract_char(this, false); // returns to their hometown safely! // just notify them with whats happened. Sendf("You have been returned to your hometown mystically, maybe this was a good thing.!\n\r"); } ////////////////////////////////////////////////////////////////////////////////////// // Give our immortals the ability to view our loaded grids and alittle bit of information // about them, this will help with understanding of the overall grid-mapping magic // that has been performed here today. COMMAND(cmd_gridlist) { BUFFER *output = new_buf(); char arg[MIL]; argument = one_argument(argument, arg); if(arg[0] == '\0' || !str_cmp(arg, "list")) { int cnt = 0; FOREACH(Lexi::List, grid_list, grid_iter) { grid_data *g = (*grid_iter); BufPrintf(output, "[%d]Grid: X%d Y%d: Last used: %s: Memory Used: %s.\n\r", cnt, g->get_x(), g->get_y(), grab_time_log(g->get_time(), size_conversion(sizeof(g))); cnt++; } BufPrintf("%d grids with memory allocated.", grid_max); page_to_char(buf_string(output), ch); free_buf(output); } // close the selected grid, if nobody is in the grid. if(!str_cmp(arg, "remove") || !str_cmp(arg, "delete") || !str_cmp(arg, "close")) { if(!is_number(argument)) { ch->Sendf("Syntax: gridlist close \n\r"); return; } int cnt = 0; FOREACH(Lexi::List, grid_list, grid_iter) { grid_data *g = (*grid_iter); if(cnt == atoi(argument)) { FOREACH(Lexi::List, wild_list, wild_iter) { CHAR_DATA *c = (*wild_iter); if(g->get_x() == c->coordinates[GRID_X] && g->get_y() == c->coordinates[GRID_Y]) { ch->Sendf(You cannot delete that grid, it is in use!"\n\r"); return; } } // close the grid close_grid(g); ch->Sendf("You have closed a worldmap grid.\n\r"); return } cnt++; } return; } ch->Sendf("Syntax: gridlist list\n\r"); ch->Sendf("Syntax: gridlist close \n\r"); return; } ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// // display changes, based on x/y range short coordinates[7]; // to replace x/y and cord variable in char_data. #define COORD_X 0 // global coords -- used to show the exact location on the worldmap #define COORD_Y 1 // global coords -- used to show the exact location on the worldmap #define GRID_X 2 // what x-grid -- our x-grid location #define GRID_Y 3 // what y-grid -- our y-grid location #define IN_X 4 // what x location within grid -- within the grid, what x location are we. #define IN_Y 5 // what y location within grid -- within the grid, what y location are we #define GRID_SAFETY 5 // how many grids can be open before we start to freak out and try to close them! #define MAX_GRID 60 // how many grids there are(60x60) #define MAX_GRID_X 50 // max X per grid (this ties into the MAX_GRID value) #define MAX_GRID_Y 50 // max Y per grid (this ties into the MAX_GRID value) ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// // We want to find out the size in KB, so we go the round-about way to determining the // size, and returning it. under 1024, we return bytes, over, we return kb, and so on. // this function is going to be used in many locations for memory management. const char *size_conversion(size_t size) { int math_controller = 1024; int new_size = size/math_controller; // in KB! (solong as size was over 1024, thi will return a kb) if(size <1024) { String str = "" << size << " bytes."; return str.c_str(); } // over 1024 in originaly size!? if(size >=1024) { // whoah, whoah whoa! We are in the megabytes (don't think we need to go anyhigher) if(new_size >= 1024) { String s = "" << new_size/1024 << " megabytes."; return s.c_str(); } // not a megabyte, not a byte, must be a kilobyte! String str = "" << new_size << " kilobytes."; return str.c_str(); } // if we ever reach this, I'll eat your hat! return "Unknown size.... SCARY!"; } ////////////////////////////////////////////////////////////////////////////////////// // Modified looking (test) int orig_grid_x = ch->coordinates[GRID_X]; int orig_grid_y = ch->coordinates[GRID_Y]; int curr_grid_x = orig_grid_x; int curr_grid_y = orig_grid_y; grid *gd = get_grid(orig_grid_x, orig_grid_y); for(start_x = ch->coordinates[IN_X]-10; start_x < ch->coordinates[IN_X]+10; start_x++) { // modify our grid locations as required if(start_x <0) curr_grid_y--; if(start_x>49) curr_grid_x++; // now we loop through our Y variable, and get our location for(start_y = ch->coordinates[IN_Y]-7; start_y < ch->coordinates[IN_Y]+7; start_y++) { if(start_y>49) curr_grid_y++; if(start_y <0) curr_grid_y--; // make sure the grids are loaded that are required to be loaded. if(curr_grid_x == MAX_GRID_X-1) { if(!grid_loaded(curr_grid_x+1, curr_grid_y)) load_grid(curr_grid_x+1, curr_grid_y); } if(curr_grid_y == MAX_GRID_Y-1) { if(!grid_loaded(curr_grid_x, curr_grid_y+1)) load_grid(curr_grid_x, curr_grid_y+1); } if(curr_grid_x == 0) { if(!grid_loaded(curr_grid_x-1, curr_grid_y)) load_grid(curr_grid_x-1, curr_grid_y); } if(curr_grid_y == 0) { if(!grid_loaded(curr_grid_x, curr_grid_y-1)) load_grid(curr_grid_x, curr_grid_y-1); } // we draw our room (simple pleasures) // start_x and and start_y will be manipulated in draw_room based on curr_grid vs orig_grid. // if the grid's aren't the same, it will change based on how far beyond the limits. // so -7 translates into 43, where as 57 turns into 6, simple mathmatics to display the appropriate // information. // // gd is used to get the location and return its drawing point. // if the orig_grid's dont' match the curr_grid, we switch to the appropriate grid // but so-long as the orig_grid is the curr grid, we don't have to lookup the grid // location, saving memory and cpu usage everytime we look. <-- important! // // this looks like it could verywell work, so-long as the modifications are made to // draw_room that check the grids, and start_x/y variables and switch them around as // required. draw_room(ch, gd, orig_grid_x, orig_grid_y, curr_grid_x, curr_grid_y, start_x, start_y); } }