1 // Copyright 2014-2018 the openage authors. See copying.md for legal info.
2 
3 #include <algorithm>
4 #include <cmath>
5 #include <limits>
6 
7 #include "../terrain/terrain.h"
8 #include "../engine.h"
9 
10 #include "ability.h"
11 #include "action.h"
12 #include "command.h"
13 #include "producer.h"
14 #include "unit.h"
15 #include "unit_texture.h"
16 
17 namespace openage {
18 
Unit(UnitContainer * c,id_t id)19 Unit::Unit(UnitContainer *c, id_t id)
20 	:
21 	id{id},
22 	unit_type{nullptr},
23 	selected{false},
24 	pop_destructables{false},
25 	container(c) {}
26 
~Unit()27 Unit::~Unit() {
28 
29 	// remove any used location from the map
30 	if (this->location) {
31 		this->location->remove();
32 	}
33 }
34 
reset()35 void Unit::reset() {
36 	this->ability_available.clear();
37 	this->action_stack.clear();
38 	this->pop_destructables = false;
39 }
40 
has_action() const41 bool Unit::has_action() const {
42 	return !this->action_stack.empty();
43 }
44 
accept_commands() const45 bool Unit::accept_commands() const {
46 	return (this->has_action() && this->top()->allow_control());
47 }
48 
is_own_unit(const Player & player)49 bool Unit::is_own_unit(const Player &player) {
50 	return player.owns(*this);
51 }
52 
top() const53 UnitAction *Unit::top() const {
54 	if (this->action_stack.empty()) {
55 		throw Error{MSG(err) << "Unit stack empty - no top action exists"};
56 	}
57 	return this->action_stack.back().get();
58 }
59 
before(const UnitAction * action) const60 UnitAction *Unit::before(const UnitAction *action) const {
61 	auto start = std::find_if(
62 		std::begin(this->action_stack),
63 		std::end(this->action_stack),
64 		[action](const std::unique_ptr<UnitAction> &a) {
65 			return action == a.get();
66 		});
67 
68 	if (start != std::begin(this->action_stack) &&
69 	    start != std::end(this->action_stack)) {
70 		return (*(start - 1)).get();
71 	}
72 	return nullptr;
73 }
74 
update(time_nsec_t lastframe_duration)75 bool Unit::update(time_nsec_t lastframe_duration) {
76 
77 	// if unit is not on the map then do nothing
78 	if (!this->location) {
79 		return true;
80 	}
81 
82 	// unit is dead (not player controlled)
83 	if (this->pop_destructables) {
84 		this->erase_after(
85 			[](std::unique_ptr<UnitAction> &e) {
86 				return e->allow_interupt() || e->allow_control();
87 			}, false);
88 	}
89 
90 	/*
91 	 * the active action is on top
92 	 */
93 	if (this->has_action()) {
94 		// TODO: change the entire unit action timing
95 		//       to a higher resolution like nsecs or usecs.
96 
97 		// time as float, in milliseconds.
98 		auto time_elapsed = lastframe_duration / 1e6;
99 
100 		this->top()->update(time_elapsed);
101 
102 		// the top primary action specifies whether
103 		// secondary actions are updated
104 		if (this->top()->allow_control()) {
105 			this->update_secondary(time_elapsed);
106 		}
107 
108 		// check completion of all primary actions,
109 		// pop completed actions and anything above
110 		this->erase_after(
111 			[](std::unique_ptr<UnitAction> &e) {
112 				return e->completed();
113 			});
114 	}
115 
116 	// apply new queued commands
117 	this->apply_all_cmds();
118 
119 	return true;
120 }
121 
update_secondary(int64_t time_elapsed)122 void Unit::update_secondary(int64_t time_elapsed) {
123 	// update secondary actions and remove when completed
124 	auto position_it = std::remove_if(
125 		std::begin(this->action_secondary),
126 		std::end(this->action_secondary),
127 		[time_elapsed](std::unique_ptr<UnitAction> &action) {
128 			action->update(time_elapsed);
129 			return action->completed();
130 		});
131 	this->action_secondary.erase(position_it, std::end(this->action_secondary));
132 }
133 
apply_all_cmds()134 void Unit::apply_all_cmds() {
135 	std::lock_guard<std::mutex> lock(this->command_queue_lock);
136 	while (!this->command_queue.empty()) {
137 		auto &action = this->command_queue.front();
138 		this->apply_cmd(action.first, action.second);
139 		this->command_queue.pop();
140 	}
141 }
142 
143 
apply_cmd(std::shared_ptr<UnitAbility> ability,const Command & cmd)144 void Unit::apply_cmd(std::shared_ptr<UnitAbility> ability, const Command &cmd) {
145 	// if the interrupt flag is set, discard ongoing actions
146 	bool is_direct = cmd.has_flag(command_flag::interrupt);
147 	if (is_direct) {
148 		this->stop_actions();
149 	}
150 	if (ability->can_invoke(*this, cmd)) {
151 		ability->invoke(*this, cmd, is_direct);
152 	}
153 }
154 
155 
draw(const Engine & engine)156 void Unit::draw(const Engine &engine) {
157 
158 	// the top action decides the graphic type and action
159 	this->draw(this->location.get(), this->top()->current_graphics(), engine);
160 }
161 
162 
draw(TerrainObject * loc,const graphic_set & grpc,const Engine & engine)163 void Unit::draw(TerrainObject *loc, const graphic_set &grpc, const Engine &engine) {
164 	ENSURE(loc != nullptr, "there should always be a location for a placed unit");
165 
166 	auto top_action = this->top();
167 	auto draw_graphic = top_action->type();
168 	if (grpc.count(draw_graphic) == 0) {
169 		this->log(MSG(warn) << "Graphic not available");
170 		return;
171 	}
172 
173 	// the texture to draw with
174 	auto draw_texture = grpc.at(draw_graphic);
175 	if (!draw_texture) {
176 		this->log(MSG(warn) << "Graphic null");
177 		return;
178 	}
179 
180 	// frame specified by the current action
181 	auto draw_frame = top_action->current_frame();
182 	this->draw(loc->pos.draw, draw_texture, draw_frame, engine);
183 
184 	// draw a shadow if the graphic is available
185 	if (grpc.count(graphic_type::shadow) > 0) {
186 		auto draw_shadow = grpc.at(graphic_type::shadow);
187 		if (draw_shadow) {
188 
189 			// position without height component
190 			// TODO: terrain elevation
191 			coord::phys3 shadow_pos = loc->pos.draw;
192 			shadow_pos.up = 0;
193 			this->draw(shadow_pos, draw_shadow, draw_frame, engine);
194 		}
195 	}
196 
197 	// draw debug details
198 	top_action->draw_debug(engine);
199 }
200 
201 
draw(coord::phys3 draw_pos,std::shared_ptr<UnitTexture> graphic,unsigned int frame,const Engine & engine)202 void Unit::draw(coord::phys3 draw_pos, std::shared_ptr<UnitTexture> graphic, unsigned int frame, const Engine &engine) {
203 
204 	// players color if available
205 	unsigned color = 0;
206 	if (this->has_attribute(attr_type::owner)) {
207 		auto &own_attr = this->get_attribute<attr_type::owner>();
208 		color = own_attr.player.color;
209 	}
210 
211 	// check if object has a direction
212 	if (this->has_attribute(attr_type::direction)) {
213 
214 		// directional textures
215 		auto &d_attr = this->get_attribute<attr_type::direction>();
216 		coord::phys3_delta draw_dir = d_attr.unit_dir;
217 		graphic->draw(engine.coord, draw_pos.to_camgame(engine.coord), draw_dir, frame, color);
218 	}
219 	else {
220 		graphic->draw(engine.coord, draw_pos.to_camgame(engine.coord), frame, color);
221 	}
222 }
223 
give_ability(std::shared_ptr<UnitAbility> ability)224 void Unit::give_ability(std::shared_ptr<UnitAbility> ability) {
225 	this->ability_available.emplace(std::make_pair(ability->type(), ability));
226 }
227 
get_ability(ability_type type)228 UnitAbility *Unit::get_ability(ability_type type) {
229 	if (this->ability_available.count(type) > 0) {
230 		return this->ability_available[type].get();
231 	}
232 	return nullptr;
233 }
234 
push_action(std::unique_ptr<UnitAction> action,bool force)235 void Unit::push_action(std::unique_ptr<UnitAction> action, bool force) {
236 	// unit not being deleted -- can control unit
237 	if (force || this->accept_commands()) {
238 	    this->action_stack.push_back(std::move(action));
239 	}
240 }
241 
secondary_action(std::unique_ptr<UnitAction> action)242 void Unit::secondary_action(std::unique_ptr<UnitAction> action) {
243 	this->action_secondary.push_back(std::move(action));
244 }
245 
add_attribute(std::shared_ptr<AttributeContainer> attr)246 void Unit::add_attribute(std::shared_ptr<AttributeContainer> attr) {
247 	this->attributes.add(attr);
248 }
249 
add_attributes(const Attributes & attr)250 void Unit::add_attributes(const Attributes &attr) {
251 	this->attributes.add_copies(attr);
252 }
253 
add_attributes(const Attributes & attr,bool shared,bool unshared)254 void Unit::add_attributes(const Attributes &attr, bool shared, bool unshared) {
255 	this->attributes.add_copies(attr, shared, unshared);
256 }
257 
has_attribute(attr_type type) const258 bool Unit::has_attribute(attr_type type) const {
259 	return this->attributes.has(type);
260 }
261 
queue_cmd(const Command & cmd)262 std::shared_ptr<UnitAbility> Unit::queue_cmd(const Command &cmd) {
263 	std::lock_guard<std::mutex> lock(this->command_queue_lock);
264 
265 	// following the specified ability priority
266 	// find suitable ability for this target if available
267 	for (auto &ability : ability_priority) {
268 		auto pair = this->ability_available.find(ability);
269 		if (pair != this->ability_available.end() &&
270 		    cmd.ability()[static_cast<int>(pair->first)] && pair->second->can_invoke(*this, cmd)) {
271 			command_queue.push(std::make_pair(pair->second, cmd));
272 			return pair->second;
273 		}
274 	}
275 	return nullptr;
276 }
277 
delete_unit()278 void Unit::delete_unit() {
279 	this->pop_destructables = true;
280 }
281 
stop_gather()282 void Unit::stop_gather() {
283 	this->erase_after(
284 		[](std::unique_ptr<UnitAction> &e) {
285 			return e->name() == "gather";
286 		}, false);
287 }
288 
stop_actions()289 void Unit::stop_actions() {
290 
291 	// work around for workers continuing to work after retasking
292 	if (this->has_attribute(attr_type::worker)) {
293 		this->stop_gather();
294 	}
295 
296 	// discard all interruptible tasks
297 	this->erase_after(
298 		[](std::unique_ptr<UnitAction> &e) {
299 			return e->allow_interupt();
300 		});
301 }
302 
get_ref()303 UnitReference Unit::get_ref() {
304 	return UnitReference(this->container, id, this);
305 }
306 
get_container() const307 UnitContainer *Unit::get_container() const {
308 	return this->container;
309 }
310 
logsource_name()311 std::string Unit::logsource_name() {
312 	return "Unit " + std::to_string(this->id);
313 }
314 
erase_after(std::function<bool (std::unique_ptr<UnitAction> &)> func,bool run_completed)315 void Unit::erase_after(std::function<bool(std::unique_ptr<UnitAction> &)> func, bool run_completed) {
316 	auto position_it = std::find_if(
317 		std::begin(this->action_stack),
318 		std::end(this->action_stack),
319 		func);
320 
321 	if (position_it != std::end(this->action_stack)) {
322 		auto completed_action = std::move(*position_it);
323 
324 		// erase from the stack
325 		this->action_stack.erase(position_it, std::end(this->action_stack));
326 
327 		// perform any completion actions
328 		if (run_completed) {
329 			completed_action->on_completion();
330 		}
331 	}
332 }
333 
334 } // openage
335