]> git.localhorst.tv Git - l2e.git/blob - MapState.cpp
b8b5eb629d8601a2de2ce2a4a03044df9b28fcad
[l2e.git] / MapState.cpp
1 #include "MapState.h"
2
3 #include "Map.h"
4 #include "Tile.h"
5 #include "TransitionState.h"
6 #include "Trigger.h"
7 #include "../app/Application.h"
8 #include "../app/Input.h"
9 #include "../battle/BattleState.h"
10 #include "../common/GameConfig.h"
11 #include "../common/GameState.h"
12 #include "../graphics/ColorFade.h"
13 #include "../menu/PartyMenu.h"
14
15 #include <algorithm>
16
17 using app::Application;
18 using app::Input;
19 using battle::BattleState;
20 using common::GameConfig;
21 using math::Fixed;
22 using math::Vector;
23 using graphics::ColorFade;
24 using menu::PartyMenu;
25
26 namespace map {
27
28 MapState::MapState(GameConfig *g, Map *map)
29 : game(g)
30 , map(map)
31 , controlled(0)
32 , pushed(0)
33 , lastLock(-1, -1)
34 , camera(100, 100, 0)
35 , walkingSpeed(64)
36 , nextDirection(-1)
37 , afterLock(false)
38 , skipLock(false)
39 , pushing(false)
40 , debug(false) {
41
42 }
43
44
45 void MapState::OnEnterState(SDL_Surface *screen) {
46         camera.Resize(screen->w, screen->h);
47         tileAnimation = GraphicsTimers().StartInterval(512);
48         LoadMap(map);
49 }
50
51 void MapState::OnExitState(SDL_Surface *screen) {
52
53 }
54
55 void MapState::OnResumeState(SDL_Surface *screen) {
56         camera.Resize(screen->w, screen->h);
57 }
58
59 void MapState::OnPauseState(SDL_Surface *screen) {
60
61 }
62
63 void MapState::OnResize(int width, int height) {
64         camera.Resize(width, height);
65 }
66
67
68 void MapState::HandleEvents(const Input &input) {
69         if (input.JustPressed(Input::ACTION_X)) {
70                 Ctrl().PushState(new PartyMenu(game));
71                 return;
72         }
73
74         if (!controlled) return;
75
76         if (input.IsDown(Input::PAD_UP)) {
77                 nextDirection = Entity::ORIENTATION_NORTH;
78         } else if (input.IsDown(Input::PAD_RIGHT)) {
79                 nextDirection = Entity::ORIENTATION_EAST;
80         } else if (input.IsDown(Input::PAD_DOWN)) {
81                 nextDirection = Entity::ORIENTATION_SOUTH;
82         } else if (input.IsDown(Input::PAD_LEFT)) {
83                 nextDirection = Entity::ORIENTATION_WEST;
84         } else {
85                 nextDirection = -1;
86         }
87
88         pushing = input.IsDown(Input::ACTION_A);
89
90         if (input.JustPressed(Input::DEBUG_1)) {
91                 debug = !debug;
92         }
93 }
94
95 void MapState::UpdateWorld(Uint32 deltaT) {
96         if (controlled && controlled->TileLock(map->Tileset()->Size())) {
97                 OnTileLock();
98         }
99         for (std::vector<Entity *>::iterator i(entities.begin()), end(entities.end()); i != end; ++i) {
100                 (*i)->Update(deltaT);
101         }
102 }
103
104 void MapState::OnTileLock() {
105         if (moveTimer.Running() && !moveTimer.JustHit()) return;
106
107         Vector<int> nowLock(ToInt(controlled->Position()));
108         bool event(false);
109         if (nowLock != lastLock) {
110                 event = OnGridLock();
111                 afterLock = true;
112                 moveTimer.Clear();
113         } else if (moveTimer.JustHit()) {
114                 event = OnGridLock();
115                 afterLock = true;
116         }
117
118         if (event) {
119                 return;
120         }
121
122         const Tile *tile(map->TileAt(nowLock));
123
124         if (nextDirection >= 0) {
125                 if (afterLock) {
126                         bool blocked(CheckBlocking());
127                         OnMove(!blocked);
128                         controlled->SetDirection(Entity::Orientation(nextDirection));
129                         if (!blocked) {
130                                 afterLock = false;
131                                 controlled->SetSpeed(walkingSpeed);
132                                 moveTimer.Clear();
133                                 if (pushed) {
134                                         pushed->SetDirection(Entity::Orientation(nextDirection));
135                                         pushed->SetSpeed(walkingSpeed);
136                                         controlled->SetPushing();
137                                 } else {
138                                         controlled->SetHandsFree();
139                                 }
140                         } else {
141                                 controlled->SetSpeed(0);
142                                 StopFollowers(*controlled);
143                                 if (!moveTimer.Running()) {
144                                         int tileSize((controlled->GetDirection() % 2) ? map->Tileset()->Width() : map->Tileset()->Height());
145                                         Fixed<8> walkingInterval(tileSize);
146                                         walkingInterval /= walkingSpeed;
147                                         moveTimer = PhysicsTimers().StartInterval(walkingInterval.Int());
148                                 }
149                                 pushed = 0;
150                         }
151                         if (!controlled->AnimationRunning()) {
152                                 controlled->StartAnimation(*this);
153                         }
154                 }
155         } else {
156                 controlled->SetSpeed(0);
157                 StopFollowers(*controlled);
158                 controlled->StopAnimation();
159                 moveTimer.Clear();
160                 if (pushed) {
161                         pushed->SetSpeed(0);
162                         pushed = 0;
163                 }
164         }
165
166         if (controlled->GetDirection() == Entity::ORIENTATION_SOUTH
167                         && tile && tile->IsLadder()) {
168                 controlled->SetOrientation(Entity::ORIENTATION_NORTH);
169         }
170
171         lastLock = nowLock;
172 }
173
174 bool MapState::CheckBlocking() {
175         if (pushed) {
176                 pushed->SetSpeed(0);
177                 pushed = 0;
178         }
179         const Tile *tile(map->TileAt(ToInt(controlled->Position())));
180         Vector<int> direction;
181         switch (nextDirection) {
182                 case Entity::ORIENTATION_NORTH:
183                         if (tile && tile->BlocksNorth()) {
184                                 return true;
185                         } else {
186                                 direction = Vector<int>(0, -map->Tileset()->Height());
187                         }
188                         break;
189                 case Entity::ORIENTATION_EAST:
190                         if (tile && tile->BlocksEast()) {
191                                 return true;
192                         } else {
193                                 direction = Vector<int>(map->Tileset()->Width(), 0);
194                         }
195                         break;
196                 case Entity::ORIENTATION_SOUTH:
197                         if (tile && tile->BlocksSouth()) {
198                                 return true;
199                         } else {
200                                 direction = Vector<int>(0, map->Tileset()->Height());
201                         }
202                         break;
203                 case Entity::ORIENTATION_WEST:
204                         if (tile && tile->BlocksWest()) {
205                                 return true;
206                         } else {
207                                 direction = Vector<int>(-map->Tileset()->Width(), 0);
208                         }
209                         break;
210                 default:
211                         return false;
212         }
213         Vector<int> nextTilePosition(direction + ToInt(controlled->Position()));
214         Vector<int> nextTileCoords(map->TileCoordinates(nextTilePosition));
215         for (std::vector<Entity *>::const_iterator i(entities.begin()), end(entities.end()); i != end; ++i) {
216                 const Entity &e(**i);
217                 if (map->TileCoordinates(ToInt(e.Position())) != nextTileCoords) continue;
218                 if (!e.Blocking()) continue;
219                 if (!pushing || !e.Pushable()) return true;
220                 if (CheckBlocking(nextTilePosition, Entity::Orientation(nextDirection))) return true;
221                 pushed = *i;
222         }
223         return false;
224 }
225
226 bool MapState::CheckBlocking(const Vector<int> &position, Entity::Orientation direction) const {
227         const Tile *tile(map->TileAt(position));
228         Vector<int> directionVector;
229         switch (direction) {
230                 case Entity::ORIENTATION_NORTH:
231                         if (tile && tile->BlocksNorth()) {
232                                 return true;
233                         } else {
234                                 directionVector = Vector<int>(0, -map->Tileset()->Height());
235                         }
236                         break;
237                 case Entity::ORIENTATION_EAST:
238                         if (tile && tile->BlocksEast()) {
239                                 return true;
240                         } else {
241                                 directionVector = Vector<int>(map->Tileset()->Width(), 0);
242                         }
243                         break;
244                 case Entity::ORIENTATION_SOUTH:
245                         if (tile && tile->BlocksSouth()) {
246                                 return true;
247                         } else {
248                                 directionVector = Vector<int>(0, map->Tileset()->Height());
249                         }
250                         break;
251                 case Entity::ORIENTATION_WEST:
252                         if (tile && tile->BlocksWest()) {
253                                 return true;
254                         } else {
255                                 directionVector = Vector<int>(-map->Tileset()->Width(), 0);
256                         }
257                         break;
258                 default:
259                         return false;
260         }
261         Vector<int> nextTileCoords(map->TileCoordinates(directionVector + position));
262         for (std::vector<Entity *>::const_iterator i(entities.begin()), end(entities.end()); i != end; ++i) {
263                 const Entity &e(**i);
264                 if (map->TileCoordinates(ToInt(e.Position())) == nextTileCoords && e.Blocking()) {
265                         return true;
266                 }
267         }
268         return false;
269 }
270
271 bool MapState::OnGridLock() {
272         if (skipLock) {
273                 skipLock = false;
274                 return false;
275         } else {
276                 LockEntities();
277                 return CheckMonster() || CheckLockTrigger();
278         }
279 }
280
281 void MapState::LockEntities() {
282         for (std::vector<Entity *>::iterator i(entities.begin()), end(entities.end()); i != end; ++i) {
283                 if (*i == controlled) {
284                         // don't lock player
285                         continue;
286                 }
287                 (*i)->Position().Lock(map->Tileset()->Size());
288         }
289 }
290
291 bool MapState::CheckMonster() {
292         Vector<int> coords(map->TileCoordinates(ToInt(controlled->Position())));
293         Vector<int> neighbor[4];
294         neighbor[0] = Vector<int>(coords.X(), coords.Y() - 1); // N
295         neighbor[1] = Vector<int>(coords.X() + 1, coords.Y()); // E
296         neighbor[2] = Vector<int>(coords.X(), coords.Y() + 1); // S
297         neighbor[3] = Vector<int>(coords.X() - 1, coords.Y()); // W
298
299         for (int i(0); i < 4; ++i) {
300                 for (std::vector<Entity *>::iterator e(entities.begin()), end(entities.end()); e != end; ++e) {
301                         if ((*e)->Hostile() && map->TileCoordinates(ToInt((*e)->Position())) == neighbor[i]) {
302                                 // TODO: move entity erase to happen after the transition or battle
303                                 LoadBattle(*controlled, **e);
304                                 entities.erase(e);
305                                 return true;
306                         }
307                 }
308         }
309         return false;
310 }
311
312 void MapState::LoadBattle(Entity &hero, Entity &monster) {
313         SDL_Surface *bg = map->BattleBackgroundAt(ToInt(monster.Position()));
314         BattleState *battleState(new BattleState(game, bg, monster.PartyLayout()));
315         for (int i(0); i < 4; ++i) {
316                 if (game->state->party[i]) {
317                         battleState->AddHero(*game->state->party[i]);
318                 }
319         }
320         if (game->state->capsule) {
321                 battleState->SetCapsule(&game->state->GetCapsule());
322         }
323         for (battle::Monster **m(monster.MonstersBegin()); m != monster.MonstersEnd(); ++m) {
324                 battleState->AddMonster(**m);
325         }
326
327         // TODO: pass turn advantage to battle, see #26
328         Entity::Orientation faceDirection;
329         if (monster.Position().Y() < hero.Position().Y()) {
330                 faceDirection = Entity::ORIENTATION_NORTH;
331         } else if (monster.Position().X() > hero.Position().X()) {
332                 faceDirection = Entity::ORIENTATION_EAST;
333         } else if (monster.Position().Y() > hero.Position().Y()) {
334                 faceDirection = Entity::ORIENTATION_SOUTH;
335         } else {
336                 faceDirection = Entity::ORIENTATION_WEST;
337         }
338         if (hero.GetOrientation() == monster.GetOrientation()
339                         && hero.GetOrientation() == faceDirection) {
340                 // advantage hero
341         } else if (hero.GetOrientation() == monster.GetOrientation()
342                         && hero.GetOrientation() == ((faceDirection + 2) % 4)) {
343                 // advantage monster
344         } else if (((monster.GetOrientation() == faceDirection && (hero.GetOrientation() % 2) != (faceDirection % 2))
345                         || (hero.GetOrientation() == faceDirection && (monster.GetOrientation() % 2) != (faceDirection % 2)))
346                         && rand() % 2) {
347                 // 50% advantage chance hero
348         } else if ((monster.GetOrientation() == (faceDirection + 2) % 4)
349                         && ((hero.GetOrientation() % 2) != (faceDirection % 2))
350                         && rand() % 2) {
351                 // 50% advantage chance monster
352         }
353
354         // TODO: other transition
355         ColorFade *fadeIn(new ColorFade(this, 0, 500, true));
356         fadeIn->SetLeadInTime(500);
357         ColorFade *fadeOut(new ColorFade(this, 0, 500));
358         fadeOut->SetLeadOutTime(500);
359
360         Ctrl().PushState(fadeIn);
361         Ctrl().PushState(battleState);
362         Ctrl().PushState(fadeOut);
363 }
364
365 bool MapState::CheckLockTrigger() {
366         Trigger *trigger(map->TriggerAt(ToInt(controlled->Position())));
367         if (!trigger || trigger->GetType() != Trigger::TYPE_CONTACT) return false;
368         RunTrigger(*trigger);
369         return true;
370 }
371
372 void MapState::OnMove(bool realMove) {
373         if (CheckMoveTrigger()) {
374                 return;
375         }
376         // TODO: evaluate monster movements
377         if (realMove) {
378                 UpdateFollower(*controlled);
379         } else {
380                 StopFollowers(*controlled);
381         }
382 }
383
384 bool MapState::CheckMoveTrigger() {
385         Trigger *trigger(map->TriggerAt(ToInt(controlled->Position())));
386         if (!trigger || int(trigger->GetType()) != nextDirection) return false;
387         RunTrigger(*trigger);
388         return true;
389 }
390
391 void MapState::RunTrigger(Trigger &trigger) {
392         if (!trigger.HasScript()) return;
393         runner.Run(*this, trigger.GetScript());
394 }
395
396 void MapState::UpdateFollower(Entity &e) {
397         if (!e.Follower()) return;
398
399         Entity &f(*e.Follower());
400         UpdateFollower(f);
401
402         Vector<int> coords(map->TileCoordinates(ToInt(e.Position())));
403         Vector<int> fCoords(map->TileCoordinates(ToInt(f.Position())));
404         Vector<int> direction(coords - fCoords);
405
406         if (direction.Y() < 0) {
407                 f.SetDirection(Entity::ORIENTATION_NORTH);
408                 f.SetSpeed(walkingSpeed);
409                 f.StartAnimation(*this);
410         } else if (direction.X() > 0) {
411                 f.SetDirection(Entity::ORIENTATION_EAST);
412                 f.SetSpeed(walkingSpeed);
413                 f.StartAnimation(*this);
414         } else if (direction.Y() > 0) {
415                 f.SetDirection(Entity::ORIENTATION_SOUTH);
416                 f.SetSpeed(walkingSpeed);
417                 f.StartAnimation(*this);
418         } else if (direction.X() < 0) {
419                 f.SetDirection(Entity::ORIENTATION_WEST);
420                 f.SetSpeed(walkingSpeed);
421                 f.StartAnimation(*this);
422         } else {
423                 f.SetSpeed(0);
424                 f.StopAnimation();
425         }
426 }
427
428 void MapState::StopFollowers(Entity &e) {
429         for (Entity *f(e.Follower()); f; f = f->Follower()) {
430                 f->SetSpeed(0);
431                 f->StopAnimation();
432         }
433 }
434
435
436 void MapState::Transition(Map *newMap, const Vector<int> &coordinates) {
437         UnloadMap();
438         Vector<int> position(coordinates * map->Tileset()->Size());
439         for (Entity *e(controlled); e; e = e->Follower()) {
440                 e->Position() = position;
441                 e->SetDirection(controlled->GetDirection());
442         }
443         LoadMap(newMap);
444         skipLock = true;
445 }
446
447 void MapState::UnloadMap() {
448         entities.clear();
449 }
450
451 void MapState::LoadMap(Map *m) {
452         map = m;
453         for (Entity *e(m->EntitiesBegin()), *end(m->EntitiesEnd()); e != end; ++e) {
454                 entities.push_back(e);
455                 e->ResetPosition(map->Tileset()->Size());
456         }
457         for (Entity *e(controlled); e; e = e->Follower()) {
458                 entities.push_back(e);
459         }
460 }
461
462
463 void MapState::Render(SDL_Surface *screen) {
464         SDL_FillRect(screen, 0, SDL_MapRGB(screen->format, 0, 0, 0));
465
466         Vector<int> offset(camera.CalculateOffset());
467         map->Render(screen, offset, tileAnimation.Iteration());
468
469         if (debug) {
470                 map->RenderDebug(screen, offset);
471         }
472
473         std::sort(entities.begin(), entities.end(), ZCompare);
474         for (std::vector<Entity *>::iterator i(entities.begin()), end(entities.end()); i != end; ++i) {
475                 (*i)->Render(screen, offset);
476         }
477 }
478
479
480 bool MapState::ZCompare(const Entity *lhs, const Entity *rhs) {
481         return lhs->Position().Y() < rhs->Position().Y();
482 }
483
484
485 void MapState::HandleSyscall(common::ScriptRunner &r) {
486         switch (r.IntegerRegister(0)) {
487                 case TRANSITION: {
488                         Ctrl().PushState(new ColorFade(this, 0, 500, true));
489                         Ctrl().PushState(new TransitionState(this, reinterpret_cast<Map *>(r.AddressRegister(0)), r.VectorRegister(0)));
490                         ColorFade *fadeOut(new ColorFade(this, 0, 500, false));
491                         fadeOut->SetLeadOutTime(500);
492                         Ctrl().PushState(fadeOut);
493                         break;
494                 }
495         }
496 }
497
498 }