1
/*
2
 *  The Mana World
3
 *  Copyright (C) 2004  The Mana World Development Team
4
 *
5
 *  This file is part of The Mana World.
6
 *
7
 *  This program is free software; you can redistribute it and/or modify
8
 *  it under the terms of the GNU General Public License as published by
9
 *  the Free Software Foundation; either version 2 of the License, or
10
 *  any later version.
11
 *
12
 *  This program is distributed in the hope that it will be useful,
13
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 *  GNU General Public License for more details.
16
 *
17
 *  You should have received a copy of the GNU General Public License
18
 *  along with this program; if not, write to the Free Software
19
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
 */
21
22
#include <queue>
23
24
#include "beingmanager.h"
25
#include "configuration.h"
26
#include "game.h"
27
#include "graphics.h"
28
#include "map.h"
29
#include "particle.h"
30
#include "simpleanimation.h"
31
#include "sprite.h"
32
#include "tileset.h"
33
34
#include "resources/ambientoverlay.h"
35
#include "resources/image.h"
36
#include "resources/resourcemanager.h"
37
38
#include "utils/dtor.h"
39
#include "utils/stringutils.h"
40
41
extern volatile int tick_time;
42
43
/**
44
 * The used side-length for tiles
45
 */
46
const int DEFAULT_TILE_SIDE_LENGTH = 32;
47
48
/**
49
 * A location on a tile map. Used for pathfinding, open list.
50
 */
51
struct Location
52
{
53
    /**
54
     * Constructor.
55
     */
56
    Location(int px, int py, MetaTile *ptile):
57
        x(px), y(py), tile(ptile)
58
    {}
59
60
    /**
61
     * Comparison operator.
62
     */
63
    bool operator< (const Location &loc) const
64
    {
65
        return tile->Fcost > loc.tile->Fcost;
66
    }
67
68
    int x, y;
69
    MetaTile *tile;
70
};
71
72
TileAnimation::TileAnimation(Animation *ani):
73
    mLastImage(NULL)
74
{
75
    mAnimation = new SimpleAnimation(ani);
76
}
77
78
TileAnimation::~TileAnimation()
79
{
80
    delete mAnimation;
81
}
82
83
void TileAnimation::update(int ticks)
84
{
85
    if (!mAnimation)
86
        return;
87
88
    // update animation
89
    mAnimation->update(ticks);
90
91
    // exchange images
92
    Image *img = mAnimation->getCurrentImage();
93
    if (img != mLastImage)
94
    {
95
        for (std::list<std::pair<MapLayer*, int> >::iterator i =
96
             mAffected.begin(); i != mAffected.end(); i++)
97
        {
98
            i->first->setTile(i->second, img);
99
        }
100
        mLastImage = img;
101
    }
102
}
103
104
MapLayer::MapLayer(int x, int y, int width, int height, bool isFringeLayer):
105
    mX(x), mY(y),
106
    mWidth(width), mHeight(height),
107
    mIsFringeLayer(isFringeLayer)
108
{
109
    const int size = mWidth * mHeight;
110
    mTiles = new Image*[size];
111
    std::fill_n(mTiles, size, (Image*) 0);
112
}
113
114
MapLayer::~MapLayer()
115
{
116
    delete[] mTiles;
117
}
118
119
void MapLayer::setTile(int x, int y, Image *img)
120
{
121
    setTile(x + y * mWidth, img);
122
}
123
124
Image* MapLayer::getTile(int x, int y) const
125
{
126
    return mTiles[x + y * mWidth];
127
}
128
129
void MapLayer::draw(Graphics *graphics, int startX, int startY,
130
                    int endX, int endY, int scrollX, int scrollY,
131
                    const MapSprites &sprites) const
