1 // Copyright 2014-2018 the openage authors. See copying.md for legal info.
2 
3 #include <initializer_list>
4 
5 #include "../engine.h"
6 #include "../log/log.h"
7 #include "../gamedata/unit.gen.h"
8 #include "../terrain/terrain.h"
9 #include "../terrain/terrain_object.h"
10 #include "../terrain/terrain_outline.h"
11 #include "../util/strings.h"
12 #include "ability.h"
13 #include "action.h"
14 #include "producer.h"
15 #include "unit.h"
16 #include "unit_texture.h"
17 
18 /** @file
19  * Many values in this file are hardcoded, due to limited understanding of how the original
20  * game files work -- as more becomes known these will be removed.
21  *
22  * It is likely the conversion from gamedata to openage units will be done by the nyan
23  * system in future
24  */
25 
26 namespace openage {
27 
allowed_terrains(const gamedata::ground_type & restriction)28 std::unordered_set<terrain_t> allowed_terrains(const gamedata::ground_type &restriction) {
29 	std::unordered_set<terrain_t> result;
30 
31 	// 1, 14, and 15 are water, 2 is shore
32 	if (restriction == gamedata::ground_type::WATER ||
33 	    restriction == gamedata::ground_type::WATER_0x0D ||
34 	    restriction == gamedata::ground_type::WATER_SHIP_0x03 ||
35 	    restriction == gamedata::ground_type::WATER_SHIP_0x0F) {
36 		result.insert(1);
37 		result.insert(2);
38 		result.insert(14);
39 		result.insert(15);
40 	}
41 	else {
42 		for (int i = 0; i < 32; ++i) {
43 			if (i != 1 && i != 14 && i != 15) {
44 				result.insert(i);
45 			}
46 		}
47 	}
48 
49 	return result;
50 }
51 
create_resource_cost(game_resource resource,int amount)52 ResourceBundle create_resource_cost(game_resource resource, int amount) {
53 	ResourceBundle resources = ResourceBundle();
54 	resources[resource] = amount;
55 	return resources;
56 }
57 
ObjectProducer(const Player & owner,const GameSpec & spec,const gamedata::unit_object * ud)58 ObjectProducer::ObjectProducer(const Player &owner, const GameSpec &spec, const gamedata::unit_object *ud)
59 	:
60 	UnitType(owner),
61 	dataspec(spec),
62 	unit_data(*ud),
63 	terrain_outline{nullptr},
64 	default_tex{spec.get_unit_texture(ud->graphic_standing0)},
65 	dead_unit_id{ud->dead_unit_id} {
66 
67 	// copy the class type
68 	this->unit_class = this->unit_data.unit_class;
69 
70 	// for now just look for type names ending with "_D"
71 	this->decay = unit_data.name.substr(unit_data.name.length() - 2) == "_D";
72 
73 	// find suitable sounds
74 	int creation_sound = this->unit_data.train_sound;
75 	int dying_sound = this->unit_data.sound_dying;
76 	if (creation_sound == -1) {
77 		creation_sound = this->unit_data.damage_sound;
78 	}
79 	if (creation_sound == -1) {
80 		creation_sound = this->unit_data.sound_selection;
81 	}
82 	if (dying_sound == -1) {
83 		dying_sound = 323; //generic explosion sound
84 	}
85 	on_create = spec.get_sound(creation_sound);
86 	on_destroy = spec.get_sound(dying_sound);
87 
88 	// convert the float to the discrete foundation size...
89 	this->foundation_size = {
90 		static_cast<int>(this->unit_data.radius_x * 2),
91 		static_cast<int>(this->unit_data.radius_y * 2),
92 	};
93 
94 	// shape of the outline
95 	if (this->unit_data.selection_shape > 1) {
96 		this->terrain_outline = radial_outline(this->unit_data.radius_x);
97 	}
98 	else {
99 		this->terrain_outline = square_outline(this->foundation_size);
100 	}
101 
102 	// graphic set
103 	auto standing = spec.get_unit_texture(this->unit_data.graphic_standing0);
104 	if (!standing) {
105 
106 		// indicates problems with data converion
107 		throw Error(MSG(err) << "Unit id " << this->unit_data.id0
108 			<< " has invalid graphic data, try reconverting the data");
109 	}
110 	this->graphics[graphic_type::standing] = standing;
111 	auto dying_tex = spec.get_unit_texture(this->unit_data.dying_graphic);
112 	if (dying_tex) {
113 		this->graphics[graphic_type::dying] = dying_tex;
114 	}
115 
116 	// default extra graphics
117 	this->graphics[graphic_type::attack] = this->graphics[graphic_type::standing];
118 	this->graphics[graphic_type::work] = this->graphics[graphic_type::standing];
119 
120 	// pull extra graphics from unit commands
121 	auto cmds = spec.get_command_data(this->unit_data.id0);
122 	for (auto cmd : cmds) {
123 
124 		// same attack / work graphic
125 		if (cmd->work_sprite_id == -1 && cmd->proceed_sprite_id > 0) {
126 			auto task = spec.get_unit_texture(cmd->proceed_sprite_id);
127 			if (task) {
128 				this->graphics[graphic_type::work] = task;
129 				this->graphics[graphic_type::attack] = task;
130 			}
131 		}
132 
133 		// seperate work and attack graphics
134 		if (cmd->work_sprite_id > 0 && cmd->proceed_sprite_id > 0 ) {
135 			auto attack = spec.get_unit_texture(cmd->proceed_sprite_id);
136 			auto work = spec.get_unit_texture(cmd->work_sprite_id);
137 			if (attack) {
138 				this->graphics[graphic_type::attack] = attack;
139 			}
140 			if (work) {
141 				this->graphics[graphic_type::work] = work;
142 			}
143 		}
144 
145 		// villager carrying resources graphics
146 		if (cmd->carry_sprite_id > 0) {
147 			auto carry = spec.get_unit_texture(cmd->carry_sprite_id);
148 			this->graphics[graphic_type::carrying] = carry;
149 		}
150 	}
151 
152 	// TODO get cost, temp fixed cost of 50 food
153 	this->cost.set(cost_type::constant, create_resource_cost(game_resource::food, 50));
154 
155 }
156 
~ObjectProducer()157 ObjectProducer::~ObjectProducer() {}
158 
id() const159 int ObjectProducer::id() const {
160 	return this->unit_data.id0;
161 }
162 
parent_id() const163 int ObjectProducer::parent_id() const {
164 	int uid = this->unit_data.id0;
165 
166 	// male types
167 	if (uid == 156 || uid == 120 || uid == 592 || uid == 123 || uid == 579 || uid == 124) {
168 		return 83;
169 	}
170 
171 	// female types
172 	else if (uid == 222 || uid == 354 || uid == 590 || uid == 218 || uid == 581 || uid == 220) {
173 		return 293;
174 	}
175 	return uid;
176 }
177 
name() const178 std::string ObjectProducer::name() const {
179 	return this->unit_data.name;
180 }
181 
initialise(Unit * unit,Player & player)182 void ObjectProducer::initialise(Unit *unit, Player &player) {
183 	ENSURE(this->owner == player, "unit init from a UnitType of a wrong player which breaks tech levels");
184 
185 	// log attributes
186 	unit->log(MSG(dbg) << "setting unit type " <<
187 		this->unit_data.id0 << " " <<
188 		this->unit_data.name);
189 
190 	// reset existing attributes
191 	unit->reset();
192 
193 	// initialise unit
194 	unit->unit_type = this;
195 
196 	// colour
197 	unit->add_attribute(std::make_shared<Attribute<attr_type::owner>>(player));
198 
199 	// hitpoints if available
200 	if (this->unit_data.hit_points > 0) {
201 		unit->add_attribute(std::make_shared<Attribute<attr_type::hitpoints>>(this->unit_data.hit_points));
202 		unit->add_attribute(std::make_shared<Attribute<attr_type::damaged>>(this->unit_data.hit_points));
203 	}
204 
205 	// collectable resources
206 	if (this->unit_data.unit_class == gamedata::unit_classes::TREES) {
207 		unit->add_attribute(std::make_shared<Attribute<attr_type::resource>>(game_resource::wood, 125));
208 	}
209 	else if (this->unit_data.unit_class == gamedata::unit_classes::BERRY_BUSH) {
210 		unit->add_attribute(std::make_shared<Attribute<attr_type::resource>>(game_resource::food, 100));
211 	}
212 	else if (this->unit_data.unit_class == gamedata::unit_classes::SEA_FISH) {
213 		unit->add_attribute(std::make_shared<Attribute<attr_type::resource>>(game_resource::food, 200));
214 	}
215 	else if (this->unit_data.unit_class == gamedata::unit_classes::PREY_ANIMAL) {
216 		unit->add_attribute(std::make_shared<Attribute<attr_type::resource>>(game_resource::food, 140));
217 	}
218 	else if (this->unit_data.unit_class == gamedata::unit_classes::SHEEP) {
219 		unit->add_attribute(std::make_shared<Attribute<attr_type::resource>>(game_resource::food, 100, 0.1));
220 	}
221 	else if (this->unit_data.unit_class == gamedata::unit_classes::GOLD_MINE) {
222 		unit->add_attribute(std::make_shared<Attribute<attr_type::resource>>(game_resource::gold, 800));
223 	}
224 	else if (this->unit_data.unit_class == gamedata::unit_classes::STONE_MINE) {
225 		unit->add_attribute(std::make_shared<Attribute<attr_type::resource>>(game_resource::stone, 350));
226 	}
227 
228 	// decaying units have a timed lifespan
229 	if (decay) {
230 		unit->push_action(std::make_unique<DecayAction>(unit), true);
231 	}
232 	else {
233 		// if destruction graphic is available
234 		if (this->dead_unit_id) {
235 			unit->push_action(
236 				std::make_unique<DeadAction>(
237 					unit,
238 					[this, unit, &player]() {
239 
240 						// modify unit to have  dead type
241 						UnitType *t = player.get_type(this->dead_unit_id);
242 						if (t) {
243 							t->initialise(unit, player);
244 						}
245 					}
246 				),
247 				true);
248 		}
249 		else if (this->graphics.count(graphic_type::dying) > 0) {
250 			unit->push_action(std::make_unique<DeadAction>(unit), true);
251 		}
252 
253 		// the default action
254 		unit->push_action(std::make_unique<IdleAction>(unit), true);
255 	}
256 
257 	// give required abilitys
258 	for (auto &a : this->type_abilities) {
259 		unit->give_ability(a);
260 	}
261 }
262 
place(Unit * u,std::shared_ptr<Terrain> terrain,coord::phys3 init_pos) const263 TerrainObject *ObjectProducer::place(Unit *u, std::shared_ptr<Terrain> terrain, coord::phys3 init_pos) const {
264 
265 	// create new object with correct base shape
266 	if (this->unit_data.selection_shape > 1) {
267 		u->make_location<RadialObject>(this->unit_data.radius_x, this->terrain_outline);
268 	}
269 	else {
270 		u->make_location<SquareObject>(this->foundation_size, this->terrain_outline);
271 	}
272 
273 	// find set of allowed terrains
274 	std::unordered_set<terrain_t> terrains = allowed_terrains(this->unit_data.terrain_restriction);
275 
276 	/*
277 	 * decide what terrain is passable using this lambda
278 	 * currently unit avoids water and tiles with another unit
279 	 * this function should be true if pos is a valid position of the object
280 	 */
281 	TerrainObject *obj_ptr = u->location.get();
282 	std::weak_ptr<Terrain> terrain_ptr = terrain;
283 	u->location->passable = [obj_ptr, terrain_ptr, terrains](const coord::phys3 &pos) -> bool {
284 
285 		// if location is deleted, then so is this lambda (deleting terrain implies location is deleted)
286 		// so locking objects here will not return null
287 		auto terrain = terrain_ptr.lock();
288 
289 		// look at all tiles in the bases range
290 		for (coord::tile check_pos : tile_list(obj_ptr->get_range(pos, *terrain))) {
291 			TileContent *tc = terrain->get_data(check_pos);
292 
293 			// invalid tile types
294 			if (!tc || terrains.count(tc->terrain_id) == 0) {
295 				return false;
296 			}
297 
298 			// compare with objects intersecting the units tile
299 			// ensure no intersections with other objects
300 			for (auto obj_cmp : tc->obj) {
301 				if (obj_ptr != obj_cmp &&
302 				    obj_cmp->check_collisions() &&
303 				    obj_ptr->intersects(*obj_cmp, pos)) {
304 					return false;
305 				}
306 			}
307 		}
308 		return true;
309 	};
310 
311 	u->location->draw = [u, obj_ptr](const Engine &e) {
312 		if (u->selected) {
313 			obj_ptr->draw_outline(e.coord);
314 		}
315 		u->draw(e);
316 	};
317 
318 	// try to place the obj, it knows best whether it will fit.
319 	auto state = this->decay? object_state::placed_no_collision : object_state::placed;
320 	if (u->location->place(terrain, init_pos, state)) {
321 		if (this->on_create) {
322 			this->on_create->play();
323 		}
324 		return u->location.get();
325 	}
326 
327 	// placing at the given position failed
328 	u->log(MSG(dbg) << "failed to place object");
329 	return nullptr;
330 }
331 
MovableProducer(const Player & owner,const GameSpec & spec,const gamedata::projectile_unit * um)332 MovableProducer::MovableProducer(const Player &owner, const GameSpec &spec, const gamedata::projectile_unit *um)
333 	:
334 	ObjectProducer(owner, spec, um),
335 	unit_data(*um),
336 	on_move{spec.get_sound(this->unit_data.command_sound_id)},
337 	on_attack{spec.get_sound(this->unit_data.command_sound_id)},
338 	projectile{this->unit_data.missile_unit_id} {
339 
340 	// extra graphics if available
341 	// villagers have invalid attack and walk graphics
342 	// it seems these come from the command data instead
343 	auto walk = spec.get_unit_texture(this->unit_data.walking_graphics0);
344 	if (!walk) {
345 
346 		// use standing instead
347 		walk = this->graphics[graphic_type::standing];
348 	}
349 	this->graphics[graphic_type::walking] = walk;
350 
351 	// reuse as carry graphic if not already set
352 	if (this->graphics.count(graphic_type::carrying) == 0) {
353 		this->graphics[graphic_type::carrying] = walk;
354 	}
355 
356 	auto attack = spec.get_unit_texture(this->unit_data.fight_sprite_id);
357 	if (attack && attack->is_valid()) {
358 		this->graphics[graphic_type::attack] = attack;
359 	}
360 
361 	// extra abilities
362 	this->type_abilities.emplace_back(std::make_shared<MoveAbility>(this->on_move));
363 	this->type_abilities.emplace_back(std::make_shared<AttackAbility>(this->on_attack));
364 }
365 
~MovableProducer()366 MovableProducer::~MovableProducer() {}
367 
initialise(Unit * unit,Player & player)368 void MovableProducer::initialise(Unit *unit, Player &player) {
369 
370 	/*
371 	 * call base function
372 	 */
373 	ObjectProducer::initialise(unit, player);
374 
375 	/*
376 	 * basic attributes
377 	 */
378 	if (!unit->has_attribute(attr_type::direction)) {
379 		unit->add_attribute(std::make_shared<Attribute<attr_type::direction>>(coord::phys3_delta{ 1, 0, 0 }));
380 	}
381 
382 	/*
383 	 * distance per millisecond -- consider original game speed
384 	 * where 1.5 in game seconds pass in 1 real second
385 	 */
386 	coord::phys_t sp = this->unit_data.speed / 666;
387 	unit->add_attribute(std::make_shared<Attribute<attr_type::speed>>(sp));
388 
389 	// projectile of melee attacks
390 	UnitType *proj_type = this->owner.get_type(this->projectile);
391 	if (this->unit_data.missile_unit_id > 0 && proj_type) {
392 
393 		// calculate requirements for ranged attacks
394 		coord::phys_t range_phys = this->unit_data.weapon_range_max;
395 		unit->add_attribute(std::make_shared<Attribute<attr_type::attack>>(proj_type, range_phys, 48000, 1));
396 	}
397 	else {
398 		unit->add_attribute(std::make_shared<Attribute<attr_type::attack>>(nullptr, 0, 0, 1));
399 	}
400 	unit->add_attribute(std::make_shared<Attribute<attr_type::formation>>());
401 }
402 
place(Unit * unit,std::shared_ptr<Terrain> terrain,coord::phys3 init_pos) const403 TerrainObject *MovableProducer::place(Unit *unit, std::shared_ptr<Terrain> terrain, coord::phys3 init_pos) const {
404 	return ObjectProducer::place(unit, terrain, init_pos);
405 }
406 
LivingProducer(const Player & owner,const GameSpec & spec,const gamedata::living_unit * ud)407 LivingProducer::LivingProducer(const Player &owner, const GameSpec &spec, const gamedata::living_unit *ud)
408 	:
409 	MovableProducer(owner, spec, ud),
410 	unit_data(*ud) {
411 
412 	// extra abilities
413 	this->type_abilities.emplace_back(std::make_shared<GarrisonAbility>(this->on_move));
414 }
415 
~LivingProducer()416 LivingProducer::~LivingProducer() {}
417 
initialise(Unit * unit,Player & player)418 void LivingProducer::initialise(Unit *unit, Player &player) {
419 
420 	/*
421 	 * call base function
422 	 */
423 	MovableProducer::initialise(unit, player);
424 
425 	// population of 1 for all movable units
426 	if (this->unit_data.unit_class != gamedata::unit_classes::SHEEP) {
427 		unit->add_attribute(std::make_shared<Attribute<attr_type::population>>(1, 0));
428 	}
429 
430 	// add worker attributes
431 	if (this->unit_data.unit_class == gamedata::unit_classes::CIVILIAN) {
432 		unit->add_attribute(std::make_shared<Attribute<attr_type::worker>>());
433 		unit->add_attribute(std::make_shared<Attribute<attr_type::resource>>());
434 		unit->add_attribute(std::make_shared<Attribute<attr_type::multitype>>());
435 
436 		// add graphic ids for resource actions
437 		auto &worker_attr = unit->get_attribute<attr_type::worker>();
438 		worker_attr.capacity = 10.0;
439 		worker_attr.gather_rate[game_resource::wood] = 0.002;
440 		worker_attr.gather_rate[game_resource::food] = 0.002;
441 		worker_attr.gather_rate[game_resource::gold] = 0.002;
442 		worker_attr.gather_rate[game_resource::stone] = 0.002;
443 
444 		auto &multitype_attr = unit->get_attribute<attr_type::multitype>();
445 		// currently not sure where the game data keeps these values
446 		// todo PREY_ANIMAL SEA_FISH
447 		if (this->parent_id() == 83) {
448 
449 			// male graphics
450 			multitype_attr.types[gamedata::unit_classes::CIVILIAN] = this->parent_type(); // get default villager
451 			multitype_attr.types[gamedata::unit_classes::BUILDING] = this->owner.get_type(156); // builder 118
452 			multitype_attr.types[gamedata::unit_classes::BERRY_BUSH] = this->owner.get_type(120); // forager
453 			multitype_attr.types[gamedata::unit_classes::SHEEP] = this->owner.get_type(592); // sheperd
454 			multitype_attr.types[gamedata::unit_classes::TREES] = this->owner.get_type(123); // woodcutter
455 			multitype_attr.types[gamedata::unit_classes::GOLD_MINE] = this->owner.get_type(579); // gold miner
456 			multitype_attr.types[gamedata::unit_classes::STONE_MINE] = this->owner.get_type(124); // stone miner
457 
458 		}
459 		else {
460 
461 			// female graphics
462 			multitype_attr.types[gamedata::unit_classes::CIVILIAN] = this->parent_type(); // get default villager
463 			multitype_attr.types[gamedata::unit_classes::BUILDING] = this->owner.get_type(222); // builder 212
464 			multitype_attr.types[gamedata::unit_classes::BERRY_BUSH] = this->owner.get_type(354); // forager
465 			multitype_attr.types[gamedata::unit_classes::SHEEP] = this->owner.get_type(590); // sheperd
466 			multitype_attr.types[gamedata::unit_classes::TREES] = this->owner.get_type(218); // woodcutter
467 			multitype_attr.types[gamedata::unit_classes::GOLD_MINE] = this->owner.get_type(581); // gold miner
468 			multitype_attr.types[gamedata::unit_classes::STONE_MINE] = this->owner.get_type(220); // stone miner
469 		}
470 		unit->give_ability(std::make_shared<GatherAbility>(this->on_attack));
471 		unit->give_ability(std::make_shared<BuildAbility>(this->on_attack));
472 		unit->give_ability(std::make_shared<RepairAbility>(this->on_attack));
473 	}
474 	else if (this->unit_data.unit_class == gamedata::unit_classes::FISHING_BOAT) {
475 		unit->add_attribute(std::make_shared<Attribute<attr_type::worker>>());
476 		unit->add_attribute(std::make_shared<Attribute<attr_type::resource>>());
477 
478 		// add fishing abilites
479 		auto &worker_attr = unit->get_attribute<attr_type::worker>();
480 		worker_attr.capacity = 15.0;
481 		worker_attr.gather_rate[game_resource::food] = 0.002;
482 
483 		unit->give_ability(std::make_shared<GatherAbility>(this->on_attack));
484 	}
485 }
486 
place(Unit * unit,std::shared_ptr<Terrain> terrain,coord::phys3 init_pos) const487 TerrainObject *LivingProducer::place(Unit *unit, std::shared_ptr<Terrain> terrain, coord::phys3 init_pos) const {
488 	return MovableProducer::place(unit, terrain, init_pos);
489 }
490 
BuildingProducer(const Player & owner,const GameSpec & spec,const gamedata::building_unit * ud)491 BuildingProducer::BuildingProducer(const Player &owner, const GameSpec &spec, const gamedata::building_unit *ud)
492 	:
493 	UnitType(owner),
494 	unit_data{*ud},
495 	texture{spec.get_unit_texture(ud->graphic_standing0)},
496 	destroyed{spec.get_unit_texture(ud->dying_graphic)},
497 	projectile{this->unit_data.missile_unit_id},
498 	foundation_terrain{ud->foundation_terrain_id},
499 	enable_collisions{this->unit_data.id0 != 109} { // 109 = town center
500 
501 	// copy the class type
502 	this->unit_class = this->unit_data.unit_class;
503 
504 	// find suitable sounds
505 	int creation_sound = this->unit_data.train_sound;
506 	int dying_sound = this->unit_data.sound_dying;
507 	if (creation_sound == -1) {
508 		creation_sound = this->unit_data.damage_sound;
509 	}
510 	if (creation_sound == -1) {
511 		creation_sound = this->unit_data.sound_selection;
512 	}
513 	if (dying_sound == -1) {
514 		dying_sound = 323; //generic explosion sound
515 	}
516 	on_create = spec.get_sound(creation_sound);
517 	on_destroy = spec.get_sound(dying_sound);
518 
519 	// convert the float to the discrete foundation size...
520 	this->foundation_size = {
521 		static_cast<int>(this->unit_data.radius_x * 2),
522 		static_cast<int>(this->unit_data.radius_y * 2),
523 	};
524 
525 	// graphic set
526 	this->graphics[graphic_type::construct] = spec.get_unit_texture(ud->construction_graphic_id);
527 	this->graphics[graphic_type::standing] = spec.get_unit_texture(ud->graphic_standing0);
528 	this->graphics[graphic_type::attack] = spec.get_unit_texture(ud->graphic_standing0);
529 	auto dying_tex = spec.get_unit_texture(ud->dying_graphic);
530 	if (dying_tex) {
531 		this->graphics[graphic_type::dying] = dying_tex;
532 	}
533 
534 	this->terrain_outline = square_outline(this->foundation_size);
535 
536 	// TODO get cost, temp fixed cost of 100 wood
537 	this->cost.set(cost_type::constant, create_resource_cost(game_resource::wood, 100));
538 }
539 
~BuildingProducer()540 BuildingProducer::~BuildingProducer() {}
541 
id() const542 int BuildingProducer::id() const {
543 	return this->unit_data.id0;
544 }
545 
parent_id() const546 int BuildingProducer::parent_id() const {
547 	return this->unit_data.id0;
548 }
549 
name() const550 std::string BuildingProducer::name() const {
551 	return this->unit_data.name;
552 }
553 
initialise(Unit * unit,Player & player)554 void BuildingProducer::initialise(Unit *unit, Player &player) {
555 	ENSURE(this->owner == player, "unit init from a UnitType of a wrong player which breaks tech levels");
556 
557 	// log type
558 	unit->log(MSG(dbg) << "setting unit type " <<
559 		this->unit_data.id0 << " " <<
560 		this->unit_data.name);
561 
562 	// initialize graphic set
563 	unit->unit_type = this;
564 
565 	auto player_attr = std::make_shared<Attribute<attr_type::owner>>(player);
566 	unit->add_attribute(player_attr);
567 
568 	// building specific attribute
569 	auto build_attr = std::make_shared<Attribute<attr_type::building>>(
570 		this->foundation_terrain,
571 		this->owner.get_type(293), // fem_villager, male is 83
572 		unit->location->pos.draw
573 	);
574 	build_attr->completion_state = this->enable_collisions ? object_state::placed : object_state::placed_no_collision;
575 	unit->add_attribute(build_attr);
576 
577 	// garrison and hp for all buildings
578 	unit->add_attribute(std::make_shared<Attribute<attr_type::garrison>>());
579 	unit->add_attribute(std::make_shared<Attribute<attr_type::hitpoints>>(this->unit_data.hit_points));
580 	unit->add_attribute(std::make_shared<Attribute<attr_type::damaged>>(this->unit_data.hit_points));
581 
582 	// population
583 	if (this->id() == 109 || this->id() == 70) { // Town center, House
584 		unit->add_attribute(std::make_shared<Attribute<attr_type::population>>(0, 5));
585 	}
586 	else if (this->id() == 82) { // Castle
587 		unit->add_attribute(std::make_shared<Attribute<attr_type::population>>(0, 20));
588 	}
589 
590 	// limits
591 	if (this->id() == 109 || this->id() == 276) { // Town center, Wonder
592 		this->have_limit = 2; // TODO change to 1, 2 is for testing
593 	}
594 
595 	bool has_destruct_graphic = this->destroyed != nullptr;
596 	unit->push_action(std::make_unique<FoundationAction>(unit, has_destruct_graphic), true);
597 
598 	UnitType *proj_type = this->owner.get_type(this->projectile);
599 	if (this->unit_data.missile_unit_id > 0 && proj_type) {
600 		coord::phys_t range_phys = this->unit_data.weapon_range_max;
601 		unit->add_attribute(std::make_shared<Attribute<attr_type::attack>>(proj_type, range_phys, 350000, 1));
602 		// formation is used only for the attack_stance
603 		unit->add_attribute(std::make_shared<Attribute<attr_type::formation>>(attack_stance::aggressive));
604 		unit->give_ability(std::make_shared<AttackAbility>());
605 	}
606 
607 	// dropsite attribute
608 	std::vector<game_resource> accepted_resources = this->get_accepted_resources();
609 	if (accepted_resources.size() != 0) {
610 		unit->add_attribute(std::make_shared<Attribute<attr_type::dropsite>>(accepted_resources));
611 	}
612 
613 	// building can train new units and ungarrison
614 	unit->give_ability(std::make_shared<SetPointAbility>());
615 	unit->give_ability(std::make_shared<TrainAbility>());
616 	unit->give_ability(std::make_shared<UngarrisonAbility>());
617 }
618 
get_accepted_resources()619 std::vector<game_resource> BuildingProducer::get_accepted_resources() {
620 	//TODO use a more general approach instead of hard coded ids
621 
622 	auto id_in = [=](std::initializer_list<int> ids) {
623 		return std::any_of(ids.begin(), ids.end(), [=](int n) { return n == this->id(); });
624 	};
625 
626 	if (this->id() == 109) { // Town center
627 		return std::vector<game_resource>{
628 			game_resource::wood,
629 			game_resource::food,
630 			game_resource::gold,
631 			game_resource::stone
632 		};
633 	} else if (id_in({584, 585, 586, 587})) { // Mine
634 		return std::vector<game_resource>{
635 			game_resource::gold,
636 			game_resource::stone
637 		};
638 	} else if (id_in({68, 129, 130, 131})) { // Mill
639 		return std::vector<game_resource>{
640 			game_resource::food
641 		};
642 	} else if (id_in({562, 563, 564, 565})) { // Lumberjack camp
643 		return std::vector<game_resource>{
644 			game_resource::wood
645 		};
646 	}
647 
648 	return std::vector<game_resource>();
649 }
650 
place(Unit * u,std::shared_ptr<Terrain> terrain,coord::phys3 init_pos) const651 TerrainObject *BuildingProducer::place(Unit *u, std::shared_ptr<Terrain> terrain, coord::phys3 init_pos) const {
652 
653 	// buildings have a square base
654 	u->make_location<SquareObject>(this->foundation_size, this->terrain_outline);
655 
656 	/*
657 	 * decide what terrain is passable using this lambda
658 	 * currently unit avoids water and tiles with another unit
659 	 * this function should be true if pos is a valid position of the object
660 	 */
661 	TerrainObject *obj_ptr = u->location.get();
662 	std::weak_ptr<Terrain> terrain_ptr = terrain;
663 	u->location->passable = [obj_ptr, terrain_ptr](const coord::phys3 &pos) -> bool {
664 		auto terrain = terrain_ptr.lock();
665 
666 		// look at all tiles in the bases range
667 		for (coord::tile check_pos : tile_list(obj_ptr->get_range(pos, *terrain))) {
668 			TileContent *tc = terrain->get_data(check_pos);
669 			if (!tc) {
670 				return false;
671 			}
672 			for (auto tobj : tc->obj) {
673 				if (tobj->check_collisions()) return false;
674 			}
675 		}
676 		return true;
677 	};
678 
679 	// drawing function
680 	bool draw_outline = this->enable_collisions;
681 	u->location->draw = [u, obj_ptr, draw_outline](const Engine &e) {
682 		if (u->selected && draw_outline) {
683 			obj_ptr->draw_outline(e.coord);
684 		}
685 		u->draw(e);
686 	};
687 
688 	// try to place the obj, it knows best whether it will fit.
689 	auto state = object_state::floating;
690 	if (!u->location->place(terrain, init_pos, state)) {
691 		return nullptr;
692 	}
693 
694 	// annex objects
695 	for (unsigned i = 0; i < 4; ++i) {
696 		const gamedata::building_annex &annex = this->unit_data.building_annex.data[i];
697 		if (annex.unit_id > 0) {
698 
699 			// make objects for annex
700 			coord::phys3 a_pos = u->location->pos.draw;
701 			a_pos.ne += annex.misplaced0;
702 			a_pos.se += annex.misplaced1;
703 			this->make_annex(*u, terrain, annex.unit_id, a_pos, i == 0);
704 		}
705 	}
706 
707 	// TODO: play sound once built
708 	if (this->on_create) {
709 		this->on_create->play();
710 	}
711 	return u->location.get();
712 }
713 
make_annex(Unit & u,std::shared_ptr<Terrain> t,int annex_id,coord::phys3 annex_pos,bool c) const714 TerrainObject *BuildingProducer::make_annex(Unit &u, std::shared_ptr<Terrain> t,
715                                             int annex_id, coord::phys3 annex_pos, bool c) const {
716 
717 	// for use in lambda drawing functions
718 	auto annex_type = this->owner.get_type(annex_id);
719 	if (!annex_type) {
720 		u.log(MSG(warn) << "Invalid annex type id " << annex_id);
721 		return nullptr;
722 	}
723 
724 	// foundation size
725 	coord::tile_delta annex_foundation = annex_type->foundation_size;
726 
727 	// producers place by the nw tile
728 	coord::phys3 start_tile = annex_pos;
729 	start_tile.ne -= annex_foundation.ne / 2.0;
730 	start_tile.se -= annex_foundation.se / 2.0;
731 
732 	// create and place on terrain
733 	TerrainObject *annex_loc = u.location->make_annex<SquareObject>(annex_foundation);
734 	object_state state = c? object_state::placed : object_state::placed_no_collision;
735 	annex_loc->place(t, start_tile, state);
736 
737 	// create special drawing functions for annexes,
738 	annex_loc->draw = [annex_loc, annex_type, &u, c](const Engine &e) {
739 
740 		// hack which draws the outline in the right order
741 		// removable once rendering system is improved
742 		if (c && u.selected) {
743 			annex_loc->get_parent()->draw_outline(e.coord);
744 		}
745 
746 		// only draw if building is completed
747 		if (u.has_attribute(attr_type::building) &&
748 		    u.get_attribute<attr_type::building>().completed >= 1.0f) {
749 			u.draw(annex_loc, annex_type->graphics, e);
750 		}
751 	};
752 	return annex_loc;
753 }
754 
ProjectileProducer(const Player & owner,const GameSpec & spec,const gamedata::missile_unit * pd)755 ProjectileProducer::ProjectileProducer(const Player &owner, const GameSpec &spec, const gamedata::missile_unit *pd)
756 	:
757 	UnitType(owner),
758 	unit_data{*pd},
759 	tex{spec.get_unit_texture(this->unit_data.graphic_standing0)},
760 	sh{spec.get_unit_texture(3379)}, // 3379 = general arrow shadow
761 	destroyed{spec.get_unit_texture(this->unit_data.dying_graphic)} {
762 
763 	// copy the class type
764 	this->unit_class = this->unit_data.unit_class;
765 
766 	// graphic set
767 	this->graphics[graphic_type::standing] = this->tex;
768 	this->graphics[graphic_type::shadow] = this->sh;
769 	if (destroyed) {
770 		this->graphics[graphic_type::dying] = destroyed;
771 	}
772 
773 	// outline
774 	terrain_outline = radial_outline(pd->radius_y);
775 }
776 
~ProjectileProducer()777 ProjectileProducer::~ProjectileProducer() {}
778 
id() const779 int ProjectileProducer::id() const {
780 	return this->unit_data.id0;
781 }
782 
parent_id() const783 int ProjectileProducer::parent_id() const {
784 	return this->unit_data.id0;
785 }
786 
name() const787 std::string ProjectileProducer::name() const {
788 	return this->unit_data.name;
789 }
790 
initialise(Unit * unit,Player & player)791 void ProjectileProducer::initialise(Unit *unit, Player &player) {
792 	ENSURE(this->owner == player, "unit init from a UnitType of a wrong player which breaks tech levels");
793 
794 	// initialize graphic set
795 	unit->unit_type = this;
796 
797 	auto player_attr = std::make_shared<Attribute<attr_type::owner>>(player);
798 	unit->add_attribute(player_attr);
799 
800 	// projectile speed
801 	coord::phys_t sp = this->unit_data.speed / 666;
802 	unit->add_attribute(std::make_shared<Attribute<attr_type::speed>>(sp));
803 	unit->add_attribute(std::make_shared<Attribute<attr_type::projectile>>(this->unit_data.projectile_arc));
804 	unit->add_attribute(std::make_shared<Attribute<attr_type::direction>>(coord::phys3_delta{ 1, 0, 0 }));
805 
806 	// if destruction graphic is available
807 	if (this->destroyed) {
808 		unit->push_action(std::make_unique<DeadAction>(unit), true);
809 	}
810 }
811 
place(Unit * u,std::shared_ptr<Terrain> terrain,coord::phys3 init_pos) const812 TerrainObject *ProjectileProducer::place(Unit *u, std::shared_ptr<Terrain> terrain, coord::phys3 init_pos) const {
813 	/*
814 	 * radial base shape without collision checking
815 	 */
816 	u->make_location<RadialObject>(this->unit_data.radius_y, this->terrain_outline);
817 
818 	TerrainObject *obj_ptr = u->location.get();
819 	std::weak_ptr<Terrain> terrain_ptr = terrain;
820 	u->location->passable = [obj_ptr, u, terrain_ptr](const coord::phys3 &pos) -> bool {
821 		if (pos.up > 64000) {
822 			return true;
823 		}
824 
825 		// avoid intersections with launcher
826 		Unit *launcher = nullptr;
827 		auto terrain = terrain_ptr.lock();
828 		if (u->has_attribute(attr_type::projectile)) {
829 			auto &pr_attr = u->get_attribute<attr_type::projectile>();
830 			if (pr_attr.launched && pr_attr.launcher.is_valid()) {
831 				launcher = pr_attr.launcher.get();
832 			}
833 			else {
834 				return true;
835 			}
836 		}
837 		else {
838 			return true;
839 		}
840 
841 		// look at all tiles in the bases range
842 		for (coord::tile check_pos : tile_list(obj_ptr->get_range(pos, *terrain))) {
843 			TileContent *tc = terrain->get_data(check_pos);
844 			if (!tc) return false;
845 
846 			// ensure no intersections with other objects
847 			for (auto obj_cmp : tc->obj) {
848 				if (obj_ptr != obj_cmp &&
849 				    &obj_cmp->unit != launcher &&
850 				    obj_cmp->check_collisions() &&
851 				    obj_ptr->intersects(*obj_cmp, pos)) {
852 					return false;
853 				}
854 			}
855 		}
856 		return true;
857 	};
858 
859 	u->location->draw = [u](const Engine &e) {
860 		u->draw(e);
861 	};
862 
863 	// try to place the obj, it knows best whether it will fit.
864 	if (u->location->place(terrain, init_pos, object_state::placed_no_collision)) {
865 		return u->location.get();
866 	}
867 	return nullptr;
868 }
869 
870 } // namespace openage
871