1 // Copyright 2015-2018 the openage authors. See copying.md for legal info.
2 
3 #include "generator.h"
4 
5 #include "../log/log.h"
6 #include "../rng/rng.h"
7 #include "../terrain/terrain_chunk.h"
8 #include "../unit/unit.h"
9 #include "../util/math_constants.h"
10 #include "game_main.h"
11 #include "game_save.h"
12 #include "game_spec.h"
13 
14 
15 namespace openage {
16 
random_tile(rng::RNG & rng,tileset_t tiles)17 coord::tile random_tile(rng::RNG &rng, tileset_t tiles) {
18 	if (tiles.empty()) {
19 		log::log(MSG(err) << "random tile failed");
20 		return coord::tile{0, 0};
21 	}
22 	uint64_t index = rng.random() % tiles.size();
23 	auto it = std::begin(tiles);
24 	std::advance(it, index);
25 	return *it;
26 }
27 
28 
Region(int size)29 Region::Region(int size)
30 	:
31 	owner{0},
32 	object_id{0},
33 	terrain_id{0},
34 	center{0, 0} {
35 	for (int ne = -size; ne < size; ++ne) {
36 		for (int se = -size; se < size; ++se) {
37 			this->tiles.emplace(coord::tile{ne, se});
38 		}
39 	}
40 }
41 
Region(coord::tile center,tileset_t tiles)42 Region::Region(coord::tile center, tileset_t tiles)
43 	:
44 	owner{0},
45 	object_id{0},
46 	terrain_id{0},
47 	center(center),
48 	tiles{tiles} {
49 }
50 
get_tiles() const51 tileset_t Region::get_tiles() const {
52 	return this->tiles;
53 }
54 
get_center() const55 coord::tile Region::get_center() const {
56 	return this->center;
57 }
58 
get_tile(rng::RNG & rng) const59 coord::tile Region::get_tile(rng::RNG &rng) const {
60 	return random_tile(rng, this->tiles);
61 }
62 
63 
subset(rng::RNG & rng,coord::tile start_point,unsigned int number,double p) const64 tileset_t Region::subset(rng::RNG &rng, coord::tile start_point, unsigned int number, double p) const {
65 	if (p == 0.0) {
66 		return tileset_t();
67 	}
68 
69 	// the set of included tiles
70 	std::unordered_set<coord::tile> subtiles;
71 	subtiles.emplace(start_point);
72 
73 	// outside layer of tiles
74 	std::unordered_set<coord::tile> edge_set;
75 
76 	while (subtiles.size() < number) {
77 		if (edge_set.empty()) {
78 
79 			// try fill the edge list
80 			for (auto &t : subtiles) {
81 
82 				// check adjacent tiles
83 				for (int i = 0; i < 4; ++i) {
84 					coord::tile adj = t + neigh_tiles[i];
85 					if (this->tiles.count(adj) &&
86 						!subtiles.count(adj)) {
87 						edge_set.emplace(adj);
88 					}
89 				}
90 			}
91 			if (edge_set.empty()) {
92 
93 				// unable to grow further
94 				return subtiles;
95 			}
96 		}
97 
98 		// transfer a random tile
99 		coord::tile next_tile = random_tile(rng, edge_set);
100 		edge_set.erase(next_tile);
101 		if (rng.probability(p)) {
102 			subtiles.emplace(next_tile);
103 		}
104 	}
105 	return subtiles;
106 }
107 
take_tiles(rng::RNG & rng,coord::tile start_point,unsigned int number,double p)108 Region Region::take_tiles(rng::RNG &rng, coord::tile start_point, unsigned int number, double p) {
109 
110 	tileset_t new_set = this->subset(rng, start_point, number, p);
111 
112 	// erase from current set
113 	for (auto &t: new_set) {
114 		this->tiles.erase(t);
115 	}
116 
117 	Region new_region(start_point, new_set);
118 	new_region.terrain_id = this->terrain_id;
119 	return new_region;
120 }
121 
take_random(rng::RNG & rng,unsigned int number,double p)122 Region Region::take_random(rng::RNG &rng, unsigned int number, double p) {
123 	return this->take_tiles(rng, this->get_tile(rng), number, p);
124 }
125 
Generator(qtsdl::GuiItemLink * gui_link)126 Generator::Generator(qtsdl::GuiItemLink *gui_link)
127 	:
128 	gui_link{gui_link}
129 {
130 	this->setv("generation_seed", 4321);
131 	this->setv("terrain_size", 2);
132 	this->setv("terrain_base_id", 0);
133 	this->setv("player_area", 850);
134 	this->setv("player_radius", 10);
135 	this->setv("load_filename", "/tmp/default_save.oas");
136 	this->setv("from_file", false);
137 	this->set_csv("player_names", std::vector<std::string>{"name1", "name2"});
138 }
139 
get_spec() const140 std::shared_ptr<GameSpec> Generator::get_spec() const {
141 	return this->spec;
142 }
143 
player_names() const144 std::vector<std::string> Generator::player_names() const {
145 	auto result = this->get_csv("player_names");
146 
147 	// gaia is player 0
148 	result.insert(result.begin(), "gaia");
149 
150 	return result;
151 }
152 
create_regions()153 void Generator::create_regions() {
154 
155 	// get option settings
156 	int seed = this->getv<int>("generation_seed");
157 	int size = this->getv<int>("terrain_size");
158 	int base_id = this->getv<int>("terrain_base_id");
159 	int p_area = this->getv<int>("player_area");
160 	int p_radius = this->getv<int>("player_radius");
161 
162 	// enforce some lower limits
163 	size = std::max(1, size);
164 	base_id = std::max(0, base_id);
165 	p_area = std::max(50, p_area);
166 	p_radius = std::max(2, p_radius);
167 
168 	rng::RNG rng(seed);
169 	Region base(size * 16);
170 	base.terrain_id = base_id;
171 	std::vector<Region> player_regions;
172 
173 	int player_count = this->player_names().size() - 1;
174 	for (int i = 0; i < player_count; ++i) {
175 		log::log(MSG(dbg) << "generate player " << i);
176 
177 		// space players in a circular pattern
178 		double angle = static_cast<double>(i) / static_cast<double>(player_count);
179 		int ne = size * p_radius * sin(math::TAU * angle);
180 		int se = size * p_radius * cos(math::TAU * angle);
181 		coord::tile player_tile{ne, se};
182 
183 		Region player = base.take_tiles(rng, player_tile, p_area, 0.5);
184 		player.terrain_id = 10;
185 
186 		Region obj_space = player.take_tiles(rng, player.get_center(), p_area / 5, 0.5);
187 		obj_space.owner = i + 1;
188 		obj_space.terrain_id = 8;
189 
190 		Region trees1 = player.take_random(rng, p_area / 10, 0.3);
191 		trees1.terrain_id = 9;
192 		trees1.object_id = 349;
193 
194 		Region trees2 = player.take_random(rng, p_area / 10, 0.3);
195 		trees2.terrain_id = 9;
196 		trees2.object_id = 351;
197 
198 		Region stone = player.take_random(rng, 5, 0.3);
199 		stone.object_id = 102;
200 
201 		Region gold = player.take_random(rng, 7, 0.3);
202 		gold.object_id = 66;
203 
204 		Region forage = player.take_random(rng, 6, 0.3);
205 		forage.object_id = 59;
206 
207 		Region sheep = player.take_random(rng, 4, 0.3);
208 		sheep.owner = obj_space.owner;
209 		sheep.object_id = 594;
210 
211 		player_regions.push_back(player);
212 		player_regions.push_back(obj_space);
213 		player_regions.push_back(trees1);
214 		player_regions.push_back(trees2);
215 		player_regions.push_back(stone);
216 		player_regions.push_back(gold);
217 		player_regions.push_back(forage);
218 		player_regions.push_back(sheep);
219 	}
220 
221 	for (int i = 0; i < 6; ++i) {
222 		Region extra_trees = base.take_random(rng, 160, 0.3);
223 		extra_trees.terrain_id = 9;
224 		extra_trees.object_id = 349;
225 		player_regions.push_back(extra_trees);
226 	}
227 
228 	// set regions
229 	this->regions.clear();
230 	this->regions.push_back(base);
231 	for (auto &r : player_regions) {
232 		this->regions.push_back(r);
233 	}
234 }
235 
236 
terrain() const237 std::shared_ptr<Terrain> Generator::terrain() const {
238 	auto terrain = std::make_shared<Terrain>(this->spec->get_terrain_meta(), true);
239 	for (auto &r : this->regions) {
240 		for (auto &tile : r.get_tiles()) {
241 			TerrainChunk *chunk = terrain->get_create_chunk(tile);
242 			chunk->get_data(tile.get_pos_on_chunk())->terrain_id = r.terrain_id;
243 		}
244 	}
245 
246 	// mark the 0, 0 tile.
247 	coord::tile debug_tile_pos{0, 0};
248 	terrain->get_data(debug_tile_pos)->terrain_id = 6;
249 	return terrain;
250 }
251 
add_units(GameMain & m) const252 void Generator::add_units(GameMain &m) const {
253 	for (auto &r : this->regions) {
254 
255 		// Regions filled with resource objects
256 		// trees / mines
257 		if (r.object_id) {
258 			Player* p = m.get_player(r.owner);
259 			auto otype = p->get_type(r.object_id);
260 			if (!otype) {
261 				break;
262 			}
263 			for (auto &tile : r.get_tiles()) {
264 				m.placed_units.new_unit(*otype, *p, tile.to_phys3(*m.terrain));
265 			}
266 		}
267 
268 		// A space for starting town center and villagers
269 		else if (r.owner) {
270 			Player* p = m.get_player(r.owner);
271 			auto tctype = p->get_type(109); // town center
272 			auto mvtype = p->get_type(83);  // male villager
273 			auto fvtype = p->get_type(293); // female villager
274 			auto sctype = p->get_type(448); // scout cavarly
275 			if (!tctype || !mvtype || !fvtype || !sctype) {
276 				break;
277 			}
278 
279 			coord::tile tile = r.get_center();
280 			tile.ne -= 1;
281 			tile.se -= 1;
282 
283 			// Place a completed town center
284 			auto ref = m.placed_units.new_unit(*tctype, *p, tile.to_phys3(*m.terrain));
285 			if (ref.is_valid()) {
286 				complete_building(*ref.get());
287 			}
288 
289 			// Place three villagers
290 			tile.ne -= 1;
291 			m.placed_units.new_unit(*fvtype, *p, tile.to_phys3(*m.terrain));
292 			tile.se += 1;
293 			m.placed_units.new_unit(*mvtype, *p, tile.to_phys3(*m.terrain));
294 			tile.se += 1;
295 			m.placed_units.new_unit(*fvtype, *p, tile.to_phys3(*m.terrain));
296 			// TODO uncomment when the scout looks better
297 			//tile.se += 2;
298 			//m.placed_units.new_unit(*sctype, *p, tile.to_tile3().to_phys3());
299 		}
300 	}
301 }
302 
create(std::shared_ptr<GameSpec> spec)303 std::unique_ptr<GameMain> Generator::create(std::shared_ptr<GameSpec> spec) {
304 	ENSURE(spec->load_complete(), "spec hasn't been checked or was invalidated");
305 	this->spec = spec;
306 
307 	if (this->getv<bool>("from_file")) {
308 		// create an empty game
309 		this->regions.clear();
310 
311 		auto game = std::make_unique<GameMain>(*this);
312 		gameio::load(game.get(), this->getv<std::string>("load_filename"));
313 
314 		return game;
315 	} else {
316 		// generation
317 		this->create_regions();
318 		return std::make_unique<GameMain>(*this);
319 	}
320 }
321 
322 } // namespace openage
323