132
{
133
    startX -= mX;
134
    startY -= mY;
135
    endX -= mX;
136
    endY -= mY;
137
138
    if (startX < 0) startX = 0;
139
    if (startY < 0) startY = 0;
140
    if (endX > mWidth) endX = mWidth;
141
    if (endY > mHeight) endY = mHeight;
142
143
    MapSprites::const_iterator si = sprites.begin();
144
145
    for (int y = startY; y < endY; y++)
146
    {
147
        // If drawing the fringe layer, make sure all sprites above this row of
148
        // tiles have been drawn
149
        if (mIsFringeLayer)
150
        {
151
            while (si != sprites.end() && (*si)->getPixelY() <= y * 32)
152
            {
153
                (*si)->setAlpha(1.0f);
154
                (*si)->draw(graphics, -scrollX, -scrollY);
155
                si++;
156
            }
157
        }
158
159
        for (int x = startX; x < endX; x++)
160
        {
161
            Image *img = getTile(x, y);
162
            if (img)
163
            {
164
                const int px = (x + mX) * 32 - scrollX;
165
                const int py = (y + mY) * 32 - scrollY + 32 - img->getHeight();
166
                graphics->drawImage(img, px, py);
167
            }
168
        }
169
    }
170
171
    // Draw any remaining sprites
172
    if (mIsFringeLayer)
173
    {
174
        while (si != sprites.end())
175
        {
176
            (*si)->setAlpha(1.0f);
177
            (*si)->draw(graphics, -scrollX, -scrollY);
178
            si++;
179
        }
180
    }
181
}
182
183
Map::Map(int width, int height, int tileWidth, int tileHeight):
184
    mWidth(width), mHeight(height),
185
    mTileWidth(tileWidth), mTileHeight(tileHeight),
186
    mMaxTileHeight(height),
187
    mOnClosedList(1), mOnOpenList(2),
188
    mLastScrollX(0.0f), mLastScrollY(0.0f)
