flare-engine-next
flare-engine-next copied to clipboard
Random map generation
This would make the game more dynamic and require less modders resources.
Some links.
http://opengameart.org/forumtopic/flare-map-generator
Looks like this one is fully based on Macros, done as OpenDocument SpreadSheet, and requires a lot of hand work.
https://github.com/Paul-Wortmann/Frost_and_Flame
Manual sayes, it has 4 Random dungeon generation algorithms. @Paul-Wortmann Is this already implemented, can you share/integrate that stuff?
I am quite interested in working on random dungeon generation. Yeah, I developed 4 different generators for Frost and Flame, you are welcome to use the code if you like. I think it could do with a lot of improvements though. The only thing that is holding me back is exactly how to integrate a random dungeon generator into Flare, I'm sure I could hack some code to get it working and integrated, but would rather do it well from the start. I think if I find the right way to integrate with Flare a random dungeon generator, I will just rewrite the code to generate dungeons from scratch for Flare. I could make a stand alone generator that spits out flare map files, actually maybe that would be a good place for me to start. If I get a chance tonight I will write some code, and let you know what I come up with. Basically a function such as: bool generateMap(mapType *mapPointer, int MAP_CAVE, char *fileName);
Ok, I pulled the generation algorithms out of Frost and Flame, and added a qt frontend, I can generate maps, but still have to write a function to convert my intermediate map data to Flare map format and then export, I wrote a basic exporter quick to test with, but the output data is not in flare format yet.
Have the beginnings of a basic exporter, just need to map the correct tiles to the map.
As for the collision layer, I am not 100% sure as to the values 0-4?
@Paul-Wortmann Nice work so far. For collision tiles, here's what I understand:
- 0 is an empty tile, so no collision is here
- 1 and 3 (red in Tiled) are for walls and other tall objects. All entities (except intangible) and hazards are blocked by these. I'm not clear on the difference between 1 and 3, since they both seem to do the same thing.
- 2 and 4 (blue in Tiled) are for pits and short objects. They block walking entities, but allow flying entities and hazards to pass over them. Again, I'm not sure about the difference between 2 and 4.
3 and 4 are invisible on the mini map if I recall correctly.
@Paul-Wortmann I have finished map exporting API. You can get it from flare-engine repo. It's fully working
@Paul-Wortmann any new work planned?
I just stumbled upon this interesting blog post on that topic: http://blog.runevision.com/2015/08/procedural-world-potentials-simulation.html
@igorko I do intend to continue work on this, currently life has been getting in the way. I left my previous job of five years this year and have taken on two different jobs to replace the one job, currently working two jobs 6am till 10pm 6 days a week. I will work on it more when time permits, I am still very much interested in procedural content generation and have not abandoned working on this, just have very little free time at the moment, my apologies. @mquinson Thanks for sharing the link, interesting read. :) I learned a lot about this topic by reading and implementing algorithms found on this site: http://www.roguebasin.com/index.php?title=Articles#Map
@pennomi had a good idea for random map generation that I think is worth considering, especially as it keeps the complexity of random map generation outside of the core engine code, and makes it possible to implement random map generation tools in any language.
Below follows the comment of @pennomi https://github.com/clintbellanger/flare-engine/issues/1484#issue-213489179
The recent comments on another thread got me thinking about procedural map generation. (Specifically where @clintbellanger mentions that it makes sense for external generator tools to be a separate codebase from the engine because each tileset is unique in what rules it would have to follow.)
One thought is that we could provide a hook in the engine to subprocess out to an external generator program. That would provide a whole lot of flexibility to the implementer of the program because it could be written in any language and could potentially be packaged as a mod. We'd just need to build out a consistent command-line interface with inputs and outputs, so it should be pretty simple. (For example, the program would take in some sort of map_id and game_seed and would return the new map_filepath) The downside is that there would be no way to guarantee the dependencies to run the subprocess exist on the target system.
A secondary thought would be to provide a very low-level LUA interface for generating maps and build that into the engine directly. It could provide tileset parsing, asset loading, and simple functions to add each tile/event/creature/etc. Seems like this would be a lot of work though. Plus not everyone enjoys LUA.
Feel free to close this issue; I highly doubt random maps should be built into the v1.0 engine. Just wanted to get my thoughts down and see if anyone was thinking along the same lines.
I'll also include my initial reply from https://github.com/clintbellanger/flare-engine/issues/1484#issuecomment-298260397
@pennomi I would warmly welcome the first approach you mentioned, with adding a hook in the engine for allowing external processes to generate maps and return a file path. This would be absolutely great, allow a lot of flexibility and still keep the engine simple. The principle of separation of concerns is powerful, and should be leveraged when possible.
I would much rather see that the first approach was taken, rather than requiring the map generation tool to be written in LUA to integrate with the engine.
If you wish to flesh out the command line interface in more detail, I'd be very happy to discuss those aspects.
Cheers /u
Should we continue this discussion in this issue or over at https://github.com/clintbellanger/flare-engine/issues/1484?
https://github.com/Paul-Wortmann/Random_Map_Generator
Hi,
I downloaded and compiled Paul's Random Map Generator. Pretty interesting.
I'm wondering if I there could be a different approach to the problem here. I'm thinking about how the The Halls of Infinity work in the Empyrean Campaign. Granted, after a few playthroughs, you begin to memorize the maps, but if one made a mod with the same mechanic and say, 100 different map .txt files to choose randomly from, then that would be close enough to the "feeling" of a truly randomly generated map, wouldn't it?
The problem would then be how to generate those .txt files quickly. I'm thinking that maybe I could take a shot at Tiled and see if I can come up with something, maybe through custom properties or through tiled objects. The advantage of trying to do this with Tiled would be that the walls can be generated quickly facing the right directions and with their corresponding collision tiles using automapping.
While I'm looking into the option in Tiled, I also wrote a (stylistically horrible but working) function that generates a mini-dungeon (more like a room actually, since it's 10 x 8 tiles). So far, the only randomness that it has is in the floor tiles. Any suggestions? (BTW: don't pay attention to the includes, I forgot to remove the ones that I'm not using)
#include <iostream>
#include <fstream>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <random>
#include <array>
#include "map_generator.h"
using namespace std;
void MapTextFile () {
//Name of the map's text file
ofstream mapfile;
mapfile.open ("outputmap.txt");
//Header
mapfile << "[header]" << endl;
mapfile << "width=8" << endl;
mapfile << "height=10" << endl;
mapfile << "tilewidth=64" << endl;
mapfile << "tileheight=32" << endl;
mapfile << "orientation=isometric" << endl;
mapfile << "background_color=0,0,0,255" << endl;
mapfile << "hero_pos=" << endl;
mapfile << "music=music/dungeon_theme.ogg" << endl;
mapfile << "tileset=tilesetdefs/tileset_dungeon.txt" << endl;
mapfile << "title=New Random Map" << endl;
mapfile << endl;
//Tilesets
mapfile << "[tilesets]" << endl;
mapfile << "tileset=../../../tiled/dungeon/tiled_collision.png,64,32,0,0" << endl;
mapfile << "tileset=../../../tiled/dungeon/tiled_dungeon.png,64,128,0,0" << endl;
mapfile << "tileset=../../../tiled/dungeon/set_rules.png,64,32,0,0" << endl;
mapfile << "tileset=../../../tiled/dungeon/tiled_dungeon_2x2.png,128,64,0,16" << endl;
mapfile << "tileset=../../../tiled/dungeon/door_left.png,64,128,-16,-8" << endl;
mapfile << "tileset=../../../tiled/dungeon/door_right.png,64,128,16,-8" << endl;
mapfile << "tileset=../../../tiled/dungeon/stairs.png,256,256,0,48" << endl;
mapfile << endl;
//Background layer
mapfile << "[layer]" << endl;
mapfile << "type=background" << endl;
mapfile << "data=" << endl;
srand((unsigned int)time(0));
for (int row = 0; row < 10; row++) {
for (int col = 0; col < 8; col++) {
mapfile << 16+(rand() %4) << ","; //id of the floor tiles: 16-19
}
mapfile << endl;
}
mapfile << endl;
//Object layer
mapfile << "[layer]" << endl;
mapfile << "type=object" << endl;
mapfile << "data=" << endl;
//North corner wall id: 77
//NE side wall id: 65
//NW side wall id: 68
//West corner wall id: 76
//East corner wall id: 78
//SE wall id: 66
//SW wall id: 67
//South corner wall id: 79
int rowsandcolswalls[10][8] = {{77,65,65,65,65,65,65,78},{68,0,0,0,0,0,0,66},{68,0,0,0,0,0,0,66},{68,0,0,0,0,0,0,66},{68,0,0,0,0,0,0,66},{68,0,0,0,0,0,0,66},{68,0,0,0,0,0,0,66},{68,0,0,0,0,0,0,66},{68,0,0,0,0,0,0,66},{76,67,67,67,67,67,67,79}};
for (int row = 0; row < 10; row ++) {
for (int col = 0; col < 8; col++) {
mapfile << rowsandcolswalls[row][col] << ",";
}
mapfile << endl;
}
mapfile << endl;
//Collision layer
mapfile << "[layer]" << endl;
mapfile << "type=collision" << endl;
mapfile << "data=" << endl;
int rowsandcolscollision[10][8] = {{1,1,1,1,1,1,1,1},{1,0,0,0,0,0,0,1},{1,0,0,0,0,0,0,1},{1,0,0,0,0,0,0,1},{1,0,0,0,0,0,0,1},{1,0,0,0,0,0,0,1},{1,0,0,0,0,0,0,1},{1,0,0,0,0,0,0,1},{1,0,0,0,0,0,0,1},{1,1,1,1,1,1,1,1}};
for (int row = 0; row < 10; row ++) {
for (int col = 0; col < 8; col++) {
mapfile << rowsandcolscollision[row][col] << ",";
}
mapfile << endl;
}
mapfile << endl;
//Event
mapfile << "[event]" << endl;
mapfile << "# Name (example: Wind sound)" << endl;
mapfile << "type=event" << endl;
mapfile << "location=0,0,1,1" << endl;
mapfile << "activate=on_load" << endl;
mapfile << "soundfx=soundfx/environment/cave_wind_loop.ogg" << endl;
mapfile << endl;
//Enemy
mapfile << "[enemy]" << endl;
mapfile << "type=enemy" << endl;
mapfile << "location=5,5,1,1" << endl;
mapfile << "category=skeleton_mage" << endl;
mapfile << "direction=7" << endl;
mapfile << "level=3" << endl;
mapfile << "number=1" << endl;
mapfile << "wander_radius=0" << endl;
mapfile << endl;
//Close the file
mapfile.close();
}
Edit: here are some screenshots. One is for the .txt file that the function generated, and the other is what the file looks like when it's opened with Tiled.
Edit 2: The walls can be randomized like the floor tiles to give them some alternative decorations (statues, book cases, etc.). The really difficult problem is going to be the random placement of several rooms in a large dungeon. I'm not sure how I should approach this problem. One option would be to use a maze generation algorithm, but I don't know if that's going to look good or be fun to play. I'm thinking of something more like Diablo 1, which splits each dungeon level into several different rectangular rooms connected by doors.
Edit 3: One of the problems that my approach has is that, if random dungeons were to be implemented, then the C++ codes for the generation of map .txt files would have to be hard coded into the engine. The problem is that this defeats the purpose of mods. Diablo 1 can get away with it because that game was not supposed to support different mods, each with their own list of maps. FLARE has been designed in a different way, which I think is better. I don't know... I'm just rambling here.
Edit 4: Small update for the function. The collision layer now produces a random-sized rectangle; also got rid of that clunky 2d-array. Still have to do the same thing for the walls in the object layer.
//Collision layer
int randrow = 5+(rand() %20);
int randcol = 5+(rand() %20);
mapfile << "[layer]" << endl;
mapfile << "type=collision" << endl;
mapfile << "# random size: " << randrow << " (height) " << "by " << randcol << " (width) "<< endl;
mapfile << "data=" << endl;
for (int row = 0; row < randrow; row++) {
for (int col = 0; col < randcol; col++) {
if (row == 0 || row == randrow - 1 || col == 0 || col == randcol - 1)
mapfile << 1 << ",";
else
mapfile << 0 << ",";
}
mapfile << endl;
}
mapfile << endl;
Edit 5: Removed the clunky 2d array in the object layer as well, though I'm not sure about the repetition of these "else if" statements. The size of the room is now random, but in a way that coincides with the collision layer, since they have to be correlated.
//Object layer
int randrow = 5+(rand() %20);
int randcol = 5+(rand() %20);
mapfile << "[layer]" << endl;
mapfile << "type=object" << endl;
mapfile << "# random size: " << randrow << " (height) " << "by " << randcol << " (width) "<< endl;
mapfile << "data=" << endl;
//North corner wall id: 77
//NE side wall id: 65
//NW side wall id: 68
//West corner wall id: 76
//East corner wall id: 78
//SE wall id: 66
//SW wall id: 67
//South corner wall id: 79
for (int row = 0; row < randrow; row++) {
for (int col = 0; col < randcol; col++) {
if (row == 0 && col == 0)
mapfile << 77 << ","; // North corner wall
else if (row == 0 && col == randcol - 1)
mapfile << 78 << ","; // East corner wall
else if (row == randrow - 1 && col == 0)
mapfile << 76 << ","; // West corner wall
else if (row == randrow - 1 && col == randcol - 1)
mapfile << 79 << ","; // South corner wall
else if (row == 0 && col != 0 && col != randcol - 1)
mapfile << 65 << ","; // NE side walls
else if (row != 0 && row != randrow -1 && col == 0)
mapfile << 68 << ","; // NW side walls
else if (row != 0 && row != randrow -1 && col == randcol - 1)
mapfile << 66 << ","; // SE side walls
else if (row == randrow -1 && col != 0 && col != randcol - 1)
mapfile << 67 << ","; // SW side walls
else
mapfile << 0 << ",";
}
mapfile << endl;
}
mapfile << endl;
Alright, version 0.1 of my humble random dungeon generator is out. Had to solve the dam thing with paper and pencil first. The style of the code is... well... utter chaos, but it works. You can get it here:
https://github.com/m7600/random_dungeon_generator
To use it, simply clone or download it, open the folder in a terminal, use cmake . and make, and then just run the executable. It will generate a .txt file called "outputmap.txt". Move outputmap.txt to a folder like /flare-game/mods/empyrean_campaign/maps, and then you can open it with Tiled.
Currently it generates a random-sized dungeon between 80-100 tiles in width/height, as well as 16 randomly sized rooms. I used srand() and rand() for this, I know that they are not the best option for generating random numbers, but I can change that later.
The walls all face the directions that they should face. The next step will be to add passages between the rooms.
Here are some screenshots, the first one shows only the floor tiles, then the walls, and finally the collision layer (which still needs some work).
Anyways, maybe this never gets implemented, but with some love it could still be a useful tool for modders, for quickly generating dungeon maps, that can be edited further with Tiled.
@m7600 Btw, we have MapSaver API in src/api folder, You can generate map into Flare Map class (in more OOP way, then just writing text directly), and then save map to file with one call using MapSaver.
Thanks igorko, but I'm confused. I thought I knew what MapSaver was, but I was actually thinking about MapRenderer. I looked at the contents of the src/api folder and examined the files. Forgive my ignorance, but I don't understand how to use the API. Is this something that is used while you are playing the game? What would be the command, and what is the path to the saved map files? Sorry for not understanding.
@m7600 The MapSaver is not something that is compiled into the game executable, but is instead something that you would copy into your own project. You would also need to copy the relevant dependencies from the Flare source files. I don't have a list, but there's a lot, since the Map class touches a lot of different areas. Finally, you need to add #define FLARE_MAP_SAVER
to your source files.
Personally, I want to have the MapSaver API be isolated in a way so that it's a single pair of CPP/H files with no dependencies. It would make integrating it into external projects that much easier. And where it wouldn't use any parts from Flare itself, I would also re-license it under something like a MIT/BSD license. But that's just me.
Ah, I see. I'm way too drunk right now, but let me see if I can articulate a coherent thought: I would have to create a Map object, then call MapSaver, and this would create a .txt file?
I would have to create a Map object, then call MapSaver, and this would create a .txt file?
Yes, that's the gist of it.
I'm 99% sure that the error message that I'm getting is due to my fault, but just in case that it would fall into the other 1%, here is what my compiler is saying. It's complaining that there's a problem with MapSaver.h:
MapSaver.h:31:14: error: expected ‘)’ before ‘*’ token
MapSaver(Map* _map);
MapSaver.h:46:2: error: ‘Map’ does not name a type
Map* map;
The Map class is defined in src/Map.cpp
. So you need to copy that and the matching src/Map.h
file. You will get similar errors for the other missing dependencies. If you're up to doing the dependency hunting here, I would advise not using the MapSaver and stick to the method of file writing you were using before.
Well, I admit that this is way over my head right now, but I don't like to give up on interesting problems. There's certainly a lot of header and source files to include, but I'll be damned if I can't solve this. Just give me a couple of weeks : D
So, is it cool with you guys if I post reports of my work in progress here, or would you rather not? No problem if you say "no".
Anyways, here is what I have so far. It's not much. I found all the dependencies, probably shouldn't include them all in main:
#define FLARE_MAP_SAVER
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
#include "src/CampaignManager.h"
#include "src/CommonIncludes.h"
#include "src/EffectManager.h"
#include "src/EngineSettings.h"
#include "src/EventManager.h"
#include "src/FileParser.h"
#include "src/ItemManager.h"
#include "src/Map.h"
#include "src/MapCollision.h"
#include "src/MapSaver.h"
#include "src/MessageEngine.h"
#include "src/ModManager.h"
#include "src/PowerManager.h"
#include "src/SharedGameResources.h"
#include "src/SharedResources.h"
#include "src/StatBlock.h"
#include "src/Stats.h"
#include "src/Utils.h"
#include "src/UtilsParsing.h"
#include "src/Widget.h"
#include "src/WidgetLabel.h"
int main () {
Map _map;
_map.filename = "randomdungeonfile.txt";
_map.title = "Random Dungeon Map";
_map.tileset = "tilesetdefs/tileset_dungeon.txt";
MapSaver::saveMap(file, tileset_definitions);
return 0;
}
It should look more like this:
int main () {
Map _map; //You should get Map instance from game, or load it from file. See Map.cpp to load from file
_map.load("randomdungeonfile.txt");
MapSaver saver(_map); //This is all you need to provide map data to saver.
saver.saveMap(""); //OR
saver.saveMap("randomdungeonfile_out.txt, ""); //tileset_definitions is empty string
// we have next comment in MapSaver.cpp
/*
* tileset_definitions is a string, containing tileset description
* for editing map in Tiled Editor. This data is not present in map,
* loaded by game, so when saving map from game, tileset_definitions
* should be empty string
*/
return 0;
}
tileset_definitions looks like that,
[tilesets]
tileset=../../../tiled/cave/tiled_collision.png,64,32,0,0
tileset=../../../tiled/cave/tiled_cave.png,64,128,0,0
tileset=../../../tiled/cave/set_rules.png,64,32,0,0
tileset=../../../tiled/cave/tiled_cave_2x2.png,128,64,0,16
IIRC it's not required for map to be correctly rendered in game, because we have separate property _map.tileset, as you mentioned in your code. [tilesets] only links to spritesheet
Thanks igorko, that's very helpful. I'm making some progress but it's taking more time than I expected. This is proving to be a very humbling experience for me.
I have been trying a different possibility. Instead of writing to .txt file with C++, I'm using Lua instead.
I know that there have been discussions about integrating Lua with Flare, and this would require a lot of refactoring. But for making random maps, the only thing that would be necessary would be for the engine to have a property called "lua_script" or something like that. The property would be a bool, and if its set to true, it would run some script called "lua_script_random_map.lua" or something like that. The script would generate a map .txt file. I'm still experimenting with this.