1 ///////////////////////////////////////////////////////////////////////////////
2 // Copyright (C) 2004-2011 by The Allacrost Project
3 // Copyright (C) 2012-2016 by Bertram (Valyria Tear)
4 // All Rights Reserved
5 //
6 // This code is licensed under the GNU GPL version 2. It is free software
7 // and you may modify it and/or redistribute it under the terms of this license.
8 // See https://www.gnu.org/copyleft/gpl.html for details.
9 ///////////////////////////////////////////////////////////////////////////////
10
11 #include "modes/map/map_sprites/map_enemy_sprite.h"
12
13 #include "modes/map/map_mode.h"
14 #include "modes/map/map_zones.h"
15
16 using namespace vt_common;
17
18 namespace vt_map
19 {
20
21 namespace private_map
22 {
23
EnemySprite()24 EnemySprite::EnemySprite() :
25 MapSprite(GROUND_OBJECT),
26 _zone(nullptr),
27 _color(1.0f, 1.0f, 1.0f, 0.0f),
28 _aggro_range(8.0f),
29 _time_before_new_destination(1200),
30 _time_to_spawn(STANDARD_ENEMY_FIRST_SPAWN_TIME),
31 _time_to_respawn(STANDARD_ENEMY_SPAWN_TIME),
32 _is_boss(false),
33 _use_path(false)
34 {
35 _object_type = ENEMY_TYPE;
36 _moving = false;
37 Reset();
38 }
39
~EnemySprite()40 EnemySprite::~EnemySprite()
41 {
42 }
43
Create()44 EnemySprite* EnemySprite::Create()
45 {
46 // The object auto register to the object supervisor
47 // and will later handle deletion.
48 return new EnemySprite();
49 }
50
Reset()51 void EnemySprite::Reset()
52 {
53 _updatable = false;
54 _collision_mask = NO_COLLISION;
55 _state = DEAD;
56 _time_elapsed = 0;
57 _color.SetAlpha(0.0f);
58
59 // Reset path finding info
60 _last_node_position = Position2D(0.0f, 0.0f);
61 _current_node = Position2D(0.0f, 0.0f);
62 _destination = Position2D(0.0f, 0.0f);
63 _current_node_id = 0;
64 _path.clear();
65 _use_path = false;
66
67 // Reset the currently selected way point
68 _current_way_point_id = 0;
69 }
70
AddEnemy(uint32_t enemy_id,float position_x,float position_y)71 void EnemySprite::AddEnemy(uint32_t enemy_id, float position_x, float position_y)
72 {
73 if(_enemy_parties.empty()) {
74 IF_PRINT_WARNING(MAP_DEBUG) << "can not add new enemy when no parties have been declared" << std::endl;
75 return;
76 }
77
78 vt_battle::BattleEnemyInfo enemy_info(enemy_id, position_x, position_y);
79 _enemy_parties.back().push_back(enemy_info);
80 }
81
82 // Static empty enemy party used to prevent temporary reference returns.
83 static const std::vector<vt_battle::BattleEnemyInfo> empty_enemy_party;
84
RetrieveRandomParty() const85 const std::vector<vt_battle::BattleEnemyInfo>& EnemySprite::RetrieveRandomParty() const
86 {
87 if(_enemy_parties.empty()) {
88 PRINT_ERROR << "No enemy parties exist and none can be created." << std::endl;
89 return empty_enemy_party;
90 }
91
92 return _enemy_parties[rand() % _enemy_parties.size()];
93 }
94
ChangeStateHostile()95 void EnemySprite::ChangeStateHostile()
96 {
97 _updatable = true;
98 _state = HOSTILE;
99 _collision_mask = WALL_COLLISION | CHARACTER_COLLISION;
100 _color.SetAlpha(1.0);
101 // Set the next spawn time, usually longer than the first one.
102 _time_to_spawn = _time_to_respawn;
103 }
104
Update()105 void EnemySprite::Update()
106 {
107 switch(_state) {
108 // Gradually increase the alpha while the sprite is fading in during spawning
109 case SPAWNING:
110 _time_elapsed += vt_system::SystemManager->GetUpdateTime();
111 if(_color.GetAlpha() < 1.0f) {
112 _color.SetAlpha((_time_elapsed / static_cast<float>(_time_to_spawn)) * 1.0f);
113 } else {
114 ChangeStateHostile();
115 }
116 break;
117
118 // Set the sprite's direction so that it seeks to collide with the map camera's position
119 case HOSTILE:
120 _HandleHostileUpdate();
121 break;
122
123 // Do nothing if the sprite is in the DEAD state, or any other state
124 case DEAD:
125 default:
126 break;
127 }
128 } // void EnemySprite::Update()
129
ChangeStateDead()130 void EnemySprite::ChangeStateDead() {
131 Reset();
132 if(_zone) _zone->EnemyDead();
133 }
134
_HandleHostileUpdate()135 void EnemySprite::_HandleHostileUpdate()
136 {
137 // Holds the x and y deltas between the sprite and map camera coordinate pairs
138 VirtualSprite* camera = MapMode::CurrentInstance()->GetCamera();
139 float camera_x = camera->GetXPosition();
140 float camera_y = camera->GetYPosition();
141
142 float xdelta = GetXPosition() - camera_x;
143 float ydelta = GetYPosition() - camera_y;
144 float abs_xdelta = fabs(xdelta);
145 float abs_ydelta = fabs(ydelta);
146
147 // Don't update enemies that are too far away...
148 if (abs_xdelta > SCREEN_GRID_X_LENGTH || abs_ydelta > SCREEN_GRID_Y_LENGTH)
149 return;
150
151 // Updates sprite animation and collision fix.
152 MapSprite::Update();
153
154 // Test whether the monster has spotted its target.
155 bool player_in_aggro_range = false;
156 if(abs_xdelta <= _aggro_range && abs_ydelta <= _aggro_range)
157 player_in_aggro_range = true;
158
159 // Handle chasing the character
160 MapMode* map_mode = MapMode::CurrentInstance();
161 if (player_in_aggro_range && map_mode->AttackAllowed()) {
162 // We first cancel the potential previous path.
163 if (!_path.empty()) {
164 // We cancel any previous path
165 _path.clear();
166 // We set the correct mask before moving normally
167 _collision_mask = WALL_COLLISION | CHARACTER_COLLISION;
168 _use_path = false;
169 }
170
171 // Check whether we're already colliding, so that even when not moving
172 // we can start a battle.
173 if (this->IsCollidingWith(camera))
174 map_mode->StartEnemyEncounter(this);
175
176 // Make the monster go toward the character
177 if(xdelta > -0.5 && xdelta < 0.5 && ydelta < 0)
178 SetDirection(SOUTH);
179 else if(xdelta > -0.5 && xdelta < 0.5 && ydelta > 0)
180 SetDirection(NORTH);
181 else if(ydelta > -0.5 && ydelta < 0.5 && xdelta > 0)
182 SetDirection(WEST);
183 else if(ydelta > -0.5 && ydelta < 0.5 && xdelta < 0)
184 SetDirection(EAST);
185 else if(xdelta < 0 && ydelta < 0)
186 SetDirection(MOVING_SOUTHEAST);
187 else if(xdelta < 0 && ydelta > 0)
188 SetDirection(MOVING_NORTHEAST);
189 else if(xdelta > 0 && ydelta < 0)
190 SetDirection(MOVING_SOUTHWEST);
191 else
192 SetDirection(MOVING_NORTHWEST);
193 _moving = true;
194
195 return;
196 }
197
198 // Handle monsters with way points.
199 if (!_way_points.empty()) {
200
201 // Update the wait time until next path between two way points.
202 if (!_use_path || !_moving)
203 _time_elapsed += vt_system::SystemManager->GetUpdateTime();
204
205 if (_path.empty() && _time_elapsed >= _time_before_new_destination) {
206 if (!_SetPathToNextWayPoint()) {
207 // Fall back to simple movement mode
208 SetRandomDirection();
209 _moving = true;
210 }
211 // The sprite is now finding its way back into the zone
212 _time_elapsed = 0;
213 }
214
215 if (_use_path && _path.empty()) {
216 // The sprite is waiting for the next destination.
217 _moving = false;
218 // We then reset the correct walk mask
219 _collision_mask = WALL_COLLISION | CHARACTER_COLLISION;
220 }
221
222 // Update the sprite direction according to the path
223 _UpdatePath();
224 return;
225 }
226
227 // Determine standard monster behavior regarding its zone.
228
229 // Update the wait time until two set destination.
230 _time_elapsed += vt_system::SystemManager->GetUpdateTime();
231
232 // Check whether the monster can get out of the zone.
233 bool can_get_out_of_zone = true;
234 if (_zone && _zone->IsRoamingRestrained() && !player_in_aggro_range)
235 can_get_out_of_zone = false;
236
237 if (!can_get_out_of_zone) {
238 // Check whether the monster is inside its zone
239 bool out_of_zone = false;
240 if (_zone && !_zone->IsInsideZone(GetXPosition(), GetYPosition()))
241 out_of_zone = true;
242
243 if (out_of_zone && _time_elapsed >= _time_before_new_destination) {
244 // The sprite is now finding its way back into the zone
245 float x_dest;
246 float y_dest;
247 _zone->RandomPosition(x_dest, y_dest);
248 LookAt(x_dest, y_dest);
249 _moving = true;
250
251 _time_elapsed = 0;
252 return;
253 }
254 }
255
256 // Make the monster wander randomly in other cases
257 if (_time_elapsed >= _time_before_new_destination) {
258 SetRandomDirection();
259 _moving = true;
260 _time_elapsed = 0;
261 }
262 }
263
Draw()264 void EnemySprite::Draw()
265 {
266 // Otherwise, only draw it if it is not in the DEAD state
267 if (!MapObject::ShouldDraw() || _state == DEAD)
268 return;
269
270 _animation->at(_current_anim_direction).Draw(_color);
271
272 // Draw collision rectangle if the debug view is on.
273 if (!vt_video::VideoManager->DebugInfoOn())
274 return;
275
276 Position2D pos = vt_video::VideoManager->GetDrawPosition();
277 Rectangle2D rect = GetScreenCollisionRectangle(pos.x, pos.y);
278 vt_video::VideoManager->DrawRectangle(rect.right - rect.left,
279 rect.bottom - rect.top,
280 vt_video::Color(1.0f, 0.0f, 0.0f, 0.6f));
281 }
282
AddWayPoint(float destination_x,float destination_y)283 void EnemySprite::AddWayPoint(float destination_x, float destination_y)
284 {
285 Position2D destination(destination_x, destination_y);
286
287 // Check whether the way point is already existing
288 for (uint32_t i = 0; i < _way_points.size(); ++i) {
289 if (_way_points[i].x == destination_x && _way_points[i].y == destination_y) {
290 PRINT_WARNING << "Way point already added: (" << destination_x << ", "
291 << destination_y << ")" << std::endl;
292 return;
293 }
294 }
295
296 _way_points.push_back(destination);
297 }
298
_SetPathToNextWayPoint()299 bool EnemySprite::_SetPathToNextWayPoint()
300 {
301 //! Will be set to true if _SetDestination() is succeeding
302 _use_path = false;
303
304 // There must be at least two way points to permit supporting those.
305 if (_way_points.size() < 2)
306 return false;
307
308 if (_current_way_point_id >= _way_points.size())
309 _current_way_point_id = 0;
310
311 bool ret = _SetDestination(_way_points[_current_way_point_id].x, _way_points[_current_way_point_id].y, 0);
312 ++_current_way_point_id;
313
314 return ret;
315 }
316
_UpdatePath()317 void EnemySprite::_UpdatePath()
318 {
319 if(!_use_path || _path.empty())
320 return;
321
322 const Position2D sprite_position = GetPosition();
323 const float distance_moved = CalculateDistanceMoved();
324
325 // Check whether the sprite has arrived at the position of the current node
326 if(vt_utils::IsFloatEqual(sprite_position.x, _current_node.x, distance_moved)
327 && vt_utils::IsFloatEqual(sprite_position.y, _current_node.y, distance_moved)) {
328 ++_current_node_id;
329
330 if(_current_node_id < _path.size()) {
331 _current_node.x = _path[_current_node_id].x;
332 _current_node.y = _path[_current_node_id].y;
333 }
334 }
335 // If the sprite has moved to a new position other than the next node, adjust its direction so it is trying to move to the next node
336 else if((sprite_position.x != _last_node_position.x)
337 || (sprite_position.y != _last_node_position.y)) {
338 _last_node_position = sprite_position;
339 }
340
341 _SetSpritePathDirection();
342
343 // End the path event
344 if(vt_utils::IsFloatEqual(sprite_position.x, _destination.x, distance_moved)
345 && vt_utils::IsFloatEqual(sprite_position.y, _destination.y, distance_moved)) {
346 _path.clear();
347 }
348 }
349
_SetDestination(float destination_x,float destination_y,uint32_t max_cost)350 bool EnemySprite::_SetDestination(float destination_x, float destination_y, uint32_t max_cost)
351 {
352 _path.clear();
353 _use_path = false;
354
355 uint32_t dest_x = static_cast<uint32_t>(destination_x);
356 uint32_t dest_y = static_cast<uint32_t>( destination_y);
357 uint32_t pos_x = static_cast<uint32_t>(GetXPosition());
358 uint32_t pos_y = static_cast<uint32_t>(GetYPosition());
359
360 // Don't check the path if the sprite is there.
361 if (pos_x == dest_x && pos_y == dest_y)
362 return false;
363
364 Position2D dest(destination_x, destination_y);
365 // We set the correct mask before finding the path
366 _collision_mask = WALL_COLLISION | CHARACTER_COLLISION;
367 _path = MapMode::CurrentInstance()->GetObjectSupervisor()->FindPath(this, dest, max_cost);
368
369 if (_path.empty())
370 return false;
371
372 // But remove wall collision afterward to avoid making it stuck in corners.
373 // Note: this function is only called when hostile, son we don't deal with
374 // the spawning collision mask.
375 _collision_mask = CHARACTER_COLLISION;
376
377 _current_node_id = 0;
378 _last_node_position = GetPosition();
379 _destination = dest;
380
381 _current_node = _path[_current_node_id];
382
383 _moving = true;
384 _use_path = true;
385 return true;
386 }
387
_SetSpritePathDirection()388 void EnemySprite::_SetSpritePathDirection()
389 {
390 if (!_use_path || _path.empty())
391 return;
392
393 uint16_t direction = 0;
394
395 const Position2D sprite_position = GetPosition();
396 const float distance_moved = CalculateDistanceMoved();
397
398 if(sprite_position.y - _current_node.y > distance_moved) {
399 direction |= NORTH;
400 } else if(sprite_position.y - _current_node.y < -distance_moved) {
401 direction |= SOUTH;
402 }
403
404 if(sprite_position.x - _current_node.x > distance_moved) {
405 direction |= WEST;
406 } else if(sprite_position.x - _current_node.x < -distance_moved) {
407 direction |= EAST;
408 }
409
410 // Determine if the sprite should move diagonally to the next node
411 if((direction & (NORTH | SOUTH)) && (direction & (WEST | EAST))) {
412 switch(direction) {
413 case(NORTH | WEST):
414 direction = MOVING_NORTHWEST;
415 break;
416 case(NORTH | EAST):
417 direction = MOVING_NORTHEAST;
418 break;
419 case(SOUTH | WEST):
420 direction = MOVING_SOUTHWEST;
421 break;
422 case(SOUTH | EAST):
423 direction = MOVING_SOUTHEAST;
424 break;
425 }
426 }
427
428 SetDirection(direction);
429 }
430
431 } // namespace private_map
432
433 } // namespace vt_map
434