189
{
190
    const int size = mWidth * mHeight;
191
192
    mMetaTiles = new MetaTile[size];
193
    for (int i = 0; i < NB_BLOCKTYPES; i++)
194
    {
195
        mOccupation[i] = new int[size];
196
        memset(mOccupation[i], 0, size * sizeof(int));
197
    }
198
}
199
200
Map::~Map()
201
{
202
    // delete metadata, layers, tilesets and overlays
203
    delete[] mMetaTiles;
204
    for (int i = 0; i < NB_BLOCKTYPES; i++)
205
    {
206
        delete[] mOccupation[i];
207
    }
208
    delete_all(mLayers);
209
    delete_all(mTilesets);
210
    delete_all(mOverlays);
211
    delete_all(mTileAnimations);
212
}
213
214
void Map::initializeOverlays()
215
{
216
    ResourceManager *resman = ResourceManager::getInstance();
217
218
    for (int i = 0;
219
         hasProperty("overlay" + toString(i) + "image");
220
         i++)
221
    {
222
        const std::string name = "overlay" + toString(i);
223
224
        Image *img = resman->getImage(getProperty(name + "image"));
225
        const float speedX = getFloatProperty(name + "scrollX");
226
        const float speedY = getFloatProperty(name + "scrollY");
227
        const float parallax = getFloatProperty(name + "parallax");
228
        const bool keepRatio = getBoolProperty(name + "keepratio");
229
230
        if (img)
231
        {
232
            mOverlays.push_back(
233
                    new AmbientOverlay(img, parallax, speedX, speedY, keepRatio));
234
235
            // The AmbientOverlay takes control over the image.
236
            img->decRef();
237
        }
238
    }
239
}
240
241
void Map::addLayer(MapLayer *layer)
242
{
243
    mLayers.push_back(layer);
244
}
245
246
void Map::addTileset(Tileset *tileset)
247
{
248
    mTilesets.push_back(tileset);
249
250
    if (tileset->getHeight() > mMaxTileHeight)
251
        mMaxTileHeight = tileset->getHeight();
252
}
253
254
bool spriteCompare(const Sprite *a, const Sprite *b)
255
{
256
    return a->getPixelY() < b->getPixelY();
257
}
258
259
void Map::update(int ticks)
260
{
261
    //update animated tiles
262
    for (std::map<int, TileAnimation*>::iterator iAni = mTileAnimations.begin();
263
         iAni != mTileAnimations.end();
264
         iAni++)
265
    {
266
        iAni->second->update(ticks);
267
    }
268
}
269
270
void Map::draw(Graphics *graphics, int scrollX, int scrollY)
271
{
272
    int endPixelY = graphics->getHeight() + scrollY + mTileHeight - 1;
273
274
    // TODO: Do this per-layer
275
    endPixelY += mMaxTileHeight - mTileHeight;
276
277
    int startX = scrollX / mTileWidth;
278
    int startY = scrollY / mTileHeight;
279
    int endX = (graphics->getWidth() + scrollX + mTileWidth - 1) / mTileWidth;
280
    int endY = endPixelY / mTileHeight;
281
282
    // Make sure sprites are sorted
283
    mSprites.sort(spriteCompare);
284
285
    // draw the game world
286
    Layers::const_iterator layeri = mLayers.begin();
287
    for (; layeri != mLayers.end(); ++layeri)
288
    {
289
        (*layeri)->draw(graphics,
290
                        startX, startY, endX, endY,
291
                        scrollX, scrollY,
292
                        mSprites);
293
    }
294
295
    // Draws beings with a lower opacity to make them visible
296
    // even when covered by a wall or some other elements...
297
    MapSprites::const_iterator si = mSprites.begin();
298
    while (si != mSprites.end())
299
    {
300
        if (Sprite *sprite = *si)
301
        {
302
            // For now, just draw sprites with only one layer.
303
            if (sprite->getNumberOfLayers() == 1)
304
            {
305
                sprite->setAlpha(0.3f);
306
                sprite->draw(graphics, -scrollX, -scrollY);
307
            }
308
        }
309
        si++;
310
    }
311
312
    drawOverlay(graphics, scrollX, scrollY,
313
            (int) config.getValue("OverlayDetail", 2));
314
}
315
316
void Map::drawCollision(Graphics *graphics, int scrollX, int scrollY)
317
{
318
    int endPixelY = graphics->getHeight() + scrollY + mTileHeight - 1;
319
    int startX = scrollX / mTileWidth;
320
    int startY = scrollY / mTileHeight;
321
    int endX = (graphics->getWidth() + scrollX + mTileWidth - 1) / mTileWidth;
322
    int endY = endPixelY / mTileHeight;
323
324
    if (startX < 0) startX = 0;
325
    if (startY < 0) startY = 0;
326
    if (endX > mWidth) endX = mWidth;
327
    if (endY > mHeight) endY = mHeight;
328
329
    for (int y = startY; y < endY; y++)
330
    {
331
        for (int x = startX; x < endX; x++)
332
        {
333
            graphics->setColor(gcn::Color(0, 0, 0, 64));
334
                graphics->drawRectangle(gcn::Rectangle(
335
                    x * mTileWidth - scrollX,
336
                    y * mTileWidth - scrollY,
337
                    33, 33));
338
339
            if (!getWalk(x, y, BLOCKMASK_WALL))
340
            {
341
                graphics->setColor(gcn::Color(0, 0, 200, 64));
342
                graphics->fillRectangle(gcn::Rectangle(
343
                    x * mTileWidth - scrollX,
344
                    y * mTileWidth - scrollY,
345
                    32, 32));
346
            }
347
348
            if (!getWalk(x, y, BLOCKMASK_MONSTER))
349
            {
350
                graphics->setColor(gcn::Color(200, 0, 0, 64));
351
                graphics->fillRectangle(gcn::Rectangle(
352
                    x * mTileWidth - scrollX,
353
                    y * mTileWidth - scrollY,
354
                    32, 32));
355
            }
356
357
            if (!getWalk(x, y, BLOCKMASK_CHARACTER))
358
            {
359
                graphics->setColor(gcn::Color(0, 200, 0, 64));
360
                graphics->fillRectangle(gcn::Rectangle(
361
                    x * mTileWidth - scrollX,
362
                    y * mTileWidth - scrollY,
363
                    32, 32));
364
            }
365
        }
366
    }
367
}
368
369
void Map::drawOverlay(Graphics *graphics,
370
                      float scrollX, float scrollY, int detail)
