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