Pac-Man Evolution
Loading...
Searching...
No Matches
Objects.cpp
1/*----------------------------------------------------------------------
2Pac-Man Evolution - Roberto Prieto
3Copyright (C) 2018-2024 MegaStorm Systems
4contact@megastormsystems.com - http://www.megastormsystems.com
5
6This software is provided 'as-is', without any express or implied
7warranty. In no event will the authors be held liable for any damages
8arising from the use of this software.
9
10Permission is granted to anyone to use this software for any purpose,
11including commercial applications, and to alter it and redistribute it
12freely, subject to the following restrictions:
13
141. The origin of this software must not be misrepresented; you must not
15claim that you wrote the original software. If you use this software
16in a product, an acknowledgment in the product documentation would be
17appreciated but is not required.
182. Altered source versions must be plainly marked as such, and must not be
19misrepresented as being the original software.
203. This notice may not be removed or altered from any source distribution.
21
22------------------------------------------------------------------------
23
24Object, State and Actor classes
25
26------------------------------------------------------------------------ */
27
28#include "Objects.h"
29#include "GameField.h"
30#include "ResourceManager.h"
31#include "BrainsFactory.h"
32#include <math.h>
33
34// Object constructor.
35Object::Object(Sint32 iObjID, Sint32 iMX, Sint32 iMY, GameField* GF)
36{
37 Sint32 j;
38 float fSpeed;
39 Main& mC64 = Main::Instance();
40 Sprite* sprObj;
41
42 // Inits vars
43 iID = iObjID;
44 iPositionX = iStartingPositionX = iMX * MAZE_TILE_SIZE;
45 iPositionY = iStartingPositionY = iMY * MAZE_TILE_SIZE;
46 iMazeX = iMazePrevX = iMX;
47 iMazeY = iMazePrevY = iMY;
48 iMazeRenderingX = iMazeRenderingY = 0;
49 bRenderExecuteSync = false;
50
51 // Pointer to needed components
52 pGameField = GF;
53
54 // Get a cloned sprite for this object. It should fit on the MAZE_TILE_SIZE x MAZE_TILE_SIZE. ToDO: autofit?
55 switch(iObjID)
56 {
57 case PME_OBJECT_PACMAN:
58 sprID = ResourceManager::Instance().get(RM_SPR_PACMAN);
59 break;
60 case PME_OBJECT_GHOST_RED:
61 sprID = ResourceManager::Instance().get(RM_SPR_GHOSTRED);
62 break;
63 case PME_OBJECT_GHOST_PINK:
64 sprID = ResourceManager::Instance().get(RM_SPR_GHOSTPINK);
65 break;
66 case PME_OBJECT_GHOST_BLUE:
67 sprID = ResourceManager::Instance().get(RM_SPR_GHOSTBLUE);
68 break;
69 case PME_OBJECT_GHOST_ORANGE:
70 sprID = ResourceManager::Instance().get(RM_SPR_GHOSTORANGE);
71 break;
72 case PME_OBJECT_PELLET_POWER1:
73 case PME_OBJECT_PELLET_POWER2:
74 case PME_OBJECT_PELLET_POWER3:
75 case PME_OBJECT_PELLET_POWER4:
76 sprID = ResourceManager::Instance().get(RM_SPR_PELLETPOWER);
77 // Randomize the speed up to 10%
78 sprObj = mC64.ISpriteMgr().get(sprID);
79 j = mC64.ITool().randWELL() % 10;
80 fSpeed = (float)j / 100.0f;
81 fSpeed += 1.0f;
82 sprObj->setSpeed(fSpeed);
83 #ifdef DEBUG_INTERNAL
84 mC64.ILogMgr().get()->msg(LML_NORMAL, " [Object] Power Pellet speed set to '%.2f'.\n", fSpeed);
85 #endif
86 break;
87 default:
88 mC64.ILogMgr().get()->msg(LML_NORMAL, " [Object] Unknown object id '%d'\n", iObjID);
89 }
90
91 #ifdef DEBUG_INTERNAL
92 Main::Instance().ILogMgr().get()->msg(LML_LOW, " [Object] Object ID '%d' created.\n", iID);
93 #endif
94}
95
96// Object destructor.
97Object::~Object()
98{
99 if(sprID != 0) Main::Instance().ISpriteMgr().close(sprID); // Remove our clon
100 sprID = 0;
101 pGameField = nullptr;
102 #ifdef DEBUG_INTERNAL
103 Main::Instance().ILogMgr().get()->msg(LML_LOW, " [Object] Object ID '%d' deleted.\n", iID);
104 #endif
105}
106
107// Object logic execution. Interface method.
108Sint32 Object::execute()
109{
110 if(bRenderExecuteSync) // Delay the execution till the first render() is performed
111 {
112 Main::Instance().ISpriteMgr().get(sprID)->setPosition(iMazeRenderingX + iPositionX, iMazeRenderingY + iPositionY, 1);
113 }
114 return 0;
115}
116
117// Object render. Interface method.
118Sint32 Object::render(Sint32 iSX, Sint32 iSY)
119{
120 iMazeRenderingX = iSX;
121 iMazeRenderingY = iSY;
122 if(!bRenderExecuteSync)
123 {
124 bRenderExecuteSync = true; // Synchronize render-execute
125 execute();
126 }
127 if(sprID != 0) Main::Instance().ISpriteMgr().get(sprID)->render();
128 return 0;
129}
130
131// Debug information. Not used in this object. Interface method.
132Sint32 Object::debug(Sint32 iMode)
133{
134 return 0;
135}
136
137// Message sent when PacMan is death, pause all ghost movements. Not used in this object. Interface method.
138Sint32 Object::msgPause()
139{
140 return 0;
141}
142
143// Message for going to Init state. Interface method.
144Sint32 Object::msgGoInit()
145{
146 iPositionX = iStartingPositionX;
147 iPositionY = iStartingPositionY;
148 iMazeX = iPositionX / MAZE_TILE_SIZE;
149 iMazeY = iPositionY / MAZE_TILE_SIZE;
150 return 0;
151}
152
153// Message sent when PacMan eats a power pellet. Not used in this object. Interface method.
154Sint32 Object::msgPelletPowerEaten(Sint32 iX, Sint32 iY)
155{
156 return 0;
157}
158
159// Message sent when a PacMan/Ghost collision happens. Not used in this object. Interface method.
160Sint32 Object::msgGhostCollision()
161{
162 return 0;
163}
164
165// Get ID.
166Sint32 Object::getID()
167{
168 return iID;
169}
170
171// Get name
172void Object::getName(string &sN)
173{
174 switch(iID)
175 {
176 case PME_OBJECT_PACMAN:
177 sN = "PacMan";
178 break;
179 case PME_OBJECT_GHOST_RED:
180 sN = "RedGhost";
181 break;
182 case PME_OBJECT_GHOST_PINK:
183 sN = "PinkGhost";
184 break;
185 case PME_OBJECT_GHOST_BLUE:
186 sN = "BlueGhost";
187 break;
188 case PME_OBJECT_GHOST_ORANGE:
189 sN = "OrangeGhost";
190 break;
191 case PME_OBJECT_PELLET_POWER1:
192 sN = "PowerPellet1";
193 break;
194 case PME_OBJECT_PELLET_POWER2:
195 sN = "PowerPellet2";
196 break;
197 case PME_OBJECT_PELLET_POWER3:
198 sN = "PowerPellet3";
199 break;
200 case PME_OBJECT_PELLET_POWER4:
201 sN = "PowerPellet4";
202 break;
203 }
204}
205
206// Get maze position
207void Object::getPositionMaze(Sint32 &iX, Sint32 &iY)
208{
209 iX = iMazeX;
210 iY = iMazeY;
211}
212
213// Get direction.
214void Object::getDirection(Sint32 &iX, Sint32 &iY)
215{
216 Sint32 iState;
217
218 // Default values
219 iX = iY = 0;
220
221 // Use current animation state of the sprite for querying the direction
222 Main::Instance().ISpriteMgr().get(sprID)->getAnimState(-1, &iState);
223 iState = SPR_GET_HIGHSTATE(iState);
224
225 if(iState == SPR_STATE_UP) --iY;
226 else if(iState == SPR_STATE_DOWN) ++iY;
227 else if(iState == SPR_STATE_LEFT) --iX;
228 else if(iState == SPR_STATE_RIGHT) ++iX;
229}
230
231// ---------------- State class ----------------
232State::State(const string& sN)
233{
234 sName = sN;
235}
236
237State::~State() {}
238
239void State::enter(Actor* pObj)
240{
241#ifdef DEBUG_FSM
242 string sObjName;
243 pObj->getName(sObjName);
244 Main::Instance().ILogMgr().get()->msg(LML_INFO, "[%d] '%s' enters '%s' state\n", Main::Instance().ITimer().getTicksNow(), sObjName.c_str(), sName.c_str());
245#endif
246}
247
248void State::exit(Actor* pObj)
249{
250#ifdef DEBUG_FSM
251 string sObjName;
252 pObj->getName(sObjName);
253 Main::Instance().ILogMgr().get()->msg(LML_INFO, "[%d] '%s' exits '%s' state\n", Main::Instance().ITimer().getTicksNow(), sObjName.c_str(), sName.c_str());
254#endif
255}
256
257void State::getName(string& sN)
258{
259 sN = sName;
260}
261
262
263// ---------------- Actor class ----------------
264// Actor constructor.
265Actor::Actor(Sint32 iObjID, Sint32 iMX, Sint32 iMY, GameField* GF) : Object(iObjID, iMX, iMY, GF)
266{
267 Sprite* sprObj;
268
269 // Init vars
270 iSpeed = 0;
271 eSS = SS_DEFAULT;
272 eAM = AM_NEXT_STEP;
273 vPath.clear();
274 pointNextStep.iX = pointNextStep.iY = 0;
275 pointTarget.iX = pointTarget.iY = 0;
276
277 // Get a cloned target sprite for this object. It should fit on the MAZE_TILE_SIZE x MAZE_TILE_SIZE. ToDO: autofit?
278 switch(iObjID)
279 {
280 case PME_OBJECT_PACMAN:
281 sprTargetID = ResourceManager::Instance().get(RM_SPR_TARGETS);
282 sprObj = Main::Instance().ISpriteMgr().get(sprTargetID);
283 sprObj->selectAnim(4);
284 break;
285 case PME_OBJECT_GHOST_RED:
286 sprTargetID = ResourceManager::Instance().get(RM_SPR_TARGETS);
287 sprObj = Main::Instance().ISpriteMgr().get(sprTargetID);
288 sprObj->selectAnim(0);
289 break;
290 case PME_OBJECT_GHOST_PINK:
291 sprTargetID = ResourceManager::Instance().get(RM_SPR_TARGETS);
292 sprObj = Main::Instance().ISpriteMgr().get(sprTargetID);
293 sprObj->selectAnim(1);
294 break;
295 case PME_OBJECT_GHOST_BLUE:
296 sprTargetID = ResourceManager::Instance().get(RM_SPR_TARGETS);
297 sprObj = Main::Instance().ISpriteMgr().get(sprTargetID);
298 sprObj->selectAnim(2);
299 break;
300 case PME_OBJECT_GHOST_ORANGE:
301 sprTargetID = ResourceManager::Instance().get(RM_SPR_TARGETS);
302 sprObj = Main::Instance().ISpriteMgr().get(sprTargetID);
303 sprObj->selectAnim(3);
304 break;
305 default:
306 sprTargetID = 0;
307 }
308 idBrain = -1;
309 pCurrentState = nullptr;
310 pPreviousState = nullptr;
311 msgGoInit();
312
313 #ifdef DEBUG_INTERNAL
314 Main::Instance().ILogMgr().get()->msg(LML_LOW, " [Actor] Actor ID '%d' created.\n", iID);
315 #endif
316}
317
318// Actor destructor.
319Actor::~Actor()
320{
321 pCurrentState = nullptr;
322 pPreviousState = nullptr;
323
324 if(sprTargetID != 0) Main::Instance().ISpriteMgr().close(sprTargetID); // Remove our clon
325 sprTargetID = 0;
326
327 iSpeed = 0;
328 eSS = SS_DEFAULT;
329
330 #ifdef DEBUG_INTERNAL
331 Main::Instance().ILogMgr().get()->msg(LML_LOW, " [Actor] Actor ID '%d' deleted.\n", iID);
332 #endif
333}
334
335// Execute the actor and perform all movement tasks
336Sint32 Actor::execute()
337{
338 Sint32 iTmpX, iTmpY;
339
340 // With no brain, exit
341 if(idBrain == -1) return -1;
342
343 // Call base method (set the object sprite position)
344 Object::execute();
345
346 // Follow the given path. The path must be a valid one.
347 if(eAM != AM_DISABLED && vPath.size() > 0)
348 {
349 iTmpX = iMazeX;
350 iTmpY = iMazeY;
351
352 // Get next step
353 if(eAM == AM_NEXT_STEP)
354 {
355 pointNextStep = vPath.back();
356 eAM = AM_WORKING;
357 }
358
359 // Go to next step. Calculations is done in pixels
360 if(eAM == AM_WORKING)
361 {
362 // Detect left tunnel
363 if(iPositionX < (-(MAZE_TILE_SIZE / 2)))
364 {
365 pGameField->moveTo(iID, iMazeX, iMazeY, MAZE_WIDTH - 1, iMazeY);
366 iMazeX = MAZE_WIDTH - 1;
367 iPositionX = (iMazeX * MAZE_TILE_SIZE) + (MAZE_TILE_SIZE / 2);
368 #ifdef DEBUG_INTERNAL
369 Main::Instance().ILogMgr().get()->msg(LML_LOW, "tunel left\n");
370 #endif
371 pointNextStep.iX = iMazeX;
372 }
373 // Detect right tunnel
374 else if(iPositionX >((MAZE_WIDTH - 1) * MAZE_TILE_SIZE) + (MAZE_TILE_SIZE / 2))
375 {
376 pGameField->moveTo(iID, iMazeX, iMazeY, 0, iMazeY);
377 iMazeX = 0;
378 iPositionX = -(MAZE_TILE_SIZE / 2);
379 #ifdef DEBUG_INTERNAL
380 Main::Instance().ILogMgr().get()->msg(LML_LOW, "tunel right\n");
381 #endif
382 pointNextStep.iX = iMazeX;
383 }
384
385 // Y axis
386 if((pointNextStep.iY * MAZE_TILE_SIZE) > iPositionY)
387 {
388 moveY(1);
389 // Control "overstep" as a state change could hit us in the middle of a tile
390 if((pointNextStep.iY * MAZE_TILE_SIZE) < iPositionY)
391 {
392 iPositionY = pointNextStep.iY * MAZE_TILE_SIZE;
393 #ifdef DEBUG_INTERNAL
394 Main::Instance().ILogMgr().get()->msg(LML_LOW, "Y+ overstep!\n");
395 #endif
396 }
397 }
398 else if((pointNextStep.iY * MAZE_TILE_SIZE) < iPositionY)
399 {
400 moveY(-1);
401 // Control "overstep" as a state change could hit us in the middle of a tile
402 if((pointNextStep.iY * MAZE_TILE_SIZE) > iPositionY)
403 {
404 iPositionY = pointNextStep.iY * MAZE_TILE_SIZE;
405 #ifdef DEBUG_INTERNAL
406 Main::Instance().ILogMgr().get()->msg(LML_LOW, "Y- overstep!\n");
407 #endif
408 }
409 }
410
411 // X axis
412 else if((pointNextStep.iX * MAZE_TILE_SIZE) > iPositionX)
413 {
414 moveX(1);
415 // Control "overstep" as a state change could hit us in the middle of a tile
416 if((pointNextStep.iX * MAZE_TILE_SIZE) < iPositionX)
417 {
418 iPositionX = pointNextStep.iX * MAZE_TILE_SIZE;
419 #ifdef DEBUG_INTERNAL
420 Main::Instance().ILogMgr().get()->msg(LML_LOW, "X+ overstep!\n");
421 #endif
422 }
423 }
424 else if((pointNextStep.iX * MAZE_TILE_SIZE) < iPositionX)
425 {
426 moveX(-1);
427 // Control "overstep" as a state change could hit us in the middle of a tile
428 if((pointNextStep.iX * MAZE_TILE_SIZE) > iPositionX)
429 {
430 iPositionX = pointNextStep.iX * MAZE_TILE_SIZE;
431 #ifdef DEBUG_INTERNAL
432 Main::Instance().ILogMgr().get()->msg(LML_LOW, "X- overstep!\n");
433 #endif
434 }
435 }
436
437 // We have reach our destination, go to next step
438 else
439 {
440 eAM = AM_NEXT_STEP;
441 vPath.pop_back();
442 }
443 }
444
445 // Update previous maze position
446 if(iTmpX != iMazeX || iTmpY != iMazeY)
447 {
448 iMazePrevX = iTmpX;
449 iMazePrevY = iTmpY;
450 iTmpX = iMazeX;
451 iTmpY = iMazeY;
452 }
453 }
454
455 return 0;
456}
457
458// Render the target sprite
459Sint32 Actor::debug(Sint32 iMode)
460{
461 // Render target sprite
462 if(iMode == 1)
463 {
464 Sprite* sprObj = Main::Instance().ISpriteMgr().get(sprTargetID);
465 if(sprObj != nullptr)
466 {
467 Sint32 iX = pointTarget.iX;
468 Sint32 iY = pointTarget.iY;
469
470 // Perform a quick clipping as the target could be outside the screen and we want to render the target although not in the exact position
471 if(iX < 0) iX = 0;
472 else if(iX > MAZE_WIDTH) iX = MAZE_WIDTH - 1;
473 if(iY < 0) iY = 0;
474 else if(iY > MAZE_HEIGHT) iY = MAZE_HEIGHT - 1;
475
476 // Set position and render it
477 sprObj->setPosition(iMazeRenderingX + iX * MAZE_TILE_SIZE, iMazeRenderingY + iY * MAZE_TILE_SIZE, 0);
478 sprObj->render();
479 }
480 }
481 return 0;
482}
483
484// Stop actor movement.
485Sint32 Actor::msgPause()
486{
487 eAM = AM_DISABLED;
488 vPath.clear();
489 return 0;
490}
491
492// Message for going Init state.
493Sint32 Actor::msgGoInit()
494{
495 Object::msgGoInit(); // Call base method
496 eSS = SS_DEFAULT;
497 Main::Instance().ISpriteMgr().get(sprID)->selectAnim(SPR_STATE_LEFT);
498 return 0;
499}
500
501// Return the Euclidean distance between given target coordinates and mazepoint(possible options)
502double Actor::euclideanDistance(Sint32 iTX, Sint32 iTY, MazePoint& vMP)
503{
504 Sint32 iDX, iDY;
505 iDX = abs(vMP.iX - iTX);
506 iDY = abs(vMP.iY - iTY);
507 vMP.dDistance = sqrt((iDX * iDX) + (iDY * iDY));
508 return vMP.dDistance;
509}
510
511// Return current state name
512Sint32 Actor::getStateName(string &sN)
513{
514 pCurrentState->getName(sN);
515 return 0;
516}
517
518// Get brain id.
519Sint32 Actor::getBrain()
520{
521 return idBrain;
522}
523
524// Set a new brain id
525Sint32 Actor::setBrain(Sint32 idB)
526{
527 // Detect random brain and remove the object type
528 if((idB & PME_BRAIN_TYPE_RANDOM) == PME_BRAIN_TYPE_RANDOM) idB = PME_BRAIN_TYPE_RANDOM;
529 if(BrainsFactory::Instance().validate(idB) == true)
530 {
531 idBrain = idB;
532 return 0;
533 }
534 return -1;
535}
536
537// Look for our brain and call it
538Sint32 Actor::think(Sint32& iTX, Sint32& iTY)
539{
540 if(idBrain != -1)
541 {
542 return BrainsFactory::Instance().think(this, idBrain, iTX, iTY, pGameField);
543 }
544 return -1;
545}
546
547Sint32 Actor::moveX(Sint32 iDir)
548{
549 Sprite* sprObj = Main::Instance().ISpriteMgr().get(sprID);
550
551 // Request RIGHT movement (pixel movement)
552 if(iDir > 0)
553 {
554 // Pixel movement
555 iPositionX += iSpeed;
556 if(((iPositionX + (MAZE_TILE_SIZE / 2)) / MAZE_TILE_SIZE) != iMazeX)
557 {
558 // Changing to a new tile
559 ++iMazeX;
560 pGameField->moveTo(iID, iMazeX - 1, iMazeY, iMazeX, iMazeY);
561 }
562
563 // Set sprite animation
564 sprObj->selectAnim(SPR_STATE_RIGHT + eSS);
565 }
566
567 // Request LEFT movement (pixel movement)
568 else
569 {
570 // Pixel movement
571 iPositionX -= iSpeed;
572 if(((iPositionX + (MAZE_TILE_SIZE / 2)) / MAZE_TILE_SIZE) != iMazeX)
573 {
574 // Changing to a new tile
575 --iMazeX;
576 pGameField->moveTo(iID, iMazeX + 1, iMazeY, iMazeX, iMazeY);
577 }
578
579 // Set sprite animation
580 sprObj->selectAnim(SPR_STATE_LEFT + eSS);
581 }
582 return 0;
583}
584
585Sint32 Actor::moveY(Sint32 iDir)
586{
587 Sprite* sprObj = Main::Instance().ISpriteMgr().get(sprID);
588
589 // Request DOWN movement (pixel movement)
590 if(iDir > 0)
591 {
592 // Pixel movement
593 iPositionY += iSpeed;
594 if(((iPositionY + (MAZE_TILE_SIZE / 2)) / MAZE_TILE_SIZE) != iMazeY)
595 {
596 // Changing to a new tile
597 ++iMazeY;
598 pGameField->moveTo(iID, iMazeX, iMazeY - 1, iMazeX, iMazeY);
599 }
600
601 // Set sprite animation
602 sprObj->selectAnim(SPR_STATE_DOWN + eSS);
603 }
604
605 // Request UP movement (pixel movement)
606 else
607 {
608 // Pixel movement
609 iPositionY -= iSpeed;
610 if(((iPositionY + (MAZE_TILE_SIZE / 2)) / MAZE_TILE_SIZE) != iMazeY)
611 {
612 // Changing to a new tile
613 --iMazeY;
614 pGameField->moveTo(iID, iMazeX, iMazeY + 1, iMazeX, iMazeY);
615 }
616
617 // Set sprite animation
618 sprObj->selectAnim(SPR_STATE_UP + eSS);
619 }
620 return 0;
621}
622
623// Perform a state change
624void Actor::stateChange(State* pNewState)
625{
626 // Is new state a valid one?
627 if(!pNewState) return;
628
629 // Keep previous state. Must be a different state.
630 if(pNewState != pCurrentState) pPreviousState = pCurrentState;
631
632 // Call the exit method of the existing state
633 if(pCurrentState) pCurrentState->exit(this);
634
635 // Change state to the new state
636 pCurrentState = pNewState;
637
638 // Call the entry method of the new state
639 pCurrentState->enter(this);
640}
641
642// Revert to previous state
643void Actor::statePrevious()
644{
645 stateChange(pPreviousState);
646}
647
648// Check current state
649bool Actor::stateCurrentCheck(State& pState)
650{
651 return typeid(*pCurrentState) == typeid(pState);
652}
653
654// Check previous state
655bool Actor::statePreviousCheck(State& pState)
656{
657 if(pPreviousState == nullptr) return false;
658 return typeid(*pPreviousState) == typeid(pState);
659}