371
{
372
    static int lastTick = tick_time;
373
374
    // Detail 0: no overlays
375
    if (detail <= 0) return;
376
377
    if (mLastScrollX == 0.0f && mLastScrollY == 0.0f)
378
    {
379
        // First call - initialisation
380
        mLastScrollX = scrollX;
381
        mLastScrollY = scrollY;
382
    }
383
384
    // Update Overlays
385
    int timePassed = get_elapsed_time(lastTick);
386
    float dx = scrollX - mLastScrollX;
387
    float dy = scrollY - mLastScrollY;
388
389
    std::list<AmbientOverlay*>::iterator i;
390
    for (i = mOverlays.begin(); i != mOverlays.end(); i++)
391
    {
392
        (*i)->update(timePassed, dx, dy);
393
    }
394
    mLastScrollX = scrollX;
395
    mLastScrollY = scrollY;
396
    lastTick = tick_time;
397
398
    // Draw overlays
399
    for (i = mOverlays.begin(); i != mOverlays.end(); i++)
400
    {
401
        (*i)->draw(graphics, graphics->getWidth(), graphics->getHeight());
402
403
        // Detail 1: only one overlay, higher: all overlays
404
        if (detail == 1)
405
            break;
406
    }
407
}
408
409
class ContainsGidFunctor
410
{
411
    public:
412
        bool operator() (const Tileset *set) const
413
        {
414
            return (set->getFirstGid() <= gid &&
415
                    gid - set->getFirstGid() < (int)set->size());
416
        }
417
        int gid;
418
} containsGid;
419
420
Tileset *Map::getTilesetWithGid(int gid) const
421
{
422
    containsGid.gid = gid;
423
424
    Tilesets::const_iterator i = find_if(mTilesets.begin(), mTilesets.end(),
425
            containsGid);
426
427
    return (i == mTilesets.end()) ? NULL : *i;
428
}
429
430
void Map::blockTile(int x, int y, BlockType type)
431
{
432
    if (type == BLOCKTYPE_NONE || !contains(x, y))
433
        return;
434
435
    const int tileNum = x + y * mWidth;
436
437
    if ((++mOccupation[type][tileNum]) > 0)
438
    {
439
        switch (type)
440
        {
441
            case BLOCKTYPE_WALL:
442
                mMetaTiles[tileNum].blockmask |= BLOCKMASK_WALL;
443
                break;
444
            case BLOCKTYPE_CHARACTER:
445
                mMetaTiles[tileNum].blockmask |= BLOCKMASK_CHARACTER;
446
                break;
447
            case BLOCKTYPE_MONSTER:
448
                mMetaTiles[tileNum].blockmask |= BLOCKMASK_MONSTER;
449
                break;
450
            default:
451
                // shut up!
452
                break;
453
        }
454
    }
455
}
456
457
bool Map::getWalk(int x, int y, unsigned char walkmask) const
458
{
459
    // You can't walk outside of the map
460
    if (!contains(x, y))
461
        return false;
462
463
    // Check if the tile is walkable
464
    return !(mMetaTiles[x + y * mWidth].blockmask & walkmask);
465
}
466
467
#ifdef EATHENA_SUPPORT
468
bool Map::occupied(int x, int y) const
469
{
470
    const Beings &beings = beingManager->getAll();
471
    for (Beings::const_iterator i = beings.begin(); i != beings.end(); i++)
472
    {
473
        const Being *being = *i;
474
475
        // job 45 is a portal, they don't collide
476
        if (being->getTileX() == x && being->getTileY() == y && being->mJob != 45)
477
            return true;
478
    }
479
480
    return false;
481
}
482
#endif
483
484
bool Map::contains(int x, int y) const
485
{
486
    return x >= 0 && y >= 0 && x < mWidth && y < mHeight;
487
}
488
489
MetaTile *Map::getMetaTile(int x, int y) const
490
{
491
    return &mMetaTiles[x + y * mWidth];
492
}
493
494
MapSprite Map::addSprite(Sprite *sprite)
495
{
496
    mSprites.push_front(sprite);
497
    return mSprites.begin();
498
}
499
500
void Map::removeSprite(MapSprite iterator)
501
{
502
    mSprites.erase(iterator);
503
}
504
505
const std::string &Map::getMusicFile() const
506
{
507
    return getProperty("music");
508
}
509
510
const std::string &Map::getName() const
511
{
512
    if (hasProperty("name"))
513
        return getProperty("name");
514
515
    return getProperty("mapname");
516
}
517
518
static int const basicCost = 100;
519
520
Path Map::findPath(int startX, int startY, int destX, int destY,
521
                   unsigned char walkmask, int maxCost)
