Mana
Loading...
Searching...
No Matches
localplayer.cpp
Go to the documentation of this file.
1/*
2 * The Mana Client
3 * Copyright (C) 2004-2009 The Mana World Development Team
4 * Copyright (C) 2009-2012 The Mana Developers
5 *
6 * This file is part of The Mana Client.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#include "localplayer.h"
23
24#include "configuration.h"
25#include "event.h"
26#include "flooritem.h"
27#include "guild.h"
28#include "item.h"
29#include "map.h"
30#include "particle.h"
31#include "playerinfo.h"
32
33#include "gui/gui.h"
34#include "gui/okdialog.h"
35
36#include "gui/widgets/chattab.h"
37
38#include "net/chathandler.h"
39#include "net/gamehandler.h"
40#include "net/guildhandler.h"
41#include "net/net.h"
42#include "net/partyhandler.h"
43#include "net/playerhandler.h"
44
45#include "resources/iteminfo.h"
47
48#include "utils/gettext.h"
49#include "utils/stringutils.h"
50
51constexpr unsigned AWAY_MESSAGE_TIMEOUT = 60 * 1000;
52
53// Actions are allowed at 5.5 per second
54constexpr unsigned ACTION_TIMEOUT = 182;
55
57
58LocalPlayer::LocalPlayer(int id, int subtype)
59 : Being(id, PLAYER, subtype, nullptr)
60 , mAwayListener(std::make_unique<AwayListener>())
61{
63
67}
68
70
72{
73 // Show XP messages
74 if (!mMessages.empty())
75 {
77 {
78 const auto &[message, color] = mMessages.front();
79
81 message,
82 getPixelX(),
83 getPixelY() - 32 - 16,
84 &userPalette->getColor(color),
85 gui->getInfoParticleFont(), true);
86
87 mMessages.pop_front();
88 mMessageTimer.set(300);
89 }
90 }
91
93
94 if (mTarget)
95 {
96 ActorSprite::Type targetType = mTarget->getType();
97 switch (targetType)
98 {
102 Net::getGameHandler()->getNpcTalkRange()) ?
104 break;
107 {
108 // Dealing with attacks
109 bool withinAttackRange = withinRange(mTarget, getAttackRange());
110 mTarget->setTargetType(withinAttackRange ?
112
113 if (!mTarget->isAlive())
114 {
115 stopAttack();
116 }
117 else if (mGoingToTarget)
118 {
119 if (!withinAttackRange && getPath().empty())
120 {
122 }
123 else if (withinAttackRange)
124 {
125 // Truncate the path to terminate at the next node.
126 // This permits to avoid a walking glitch in tile path
127 // mode.
128 if (!mPath.empty())
129 {
131 setDestination(mPath.front());
132 }
133
134 mKeepAttacking = true;
135 mGoingToTarget = false;
136 }
137 }
138 else if (mKeepAttacking)
139 {
140 attack(mTarget, true);
141 }
142 break;
143 }
144 default:
145 break;
146 }
147 }
148 else if (mPickUpTarget
149 && withinRange(mPickUpTarget, Net::getGameHandler()->getPickupRange()))
150 {
152 mPickUpTarget = nullptr;
153 }
154
155 Being::logic();
156}
157
158void LocalPlayer::setAction(Action action, int attackId)
159{
160 if (action == DEAD)
161 {
163 setTarget(nullptr);
164 }
165
166 Being::setAction(action, attackId);
167}
168
170{
171 mGMLevel = level;
172
173 if (level > 0)
174 setGM(true);
175}
176
177
179{
180 // Compute where the next tile will be set.
181 int dx = 0;
182 int dy = 0;
183 if (dir & Being::UP)
184 dy--;
185 if (dir & Being::DOWN)
186 dy++;
187 if (dir & Being::LEFT)
188 dx--;
189 if (dir & Being::RIGHT)
190 dx++;
191
192 Vector pos = getPosition();
193
194 // If no map, no direction or talking to NPC, give back the current player position
195 if (!mMap || (!dx && !dy) || PlayerInfo::isTalking())
196 return Position((int)pos.x, (int)pos.y);
197
198 const int tileW = mMap->getTileWidth();
199 const int tileH = mMap->getTileHeight();
200
201 // Get the current tile pos and its offset
202 const int tileX = (int)pos.x / tileW;
203 const int tileY = (int)pos.y / tileH;
204 int offsetX = (int)pos.x % tileW;
205 int offsetY = (int)pos.y % tileH;
206
207 // Get the walkability of every surrounding tiles.
208 const unsigned char walkMask = getWalkMask();
209 bool wTopLeft = mMap->getWalk(tileX - 1, tileY - 1, walkMask);
210 bool wTop = mMap->getWalk(tileX, tileY - 1, walkMask);
211 bool wTopRight = mMap->getWalk(tileX + 1, tileY - 1, walkMask);
212 bool wLeft = mMap->getWalk(tileX - 1, tileY, walkMask);
213 bool wRight = mMap->getWalk(tileX + 1, tileY, walkMask);
214 bool wBottomLeft = mMap->getWalk(tileX - 1, tileY + 1, walkMask);
215 bool wBottom = mMap->getWalk(tileX, tileY + 1, walkMask);
216 bool wBottomRight = mMap->getWalk(tileX + 1, tileY + 1, walkMask);
217
218 // Make diagonals unwalkable when both straight directions are blocking
219 if (!wTop)
220 {
221 if (!wRight)
222 wTopRight = false;
223 if (!wLeft)
224 wTopLeft = false;
225 }
226 if (!wBottom)
227 {
228 if (!wRight)
229 wBottomRight = false;
230 if (!wLeft)
231 wBottomLeft = false;
232 }
233
234 const int collisionRadius = getCollisionRadius();
235
236 // We'll make tests for each desired direction
237
238 // Handle diagonal cases by setting the way back to a straight direction
239 // when necessary.
240 if (dx && dy)
241 {
242 // Going top-right
243 if (dx > 0 && dy < 0)
244 {
245 // Choose a straight direction when diagonal target is blocked
246 if (!wTop && wRight)
247 dy = 0;
248 else if (wTop && !wRight)
249 dx = 0;
250 else if (!wTop && !wRight)
251 return Position(tileX * tileW + tileW
252 - collisionRadius,
253 tileY * tileH + collisionRadius);
254 else if (!wTopRight)
255 {
256 // Both straight direction are walkable
257 // Go right when below the corner
258 if (offsetY >=
259 (offsetX / tileH - (offsetX / tileW * tileH)))
260 dy = 0;
261 else // Go up otherwise
262 dx = 0;
263 }
264 else // The top-right diagonal is walkable
265 {
266 return mMap->checkNodeOffsets(collisionRadius,
267 walkMask,
268 Position((int)pos.x + tileW,
269 (int)pos.y - tileH));
270 }
271 }
272
273 // Going top-left
274 if (dx < 0 && dy < 0)
275 {
276 // Choose a straight direction when diagonal target is blocked
277 if (!wTop && wLeft)
278 dy = 0;
279 else if (wTop && !wLeft)
280 dx = 0;
281 else if (!wTop && !wLeft)
282 return Position(tileX * tileW + collisionRadius,
283 tileY * tileH + collisionRadius);
284 else if (!wTopLeft)
285 {
286 // Go left when below the corner
287 if (offsetY >= (offsetX / mMap->getTileWidth()
288 * mMap->getTileHeight()))
289 dy = 0;
290 else // Go up otherwise
291 dx = 0;
292 }
293 else // The diagonal is walkable
294 return mMap->checkNodeOffsets(collisionRadius,
295 walkMask,
296 Position((int)pos.x - tileW,
297 (int)pos.y - tileH));
298 }
299
300 // Going bottom-left
301 if (dx < 0 && dy > 0)
302 {
303 // Choose a straight direction when diagonal target is blocked
304 if (!wBottom && wLeft)
305 dy = 0;
306 else if (wBottom && !wLeft)
307 dx = 0;
308 else if (!wBottom && !wLeft)
309 return Position(tileX * tileW + collisionRadius,
310 tileY * tileH + tileH - collisionRadius);
311 else if (!wBottomLeft)
312 {
313 // Both straight direction are walkable
314 // Go down when below the corner
315 if (offsetY >= (offsetX / mMap->getTileHeight()
316 - (offsetX / mMap->getTileWidth()
317 * mMap->getTileHeight()) ))
318 dx = 0;
319 else // Go left otherwise
320 dy = 0;
321 }
322 else // The diagonal is walkable
323 return mMap->checkNodeOffsets(collisionRadius,
324 walkMask,
325 Position((int)pos.x - tileW,
326 (int)pos.y + tileH));
327 }
328
329 // Going bottom-right
330 if (dx > 0 && dy > 0)
331 {
332 // Choose a straight direction when diagonal target is blocked
333 if (!wBottom && wRight)
334 dy = 0;
335 else if (wBottom && !wRight)
336 dx = 0;
337 else if (!wBottom && !wRight)
338 return Position(tileX * tileW + tileW - collisionRadius,
339 tileY * tileH + tileH - collisionRadius);
340 else if (!wBottomRight)
341 {
342 // Both straight direction are walkable
343 // Go down when below the corner
344 if (offsetY >= (offsetX / mMap->getTileWidth()
345 * mMap->getTileHeight()))
346 dx = 0;
347 else // Go right otherwise
348 dy = 0;
349 }
350 else // The diagonal is walkable
351 return mMap->checkNodeOffsets(collisionRadius,
352 walkMask,
353 Position((int)pos.x + tileW,
354 (int)pos.y + tileH));
355 }
356
357 } // End of diagonal cases
358
359 // Straight directions
360 // Right direction
361 if (dx > 0 && !dy)
362 {
363 // If the straight destination is blocked,
364 // Make the player go the closest possible.
365 if (!wRight)
366 return Position(tileX * tileW + tileW - collisionRadius,
367 (int)pos.y);
368 else
369 {
370 if (!wTopRight)
371 {
372 // If we're going to collide with the top-right corner
373 if (offsetY - collisionRadius < 0)
374 {
375 // We make the player corrects its offset
376 // before going further
377 return Position(tileX * tileW
378 + tileW - collisionRadius,
379 tileY * tileH + collisionRadius);
380
381 }
382 }
383
384 if (!wBottomRight)
385 {
386 // If we're going to collide with the bottom-right corner
387 if (offsetY + collisionRadius > tileH)
388 {
389 // We make the player corrects its offset
390 // before going further
391 return Position(tileX * tileW
392 + tileW - collisionRadius,
393 tileY * tileH
394 + tileH - collisionRadius);
395
396 }
397 }
398 // If the way is clear, step up one checked tile ahead.
399 return mMap->checkNodeOffsets(collisionRadius, walkMask,
400 Position((int)pos.x + tileW,
401 (int)pos.y));
402 }
403 }
404
405 // Left direction
406 if (dx < 0 && !dy)
407 {
408 // If the straight destination is blocked,
409 // Make the player go the closest possible.
410 if (!wLeft)
411 return Position(tileX * tileW + collisionRadius, (int)pos.y);
412 else
413 {
414 if (!wTopLeft)
415 {
416 // If we're going to collide with the top-left corner
417 if (offsetY - collisionRadius < 0)
418 {
419 // We make the player corrects its offset
420 // before going further
421 return Position(tileX * tileW + collisionRadius,
422 tileY * tileH + collisionRadius);
423
424 }
425 }
426
427 if (!wBottomLeft)
428 {
429 // If we're going to collide with the bottom-left corner
430 if (offsetY + collisionRadius > tileH)
431 {
432 // We make the player corrects its offset
433 // before going further
434 return Position(tileX * tileW + collisionRadius,
435 tileY * tileH
436 + tileH - collisionRadius);
437
438 }
439 }
440 // If the way is clear, step up one checked tile ahead.
441 return mMap->checkNodeOffsets(collisionRadius, walkMask,
442 Position((int)pos.x - tileW,
443 (int)pos.y));
444 }
445 }
446
447 // Up direction
448 if (!dx && dy < 0)
449 {
450 // If the straight destination is blocked,
451 // Make the player go the closest possible.
452 if (!wTop)
453 return Position((int)pos.x, tileY * tileH + collisionRadius);
454 else
455 {
456 if (!wTopLeft)
457 {
458 // If we're going to collide with the top-left corner
459 if (offsetX - collisionRadius < 0)
460 {
461 // We make the player corrects its offset
462 // before going further
463 return Position(tileX * tileW + collisionRadius,
464 tileY * tileH + collisionRadius);
465
466 }
467 }
468
469 if (!wTopRight)
470 {
471 // If we're going to collide with the top-right corner
472 if (offsetX + collisionRadius > tileW)
473 {
474 // We make the player corrects its offset
475 // before going further
476 return Position(tileX * tileW
477 + tileW - collisionRadius,
478 tileY * tileH + collisionRadius);
479
480 }
481 }
482 // If the way is clear, step up one checked tile ahead.
483 return mMap->checkNodeOffsets(collisionRadius, walkMask,
484 Position((int)pos.x,
485 (int)pos.y - tileH));
486 }
487 }
488
489 // Down direction
490 if (!dx && dy > 0)
491 {
492 // If the straight destination is blocked,
493 // Make the player go the closest possible.
494 if (!wBottom)
495 return Position((int)pos.x, tileY * tileH
496 + tileH - collisionRadius);
497 else
498 {
499 if (!wBottomLeft)
500 {
501 // If we're going to collide with the bottom-left corner
502 if (offsetX - collisionRadius < 0)
503 {
504 // We make the player corrects its offset
505 // before going further
506 return Position(tileX * tileW + collisionRadius,
507 tileY * tileH
508 + tileH - collisionRadius);
509 }
510 }
511
512 if (!wBottomRight)
513 {
514 // If we're going to collide with the bottom-right corner
515 if (offsetX + collisionRadius > tileW)
516 {
517 // We make the player corrects its offset
518 // before going further
519 return Position(tileX * tileW
520 + tileW - collisionRadius,
521 tileY * tileH
522 + tileH - collisionRadius);
523
524 }
525 }
526 // If the way is clear, step up one checked tile ahead.
527 return mMap->checkNodeOffsets(collisionRadius, walkMask,
528 Position((int)pos.x,
529 (int)pos.y + tileH));
530 }
531 }
532
533 // Return the current position if everything else has failed.
534 return Position((int)pos.x, (int)pos.y);
535}
536
537void LocalPlayer::nextTile(unsigned char dir = 0)
538{
539 if (!mMap || !dir)
540 return;
541
542 const Vector &pos = getPosition();
543 Position destination = getNextWalkPosition(dir);
544
545 if ((int)pos.x != destination.x
546 || (int)pos.y != destination.y)
547 {
548 lookAt(destination);
549 setDestination(destination.x, destination.y);
550 }
551 else if (dir != mDirection)
552 {
553 // If the being can't move, just change direction
555 setDirection(dir);
556 }
557}
558
559bool LocalPlayer::checkInviteRights(const std::string &guildName)
560{
561 if (Guild *guild = getGuild(guildName))
562 return guild->getInviteRights();
563
564 return false;
565}
566
568{
569 if (being->getType() != PLAYER)
570 return;
571
572 // TODO: Allow user to choose which guild to invite being to
573 // For now, just invite to the first guild you have permissions to invite with
574 for (auto &[_, guild] : mGuilds)
575 {
576 if (checkInviteRights(guild->getName()))
577 {
578 Net::getGuildHandler()->invite(guild->getId(), being);
579 return;
580 }
581 }
582}
583
585{
586 if (!item)
587 return;
588
590
591 if (withinRange(item, Net::getGameHandler()->getPickupRange()))
592 {
594 // We found it, so set the player direction to it
595 // if the player does not move
596 if (getDestination() == getPosition())
597 lookAt(item->getPosition());
598 mPickUpTarget = nullptr;
599 }
600 else
601 {
603 setDestination(item->getPixelX(), item->getPixelY());
604 mPickUpTarget = item;
605 }
606}
607
609{
610 return mTarget;
611}
612
614{
615 if ((!mLastTargetTimer.passed() || target == this) && target)
616 return;
617
618 // Targeting allowed 4 times a second
619 if (target)
621
622 if (target == mTarget)
623 return;
624
625 if (!target && mAction != ATTACK)
626 mKeepAttacking = false;
627
628 Being *oldTarget = nullptr;
629 if (mTarget)
630 {
631 mTarget->untarget();
632 oldTarget = mTarget;
633 }
634
636 mTarget->setShowName(false);
637
638 mTarget = target;
639
640 if (oldTarget)
641 oldTarget->updateName();
642 if (mTarget)
644
645 if (target && target->getType() == ActorSprite::MONSTER)
646 target->setShowName(true);
647}
648
650{
651 if (!mMap)
652 return;
653
654 int srcX = x;
655 int srcY = y;
656 int dstX = (int)mDest.x;
657 int dstY = (int)mDest.y;
658 int tileWidth = mMap->getTileWidth();
659 int tileHeight = mMap->getTileHeight();
660 if (!Net::getPlayerHandler()->usePixelPrecision())
661 {
662 // For tile-based clients, we accept positions on the same tile.
663 srcX = srcX / tileWidth;
664 srcY = srcY / tileHeight;
665 dstX = dstX / tileWidth;
666 dstY = dstY / tileHeight;
667 }
668
669 // Only send a new message to the server when destination changes
670 if (srcX != dstX || srcY != dstY)
671 {
673 // Note: Being::setDestination() updates mDest, so we get the new
674 // destination.
675 dstX = (int)mDest.x;
676 dstY = (int)mDest.y;
677
678 if (!Net::getPlayerHandler()->usePixelPrecision())
679 {
680 dstX = dstX / tileWidth;
681 dstY = dstY / tileHeight;
682 }
683
684 // If the destination given to being class is accepted,
685 // we inform the Server.
686 if (srcX == dstX && srcY == dstY)
688 }
689}
690
692{
693 // This function is called by Game::handleInput()
694
695 // Don't compute a new path before the last one set by keyboard is finished.
696 // This permits to avoid movement glitches and server spamming.
697 if (!isPathSetByMouse())
698 {
699 const Vector &pos = getPosition();
700 const Vector &dest = getDestination();
701
702 if (pos.x != dest.x || pos.y != dest.y)
703 {
704 mWalkingDir = dir;
705 return;
706 }
707 }
708
709 // If the player is pressing a key, and its different from what he has
710 // been pressing, stop (do not send this stop to the server) and
711 // start in the new direction
712 if (dir && dir != mWalkingDir)
713 {
714 stopWalking(false);
715 }
716 // Else, he is not pressing a key, and the current path hasn't been sent by
717 // mouse, then let the path die (1/2 tile after that.) This permit to avoid
718 // desyncs with other clients.
719 else if (!dir)
720 {
721 mWalkingDir = 0;
722 return;
723 }
724
726
727 mWalkingDir = dir;
728
729 // If we're not already walking, start walking.
730 if (mAction != MOVE && dir)
731 {
732 startWalking(dir);
733 }
734 else if (mAction == MOVE)
735 {
736 nextTile(dir);
737 }
738}
739
740void LocalPlayer::startWalking(unsigned char dir)
741{
742 if (!mMap || !dir)
743 return;
744
745 if (mAction == MOVE && !mPath.empty())
746 {
747 // Just finish the current action, otherwise we get out of sync
748 const Vector &pos = getPosition();
749 Being::setDestination(pos.x, pos.y);
750 return;
751 }
752
753 nextTile(dir);
754}
755
756void LocalPlayer::stopWalking(bool sendToServer)
757{
758 if (mAction == MOVE)
759 {
760 mWalkingDir = 0;
761
762 setDestination((int) getPosition().x, (int) getPosition().y);
763 if (sendToServer)
765 (int) getPosition().y);
767 }
768
769 // No path set anymore, so we reset the path by mouse flag
770 mPathSetByMouse = false;
771
772 clearPath();
773}
774
776{
778 return;
780
781 Being::Action newAction;
782 switch (mAction)
783 {
784 case STAND: newAction = SIT; break;
785 case SIT: newAction = STAND; break;
786 default: return;
787 }
788
790}
791
792void LocalPlayer::emote(int emoteId)
793{
795 return;
797
798 Net::getPlayerHandler()->emote(emoteId);
799}
800
801void LocalPlayer::attack(Being *target, bool keep)
802{
804 return;
805
806 // Can only attack when standing still
807 if (mAction != STAND && mAction != ATTACK)
808 return;
809
810 if (!target || target->getType() == ActorSprite::NPC)
811 return;
812
813 // Can't attack more times than its attack speed
814 static Timer lastAttackTimer;
815 if (!lastAttackTimer.passed())
816 return;
817
818 lastAttackTimer.set(mAttackSpeed);
819
820 mKeepAttacking = keep;
821
822 if (mTarget != target || !mTarget)
823 {
825 setTarget(target);
826 }
827
829
831
833
834 Net::getPlayerHandler()->attack(target->getId());
835}
836
838{
839 if (mTarget)
840 {
841 if (mAction == ATTACK)
843 setTarget(nullptr);
844 }
847}
848
849void LocalPlayer::pickedUp(const ItemInfo &itemInfo, int amount,
850 unsigned char fail)
851{
852 if (fail)
853 {
854 const char* msg;
855 switch (fail)
856 {
857 case PICKUP_BAD_ITEM:
858 msg = N_("Tried to pick up nonexistent item."); break;
859 case PICKUP_TOO_HEAVY: msg = N_("Item is too heavy."); break;
860 case PICKUP_TOO_FAR: msg = N_("Item is too far away"); break;
861 case PICKUP_INV_FULL: msg = N_("Inventory is full."); break;
862 case PICKUP_STACK_FULL: msg = N_("Stack is too big."); break;
864 msg = N_("Item belongs to someone else."); break;
865 default: msg = N_("Unknown problem picking up item."); break;
866 }
868 {
869 serverNotice(_(msg));
870 }
872 {
873 // Show pickup notification
875 }
876 }
877 else
878 {
880 {
881 // TRANSLATORS: This sentence may be translated differently
882 // for different grammatical numbers (singular, plural, ...)
883 serverNotice(strprintf(ngettext("You picked up %d [@@%d|%s@@].",
884 "You picked up %d [@@%d|%s@@].",
885 amount),
886 amount, itemInfo.id, itemInfo.name.c_str()));
887 }
888
890 {
891 // Show pickup notification
892 std::string msg;
893 if (amount > 1)
894 msg = strprintf("%i ", amount);
895 msg += itemInfo.name;
897 }
898 }
899}
900
902{
903 // When the range is more than the minimal, we accept it
904 if (range > -1)
905 {
906 mAttackRange = range;
907 }
909 {
910 // TODO: Fix this to be more generic
912 if (weapon)
913 {
914 const ItemInfo &info = weapon->getInfo();
915 if (info.attackRange > -1)
916 mAttackRange = info.attackRange;
917 }
918 }
919}
920
921bool LocalPlayer::withinRange(Actor *target, int range) const
922{
923 if (!target || range < 0)
924 return false;
925
926 const Vector &targetPos = target->getPosition();
927 const Vector &pos = getPosition();
928 const int dx = abs(targetPos.x - pos.x);
929 const int dy = abs(targetPos.y - pos.y);
930 return dx <= range && dy <= range;
931}
932
934{
935 if (!target)
936 return;
937
939
940 setTarget(target);
941 mGoingToTarget = true;
942 mKeepAttacking = true;
943
945 setDestination(target->getPosition());
946}
947
948void LocalPlayer::addMessageToQueue(const std::string &message, int color)
949{
950 mMessages.emplace_back(message, color);
951}
952
953void LocalPlayer::event(Event::Channel channel, const Event &event)
954{
955 if (channel == Event::ActorSpriteChannel)
956 {
957 if (event.getType() == Event::Destroyed)
958 {
959 ActorSprite *actor = event.getActor("source");
960
961 if (mPickUpTarget == actor)
962 mPickUpTarget = nullptr;
963
964 if (mTarget == actor)
965 mTarget = nullptr;
966 }
967 }
968 else if (channel == Event::AttributesChannel)
969 {
970 if (event.getType() == Event::UpdateAttribute)
971 {
972 if (event.getInt("id") == EXP)
973 {
974 int change = 0;
975 int oldXp = event.getInt("oldValue");
976 int newXp = event.getInt("newValue");
977
978 // When the new XP is lower than the old one,
979 // it means that a new level has been reached.
980 // Thus, the xp difference can only be obtained
981 // with the exp needed for the next level.
982 // The new XP value is then the XP obtained for the new level.
983 if (newXp < oldXp)
984 {
986 - oldXp + newXp;
987 }
988 else
989 {
990 change = newXp - oldXp;
991 }
992
993 if (change > 0)
994 addMessageToQueue(toString(change) + " xp");
995 }
996 }
997 }
998 else if (channel == Event::ConfigChannel)
999 {
1000 if (event.getType() == Event::ConfigOptionChanged)
1001 {
1002 if (event.hasValue(&Config::showOwnName))
1003 {
1005 }
1006 else if (event.hasValue(&Config::visibleNames))
1007 {
1008 return;
1009 }
1010 }
1011 }
1012
1013 Being::event(channel, event);
1014}
1015
1016void LocalPlayer::updateStatusEffect(int id, bool newStatus)
1017{
1019 event.setInt("index", id);
1020 event.setBool("newStatus", newStatus);
1021 event.trigger(Event::ActorSpriteChannel);
1022
1023 Being::updateStatusEffect(id, newStatus);
1024}
1025
1026static std::string afkMessage()
1027{
1028 return config.afkMessage.empty() ? _("I am away from keyboard")
1029 : config.afkMessage;
1030}
1031
1033{
1034 if (mAwayMode == away)
1035 return;
1036
1037 mAwayMode = away;
1038
1039 if (mAwayMode)
1040 {
1041 mAwayListener->showDialog(afkMessage());
1042 mAfkTimer.reset();
1043 }
1044 else
1045 {
1046 mAwayListener->closeDialog();
1047 }
1048}
1049
1050void LocalPlayer::afkRespond(ChatTab *tab, const std::string &nick)
1051{
1052 if (!mAwayMode || !mAfkTimer.passed())
1053 return;
1054
1055 auto msg = strprintf(_("*AFK*: %s"), afkMessage().c_str());
1056
1057 Net::getChatHandler()->privateMessage(nick, msg);
1058 if (!tab)
1059 {
1060 localChatTab->chatLog(getName() + " : " + msg,
1061 ACT_WHISPER, false);
1062 }
1063 else
1064 {
1065 tab->chatLog(getName(), msg);
1066 }
1067
1069}
1070
1072{
1073 if (mAwayDialog)
1074 {
1075 mAwayDialog->removeActionListener(this);
1076 mAwayDialog->removeDeathListener(this);
1078 }
1079}
1080
1081void AwayListener::showDialog(const std::string &message)
1082{
1083 if (mAwayDialog)
1084 return;
1085
1086 mAwayDialog = new OkDialog(_("Away"), message);
1087 mAwayDialog->addActionListener(this);
1088 mAwayDialog->addDeathListener(this);
1089}
1090
1096
1097void AwayListener::action(const gcn::ActionEvent &event)
1098{
1099 if (event.getId() == "ok")
1100 local_player->setAwayMode(false);
1101}
1102
1103void AwayListener::death(const gcn::Event &event)
1104{
1105 if (mAwayDialog == event.getSource())
1106 mAwayDialog = nullptr;
1107}
@ ACT_WHISPER
Definition chatwindow.h:52
An action consists of several animations, one for each direction.
Definition action.h:32
void setTargetType(TargetCursorType type)
Sets the target animation for this actor.
int getId() const
Definition actorsprite.h:63
void untarget()
Untargets the actor.
Definition actor.h:35
int getPixelX() const
Returns the pixels X coordinate of the actor.
Definition actor.h:65
int getPixelY() const
Returns the pixel Y coordinate of the actor.
Definition actor.h:71
const Vector & getPosition() const
Returns the pixel position of this actor.
Definition actor.h:53
Map * mMap
Definition actor.h:113
void action(const gcn::ActionEvent &event) override
OkDialog * mAwayDialog
Definition localplayer.h:54
~AwayListener() override
void showDialog(const std::string &message)
void death(const gcn::Event &event) override
Definition being.h:65
void event(Event::Channel channel, const Event &event) override
Definition being.cpp:1365
void setDestination(int ex, int ey)
Creates a path for the being from current position to ex and ey.
Definition being.cpp:197
void setShowName(bool doShowName)
Definition being.cpp:473
void setDirection(uint8_t direction)
Sets the current direction.
Definition being.cpp:782
Action mAction
Action the being is performing.
Definition being.h:505
void updateName()
Definition being.cpp:1029
unsigned char getWalkMask() const
Gets the way the object is blocked by other objects.
Definition being.cpp:163
Type getType() const final
Returns the type of the ActorSprite.
Definition being.h:122
virtual void updateStatusEffect(int index, bool newStatus)
Notify self that a status effect has flipped.
Definition being.cpp:612
std::map< int, Guild * > mGuilds
Definition being.h:535
void lookAt(const Vector &destVec)
Make the being look at a given pixel position.
Definition being.cpp:702
int getCollisionRadius() const
Returns the being's pixel radius used to detect collisions.
Definition being.cpp:804
Vector mDest
destination coordinates.
Definition being.h:527
@ RIGHT
Definition being.h:107
@ DOWN
Definition being.h:104
@ UP
Definition being.h:106
@ LEFT
Definition being.h:105
Action
Action the being is currently performing WARNING: Has to be in sync with the same enum in the Being c...
Definition being.h:73
@ SIT
Definition being.h:77
@ MOVE
Definition being.h:75
@ ATTACK
Definition being.h:76
@ STAND
Definition being.h:74
@ DEAD
Definition being.h:78
bool isAlive() const
Returns whether this being is still alive.
Definition being.h:351
void setGM(bool gm)
Triggers whether or not to show the name as a GM name.
Definition being.cpp:1337
virtual void setAction(Action action, int attackId=1)
Sets the current action.
Definition being.cpp:624
const std::string & getName() const
Returns the name of the being.
Definition being.h:190
void logic() override
Performs being logic.
Definition being.cpp:810
Guild * getGuild(const std::string &guildName) const
Returns a pointer to the specified guild that the being is in.
Definition being.cpp:518
uint8_t mDirection
Facing direction.
Definition being.h:508
void clearPath()
Removes all path nodes from this being.
Definition being.cpp:254
const Vector & getDestination() const
Returns the destination for this being.
Definition being.h:140
const Path & getPath() const
Returns the path this being is following.
Definition being.h:397
int mAttackSpeed
Attack speed.
Definition being.h:503
Path mPath
Definition being.h:523
A tab for the chat window.
Definition chattab.h:36
void chatLog(std::string line, Own own=BY_SERVER, bool ignoreRecord=false)
Adds a line of text to our message list.
Definition chattab.cpp:111
void listen(Event::Channel channel)
Definition event.h:42
@ UpdateAttribute
Definition event.h:99
@ ConfigOptionChanged
Definition event.h:68
@ UpdateStatusEffect
Definition event.h:101
@ Destroyed
Definition event.h:71
Channel
Definition event.h:45
@ AttributesChannel
Definition event.h:47
@ ActorSpriteChannel
Definition event.h:46
@ ConfigChannel
Definition event.h:51
An item lying on the floor.
Definition flooritem.h:32
gcn::Font * getInfoParticleFont() const
Return the Font used for "Info Particles", i.e.
Definition gui.h:114
Definition guild.h:55
Defines a class for storing generic item infos.
Definition iteminfo.h:100
std::string name
Definition iteminfo.h:113
int id
Item ID.
Definition iteminfo.h:112
Represents one or more instances of a certain item type.
Definition item.h:35
const ItemInfo & getInfo() const
Returns information about this item type.
Definition item.h:103
The local player character.
Definition localplayer.h:75
void setTarget(Being *target)
Sets the target being of the player.
std::list< std::pair< std::string, int > > mMessages
Queued messages.
void setAttackRange(int range)
Sets the attack range.
void afkRespond(ChatTab *tab, const std::string &nick)
void pickedUp(const ItemInfo &itemInfo, int amount, unsigned char fail)
Shows item pickup notifications.
void setGMLevel(int level)
void inviteToGuild(Being *being)
Invite a player to join guild.
void addMessageToQueue(const std::string &message, int color=UserPalette::EXP_INFO)
Being * getTarget() const
Returns the current target of the player.
int getAttackRange() const
Gets the attack range.
void updateStatusEffect(int id, bool newStatus) override
Notify self that a status effect has flipped.
void setAction(Action action, int attackId=1) override
Sets the current action.
virtual void setDestination(int x, int y)
Sets a new destination for this being to walk to.
FloorItem * mPickUpTarget
std::unique_ptr< AwayListener > mAwayListener
Position getNextWalkPosition(unsigned char dir) const
Compute the next pathnode location when walking using keyboard.
bool mKeepAttacking
Whether or not to continue to attack.
bool mGoingToTarget
Timer mMessageTimer
void setWalkingDir(int dir)
Sets a new direction to keep walking in, when using the keyboard or the joystick.
bool isPathSetByMouse() const
Tells if the path has been set by mouse.
void event(Event::Channel channel, const Event &event) override
void emote(int emoteId)
void setGotoTarget(Being *target)
Sets going to being to attack.
Being * mTarget
bool withinRange(Actor *target, int range) const
Returns whether the target is in range (in pixels).
void setAwayMode(bool away)
void pickUp(FloorItem *item)
Timer mLastTargetTimer
Timer for last targeting action.
void cancelGoToTarget()
Cancel a possible target destination in progress, but not the targeting.
LocalPlayer(int id=65535, int subtype=0)
void attack(Being *target=nullptr, bool keep=false)
bool mPathSetByMouse
Tells if the path was set using mouse.
void stopWalking(bool sendToServer=true)
Stops the player dead in his tracks.
int mWalkingDir
The direction the player is walking in.
~LocalPlayer() override
Timer mLastActionTimer
Timeout for the last action.
Timer mAfkTimer
bool checkInviteRights(const std::string &guildName)
Check the player has permission to invite users to specific guild.
void logic() override
Performs being logic.
void pathSetByMouse()
Tells that the path has been set by mouse.
void stopAttack()
void nextTile(unsigned char dir)
set the next path tile when walking and using the keyboard.
void startWalking(unsigned char dir)
Make the character starts to walk.
int getTileHeight() const
Returns the tile height used by this map.
Definition map.h:271
int getTileWidth() const
Returns the tile width of this map.
Definition map.h:265
Position checkNodeOffsets(int radius, unsigned char walkMask, const Position &position) const
Check the current position against surrounding blocking tiles, and correct the position offset within...
Definition map.cpp:615
bool getWalk(int x, int y, unsigned char walkmask=BLOCKMASK_WALL) const
Gets walkability for a tile with a blocking bitmask.
Definition map.cpp:541
virtual void privateMessage(const std::string &recipient, const std::string &text)=0
virtual void invite(int guildId, const std::string &name)=0
virtual void setDestination(int x, int y, int direction=-1)=0
virtual void attack(int id)=0
virtual void emote(int emoteId)=0
virtual void setDirection(char direction)=0
virtual void changeAction(Being::Action action)=0
virtual void pickUp(FloorItem *floorItem)=0
An 'Ok' button dialog.
Definition okdialog.h:34
const gcn::Color & getColor(int type) const
Gets the color associated with the type.
Definition palette.h:72
Particle * addTextRiseFadeOutEffect(const std::string &text, int x, int y, const gcn::Color *color, gcn::Font *font, bool outline=false)
Creates a standalone text particle.
Definition particle.cpp:383
Simple timer that can be used to check if a certain amount of time has passed.
Definition time.h:62
bool passed() const
Returns whether the timer has passed.
Definition time.h:88
void reset()
Reset the timer.
Definition time.h:75
void set(uint32_t ms=0)
Sets the timer with an optional duration in milliseconds.
Definition time.h:69
Vector class.
Definition vector.h:33
float y
Definition vector.h:172
float x
Definition vector.h:171
void scheduleDelete()
Schedule this window for deletion.
Definition window.cpp:299
Config config
Global settings (config.xml)
Definition client.cpp:97
UserPalette * userPalette
Definition client.cpp:103
ChatTab * localChatTab
Definition game.cpp:117
void serverNotice(const std::string &message)
Definition event.h:319
Particle * particleEngine
Definition game.cpp:113
#define N_(s)
Definition gettext.h:39
#define _(s)
Definition gettext.h:38
Gui * gui
The GUI system.
Definition gui.cpp:50
constexpr unsigned ACTION_TIMEOUT
constexpr unsigned AWAY_MESSAGE_TIMEOUT
LocalPlayer * local_player
LocalPlayer * local_player
@ PICKUP_DROP_STEAL
Definition localplayer.h:68
@ PICKUP_TOO_FAR
Definition localplayer.h:65
@ PICKUP_BAD_ITEM
Definition localplayer.h:63
@ PICKUP_TOO_HEAVY
Definition localplayer.h:64
@ PICKUP_STACK_FULL
Definition localplayer.h:67
@ PICKUP_INV_FULL
Definition localplayer.h:66
ServerType getNetworkType()
Definition net.cpp:200
GameHandler * getGameHandler()
Definition net.cpp:75
ChatHandler * getChatHandler()
Definition net.cpp:70
PlayerHandler * getPlayerHandler()
Definition net.cpp:110
GuildHandler * getGuildHandler()
Definition net.cpp:85
Equipment * getEquipment()
Returns the player's equipment.
void logic()
Does necessary updates every tick.
bool isTalking()
Returns true if the player is involved in a NPC interaction, false otherwise.
int getAttribute(int id)
Returns the value of the given attribute.
@ EQUIP_FIGHT1_SLOT
Definition iteminfo.h:183
@ EXP
Definition playerinfo.h:33
@ EXP_NEEDED
Definition playerinfo.h:33
std::string strprintf(char const *format,...)
A safe version of sprintf that returns a std::string of the result.
std::string toString(const T &arg)
Converts the given value to a string using std::stringstream.
Definition stringutils.h:68
bool showPickupChat
bool visibleNames
bool showOwnName
bool showPickupParticle
std::string afkMessage
A position along a being's path.
Definition position.h:31
int y
Definition position.h:37
int x
Definition position.h:36