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 /** ****************************************************************************
12 *** \file    map_zones.cpp
13 *** \author  Guillaume Anctil, drakkoon@allacrost.org
14 *** \author  Yohann Ferreira, yohann ferreira orange fr
15 *** \brief   Source file for map mode zones.
16 *** ***************************************************************************/
17 
18 #include "modes/map/map_zones.h"
19 
20 #include "modes/map/map_sprites/map_enemy_sprite.h"
21 
22 #include "utils/utils_random.h"
23 
24 using namespace vt_utils;
25 using namespace vt_common;
26 
27 namespace vt_map
28 {
29 
30 namespace private_map
31 {
32 
33 // -----------------------------------------------------------------------------
34 // ---------- MapZone Class Functions
35 // -----------------------------------------------------------------------------
36 
MapZone(uint16_t left_col,uint16_t right_col,uint16_t top_row,uint16_t bottom_row)37 MapZone::MapZone(uint16_t left_col, uint16_t right_col, uint16_t top_row, uint16_t bottom_row) :
38     _interaction_icon(nullptr)
39 {
40     AddSection(left_col, right_col, top_row, bottom_row);
41     // Register to the object supervisor
42     MapMode::CurrentInstance()->GetObjectSupervisor()->AddZone(this);
43 }
44 
~MapZone()45 MapZone::~MapZone()
46 {
47     if (_interaction_icon)
48         delete _interaction_icon;
49 }
50 
Create(uint16_t left_col,uint16_t right_col,uint16_t top_row,uint16_t bottom_row)51 MapZone* MapZone::Create(uint16_t left_col, uint16_t right_col, uint16_t top_row, uint16_t bottom_row)
52 {
53     // The zone auto registers to the object supervisor
54     // and will later handle deletion.
55     return new MapZone(left_col, right_col, top_row, bottom_row);
56 }
57 
AddSection(uint16_t left_col,uint16_t right_col,uint16_t top_row,uint16_t bottom_row)58 void MapZone::AddSection(uint16_t left_col, uint16_t right_col, uint16_t top_row, uint16_t bottom_row)
59 {
60     if(left_col >= right_col) {
61         uint16_t temp = left_col;
62         left_col = right_col;
63         right_col = temp;
64     }
65 
66     if(top_row >= bottom_row) {
67         uint16_t temp = bottom_row;
68         bottom_row = top_row;
69         top_row = temp;
70     }
71 
72     _sections.push_back(Rectangle2D(left_col, right_col, top_row, bottom_row));
73 }
74 
IsInsideZone(float pos_x,float pos_y) const75 bool MapZone::IsInsideZone(float pos_x, float pos_y) const
76 {
77     // Check each section of the zone
78     // and check whether the tile position is within the section bounds.
79     for(auto it = _sections.begin(); it != _sections.end(); ++it) {
80         if((*it).Contains(Position2D(GetFloatInteger(pos_x), GetFloatInteger(pos_y)))) {
81             return true;
82         }
83     }
84     return false;
85 }
86 
Update()87 void MapZone::Update()
88 {
89     if (_interaction_icon)
90         _interaction_icon->Update();
91 }
92 
Draw()93 void MapZone::Draw()
94 {
95     // Verify each section of the zone and check if the position is within the section bounds.
96     for(auto it = _sections.begin(); it != _sections.end(); ++it) {
97         if(_ShouldDraw(*it)) {
98             vt_video::VideoManager->DrawRectangle((it->right - it->left) * GRID_LENGTH,
99                                                   (it->bottom - it->top) * GRID_LENGTH,
100                                                    vt_video::Color(1.0f, 0.6f, 0.0f, 0.6f));
101         }
102     }
103 }
104 
RandomPosition(float & x,float & y)105 void MapZone::RandomPosition(float& x, float& y)
106 {
107     // Select a random ZoneSection
108     uint16_t i = RandomBoundedInteger(0, _sections.size() - 1);
109 
110     // Select a random x and y position inside that section
111     x = (float)RandomBoundedInteger(_sections[i].left, _sections[i].right);
112     y = (float)RandomBoundedInteger(_sections[i].top, _sections[i].bottom);
113 }
114 
SetInteractionIcon(const std::string & animation_filename)115 void MapZone::SetInteractionIcon(const std::string& animation_filename)
116 {
117     if (_interaction_icon)
118         delete _interaction_icon;
119     _interaction_icon = new vt_video::AnimatedImage();
120     if (!_interaction_icon->LoadFromAnimationScript(animation_filename)) {
121         PRINT_WARNING << "Interaction icon animation filename couldn't be loaded: " << animation_filename << std::endl;
122     }
123 }
124 
DrawInteractionIcon()125 void MapZone::DrawInteractionIcon()
126 {
127     if (!_interaction_icon)
128         return;
129 
130     for(auto it = _sections.begin(); it != _sections.end(); ++it) {
131         const Rectangle2D& section = *it;
132         if(!_ShouldDraw(section))
133             continue;
134 
135         // Determine the center position coordinates for the camera
136         Position2D pos(section.left + (section.right - section.left) / 2.0f,
137                        section.top + (section.bottom - section.top));
138 
139         MapMode* map_mode = MapMode::CurrentInstance();
140         vt_video::Color icon_color(1.0f, 1.0f, 1.0f, 0.0f);
141         float icon_alpha = 1.0f - (fabs(pos.x - map_mode->GetCamera()->GetXPosition())
142                                 + fabs(pos.y - map_mode->GetCamera()->GetYPosition())) / INTERACTION_ICON_VISIBLE_RANGE;
143         if (icon_alpha < 0.0f)
144             icon_alpha = 0.0f;
145         icon_color.SetAlpha(icon_alpha);
146 
147         vt_video::VideoManager->MoveRelative(0.0f, -1.0f * GRID_LENGTH);
148         _interaction_icon->Draw(icon_color);
149     }
150 }
151 
_ShouldDraw(const Rectangle2D & section)152 bool MapZone::_ShouldDraw(const Rectangle2D& section)
153 {
154     MapMode* map_mode = MapMode::CurrentInstance();
155 
156     // Determine if the sprite is off-screen and if so, don't draw it.
157     if (!section.IntersectsWith(map_mode->GetMapFrame().screen_edges))
158         return false;
159 
160     // Determine the center position coordinates for the camera
161     Position2D pos(section.left + (section.right - section.left) / 2.0f,
162                    section.top + (section.bottom - section.top));
163 
164     // Move the drawing cursor to the appropriate coordinates for this sprite
165     vt_video::VideoManager->Move(map_mode->GetScreenXCoordinate(pos.x),
166                                  map_mode->GetScreenYCoordinate(pos.y));
167     return true;
168 }
169 
MapZone(const MapZone &)170 MapZone::MapZone(const MapZone&)
171 {
172     throw vt_utils::Exception("Not Implemented!", __FILE__, __LINE__, __FUNCTION__);
173 }
174 
operator =(const MapZone &)175 MapZone& MapZone::operator=(const MapZone&)
176 {
177     throw vt_utils::Exception("Not Implemented!", __FILE__, __LINE__, __FUNCTION__);
178     return *this;
179 }
180 
181 // -----------------------------------------------------------------------------
182 // ---------- CameraZone Class Functions
183 // -----------------------------------------------------------------------------
184 
CameraZone(uint16_t left_col,uint16_t right_col,uint16_t top_row,uint16_t bottom_row)185 CameraZone::CameraZone(uint16_t left_col, uint16_t right_col, uint16_t top_row, uint16_t bottom_row) :
186     MapZone(left_col, right_col, top_row, bottom_row),
187     _camera_inside(false),
188     _was_camera_inside(false)
189 {
190 }
191 
Create(uint16_t left_col,uint16_t right_col,uint16_t top_row,uint16_t bottom_row)192 CameraZone* CameraZone::Create(uint16_t left_col, uint16_t right_col, uint16_t top_row, uint16_t bottom_row)
193 {
194     // The zone auto registers to the object supervisor
195     // and will later handle deletion.
196     return new CameraZone(left_col, right_col, top_row, bottom_row);
197 }
198 
Update()199 void CameraZone::Update()
200 {
201     MapZone::Update();
202 
203     _was_camera_inside = _camera_inside;
204 
205     // Update only if camera is on a real sprite
206     if(MapMode::CurrentInstance()->IsCameraOnVirtualFocus())
207         return;
208 
209     VirtualSprite *camera = MapMode::CurrentInstance()->GetCamera();
210     if(camera == nullptr) {
211         _camera_inside = false;
212     }
213     // Camera must share a context with the zone and be within its borders
214     else if(IsInsideZone(camera->GetXPosition(), camera->GetYPosition())) {
215         _camera_inside = true;
216     } else {
217         _camera_inside = false;
218     }
219 }
220 
CameraZone(const CameraZone &)221 CameraZone::CameraZone(const CameraZone&) :
222     MapZone(0, 0, 0, 0)
223 {
224     throw vt_utils::Exception("Not Implemented!", __FILE__, __LINE__, __FUNCTION__);
225 }
226 
operator =(const CameraZone &)227 CameraZone& CameraZone::operator=(const CameraZone&)
228 {
229     throw vt_utils::Exception("Not Implemented!", __FILE__, __LINE__, __FUNCTION__);
230     return *this;
231 }
232 
233 // -----------------------------------------------------------------------------
234 // ---------- EnemyZone Class Functions
235 // -----------------------------------------------------------------------------
236 
EnemyZone(uint16_t left_col,uint16_t right_col,uint16_t top_row,uint16_t bottom_row)237 EnemyZone::EnemyZone(uint16_t left_col, uint16_t right_col,
238                      uint16_t top_row, uint16_t bottom_row):
239     MapZone(left_col, right_col, top_row, bottom_row),
240     _enabled(true),
241     _roaming_restrained(true),
242     _active_enemies(0),
243     _spawns_left(-1), // Infinite spawns permitted.
244     _spawn_timer(STANDARD_ENEMY_FIRST_SPAWN_TIME),
245     _dead_timer(STANDARD_ENEMY_DEAD_TIME),
246     _spawn_zone(nullptr)
247 {
248     // Done so that when the zone updates for the first time, an inactive enemy will immediately be selected and begin spawning
249     _dead_timer.Finish();
250 }
251 
~EnemyZone()252 EnemyZone::~EnemyZone()
253 {
254     if (_spawn_zone != nullptr) {
255         delete _spawn_zone;
256         _spawn_zone = nullptr;
257     }
258 
259     for (auto& enemy_owned : _enemies_owned) {
260         if (enemy_owned != nullptr) {
261             delete enemy_owned;
262             enemy_owned = nullptr;
263         }
264     }
265     _enemies_owned.clear();
266 }
267 
Create(uint16_t left_col,uint16_t right_col,uint16_t top_row,uint16_t bottom_row)268 EnemyZone* EnemyZone::Create(uint16_t left_col, uint16_t right_col, uint16_t top_row, uint16_t bottom_row)
269 {
270     // The zone auto registers to the object supervisor
271     // and will later handle deletion.
272     return new EnemyZone(left_col, right_col, top_row, bottom_row);
273 }
274 
AddEnemy(EnemySprite * enemy,uint8_t enemy_number)275 void EnemyZone::AddEnemy(EnemySprite* enemy, uint8_t enemy_number)
276 {
277     if(enemy_number == 0) {
278         IF_PRINT_WARNING(MAP_DEBUG) << "function called with a zero value count argument" << std::endl;
279         return;
280     }
281 
282     // Prepare the first enemy
283     enemy->SetZone(this);
284     _enemies.push_back(enemy);
285 
286     // Create any additional copies of the enemy and add them as well
287     for (uint8_t i = 1; i < enemy_number; ++i) {
288         EnemySprite* copy = new EnemySprite(*enemy);
289         copy->Reset();
290         _enemies.push_back(copy);
291         _enemies_owned.push_back(copy);
292     }
293 }
294 
AddSpawnSection(uint16_t left_col,uint16_t right_col,uint16_t top_row,uint16_t bottom_row)295 void EnemyZone::AddSpawnSection(uint16_t left_col, uint16_t right_col, uint16_t top_row, uint16_t bottom_row)
296 {
297     if(left_col >= right_col) {
298         uint16_t temp = left_col;
299         left_col = right_col;
300         right_col = temp;
301     }
302 
303     if(top_row >= bottom_row) {
304         uint16_t temp = bottom_row;
305         bottom_row = top_row;
306         top_row = temp;
307     }
308 
309     // Make sure that this spawn section fits entirely inside one of the roaming sections
310     bool okay_to_add = false;
311     for(Rectangle2D section : _sections) {
312         if (section.Contains(Position2D(left_col, top_row)) &&
313                 section.Contains(Position2D(right_col, bottom_row))) {
314             okay_to_add = true;
315             break;
316         }
317     }
318 
319     if(okay_to_add == false) {
320         IF_PRINT_WARNING(MAP_DEBUG) << "could not add section as it did not fit inside any single roaming zone section" << std::endl;
321         return;
322     }
323 
324     // Create the spawn zone if it does not exist and add the new section
325     if(_spawn_zone == nullptr) {
326         _spawn_zone = new MapZone(left_col, right_col, top_row, bottom_row);
327     } else {
328         _spawn_zone->AddSection(left_col, right_col, top_row, bottom_row);
329     }
330 }
331 
EnemyDead()332 void EnemyZone::EnemyDead()
333 {
334     if(_active_enemies == 0) {
335         IF_PRINT_WARNING(MAP_DEBUG) << "function called when no enemies were active" << std::endl;
336     } else {
337         --_active_enemies;
338     }
339 }
340 
Update()341 void EnemyZone::Update()
342 {
343     // When spawning an enemy in a random zone location, sometimes it is occupied by another
344     // object or that section is unwalkable. We try only a few different spawn locations before
345     // giving up and waiting for the next call to Update(). Otherwise this function could
346     // potentially take a noticable amount of time to complete
347     const int8_t SPAWN_RETRIES = 50;
348 
349     // Don't update when the zone is disabled.
350     if (!_enabled)
351         return;
352 
353     // Test whether a respawn is still permitted
354     if (_spawns_left == 0)
355         return;
356 
357     if (_enemies.empty())
358         return;
359 
360     MapZone::Update();
361 
362     // Update timers
363     _spawn_timer.Update();
364     _dead_timer.Update();
365 
366     // If we're in the process of spawning an enemy, exit immediately as we want to wait for the timer to continue
367     if (_spawn_timer.IsRunning()) {
368         return;
369     }
370 
371     // If no enemies are inactive (in the dead state), there's nothing left to do
372     if (_active_enemies >= _enemies.size()) {
373         return;
374     }
375 
376     // If there are dead enemies, no enemies are respawning, and the dead timer is not active, begin the dead timer
377     if (_dead_timer.IsInitial()) {
378         _dead_timer.Run();
379         return;
380     }
381     // If the dead timer hasn't completed, there's nothing left to do
382     else if (_dead_timer.IsFinished() == false) {
383         return;
384     }
385 
386     // When the dead timer completes, spawn in a new enemy
387     // Select a dead enemy to spawn
388     uint32_t index = 0;
389     for(uint32_t i = 0; i < _enemies.size(); ++i) {
390         if (_enemies[i]->IsDead()) {
391             index = i;
392             break;
393         }
394     }
395 
396     // Used to retain random position coordinates in the zone
397     float x = 0.0f;
398     float y = 0.0f;
399     // Number of times to try finding a valid spawning location
400     int8_t retries = SPAWN_RETRIES;
401     // Holds the result of a collision detection check
402     uint32_t collision = NO_COLLISION;
403 
404     // Select a random position inside the zone to place the spawning enemy
405     _enemies[index]->SetCollisionMask(WALL_COLLISION | CHARACTER_COLLISION);
406     MapZone* spawning_zone = nullptr;
407     if (!HasSeparateSpawnZone()) {
408         spawning_zone = this;
409     } else {
410         spawning_zone = _spawn_zone;
411     }
412     // If there is a collision, retry a different location
413     do {
414         spawning_zone->RandomPosition(x, y);
415         _enemies[index]->SetPosition(x, y);
416         collision = MapMode::CurrentInstance()->GetObjectSupervisor()->DetectCollision(_enemies[index],
417                     _enemies[index]->GetXPosition(),
418                     _enemies[index]->GetYPosition(),
419                     nullptr);
420     } while (collision != NO_COLLISION && --retries > 0);
421 
422     // Otherwise, spawn the enemy and reset the spawn timer
423     if (collision == NO_COLLISION) {
424         // Set the correct timer duration to whether do a quick first spawn,
425         // or a longer standard spawn time from the second time.
426         _dead_timer.Reset();
427         _spawn_timer.SetDuration(_enemies[index]->GetTimeToSpawn());
428         _spawn_timer.Reset();
429         _spawn_timer.Run();
430         _enemies[index]->ChangeStateSpawning();
431         ++_active_enemies;
432     } else {
433         PRINT_WARNING << "Couldn't spawn a monster within " << SPAWN_RETRIES
434                       << " tries. Check the enemy zones of map script:"
435                       << MapMode::CurrentInstance()->GetMapScriptFilename() << std::endl;
436     }
437 } // void EnemyZone::Update()
438 
Draw()439 void EnemyZone::Draw()
440 {
441     // Don't draw when the zone is disabled.
442     if (!_enabled)
443         return;
444 
445     // Verify each section of the zone and check if the position is within the section bounds.
446     for(auto it = _sections.begin(); it != _sections.end(); ++it) {
447         if(_ShouldDraw(*it)) {
448             vt_video::VideoManager->DrawRectangle((it->right - it->left) * GRID_LENGTH,
449                                                   (it->bottom - it->top) * GRID_LENGTH,
450                                                    vt_video::Color(0.0f, 0.0f, 0.0f, 0.5f));
451         }
452     }
453 }
454 
EnemyZone(const EnemyZone &)455 EnemyZone::EnemyZone(const EnemyZone&) :
456     MapZone(0, 0, 0, 0)
457 {
458     throw vt_utils::Exception("Not Implemented!", __FILE__, __LINE__, __FUNCTION__);
459 }
460 
operator =(const EnemyZone &)461 EnemyZone& EnemyZone::operator=(const EnemyZone&)
462 {
463     throw vt_utils::Exception("Not Implemented!", __FILE__, __LINE__, __FUNCTION__);
464     return *this;
465 }
466 
467 } // namespace private_map
468 
469 } // namespace vt_map
470