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