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 "localplayer.h"
23
24
#include "configuration.h"
25
#include "equipment.h"
26
#include "flooritem.h"
27
#include "game.h"
28
#include "graphics.h"
29
#include "inventory.h"
30
#include "item.h"
31
#include "log.h"
32
#include "map.h"
33
#include "monster.h"
34
#include "particle.h"
35
#include "simpleanimation.h"
36
#include "sound.h"
37
#include "statuseffect.h"
38
#include "text.h"
39
40
#include "gui/gui.h"
41
#include "gui/ministatus.h"
42
#include "gui/palette.h"
43
#include "gui/skilldialog.h"
44
#include "gui/statuswindow.h"
45
#include "gui/storagewindow.h"
46
47
#include "gui/widgets/chattab.h"
48
49
#include "net/inventoryhandler.h"
50
#include "net/net.h"
51
#include "net/partyhandler.h"
52
#include "net/playerhandler.h"
53
#include "net/specialhandler.h"
54
#include "net/tradehandler.h"
55
56
#include "effectmanager.h"
57
#ifdef TMWSERV_SUPPORT
58
#include "guild.h"
59
60
//#include "net/tmwserv/gameserver/player.h"
61
#include "net/tmwserv/chatserver/guild.h"
62
#endif
63
64
#include "resources/animation.h"
65
#include "resources/imageset.h"
66
#include "resources/itemdb.h"
67
#include "resources/iteminfo.h"
68
#include "resources/resourcemanager.h"
69
70
#include "utils/gettext.h"
71
#include "utils/stringutils.h"
72
73
#include <cassert>
74
75
#ifdef TMWSERV_SUPPORT
76
// This is the minimal delay between to permitted
77
// setDestination() calls using the keyboard.
78
// TODO: This can fine tuned later on when running is added...
79
const short walkingKeyboardDelay = 1000;
80
#endif
81
82
LocalPlayer *player_node = NULL;
83
84
LocalPlayer::LocalPlayer(int id, int job, Map *map):
85
    Player(id, job, map),
86
#ifdef EATHENA_SUPPORT
87
    mAttackRange(0),
88
#endif
89
    mEquipment(new Equipment),
90
    mInStorage(false),
91
    mTargetTime(-1),
92
    mLastTarget(-1),
93
    mCharacterPoints(0),
94
    mCorrectionPoints(0),
95
    mLevel(1),
96
    mExp(0), mExpNeeded(0),
97
    mMp(0), mMaxMp(0),
98
    mMoney(0),
99
    mTotalWeight(1), mMaxWeight(1),
100
    mHp(1), mMaxHp(1),
101
    mSkillPoints(0),
102
    mTarget(NULL), mPickUpTarget(NULL),
103
    mTrading(false), mGoingToTarget(false), mKeepAttacking(false),
104
    mLastAction(-1),
105
    mWalkingDir(0),
106
    mPathSetByMouse(false),
107
    mDestX(0), mDestY(0),
108
    mInventory(new Inventory(INVENTORY_SIZE)),
109
#ifdef TMWSERV_SUPPORT
110
    mLocalWalkTime(-1),
111
#endif
112
    mStorage(new Inventory(STORAGE_SIZE)),
113
    mMessageTime(0)
