/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // simple maze-generation! // Its really simple, if a room is flagged as a 1, it is enterable, if it is beside another room flagged with a 1 // that too becomes linked for movement. If the room is a 2, it checks for up/down, in-which case, if it finds a 1 or a 2 // above or below it in Z value's, then movement up or down is available, (or both) // Maze stems off nicely, if it reaches a point where it maxes out failed attempts, it moves to a new random location // and starts generating another map from there. In hopes that they infact-meet up somewhere. // if it fails 10 times in a row, it aborts the generation where it is, logs it, and lets the maze work. // if the maze at this point in time is completely broken, the player who auto-genned it will know, as it wont' be // very usable. // if the maze gen fails, broken becomes true, and it notifies the player that with it being broken, they *CANNOT* // complete the maze, and will have to re-enter, and hope that the next one does not fail. // // The generation here is quick, and painful. We want to ensure that the map is carefully designed as-to not be too // generic. Which is why the start-z/y/x exist, making each map alittle different from the last, based entirely off of // how it selects the start-point, and the random direction it chooses to go. // // Memory intensity: This system is very memory intensive, massive maps, being created on the fly, constantly. This will // need to have some-level of management, such as you cannot create a maze if you've recently been in one (like 30 minutes) // stopping players from entering/exiting until they get the 'type' of map they want. Only caveat for this is if the maze // is marked as broken. In that case, don't flag them. // // On topic of memory intensity. To avoid massive cpu spiking, it would be recommended that the cooldown between mazes // per player be a min of half an hour. // // *Note: If flagging a player, if they quit the maze before completion, or if they complete it, the wait time should // always be the same. Infact, quitters should most likely be lagged longer, just because they were part of it. // // If an entire group quits, at that point in time, the maze is deleted from memory. Upon completion of the maze // all players will be sent back to the summoning stone for the maze, including the person who entered the maze itself. // summon stone should be approx 15/20 x/y coords away from a given maze. // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// Lexi::Listmaze_list; // keep track of our globally used mazes! #define ROOM_VNUM_MAZE 4 // Like wilderness. Makes life easier. class maze_data { public: maze_data() { sz = 0; sy = 0; sx = 0; actual_size = 0; mx = 0; broken = false; level = 0; treasure = 0; repop = 0; rare_drop = 0; elite = false; monsters_left = 0; bosses_left = 0; started = false; puzzle_start = 0; puzzles_left = 0; } ~maze_data() { free_string(name);} private: vnum_t repop; vnum_t rare_drop; short maze[30][250][250]; short puzzle[30][250][250]; // puzzles to go into the next room! short summon_x; // the mazes X summon-stone location short summon_y; // the mazes Y summon-stone location short sz; // Start Z loc short sx; // Start X loc short sy; // Start Y loc short actual_size; // actual size short mx; // max size (should be on-par with actual-size) short level; short treasure; short monsters_left; // how many monsters are left (after each kill) short bosses_left; // how many bosses are left! short mob_start; // original mob counts short boss_Start; // original boss counts short puzzle_start; // how many puzzles will be in the maze short puzzles_left; // how many puzzles are left! short players_in_maze; // how many players are in the maze! const char *maze_name; // name of the maze/dungeon your in! bool elite; bool broken; bool maze_started; public: short get_start_x() { return sx; } short get_start_y() { return sy; } short get_start_z() { return sz; } short get_summon_x() { return summon_x; } short get_summon_y() { return summon_y; } short get_players() { return players_in_maze; } short get_puzzles() { return puzzles_left; } short get_maze(short z, short x, short y) { return maze[z][x][y]; } // (0-1-2 (hopefully it doesn't exceed the max)) short get_size() { return actual_size; } short get_max() { return mx; } short get_level() {return level; } short get_monsters() { return monsters_left; } short get_bosses() { return bosses_left; } const char *get_name() { return maze_name; } bool get_broken() { return broken; } bool started() { return maze_started; } bool get_elite() { return elite; } bool get_broken() { return broken; } void set_start() { maze_started = true; } void kill_mob(CHAR_DATA *grp) { // done on raw-kill if(monsters_left > 0) { monsters_left--; if(monsters_left == 0) { // insert leaderboard here (for ALL group members) if(elite) { // secondary leaderboard for elite monsters } } } return; } void kill_boss(CHAR_DATA *grp) { // done on raw-kill if(bosses_left > 0) { bosses_left--; if(bosses_left == 0) { // clearing dungeons = hardcore. // insert leaderboard here (for ALL group members) if(elite) { // secondary leaderboard for elite boss's } } } return; } ///////////////////////////////////////////////////////////////////////////////////////// // Generates a puzzle so that players cannot pass until they have figure'd it out. // it can be anything from a saying, phrase, special command issue'd. // the puzzles are selected from lua scripts. // // lua-progs that are puzzles, their description is used not to describe its functionality // but should infact, be a puzzle, riddle, something for the players to figure out. // // puzzle's are marked as staff 3 in the lua. // // Puzzles are what make the the maze's worth-while. Solving puzzles helps progress through // the map, aswell as get bonus's, leaderboard titles. And all sorts of other fun-ness. // Note, after-awhile, the puzzles will be, well, easy, as they will start repeating, which // means we will have to write hundreds of puzzles, if not thousands to make this worth-while. // but hey, thats no worries, as it is *WELL* worth it. // void generate_puzzle(short z, short y, short z) { // puzzle will be the vnum of the puzzle chosen. } ///////////////////////////////////////////////////////////////////////////////////////// // Spawn a mobile at position z/x/y, roughly mob_level, from repop_vnum, if marked elite // we increase stats. void spawn_mobile(short z, short x, short y, short mob_level, vnum_t repop_vnum, bool elite) { // 55 % chance of spawning a mobile. // with the size of mazes, (being up to 530 rooms) it seems to reason that // nomatter what, mobs are going to spawn. And be quite nasty! // This should allow us to cap out on our mobs/bosses easily enough, most likely // with room-to-spare, but thats why we let the mobs wander! if(number_percent() > 55) return; if(mob_start == monsters_left) return; WILD_MOB *wmob = find_wild_pattern(repop_vnum); if(!wmob) return; int spawn = number_range(0,10); MOB_INDEX_DATA *mob = get_mob_index(wmob->vnum_list[spawn]); if(!mob) return; CHAR_DATA *instanced = create_mobile(mob); // move to the coordinates instanced->cord[CORD_Z] = z; instanced->cord[CORD_X] = x; instanced->cord[CORD_Y] = y; //////////////////////////////////////////////////////////////////////// // Ensure that the instanced mobs will group up against the enemies. instanced->group = (repop_vnum+1); // should ensure their groupie-ness! char_to_room(get_room_index(ROOM_VNUM_MAZE), instanced); // move into the maze // set the level instanced->level = number_range(mob_level-5, mob_level+5); if(instanced->level < 1) instanced->level = 1; // We want *ALL* of the mobs to be extremely hostile, so we go aggressive for good measure. if(!IS_SET(instanced->act, ACT_AGGRESSIVE)) SET_BIT(instanced->act, ACT_AGGRESSIVE); // Let the mobiles wander, this should help spread out the beasties nicely // especially if they all spawn at the very start of the maze, and not at the end of its // generation. Eventually, the mobs should walk all over the place :) if(IS_SET(instanced->act, ACT_SENTINEL)) REMOVE_BIT(instanced->act, ACT_SENTINEL); // set our boss's. if(instanced->level == mob_level+5 && boss_left != boss_start) { if(!IS_SET(instanced->act, ACT_BOSS)) SET_BIT(instanced->act, ACT_BOSS); bosses_left++; } // increment our monsters. monsters_left++; // this fixes the stats with the new level and if a boss, with the new boss flag. correct_mobile(instanced); // equip the monsters with random gear so that it is worth while to kill these // creatures. *Note: If the mobs have a rare drops on them, this gets even // more interesting, as on kill, they risk creating a nice random object. maze_output(instanced); return; } ///////////////////////////////////////////////////////////////////////////////////////// // This will populate the maze with mobs from the repop_vnum, which loads the spawns from // wild_mob list, normally reserved for patterns, is now being used here to help populate // the random maze. // mob_level assigns the approximate level of the mobs // repop_vnum pulls the respawned mobs from the pattern maps. // treasure-level is the approximate worth of the treasure (1-10, 10 being best) // rare_drop_vnum is the vnum of the object that will be the random-drop on the boss // mob in the dungeon. // elite means the players selected elite mobs when they summoned in their friends. // this means mobs are even stronger, and smarter. // aswell as improving the treasure (even if it was a 10, they get better) ///////////////////////////////////////////////////////////////////////////////////////// // Maze generation as easy as maze->generate_maze(20,100,88, 1800, 325, 88, 10, 7, 25, 3, 5, 88, true, "Jaspers Dungeon of Territorial Doom"); // we randomly generate the maze! // this is fun, smart, and overall, exciting. void generate_maze(short z, short x, short y, short sum_x, short sum_y, short mob_count, short boss_count, puzzle_count, short mob_level, vnum_t repop_vnum, short treasure_level, vnum_t rare_drop_vnum, bool elite, const char *name) { int xy, yy, zy; int max_size = 0; spawn_mobile(current_z, current_x, current_y, mob_level, repop_vnum, elite); //////////////////////////////////////////////////////////////////////// // x/y/z variables just allocate our max-size(in total) they don't manage // how far east/west/up/down/north/south things will go. // those are maxed anyways. However, we always ensure we are more then // taken-care of with our overall layout. // ensure our maximums are not exceeded if(x > 250) x = 250; if(y > 250) y = 250; if(z > 30) z = 30; maze_name = str_dup(name); mob_start = mob_count; boss_start = boss_count; puzzle_start = puzzle_count; //////////////////////////////////////////////////////////////////////// // zero the map first. (safe practice before having fun) for(xy = 0; xy < x; xy++ ) { for(yy = 0; yy < y; yy++ ) { for(zy = 0; zy < z; zy++ ) { maze[x][y][z] = 0; } } } int counter = 0; int start_room_x, start_room_y, start_room_z; summon_x = sum_x; summon_y = sum_y; max_size == (x+y)+z; // z feels left out ;) (making a joke with code) mx = max_size; // set our 'supposed' to be size. start_room_x = number_range(0,x); start_room_y = number_range(0,y); start_room_z = number_range(0,z); //////////////////////////////////////////////////////////////////////// // make that room in the maze exist! maze[start_room_z][start_room_x][start_room_y] = 1; // our start-room in the maze! (MUAHAHAHAHAHA) sz = start_room_z; sx = start_room_x; sy = start_room_y; // so we know where we are in the map! int current_x = start_room_x; int current_y = start_room_y; int current_z = start_room_z; int attempt = 0; int attempt_maxed 0; // Lets make the maze! for(;;) { int direction = number_range(0,6); bool failed = false; short jump_back = false; // map is done! Reached our max-size! Time to populate! (if we managed to breach it // with the jumpback code, then we say whatever, and carry on peacefully) if(count >= max_size) break; if(attempt == 6) { // failed 6 times, that means all exits around the person, are filled. // and it wouldn't progress anyfurther. (or it just had bad luck // randomizing the same number, but, to prevent a potential lockup // we move the current_x/y/z locations to a random spot, and hope // it manages to link up with the rest. Else wise, the dungeon // could be pooched! attempt = 0; // reset attempt count! attempt_maxed++; // maxed our attempt 6 times // now we count that. // if we have a massive glitch // we'd like to know! if(attempt_max == 10) // we maxed our attempts 10 times { // now we do some fancy logging when this happens. broken = true; // yup, its broken. log_string("[MAZE GEN] %s reached max generation attempts, with a count of %d of %d", __PRETTY_FUNCTION__, count, max_size); break; // we don't want to lockup } // so we break the generation // and hope that all is well. current_x = number_range(0,x); current_y = number_range(0,y); current_z = number_range(0,z); } // here is where we get.... creepy! jump_back = number_range(0,11); /////////////////////////////////////////////////////////////////////////////////////////////////////// // Jumping back, this got the name because it creates stretch's off of the main-path and then jumps // back to the path, and carries on with the normal map generation. Sick little plan. // This will be interesting. // *Note: east/west/north/south directions care if a room is already-set, and won't move into it. // however, up/down don't care, and will move into it, and change the exits to 2, so it knows it can // go up or down. // the bonus stretches can head off up to 12 rooms (including the original off-shoot) // the extra stretches are a percent chance to happen, they do not always occur when jump-back takes // place, this should help create little balls of extra-joy. /////////////////////////////////////////////////////////////////////////////////////////////////////// if(jump_back == 4) { // why 4? Why not! short where = number_range(0,3); short max_p = number_range(3,11); // change in x/y/z location. switch(where) { // NORTH/SOUTH MANIPULATION default: case 0: if(number_range(0,1) == 1) { current_x++; if(current_x != 250) { if(maze[current_z][current_x][current_y] == 0) { maze[current_z][current_x][current_y] = 1; count++; if(number_percent() > number_range(78,100)) for(int p = 0; p < max_p; p++) { if(current_x+p < 250) { if(maze[current_z][current_x+p][current_y] == 0) { maze[current_z][current_x+p][current_y] = 1; count++; spawn_mobile(current_z, current_x+p, current_y, mob_level, repop_vnum, elite); } } } } current_x--; // go back to our original x location and continue from there! } else { current_x++; if(current_x != 250) { if(maze[current_z][current_x][current_y] == 0) { maze[current_z][current_x][current_y] = 1; count++; if(number_percent() > number_range(78,100)) for(int p = 0; p < max_p; p++) { if(current_x-p > -1) { if(maze[current_z][current_x-p][current_y] == 0) { maze[current_z][current_x-p][current_y] = 1; count++; spawn_mobile(current_z, current_x-p, current_y, mob_level, repop_vnum, elite); } } } } current_x--; // go back to our original x location and continue from there! } // EAST/WEST CHANGING case 1: if(number_range(0,1) == 1) { current_y++; if(current_y != 250) { if(maze[current_z][current_x][current_y] == 0) { maze[current_z][current_x][current_y] = 1; count++; if(number_percent() > number_range(78,100)) for(int p = 0; p < 5; max_p++) { if(current_y+p < 250) { if(maze[current_z][current_x][current_y+p] == 0) { maze[current_z][current_x][current_y+p] = 1; count++; spawn_mobile(current_z, current_x, current_y+p, mob_level, repop_vnum, elite); } } } } current_y--; // go back to our original x location and continue from there! } else { current_y++; if(current_y != 250) { if(maze[current_z][current_x][current_y] == 0) { maze[current_z][current_x][current_y] = 1; count++; if(number_percent() > number_range(78,100)) for(int p = 0; p < max_p; p++) { if(current_y-p > -1) { if(maze[current_z][current_x][current_y-p] == 0) { maze[current_z][current_x][current_y-p] = 1; count++; spawn_mobile(current_z, current_x, current_y-p, mob_level, repop_vnum, elite); } } } } current_y--; // go back to our original x location and continue from there! } /////////////////////////////////////////////////////////////////////////////////////////// // UP/DOWN MANIPULATION.. THIS *IS* VITAL AND SCARY AT THE SAME TIME! case 2: if(number_range(0,1) == 1) { current_z++; if(current_z != 30) { maze[current_z][current_x][current_y] = 2; count++; if(number_percent() > number_range(78,100)) for(int p = 0; p < max_p; p++) { if(current_x+p < 30) { maze[current_z+p][current_x][current_y] = 2; count++; spawn_mobile(current_z+p, current_x, current_y, mob_level, repop_vnum, elite); } } current_z--; // go back to our original x location and continue from there! } else { current_z++; if(current_z != 30) { maze[current_z][current_x][current_y] = 2; count++; if(number_percent() > number_range(78,100)) for(int p = 0; p < max_p; p++) { if(current_z-1p > -1) { maze[current_z-p][current_x][current_y] = 2; count++; spawn_mobile(current_z+p, current_x, current_y, mob_level, repop_vnum, elite); } } current_z--; // go back to our original x location and continue from there! } break; } } /////////////////////////////////////////////////////////////////////////////////////////////////////// // Continuing the insanity here: // Our now official goal, if we did, or did not stem off, is to now change our direction, and parse // through and hopefully generate a really cool new design/layout for the overall map // because of the treeing system above, we hope to see many sub-paths carry off from the original // path.. Perfect to hide evil little minions in them. /////////////////////////////////////////////////////////////////////////////////////////////////////// switch(direction) { default: case DIR_NORTH: // cannot go any-further north! if(current_x+1 == 250){ failed = true; break; } if(maze[current_x][current_x+1][current_y] == 0) { current_x++; maze[current_z][current_x][current_y] = 1; spawn_mobile(current_z, current_x, current_y, mob_level, repop_vnum, elite); // here we go with a chance to make an extra direction (helps space-out the map) if(current_x+1 == 250) break; // not failed because we did increment. if(number_range(0,5) == number_range(0,5)) { if(maze[current_z][current_x+1][current_y] == 0) { current_x++; count++; // so we keep our counts properly. maze[current_z][current_x][current_y] = 1; } } } else { failed = true; } break; case DIR_SOUTH: // cannot go any-further north! if(current_x-1 == -1){ failed = true; break; } if(maze[current_x][current_x-1][current_y] == 0) { current_x--; maze[current_z][current_x][current_y] = 1; spawn_mobile(current_z, current_x, current_y, mob_level, repop_vnum, elite); // here we go with a chance to make an extra direction (helps space-out the map) if(current_x-1 == -1) break; // not failed because we did increment. if(number_range(0,5) == number_range(0,5)) { if(maze[current_z][current_x-1][current_y] == 0) { current_x--; count++; // so we keep our counts properly. maze[current_z][current_x][current_y] = 1; } } } else { failed = true; } break; case DIR_EAST: // cannot go any-further north! if(current_y+1 == 250){ failed = true; break; } if(maze[current_z][current_x][current_y+1] == 0) { current_y++; maze[current_z][current_x][current_y] = 1; spawn_mobile(current_z, current_x, current_y, mob_level, repop_vnum, elite); // here we go with a chance to make an extra direction (helps space-out the map) if(current_y+1 == 250) break; // not failed because we did increment. if(number_range(0,5) == number_range(0,5)) { if(maze[current_z][current_x][current_y+1] == 0) { current_y++; count++; // so we keep our counts properly. maze[current_z][current_x][current_y] = 1; } } } else { failed = true; } break; case DIR_WEST: // cannot go any-further north! if(current_y-1 == -1){ failed = true; break; } if(maze[current_z][current_x][current_y-1] == 0) { current_y++; maze[current_z][current_x][current_y] = 1; spawn_mobile(current_z, current_x, current_y, mob_level, repop_vnum, elite); // here we go with a chance to make an extra direction (helps space-out the map) if(current_y-1 == 250) break; // not failed because we did increment. if(number_range(0,5) == number_range(0,5)) { if(maze[current_z][current_x][current_y-1] == 0) { current_y--; count++; // so we keep our counts properly. maze[current_z][current_x][current_y] = 1; } } } else { failed = true; } break; case DIR_UP: // cannot go any-further north! if(current_z+1 == 30){ failed = true; break; } // up/down don't care if the value is 0, it creates a up/down link anyways! // helps create a random-link. current_z++; spawn_mobile(current_z, current_x, current_y, mob_level, repop_vnum, elite); maze[current_z][current_x][current_y] = 2; // 2 means it links up! // here we go with a chance to make an extra direction (helps space-out the map) if(current_z+1 == 30) break; // not failed because we did increment. if(number_range(0,5) == number_range(0,5)) { current_z++; count++; // so we keep our counts properly. maze[current_z][current_x][current_y] = 2; } break; case DIR_DOWN: // cannot go any-further north! if(current_z-1 == -1){ failed = true; break; } // up/down don't care if the value is 0, it creates a up/down link anyways! // helps create a random-link. current_z--; spawn_mobile(current_z, current_x, current_y, mob_level, repop_vnum, elite); maze[current_z][current_x][current_y] = 2; // 2 means it links up or down (or both) // here we go with a chance to make an extra direction (helps space-out the map) if(current_z+1 == 30) break; // not failed because we did increment. if(number_range(0,5) == number_range(0,5)) { current_z--; count++; // so we keep our counts properly. maze[current_z][current_x][current_y] = 2; } break; } if(!failed) count++; else attempt++; // saves us from infinate loop! } actual_size = count; maze_list.push_back(this); } }; //////////////////////////////////////////////////////////////////////////////////////// // display some minor information about the maze in question. COMMAND(do_maze) { if(ch->in_maze == NULL) { ch->Sendf("But your not in a maze!\n\r"); return; } maze_data *maze = ch->in_maze; if(NullString(argument)) { ch->Sendf("Syntax: maze \n\r"); return; } if(!str_cmp(argument, "summon")) { if(maze->started()) { ch->Sendf("You cannot summon once the maze has started!\n\r"); return; } // pull the groupies to the maze from the summoning stone. FOREACH(Lexi::List, player_list, player_iter) { CHAR_DATA *groupie = (*player_iter); if(is_same_group(ch, groupie) && groupie->x == maze->summon_x && groupie->y == maze->summon_y) { if(groupie->recent_maze != 0) continue; char_from_room(groupie); char_to_room(get_room_index(4), ch); groupie->cord[CORD_X] = ch->cord[CORD_X]; groupie->cord[CORD_Y] = ch->cord[CORD_Y]; groupie->cord[CORD_Z] = ch->cord[CORD_Z]; groupie->in_maze = ch->in_maze; do_look(groupie, "auto"); in_maze->get_players()++; // increment the players (crazy technique here) } } } if(!str_cmp(argument, "start")) { if(maze->started()) { ch->Sendf("The maze has already been started! Hurry up!\n\r"); return; } maze->set_start(); do_function(ch, &do_gtell, "Maze started up! Prepare yourselves %s!", ch->rebel ? "Comrades" : "my good chums"); return; } // just know alittle about your dungeon of ultimate doom! if(!str_cmp(argument, "info")) { ch->Sendf("\n\r+---------------------[ %s%s ]---------------------+\n\r", maze->get_broken ? "[Broken] " : "", maze->get_name()); ch->Sendf(" [ %s ] [Level: %d]", maze->get_elite() ? "Elite" : "Normal", maze->get_level()); ch->Sendf(" [Bosses Left: %d]", maze->get_bosses()); ch->Sendf(" [Monsters Left: %d]", maze->get_monsters()); ch->Sendf(" [Puzzles Left: %d]", maze->get_puzzles()); return; } if(!str_cmp(argument, "quit")) { do_function(ch, &do_gtell, "Sorry %s, but I have to bail! %s.", ch->rebel ? "my friends" : "guys", ch->rebel ? "Cheers" : "Good-bye"); // is it really that simple? Yes, because we aren't modifying the x/y locations, it checks to see if your in_maze, if so. // it reads and does everything differently! This will restore you back to the worldmap. where you entered (summoning stone) char_from_room(ch); char_to_room(get_room_index(3), ch); // wilderness! // send them back to the summon stone! ch->x = in_maze->get_summon_x(); ch->y = in_maze->get_summon_y(); ch->in_maze = NULL; ch->recent_maze = 35; // 35 ticks until you can enter a new one // you cannot be summoned in unless you have 0 return; } ch->Sendf("What option is that!\n\r"); return; } //////////////////////////////////////////////////////////////////////////////////////// // Just display what mazes are open. Since they are linked from the world-map // and are instanced *PER* group, we should see plenty of these open. COMMAND(cmd_mazelist) { BUFFER *output; int count = 0; output = new_buf(); BufPrintf(output, "Mazes currently in-use!\n\r"); FOREACH(Lexi::List, maze_list, maze_iter) { maze_data *maze; BufPrintf(output, "| %3d: | Start: z%5d x%5d y%5d | Max size: %5d | Actual Size: %5d | Broken: %5s|\n\r", count, maze->get_start_z(), maze->get_start_x(), maze->get_start_y(), maze->get_max(), maze->get_broken() ? "Yes" : "No"); count++; } BufPrintf(output, "There are currently %d mazes in-use!\n\r", count); page_to_char(buf_string(output), ch); free_buf(output); return; }