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