114
{
115
    // Variable to keep the local player from doing certain actions before a map
116
    // is initialized. e.g. drawing a player's name using the TextManager, since
117
    // it appears to be dependant upon map coordinates for updating drawing.
118
    mMapInitialized = false;
119
120
    mUpdateName = true;
121
122
    mTextColor = &guiPalette->getColor(Palette::PLAYER);
123
    mNameColor = &guiPalette->getColor(Palette::SELF);
124
125
    initTargetCursor();
126
127
    config.addListener("showownname", this);
128
    setShowName(config.getValue("showownname", 1));
129
}
130
131
LocalPlayer::~LocalPlayer()
132
{
133
    delete mInventory;
134
#ifdef EATHENA_SUPPORT
135
    delete mStorage;
136
#endif
137
138
    config.removeListener("showownname", this);
139
140
    for (int i = Being::TC_SMALL; i < Being::NUM_TC; i++)
141
    {
142
        delete mTargetCursor[0][i];
143
        delete mTargetCursor[1][i];
144
        mTargetCursorImages[0][i]->decRef();
145
        mTargetCursorImages[1][i]->decRef();
146
    }
147
}
148
149
void LocalPlayer::logic()
150
{
151
    // Actions are allowed once per second
152
    if (get_elapsed_time(mLastAction) >= 1000)
153
        mLastAction = -1;
154
155
    // Show XP messages
156
    if (!mMessages.empty())
157
    {
158
        if (mMessageTime == 0)
159
        {
160
            //const Vector &pos = getPosition();
161
162
            MessagePair info = mMessages.front();
163
164
            particleEngine->addTextRiseFadeOutEffect(
165
                    info.first,
166
                    /*(int) pos.x,
167
                    (int) pos.y - 48,*/
168
                    getPixelX(),
169
                    getPixelY() - 48,
170
                    &guiPalette->getColor(info.second),
171
                    gui->getInfoParticleFont(), true);
172
173
            mMessages.pop_front();
174
            mMessageTime = 30;
175
        }
176
        mMessageTime--;
177
    }
178
179
    if ((mSpecialRechargeUpdateNeeded%11) == 0)
180
    {
181
        mSpecialRechargeUpdateNeeded = 0;
182
        for (std::map<int, Special>::iterator i = mSpecials.begin();
183
             i != mSpecials.end();
184
             i++)
185
        {
186
            i->second.currentMana += i->second.recharge;
187
            if (i->second.currentMana > i->second.neededMana)
188
            {
189
                i->second.currentMana = i->second.neededMana;
190
            }
191
        }
192
    }
193
    mSpecialRechargeUpdateNeeded++;
194
195
    // Targeting allowed 4 times a second
196
    if (get_elapsed_time(mLastTarget) >= 250)
197
        mLastTarget = -1;
198
199
    // Remove target if its been on a being for more than a minute
200
    if (get_elapsed_time(mTargetTime) >= 60000)
201
    {
202
        mTargetTime = -1;
203
        setTarget(NULL);
204
        mLastTarget = -1;
205
    }
206
207
    if (mTarget)
208
    {
209
        if (mTarget->getType() == Being::NPC)
210
        {
211
            // NPCs are always in range
212
            mTarget->setTargetAnimation(
213
                mTargetCursor[0][mTarget->getTargetCursorSize()]);
214
        }
215
        else
216
        {
217
#ifdef TMWSERV_SUPPORT
218
            // Find whether target is in range
219
            const int rangeX = abs(mTarget->getPosition().x - getPosition().x);
220
            const int rangeY = abs(mTarget->getPosition().y - getPosition().y);
221
#else
222
            // Find whether target is in range
223
            const int rangeX = abs(mTarget->getTileX() - getTileX());
224
            const int rangeY = abs(mTarget->getTileY() - getTileY());
225
#endif
226
            const int attackRange = getAttackRange();
227
            const int inRange = rangeX > attackRange || rangeY > attackRange
228
                                                                    ? 1 : 0;
229
            mTarget->setTargetAnimation(
230
                mTargetCursor[inRange][mTarget->getTargetCursorSize()]);
231
232
            if (mTarget->mAction == DEAD)
233
                stopAttack();
234
235
            if (mKeepAttacking && mTarget)
236
                attack(mTarget, true);
237
        }
238
    }
239
240
    Player::logic();
241
}
242
243
void LocalPlayer::setAction(Action action, int attackType)
244
{
245
    if (action == DEAD)
246
    {
247
        mLastTarget = -1;
248
        setTarget(NULL);
249
    }
250
251
    Player::setAction(action, attackType);
252
}
253
254
void LocalPlayer::setGM(bool gm)
255
{
256
    mIsGM = gm;
257
}
258
259
void LocalPlayer::setGMLevel(int level)
260
{
261
    mGMLevel = level;
262
263
    if (level > 0)
264
        setGM(true);
265
}
266
267
268
void LocalPlayer::nextStep(unsigned char dir = 0)
269
{
270
#ifdef EATHENA_SUPPORT
271
    // TODO: Fix picking up when reaching target (this method is obsolete)
272
    // TODO: Fix holding walking button to keep walking smoothly
273
    if (mPath.empty())
274
    {
275
        if (mPickUpTarget)
276
            pickUp(mPickUpTarget);
277
278
        if (mWalkingDir)
279
            startWalking(mWalkingDir);
280
    }
281
282
    // TODO: Fix automatically walking within range of target, when wanted
283
    if (mGoingToTarget && mTarget && withinAttackRange(mTarget))
284
    {
285
        mAction = Being::STAND;
286
        attack(mTarget, true);
287
        mGoingToTarget = false;
288
        mPath.clear();
289
        return;
290
    }
291
    else if (mGoingToTarget && !mTarget)
292
    {
293
        mGoingToTarget = false;
294
        mPath.clear();
295
    }
296
297
298
    Player::nextStep();
299
#else // TMWSERV_SUPPORT
300
        if (!mMap || !dir)
301
        return;
302
303
    const Vector &pos = getPosition();
304
305
    // Compute where the next step will be set.
306
307
    int dx = 0, dy = 0;
308
    if (dir & UP)
309
        dy--;
310
    if (dir & DOWN)
311
        dy++;
312
    if (dir & LEFT)
313
        dx--;
314
    if (dir & RIGHT)
315
        dx++;
316
317
    // Prevent skipping corners over colliding tiles
318
    if (dx && !mMap->getWalk(((int) pos.x + dx) / 32,
319
                             (int) pos.y / 32, getWalkMask()))
320
        dx = 16 - (int) pos.x % 32;
321
    if (dy && !mMap->getWalk((int) pos.x / 32,
322
                             ((int) pos.y + dy) / 32, getWalkMask()))
323
        dy = 16 - (int) pos.y % 32;
324
325
    // Choose a straight direction when diagonal target is blocked
326
    if (dx && dy && !mMap->getWalk((pos.x + dx) / 32,
327
                                   (pos.y + dy) / 32, getWalkMask()))
328
        dx = 16 - (int) pos.x % 32;
329
330
    int dScaler; // Distance to walk
331
332
    // Checks our path up to 1 tiles, if a blocking tile is found
333
    // We go to the last good tile, and break out of the loop
334
    for (dScaler = 1; dScaler <= 32; dScaler++)
335
    {
336
        if ( (dx || dy) &&
337
             !mMap->getWalk( ((int) pos.x + (dx * dScaler)) / 32,
338
                             ((int) pos.y + (dy * dScaler)) / 32, getWalkMask()) )
339
        {
340
            dScaler--;
341
            break;
342
        }
343
    }
344
345
    if (dScaler > 0)
346
    {
347
        //effectManager->trigger(15, (int) pos.x + (dx * dScaler), (int) pos.y + (dy * dScaler));
348
        setDestination((int) pos.x + (dx * dScaler), (int) pos.y + (dy * dScaler));
349
    }
350
    else if (dir)
351
    {
352
        // If the being can't move, just change direction
353
        Net::getPlayerHandler()->setDirection(dir);
354
        setDirection(dir);
355
    }
356
#endif
357
}
358
359
360
#ifdef TMWSERV_SUPPORT
361
bool LocalPlayer::checkInviteRights(const std::string &guildName)
362
{
363
    Guild *guild = getGuild(guildName);
364
    if (guild)
365
    {
366
        return guild->getInviteRights();
367
    }
368
369
    return false;
370
}
371
372
void LocalPlayer::inviteToGuild(Being *being)
373
{
374
    // TODO: Allow user to choose which guild to invite being to
375
    // For now, just invite to the first guild you have permissions to invite with
376
    std::map<int, Guild*>::iterator itr = mGuilds.begin();
377
    std::map<int, Guild*>::iterator itr_end = mGuilds.end();
378
    for (; itr != itr_end; ++itr)
379
    {
380
        if (checkInviteRights(itr->second->getName()))
381
        {
382
            Net::ChatServer::Guild::invitePlayer(being->getName(), itr->second->getId());
383
            return;
384
        }
385
    }
386
}
387
388
void LocalPlayer::clearInventory()
389
{
390
    mEquipment->clear();
391
    mInventory->clear();
392
}
393
394
void LocalPlayer::setInvItem(int index, int id, int amount)
395
{
396
    bool equipment = false;
397
    int itemType = ItemDB::get(id).getType();
398
    if (itemType != ITEM_UNUSABLE && itemType != ITEM_USABLE)
399
        equipment = true;
400
    mInventory->setItem(index, id, amount, equipment);
401
}
402
403
#endif
404
405
void LocalPlayer::pickUp(FloorItem *item)
406
{
407
    int dx = item->getX() - (int) getPosition().x / 32;
408
    int dy = item->getY() - (int) getPosition().y / 32;
409
410
    if (dx * dx + dy * dy < 4)
411
    {
412
        Net::getPlayerHandler()->pickUp(item);
413
        mPickUpTarget = NULL;
414
    }
415
    else
416
    {
417
#ifdef TMWSERV_SUPPORT
418
        setDestination(item->getX() * 32 + 16, item->getY() * 32 + 16);
419
#else
420
        setDestination(item->getX(), item->getY());
421
#endif
422
        mPickUpTarget = item;
423
#ifdef EATHENA_SUPPORT
424
        stopAttack();
425
#endif
426
    }
427
}
428
429
Being *LocalPlayer::getTarget() const
430
{
431
    return mTarget;
432
}
433
434
void LocalPlayer::setTarget(Being *target)
435
{
436
    if (mLastTarget != -1 || target == this)
437
        return;
438
439
    mLastTarget = tick_time;
440
441
    if (target == mTarget)
442
        return;
443
444
    if (target || mAction == ATTACK)
445
    {
446
        mTargetTime = tick_time;
447
    }
448
    else
449
    {
450
        mKeepAttacking = false;
451
        mTargetTime = -1;
452
    }
453
454
    if (mTarget)
455
        mTarget->untarget();
456
457
    if (mTarget && mTarget->getType() == Being::MONSTER)
458
        static_cast<Monster *>(mTarget)->setShowName(false);
459
460
    mTarget = target;
461
462
    if (target && target->getType() == Being::MONSTER)
463
        static_cast<Monster *>(target)->setShowName(true);
464
}
465
466
#ifdef TMWSERV_SUPPORT
467
void LocalPlayer::setDestination(int x, int y)
468
#else
469
void LocalPlayer::setDestination(Uint16 x, Uint16 y)
470
#endif
471
{
472
#ifdef TMWSERV_SUPPORT
473
    // Check the walkability of the destination
474
    // If the destination is a wall, don't go there!
475
    if (!mMap->getWalk(x / 32, y / 32))
476
        return;
477
478
    // check if we're finding a path to the seeked destination
479
    // If the path is empty... and isn't on the same tile,
480
    // then, it's an unvalid one.
481
    Vector playerPosition = getPosition();
482
    if (((int)(playerPosition.x / 32) != x / 32)
483
      || (((int)playerPosition.y / 32) != y / 32))
484
    {
485
        Path evaluatedPath = mMap->findPath(playerPosition.x / 32,
486
                                        playerPosition.y / 32,
487
                                        x / 32, y / 32,
488
                                        getWalkMask());
489
        if (evaluatedPath.empty())
490
            return;
491
    }
492
493
    // Fix coordinates so that the player does not seem to dig into walls.
494
    const int tx = x / 32;
495
    const int ty = y / 32;
496
    int fx = x % 32;
497
    int fy = y % 32;
498
499
    if (fx != 16 && !mMap->getWalk(tx + fx / 16 * 2 - 1, ty, getWalkMask()))
500
        fx = 16;
501
    if (fy != 16 && !mMap->getWalk(tx, ty + fy / 16 * 2 - 1, getWalkMask()))
502
        fy = 16;
503
    if (fx != 16 && fy != 16 && !mMap->getWalk(tx + fx / 16 * 2 - 1,
504
                                               ty + fy / 16 * 2 - 1,
505
                                               getWalkMask()))
506
        fx = 16;
507
508
    x = tx * 32 + fx;
509
    y = ty * 32 + fy;
510
#endif
511
512
    // Only send a new message to the server when destination changes
513
    if (x != mDestX || y != mDestY)
514
    {
515
        mDestX = x;
516
        mDestY = y;
517
518
        Net::getPlayerHandler()->setDestination(x, y, mDirection);
519
    }
520
521
    mPickUpTarget = NULL;
522
    mKeepAttacking = false;
523
524
    Being::setDestination(x, y);
525
}
526
527
void LocalPlayer::setWalkingDir(int dir)
528
{
529
    // This function is called by Game::handleInput()
530
531
#ifdef TMWSERV_SUPPORT
532
        // First if player is pressing key for the direction he is already
533
        // going, do nothing more...
534
535
        // Else if he is pressing a key, and its different from what he has
536
        // been pressing, stop (do not send this stop to the server) and
537
        // start in the new direction
538
        if (dir && (dir != getWalkingDir()))
539
            player_node->stopWalking(false);
540
541
        // Else, he is not pressing a key,
542
        // and the current path hasn't been sent by mouse,
543
        // then, stop (sending to server).
544
        else if (!dir)
545
        {
546
            if (!mPathSetByMouse)
547
                player_node->stopWalking(true);
548
            return;
549
        }
550
551
        // If the delay to send another walk message to the server hasn't expired,
552
        // don't do anything or we could get disconnected for spamming the server
553
        if (get_elapsed_time(mLocalWalkTime) < walkingKeyboardDelay)
554
            return;
555
#endif
556
557
    mWalkingDir = dir;
558
559
    // If we're not already walking, start walking.
560
    if (mAction != WALK && dir)
561
    {
562
        startWalking(dir);
563
    }
564
#ifdef TMWSERV_SUPPORT
565
    else if (mAction == WALK)
566
    {
567
        nextStep(dir);
568
    }
569
#endif
570
}
571
572
void LocalPlayer::startWalking(unsigned char dir)
573
{
574
    // This function is called by setWalkingDir(),
575
    // but also by nextStep() for eAthena...
576
577
    // TODO: Evaluate the implementation of this method for tmwserv
578
    if (!mMap || !dir)
579
        return;
580
581
#ifdef TMWSERV_SUPPORT
582
    const Vector &pos = getPosition();
583
#endif
584
585
    if (mAction == WALK && !mPath.empty())
586
    {
587
        // Just finish the current action, otherwise we get out of sync
588
#ifdef TMWSERV_SUPPORT
589
        Being::setDestination(pos.x, pos.y);
590
#else
591
        Being::setDestination(getTileX(), getTileY());
592
#endif
593
        return;
594
    }
595
596
    int dx = 0, dy = 0;
597
    if (dir & UP)
598
        dy--;
599
    if (dir & DOWN)
600
        dy++;
601
    if (dir & LEFT)
602
        dx--;
603
    if (dir & RIGHT)
604
        dx++;
605
606
#ifdef EATHENA_SUPPORT
607
    // Prevent skipping corners over colliding tiles
608
    if (dx && !mMap->getWalk(getTileX() + dx, getTileY(), getWalkMask()))
609
        dx = 0;
610
    if (dy && !mMap->getWalk(getTileX(), getTileY() + dy, getWalkMask()))
611
        dy = 0;
612
613
    // Choose a straight direction when diagonal target is blocked
614
    if (dx && dy && !mMap->getWalk(getTileX() + dx, getTileY() + dy, getWalkMask()))
615
        dx = 0;
616
617
    // Walk to where the player can actually go
618
    if ((dx || dy) && mMap->getWalk(getTileX() + dx, getTileY() + dy, getWalkMask()))
619
    {
620
        setDestination(getTileX() + dx, getTileY() + dy);
621
    }
622
    else if (dir)
623
    {
624
        // If the being can't move, just change direction
625
        Net::getPlayerHandler()->setDirection(dir);
626
        setDirection(dir);
627
    }
628
#else // TMWSERV_SUPPORT
629
    nextStep(dir);
630
#endif
631
}
632
633
void LocalPlayer::stopWalking(bool sendToServer)
634
{
635
    if (mAction == WALK && mWalkingDir) {
636
        mWalkingDir = 0;
637
#ifdef TMWSERV_SUPPORT
638
        mLocalWalkTime = 0;
639
#endif
640
        Being::setDestination(getPosition().x,getPosition().y);
641
        if (sendToServer)
642
             Net::getPlayerHandler()->setDestination(getPosition().x,
643
                                                     getPosition().y);
644
        setAction(STAND);
645
    }
646
647
    // No path set anymore, so we reset the path by mouse flag
648
    mPathSetByMouse = false;
649
650
    clearPath();
651
}
652
653
void LocalPlayer::toggleSit()
654
{
655
    if (mLastAction != -1)
656
        return;
657
    mLastAction = tick_time;
658
659
    Being::Action newAction;
660
    switch (mAction)
661
    {
662
        case STAND: newAction = SIT; break;
663
        case SIT: newAction = STAND; break;
664
        default: return;
665
    }
666
667
    Net::getPlayerHandler()->changeAction(newAction);
668
}
669
670
void LocalPlayer::emote(Uint8 emotion)
671
{
672
    if (mLastAction != -1)
673
        return;
674
    mLastAction = tick_time;
675
676
    Net::getPlayerHandler()->emote(emotion);
677
}
678
679
#ifdef TMWSERV_SUPPORT
680
/*
681
void LocalPlayer::attack()
682
{
683
    if (mLastAction != -1)
684
        return;
685
686
    // Can only attack when standing still
687
    if (mAction != STAND && mAction != ATTACK)
688
        return;
689
690
    //Face direction of the target
691
    if(mTarget){
692
        unsigned char dir = 0;
693
        int x = 0, y = 0;
694
        Vector plaPos = this->getPosition();
695
        Vector tarPos = mTarget->getPosition();
696
        x = plaPos.x - tarPos.x;
697
        y = plaPos.y - tarPos.y;
698
        if(abs(x) < abs(y)){
699
            //Check to see if target is above me or below me
700
            if(y > 0){
701
               dir = UP;
702
            } else {
703
               dir = DOWN;
704
            }
705
        } else {
706
            //check to see if the target is to the left or right of me
707
            if(x > 0){
708
               dir = LEFT;
709
            } else {
710
               dir = RIGHT;
711
            }
712
        }
713
        setDirection(dir);
714
    }
715
716
    mLastAction = tick_time;
717
718
    setAction(ATTACK);
719
720
    if (mEquippedWeapon)
721
    {
722
        std::string soundFile = mEquippedWeapon->getSound(EQUIP_EVENT_STRIKE);
723
        if (soundFile != "") sound.playSfx(soundFile);
724
    }
725
    else {
726
        sound.playSfx("sfx/fist-swish.ogg");
727
    }
728
    Net::GameServer::Player::attack(getSpriteDirection());
729
}
730
*/
731
732
void LocalPlayer::useSpecial(int special)
733
{
734
    Net::getSpecialHandler()->use(special);
735
}
736
737
void LocalPlayer::setSpecialStatus(int id, int current, int max, int recharge)
738
{
739
    logger->log("SpecialUpdate Skill #%d -- (%d/%d) -> %d", id, current, max, recharge);
740
    mSpecials[id].currentMana = current;
741
    mSpecials[id].neededMana = max;
742
    mSpecials[id].recharge = recharge;
743
}
744
745
#endif
746
747
void LocalPlayer::attack(Being *target, bool keep)
748
{
749
#ifdef TMWSERV_SUPPORT
750
    if (mLastAction != -1)
751
        return;
752
753
    // Can only attack when standing still
754
    if (mAction != STAND && mAction != ATTACK)
755
        return;
756
#endif
757
758
    mKeepAttacking = keep;
759
760
    if (!target || target->getType() == Being::NPC)
761
        return;
762
763
    if (mTarget != target || !mTarget)
764
    {
765
        mLastTarget = -1;
766
        setTarget(target);
767
    }
768
#ifdef TMWSERV_SUPPORT
769
    Vector plaPos = this->getPosition();
770
    Vector tarPos = mTarget->getPosition();
771
    int dist_x = plaPos.x - tarPos.x;
772
    int dist_y = plaPos.y - tarPos.y;
773
#else
774
    int dist_x = target->getTileX() - getTileX();
775
    int dist_y = target->getTileY() - getTileY();
776
777
    // Must be standing to attack
778
    if (mAction != STAND)
779
        return;
780
#endif
781
782
#ifdef TMWSERV_SUPPORT
783
    if (abs(dist_y) >= abs(dist_x))
784
    {
785
        if (dist_y < 0)
786
            setDirection(DOWN);
787
        else
788
            setDirection(UP);
789
    }
790
    else
791
    {
792
        if (dist_x < 0)
793
            setDirection(RIGHT);
794
        else
795
            setDirection(LEFT);
796
    }
797
#else
798
    if (abs(dist_y) >= abs(dist_x))
799
    {
800
        if (dist_y > 0)
801
            setDirection(DOWN);
802
        else
803
            setDirection(UP);
804
    }
805
    else
806
    {
807
        if (dist_x > 0)
808
            setDirection(RIGHT);
809
        else
810
            setDirection(LEFT);
811
    }
812
#endif
813
814
#ifdef TMWSERV_SUPPORT
815
    mLastAction = tick_time;
816
#else
817
    mWalkTime = tick_time;
818
    mTargetTime = tick_time;
819
#endif
820
821
    setAction(ATTACK);
822
823
    if (mEquippedWeapon)
824
    {
825
        std::string soundFile = mEquippedWeapon->getSound(EQUIP_EVENT_STRIKE);
826
        if (!soundFile.empty())
827
            sound.playSfx(soundFile);
828
    }
829
    else
830
    {
831
        sound.playSfx("sfx/fist-swish.ogg");
832
    }
833
834
    Net::getPlayerHandler()->attack(target->getId());
835
#ifdef EATHENA_SUPPORT
836
    if (!keep)
837
        stopAttack();
838
#endif
839
}
840
841
void LocalPlayer::stopAttack()
842
{
843
    if (mTarget)
844
    {
845
        if (mAction == ATTACK)
846
            setAction(STAND);
847
        setTarget(NULL);
848
    }
849
    mLastTarget = -1;
850
}
851
852
void LocalPlayer::raiseAttribute(size_t attr)
853
{
854
    // we assume that the server allows the change. When not we will undo it later.
855
    mCharacterPoints--;
856
    IntMap::iterator it = mAttributeBase.find(attr);
857
    if (it != mAttributeBase.end())
858
        (*it).second++;
859
    Net::getPlayerHandler()->increaseAttribute(attr);
860
}
861
862
void LocalPlayer::lowerAttribute(size_t attr)
863
{
864
    // we assume that the server allows the change. When not we will undo it later.
865
    mCorrectionPoints--;
866
    mCharacterPoints++;
867
    IntMap::iterator it = mAttributeBase.find(attr);
868
    if (it != mAttributeBase.end())
869
        (*it).second--;
870
    Net::getPlayerHandler()->decreaseAttribute(attr);
871
}
872
873
void LocalPlayer::setAttributeBase(int num, int value)
874
{
875
    int old = mAttributeBase[num];
876
877
    mAttributeBase[num] = value;
878
    if (skillDialog)
879
    {
880
        if (skillDialog->update(num).empty() || !(value > old))
881
            return;
882
883
        effectManager->trigger(1, this);
884
    }
885
886
    if (statusWindow)
887
        statusWindow->update(num);
888
}
889
890
void LocalPlayer::setAttributeEffective(int num, int value)
891
{
892
    mAttributeEffective[num] = value;
893
    if (skillDialog)
894
        skillDialog->update(num);
895
896
    if (statusWindow)
897
        statusWindow->update(num);
898
}
899
900
void LocalPlayer::setCharacterPoints(int n)
901
{
902
    mCharacterPoints = n;
903
904
    if (statusWindow)
905
        statusWindow->update(StatusWindow::CHAR_POINTS);
906
}
907
908
void LocalPlayer::setCorrectionPoints(int n)
909
{
910
    mCorrectionPoints = n;
911
912
    if (statusWindow)
913
        statusWindow->update(StatusWindow::CHAR_POINTS);
914
}
915
916
void LocalPlayer::setSkillPoints(int points)
917
{
918
    mSkillPoints = points;
919
    if (skillDialog)
920
        skillDialog->update();
921
}
922
923
void LocalPlayer::setExperience(int skill, int current, int next)
924
{
925
    std::pair<int, int> cur = getExperience(skill);
926
    int diff = current - cur.first;
927
928
    cur = std::pair<int, int>(current, next);
929
930
    mSkillExp[skill] = cur;
931
    std::string name;
932
    if (skillDialog)
933
        name = skillDialog->update(skill);
934
935
    if (mMap && cur.first != -1 && diff > 0 && !name.empty())
936
    {
937
        addMessageToQueue(strprintf("%d %s xp", diff, name.c_str()));
938
    }
939
}
940
941
std::pair<int, int> LocalPlayer::getExperience(int skill)
942
{
943
    return mSkillExp[skill];
944
}
945
946
void LocalPlayer::setHp(int value)
947
{
948
    mHp = value;
949
950
    if (statusWindow)
951
        statusWindow->update(StatusWindow::HP);
952
}
953
954
void LocalPlayer::setMaxHp(int value)
955
{
956
    mMaxHp = value;
957
958
    if (statusWindow)
959
        statusWindow->update(StatusWindow::HP);
960
}
961
962
void LocalPlayer::setLevel(int value)
963
{
964
    mLevel = value;
965
966
    if (statusWindow)
967
        statusWindow->update(StatusWindow::LEVEL);
968
}
969
970
void LocalPlayer::setExp(int value)
971
{
972
    if (mMap && value > mExp)
973
    {
974
        addMessageToQueue(toString(value - mExp) + " xp");
975
    }
976
    mExp = value;
977
978
    if (statusWindow)
979
        statusWindow->update(StatusWindow::EXP);
980
}
981
982
void LocalPlayer::setExpNeeded(int value)
983
{
984
    mExpNeeded = value;
985
986
    if (statusWindow)
987
        statusWindow->update(StatusWindow::EXP);
988
}
989
990
void LocalPlayer::setMP(int value)
991
{
992
    mMp = value;
993
994
    if (statusWindow)
995
        statusWindow->update(StatusWindow::MP);
996
}
997
998
void LocalPlayer::setMaxMP(int value)
999
{
1000
    mMaxMp = value;
1001
1002
    if (statusWindow)
1003
        statusWindow->update(StatusWindow::MP);
1004
}
1005
1006
void LocalPlayer::setMoney(int value)
1007
{
1008
    mMoney = value;
1009
1010
    if (statusWindow)
1011
        statusWindow->update(StatusWindow::MONEY);
1012
}
1013
1014
void LocalPlayer::pickedUp(const ItemInfo &itemInfo, int amount)
1015
{
1016
    if (!amount)
1017
    {
1018
        if (config.getValue("showpickupchat", 1))
1019
        {
1020
            localChatTab->chatLog(_("Unable to pick up item."), BY_SERVER);
1021
        }
1022
    }
1023
    else
1024
    {
1025
        if (config.getValue("showpickupchat", 1))
1026
        {
1027
            // TRANSLATORS: This sentence may be translated differently
1028
            // for different grammatical numbers (singular, plural, ...)
1029
            localChatTab->chatLog(strprintf(ngettext("You picked up %d "
1030
                    "[@@%d|%s@@].", "You picked up %d [@@%d|%s@@].", amount),
1031
                    amount, itemInfo.getId(), itemInfo.getName().c_str()),
1032
                    BY_SERVER);
1033
        }
1034
1035
        if (mMap && config.getValue("showpickupparticle", 0))
1036
        {
1037
            // Show pickup notification
1038
            addMessageToQueue(itemInfo.getName(), Palette::PICKUP_INFO);
1039
        }
1040
    }
1041
}
1042
1043
int LocalPlayer::getAttackRange()
1044
{
1045
#ifdef TMWSERV_SUPPORT
1046
    Item *weapon = mEquipment->getEquipment(EQUIP_FIGHT1_SLOT);
1047
    if (weapon)
1048
    {
1049
        const ItemInfo info = weapon->getInfo();
1050
        return info.getAttackRange();
1051
    }
1052
    return 48; // unarmed range
1053
#else
1054
    return mAttackRange;
1055
#endif
1056
}
1057
1058
bool LocalPlayer::withinAttackRange(Being *target)
1059
{
1060
#ifdef TMWSERV_SUPPORT
1061
    const Vector &targetPos = target->getPosition();
1062
    const Vector &pos = getPosition();
1063
    const int dx = abs(targetPos.x - pos.x);
1064
    const int dy = abs(targetPos.y - pos.y);
1065
    const int range = getAttackRange();
1066
1067
    return !(dx > range || dy > range);
1068
#else
1069
    int dist_x = abs(target->getTileX() - getTileY());
1070
    int dist_y = abs(target->getTileY() - getTileX());
1071
1072
    if (dist_x > getAttackRange() || dist_y > getAttackRange())
1073
    {
1074
        return false;
1075
    }
1076
1077
    return true;
1078
#endif
1079
}
1080
1081
void LocalPlayer::setGotoTarget(Being *target)
1082
{
1083
    mLastTarget = -1;
1084
#ifdef TMWSERV_SUPPORT
1085
    mTarget = target;
1086
    mGoingToTarget = true;
1087
    const Vector &targetPos = target->getPosition();
1088
    setDestination(targetPos.x, targetPos.y);
1089
#else
1090
    setTarget(target);
1091
    mGoingToTarget = true;
1092
    setDestination(target->getTileX(), target->getTileY());
1093
#endif
1094
}
1095
1096
extern MiniStatusWindow *miniStatusWindow;
1097
1098
void LocalPlayer::handleStatusEffect(StatusEffect *effect, int effectId)
1099
{
1100
    Being::handleStatusEffect(effect, effectId);
1101
1102
    if (effect)
1103
    {
1104
        effect->deliverMessage();
1105
        effect->playSFX();
1106
1107
        AnimatedSprite *sprite = effect->getIcon();
1108
1109
        if (!sprite) {
1110
            // delete sprite, if necessary
1111
            for (unsigned int i = 0; i < mStatusEffectIcons.size();)
1112
                if (mStatusEffectIcons[i] == effectId) {
1113
                    mStatusEffectIcons.erase(mStatusEffectIcons.begin() + i);
1114
                    miniStatusWindow->eraseIcon(i);
1115
                } else i++;
1116
        } else {
1117
            // replace sprite or append
1118
            bool found = false;
1119
1120
            for (unsigned int i = 0; i < mStatusEffectIcons.size(); i++)
1121
                if (mStatusEffectIcons[i] == effectId) {
1122
                    miniStatusWindow->setIcon(i, sprite);
1123
                    found = true;
1124
                    break;
1125
                }
1126
1127
            if (!found) { // add new
1128
                int offset = mStatusEffectIcons.size();
1129
                miniStatusWindow->setIcon(offset, sprite);
1130
                mStatusEffectIcons.push_back(effectId);
1131
            }
1132
        }
1133
    }
1134
}
1135
1136
void LocalPlayer::initTargetCursor()
1137
{
1138
    // Load target cursors
1139
    loadTargetCursor("graphics/gui/target-cursor-blue-s.png", 44, 35,
1140
                     false, TC_SMALL);
1141
    loadTargetCursor("graphics/gui/target-cursor-red-s.png", 44, 35,
1142
                     true, TC_SMALL);
1143
    loadTargetCursor("graphics/gui/target-cursor-blue-m.png", 62, 44,
1144
                     false, TC_MEDIUM);
1145
    loadTargetCursor("graphics/gui/target-cursor-red-m.png", 62, 44,
1146
                     true, TC_MEDIUM);
1147
    loadTargetCursor("graphics/gui/target-cursor-blue-l.png", 82, 60,
1148
                     false, TC_LARGE);
1149
    loadTargetCursor("graphics/gui/target-cursor-red-l.png", 82, 60,
1150
                     true, TC_LARGE);
1151
}
1152
1153
void LocalPlayer::loadTargetCursor(const std::string &filename,
1154
                                   int width, int height,
1155
                                   bool outRange, TargetCursorSize size)
