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