522
{
523
    // Path to be built up (empty by default)
524
    Path path;
525
526
    // Declare open list, a list with open tiles sorted on F cost
527
    std::priority_queue<Location> openList;
528
529
    // Return when destination not walkable
530
    if (!getWalk(destX, destY, walkmask)) return path;
531
532
    // Reset starting tile's G cost to 0
533
    MetaTile *startTile = getMetaTile(startX, startY);
534
    startTile->Gcost = 0;
535
536
    // Add the start point to the open list
537
    openList.push(Location(startX, startY, startTile));
538
539
    bool foundPath = false;
540
541
    // Keep trying new open tiles until no more tiles to try or target found
542
    while (!openList.empty() && !foundPath)
543
    {
544
        // Take the location with the lowest F cost from the open list.
545
        Location curr = openList.top();
546
        openList.pop();
547
548
        // If the tile is already on the closed list, this means it has already
549
        // been processed with a shorter path to the start point (lower G cost)
550
        if (curr.tile->whichList == mOnClosedList)
551
        {
552
            continue;
553
        }
554
555
        // Put the current tile on the closed list
556
        curr.tile->whichList = mOnClosedList;
557
558
        // Check the adjacent tiles
559
        for (int dy = -1; dy <= 1; dy++)
560
        {
561
            for (int dx = -1; dx <= 1; dx++)
562
            {
563
                // Calculate location of tile to check
564
                const int x = curr.x + dx;
565
                const int y = curr.y + dy;
566
567
                // Skip if if we're checking the same tile we're leaving from,
568
                // or if the new location falls outside of the map boundaries
569
                if ((dx == 0 && dy == 0) || !contains(x, y))
570
                {
571
                    continue;
572
                }
573
574
                MetaTile *newTile = getMetaTile(x, y);
575
576
                // Skip if the tile is on the closed list or is not walkable
577
                // unless its the destination tile
578
                if (newTile->whichList == mOnClosedList ||
579
                    ((newTile->blockmask & walkmask)
580
                     && !(x == destX && y == destY)))
581
                {
582
                    continue;
583
                }
584
585
                // When taking a diagonal step, verify that we can skip the
586
                // corner.
587
                if (dx != 0 && dy != 0)
588
                {
589
                    MetaTile *t1 = getMetaTile(curr.x, curr.y + dy);
590
                    MetaTile *t2 = getMetaTile(curr.x + dx, curr.y);
591
592
                    if ((t1->blockmask | t2->blockmask) & BLOCKMASK_WALL)
593
                        continue;
594
                }
595
596
                // Calculate G cost for this route, ~sqrt(2) for moving diagonal
597
                int Gcost = curr.tile->Gcost +
598
                    (dx == 0 || dy == 0 ? basicCost : basicCost * 362 / 256);
599
600
                /* Demote an arbitrary direction to speed pathfinding by
601
                   adding a defect (TODO: change depending on the desired
602
                   visual effect, e.g. a cross-product defect toward
603
                   destination).
604
                   Important: as long as the total defect along any path is
605
                   less than the basicCost, the pathfinder will still find one
606
                   of the shortest paths! */
607
                if (dx == 0 || dy == 0)
608
                {
609
                    // Demote horizontal and vertical directions, so that two
610
                    // consecutive directions cannot have the same Fcost.
611
                    ++Gcost;
612
                }
613
614
#ifdef EATHENA_SUPPORT
615
                // It costs extra to walk through a being (needs to be enough
616
                // to make it more attractive to walk around).
617
                if (occupied(x, y))
618
                {
619
                    Gcost += 3 * basicCost;
620
                }
621
#endif
622
623
                // Skip if Gcost becomes too much
624
                // Warning: probably not entirely accurate
625
                if (Gcost > maxCost * basicCost)
626
                {
627
                    continue;
628
                }
629
630
                if (newTile->whichList != mOnOpenList)
631
                {
632
                    // Found a new tile (not on open nor on closed list)
633
634
                    /* Update Hcost of the new tile. The pathfinder does not
635
                       work reliably if the heuristic cost is higher than the
636
                       real cost. In particular, using Manhattan distance is
637
                       forbidden here. */
638
                    int dx = std::abs(x - destX), dy = std::abs(y - destY);
639
                    newTile->Hcost = std::abs(dx - dy) * basicCost +
640
                        std::min(dx, dy) * (basicCost * 362 / 256);
641
642
                    // Set the current tile as the parent of the new tile
643
                    newTile->parentX = curr.x;
644
                    newTile->parentY = curr.y;
645
646
                    // Update Gcost and Fcost of new tile
647
                    newTile->Gcost = Gcost;
648
                    newTile->Fcost = Gcost + newTile->Hcost;
649
650
                    if (x != destX || y != destY) {
651
                        // Add this tile to the open list
652
                        newTile->whichList = mOnOpenList;
653
                        openList.push(Location(x, y, newTile));
654
                    }
655
                    else {
656
                        // Target location was found
657
                        foundPath = true;
658
                    }
659
                }
660
                else if (Gcost < newTile->Gcost)
661
                {
662
                    // Found a shorter route.
663
                    // Update Gcost and Fcost of the new tile
664
                    newTile->Gcost = Gcost;
665
                    newTile->Fcost = Gcost + newTile->Hcost;
666
667
                    // Set the current tile as the parent of the new tile
668
                    newTile->parentX = curr.x;
669
                    newTile->parentY = curr.y;
670
671
                    // Add this tile to the open list (it's already
672
                    // there, but this instance has a lower F score)
673
                    openList.push(Location(x, y, newTile));
674
                }
675
            }
676
        }
677
    }
678
679
    // Two new values to indicate whether a tile is on the open or closed list,
680
    // this way we don't have to clear all the values between each pathfinding.
681
    mOnClosedList += 2;
682
    mOnOpenList += 2;
683
684
    // If a path has been found, iterate backwards using the parent locations
685
    // to extract it.
686
    if (foundPath)
687
    {
688
        int pathX = destX;
689
        int pathY = destY;
690
691
        while (pathX != startX || pathY != startY)
692
        {
693
            // Add the new path node to the start of the path list
694
            path.push_front(Position(pathX, pathY));
695
696
            // Find out the next parent
697
            MetaTile *tile = getMetaTile(pathX, pathY);
698
            pathX = tile->parentX;
699
            pathY = tile->parentY;
700
        }
701
    }
702
703
    return path;
704
}
705
706
void Map::addParticleEffect(const std::string &effectFile, int x, int y)
707
{
708
    ParticleEffectData newEffect;
709
    newEffect.file = effectFile;
710
    newEffect.x = x;
711
    newEffect.y = y;
712
    particleEffects.push_back(newEffect);
713
}
714
715
void Map::initializeParticleEffects(Particle *particleEngine)
716
{
717
    if (config.getValue("particleeffects", 1))
718
    {
719
        for (std::list<ParticleEffectData>::iterator i = particleEffects.begin();
720
             i != particleEffects.end();
721
             i++
722
            )
723
        {
724
            particleEngine->addEffect(i->file, i->x, i->y);
725
        }
726
    }
727
}
728
729
TileAnimation *Map::getAnimationForGid(int gid) const
730
{
731
    std::map<int, TileAnimation*>::const_iterator i = mTileAnimations.find(gid);
732
    return (i == mTileAnimations.end()) ? NULL : i->second;
733
}