1156
{
1157
    assert(size > -1);
1158
    assert(size < 3);
1159
1160
    ResourceManager *resman = ResourceManager::getInstance();
1161
1162
    ImageSet *currentImageSet = resman->getImageSet(filename, width, height);
1163
    Animation *anim = new Animation;
1164
1165
    for (unsigned int i = 0; i < currentImageSet->size(); ++i)
1166
    {
1167
        anim->addFrame(currentImageSet->get(i), 75,
1168
                      (16 - (currentImageSet->getWidth() / 2)),
1169
                      (16 - (currentImageSet->getHeight() / 2)));
1170
    }
1171
1172
    SimpleAnimation *currentCursor = new SimpleAnimation(anim);
1173
1174
    const int index = outRange ? 1 : 0;
1175
1176
    mTargetCursorImages[index][size] = currentImageSet;
1177
    mTargetCursor[index][size] = currentCursor;
1178
}
1179
1180
#ifdef EATHENA_SUPPORT
1181
void LocalPlayer::setInStorage(bool inStorage)
1182
{
1183
    mInStorage = inStorage;
1184
1185
    storageWindow->setVisible(inStorage);
1186
}
1187
#endif
1188
1189
void LocalPlayer::addMessageToQueue(const std::string &message,
1190
                                    Palette::ColorType color)
1191
{
1192
    mMessages.push_back(MessagePair(message, color));
1193
}
1194
1195
void LocalPlayer::optionChanged(const std::string &value)
1196
{
1197
    if (value == "showownname")
1198
    {
1199
        setShowName(config.getValue("showownname", 1));
1200
    }
1201
}