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