00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "being.h"
00023
00024 #include "animatedsprite.h"
00025 #include "configuration.h"
00026 #include "effectmanager.h"
00027 #include "game.h"
00028 #include "graphics.h"
00029 #include "localplayer.h"
00030 #include "log.h"
00031 #include "map.h"
00032 #include "particle.h"
00033 #include "simpleanimation.h"
00034 #include "sound.h"
00035 #include "text.h"
00036 #include "statuseffect.h"
00037
00038 #include "gui/speechbubble.h"
00039
00040 #include "resources/colordb.h"
00041 #include "resources/emotedb.h"
00042 #include "resources/image.h"
00043 #include "resources/itemdb.h"
00044 #include "resources/iteminfo.h"
00045 #include "resources/resourcemanager.h"
00046
00047 #include "gui/gui.h"
00048 #include "gui/palette.h"
00049 #include "gui/speechbubble.h"
00050
00051 #include "utils/dtor.h"
00052 #include "utils/gettext.h"
00053 #include "utils/stringutils.h"
00054 #include "utils/xml.h"
00055
00056 #include <cassert>
00057 #include <cmath>
00058
00059 namespace {
00060 const bool debug_movement = true;
00061 }
00062
00063
00064 #define BEING_EFFECTS_FILE "effects.xml"
00065 #define HAIR_FILE "hair.xml"
00066
00067 int Being::mNumberOfHairColors = 1;
00068 int Being::mNumberOfHairstyles = 1;
00069 std::vector<std::string> Being::hairColors;
00070
00071 static const int DEFAULT_WIDTH = 32;
00072 static const int DEFAULT_HEIGHT = 32;
00073
00074 Being::Being(int id, int job, Map *map):
00075 #ifdef EATHENA_SUPPORT
00076 mX(0), mY(0),
00077 mWalkTime(0),
00078 #endif
00079 mEmotion(0), mEmotionTime(0),
00080 mSpeechTime(0),
00081 mAttackSpeed(350),
00082 mAction(STAND),
00083 mJob(job),
00084 mId(id),
00085 mDirection(DOWN),
00086 #ifdef TMWSERV_SUPPORT
00087 mSpriteDirection(DIRECTION_DOWN),
00088 #endif
00089 mMap(NULL),
00090 mIsGM(false),
00091 mParticleEffects(config.getValue("particleeffects", 1)),
00092 mEquippedWeapon(NULL),
00093 #ifdef TMWSERV_SUPPORT
00094 mHairStyle(0),
00095 #else
00096 mHairStyle(1),
00097 #endif
00098 mHairColor(0),
00099 mGender(GENDER_UNSPECIFIED),
00100 mStunMode(0),
00101 mSprites(VECTOREND_SPRITE, NULL),
00102 mSpriteIDs(VECTOREND_SPRITE, 0),
00103 mSpriteColors(VECTOREND_SPRITE, ""),
00104 mStatusParticleEffects(&mStunParticleEffects, false),
00105 mChildParticleEffects(&mStatusParticleEffects, false),
00106 mMustResetParticles(false),
00107 #ifdef TMWSERV_SUPPORT
00108 mWalkSpeed(100),
00109 #else
00110 mWalkSpeed(150),
00111 #endif
00112 mPx(0), mPy(0),
00113 mUsedTargetCursor(NULL)
00114 {
00115 setMap(map);
00116
00117 mSpeechBubble = new SpeechBubble;
00118
00119 mNameColor = &guiPalette->getColor(Palette::CHAT);
00120 mText = 0;
00121 }
00122
00123 Being::~Being()
00124 {
00125 mUsedTargetCursor = NULL;
00126 delete_all(mSprites);
00127 clearPath();
00128
00129 if (player_node && player_node->getTarget() == this)
00130 player_node->setTarget(NULL);
00131
00132 setMap(NULL);
00133
00134 delete mSpeechBubble;
00135 delete mText;
00136 }
00137
00138 void Being::setPosition(const Vector &pos)
00139 {
00140 mPos = pos;
00141
00142
00143 mPx = (int) pos.x;
00144 mPy = (int) pos.y;
00145
00146 updateCoords();
00147
00148 if (mText)
00149 mText->adviseXY(mPx,
00150 mPy - getHeight() - mText->getHeight());
00151 }
00152
00153 #ifdef EATHENA_SUPPORT
00154 void Being::setDestination(Uint16 destX, Uint16 destY)
00155 {
00156 if (mMap)
00157 setPath(mMap->findPath(mX, mY, destX, destY, getWalkMask()));
00158 }
00159 #endif
00160
00161 #ifdef TMWSERV_SUPPORT
00162 void Being::adjustCourse(int srcX, int srcY, int dstX, int dstY)
00163 {
00164 if (debug_movement)
00165 printf("%p adjustCourse(%d, %d, %d, %d)\n",
00166 (void*) this, srcX, srcY, dstX, dstY);
00167
00168 mDest.x = dstX;
00169 mDest.y = dstY;
00170
00171
00172 if (mMap && fabsf((mDest - mPos).length()) > 32) {
00173 setPath(mMap->findPath((int) mPos.x / 32, (int) mPos.y / 32,
00174 dstX / 32, dstY / 32, getWalkMask()));
00175 } else {
00176 setPath(Path());
00177 }
00178
00179
00180
00181
00182
00183
00184
00185
00186
00187
00188
00189
00190
00191
00192
00193
00194
00195
00196
00197
00198
00199
00200
00201
00202
00203
00204
00205
00206
00207
00208
00209
00210
00211
00212
00213
00214
00215
00216
00217
00218
00219
00220
00221
00222
00223
00224
00225
00226
00227
00228
00229
00230
00231
00232
00233
00234
00235
00236
00237
00238
00239
00240
00241
00242
00243
00244
00245
00246
00247
00248
00249
00250
00251
00252
00253
00254
00255
00256
00257
00258
00259
00260
00261
00262
00263
00264
00265
00266
00267
00268
00269
00270
00271
00272
00273
00274
00275
00276
00277
00278
00279
00280
00281
00282
00283
00284
00285
00286
00287
00288
00289
00290
00291
00292
00293
00294
00295
00296
00297
00298
00299
00300
00301
00302
00303
00304
00305
00306
00307
00308 }
00309
00310 void Being::adjustCourse(int srcX, int srcY)
00311 {
00312 if (debug_movement)
00313 printf("%p adjustCourse(%d, %d)\n", (void*) this, srcX, srcY);
00314
00315 if (!mPath.empty())
00316 adjustCourse(srcX, srcY, mPath.back().x * 32, mPath.back().y * 32);
00317 }
00318
00319 void Being::setDestination(int destX, int destY)
00320 {
00321 if (debug_movement)
00322 printf("%p setDestination(%d, %d)\n", (void*) this, destX, destY);
00323
00324 adjustCourse((int) mPos.x, (int) mPos.y, destX, destY);
00325 }
00326 #endif // TMWSERV_SUPPORT
00327
00328 void Being::clearPath()
00329 {
00330 mPath.clear();
00331 }
00332
00333 void Being::setPath(const Path &path)
00334 {
00335 mPath = path;
00336 #ifdef TMWSERV_SUPPORT
00337 std::cout << this << " New path: " << path << std::endl;
00338 #else
00339 if (mAction != WALK && mAction != DEAD)
00340 {
00341 nextStep();
00342 mWalkTime = tick_time;
00343 }
00344 #endif
00345 }
00346
00347 void Being::setHairStyle(int style, int color)
00348 {
00349 mHairStyle = style < 0 ? mHairStyle : style % mNumberOfHairstyles;
00350 mHairColor = color < 0 ? mHairColor : color % ColorDB::size();
00351 }
00352
00353 void Being::setSprite(int slot, int id, const std::string &color)
00354 {
00355 assert(slot >= BASE_SPRITE && slot < VECTOREND_SPRITE);
00356 mSpriteIDs[slot] = id;
00357 mSpriteColors[slot] = color;
00358 }
00359
00360 void Being::setSpeech(const std::string &text, int time)
00361 {
00362 mSpeech = text;
00363
00364
00365 trim(mSpeech);
00366
00367
00368 std::string::size_type start = mSpeech.find('[');
00369 std::string::size_type end = mSpeech.find(']', start);
00370
00371 while (start != std::string::npos && end != std::string::npos)
00372 {
00373
00374 while ((mSpeech.find('[', start + 1) != std::string::npos) &&
00375 (mSpeech.find('[', start + 1) < end))
00376 {
00377 start = mSpeech.find('[', start + 1);
00378 }
00379
00380 std::string::size_type position = mSpeech.find('|');
00381 if (mSpeech[start + 1] == '@' && mSpeech[start + 2] == '@')
00382 {
00383 mSpeech.erase(end, 1);
00384 mSpeech.erase(start, (position - start) + 1);
00385 }
00386 position = mSpeech.find('@');
00387
00388 while (position != std::string::npos)
00389 {
00390 mSpeech.erase(position, 2);
00391 position = mSpeech.find('@');
00392 }
00393
00394 start = mSpeech.find('[', start + 1);
00395 end = mSpeech.find(']', start);
00396 }
00397
00398 if (!mSpeech.empty())
00399 mSpeechTime = time <= SPEECH_MAX_TIME ? time : SPEECH_MAX_TIME;
00400
00401 const int speech = (int) config.getValue("speech", NAME_IN_BUBBLE);
00402 if (speech == TEXT_OVERHEAD) {
00403 if (mText)
00404 delete mText;
00405
00406 mText = new Text(mSpeech,
00407 mPx, mPy - getHeight(),
00408 gcn::Graphics::CENTER,
00409 &guiPalette->getColor(Palette::PARTICLE),
00410 true);
00411 }
00412 }
00413
00414 void Being::takeDamage(Being *attacker, int amount, AttackType type)
00415 {
00416 gcn::Font *font;
00417 std::string damage = amount ? toString(amount) : type == FLEE ?
00418 "dodge" : "miss";
00419 const gcn::Color* color;
00420
00421 font = gui->getInfoParticleFont();
00422
00423
00424 if (type == CRITICAL || type == FLEE)
00425 {
00426 color = &guiPalette->getColor(Palette::HIT_CRITICAL);
00427 }
00428 else if (!amount)
00429 {
00430 if (attacker == player_node)
00431 {
00432
00433
00434 color = &guiPalette->getColor(Palette::HIT_MONSTER_PLAYER);
00435 }
00436 else
00437 {
00438 color = &guiPalette->getColor(Palette::MISS);
00439 }
00440 }
00441 else if (getType() == MONSTER)
00442 {
00443 color = &guiPalette->getColor(Palette::HIT_PLAYER_MONSTER);
00444 }
00445 else
00446 {
00447 color = &guiPalette->getColor(Palette::HIT_MONSTER_PLAYER);
00448 }
00449
00450
00451 particleEngine->addTextSplashEffect(damage,
00452 mPx + 16, mPy + 16,
00453 color, font, true);
00454
00455 if (amount > 0)
00456 {
00457 if (type != CRITICAL)
00458 {
00459 effectManager->trigger(26, this);
00460 }
00461 else
00462 {
00463 effectManager->trigger(28, this);
00464 }
00465 }
00466 }
00467
00468 #ifdef TMWSERV_SUPPORT
00469 void Being::handleAttack()
00470 #else
00471 void Being::handleAttack(Being *victim, int damage, AttackType type)
00472 #endif
00473 {
00474 setAction(Being::ATTACK);
00475 #ifdef EATHENA_SUPPORT
00476 mFrame = 0;
00477 mWalkTime = tick_time;
00478 #endif
00479 }
00480
00481 void Being::setMap(Map *map)
00482 {
00483
00484 if (mMap)
00485 mMap->removeSprite(mSpriteIterator);
00486
00487 mMap = map;
00488
00489
00490 if (mMap)
00491 mSpriteIterator = mMap->addSprite(this);
00492
00493
00494 mChildParticleEffects.clear();
00495 mMustResetParticles = true;
00496 }
00497
00498 void Being::controlParticle(Particle *particle)
00499 {
00500 mChildParticleEffects.addLocally(particle);
00501 }
00502
00503 void Being::setAction(Action action, int attackType)
00504 {
00505 SpriteAction currentAction = ACTION_INVALID;
00506
00507 switch (action)
00508 {
00509 case WALK:
00510 currentAction = ACTION_WALK;
00511 break;
00512 case SIT:
00513 currentAction = ACTION_SIT;
00514 break;
00515 case ATTACK:
00516 if (mEquippedWeapon)
00517 currentAction = mEquippedWeapon->getAttackType();
00518 else
00519 currentAction = ACTION_ATTACK;
00520
00521 for (int i = 0; i < VECTOREND_SPRITE; i++)
00522 {
00523 if (mSprites[i])
00524 mSprites[i]->reset();
00525 }
00526 break;
00527 case HURT:
00528
00529
00530
00531 break;
00532 case DEAD:
00533 currentAction = ACTION_DEAD;
00534 break;
00535 case STAND:
00536 currentAction = ACTION_STAND;
00537 break;
00538 }
00539
00540 if (currentAction != ACTION_INVALID)
00541 {
00542 for (int i = 0; i < VECTOREND_SPRITE; i++)
00543 {
00544 if (mSprites[i])
00545 mSprites[i]->play(currentAction);
00546 }
00547 mAction = action;
00548 }
00549 }
00550
00551 void Being::setDirection(Uint8 direction)
00552 {
00553 if (mDirection == direction)
00554 return;
00555
00556 #ifdef TMWSERV_SUPPORT
00557
00558 int mFaceDirection = mDirection & direction;
00559 if (!mFaceDirection)
00560 mFaceDirection = direction;
00561 mDirection = direction;
00562
00563 SpriteDirection dir;
00564 if (mFaceDirection & UP)
00565 dir = DIRECTION_UP;
00566 else if (mFaceDirection & DOWN)
00567 dir = DIRECTION_DOWN;
00568 else if (mFaceDirection & RIGHT)
00569 dir = DIRECTION_RIGHT;
00570 else
00571 dir = DIRECTION_LEFT;
00572 mSpriteDirection = dir;
00573 #else
00574 mDirection = direction;
00575 SpriteDirection dir = getSpriteDirection();
00576 #endif
00577
00578 for (int i = 0; i < VECTOREND_SPRITE; i++)
00579 {
00580 if (mSprites[i])
00581 mSprites[i]->setDirection(dir);
00582 }
00583 }
00584
00585 #ifdef EATHENA_SUPPORT
00586 SpriteDirection Being::getSpriteDirection() const
00587 {
00588 SpriteDirection dir;
00589
00590 if (mDirection & UP)
00591 dir = DIRECTION_UP;
00592 else if (mDirection & DOWN)
00593 dir = DIRECTION_DOWN;
00594 else if (mDirection & RIGHT)
00595 dir = DIRECTION_RIGHT;
00596 else
00597 dir = DIRECTION_LEFT;
00598
00599 return dir;
00600 }
00601
00602 void Being::nextStep()
00603 {
00604 if (mPath.empty())
00605 {
00606 setAction(STAND);
00607 return;
00608 }
00609
00610 Position pos = mPath.front();
00611 mPath.pop_front();
00612
00613 int dir = 0;
00614 if (pos.x > mX)
00615 dir |= RIGHT;
00616 else if (pos.x < mX)
00617 dir |= LEFT;
00618 if (pos.y > mY)
00619 dir |= DOWN;
00620 else if (pos.y < mY)
00621 dir |= UP;
00622
00623 setDirection(dir);
00624
00625 if (!mMap->getWalk(pos.x, pos.y, getWalkMask()))
00626 {
00627 setAction(STAND);
00628 return;
00629 }
00630
00631 mX = pos.x;
00632 mY = pos.y;
00633 setAction(WALK);
00634 mWalkTime += mWalkSpeed / 10;
00635 }
00636 #endif
00637
00638 void Being::logic()
00639 {
00640
00641 if (mSpeechTime > 0)
00642 mSpeechTime--;
00643
00644
00645 if (mSpeechTime == 0 && mText)
00646 {
00647 delete mText;
00648 mText = 0;
00649 }
00650
00651 #ifdef TMWSERV_SUPPORT
00652 const Vector dest = (mPath.empty()) ?
00653 mDest : Vector(mPath.front().x * 32 + 16,
00654 mPath.front().y * 32 + 16);
00655
00656 Vector dir = dest - mPos;
00657 const float length = dir.length();
00658
00659
00660
00661
00662 if (length > 2.0f) {
00663 const float speed = mWalkSpeed / 100.0f;
00664 setPosition(mPos + (dir / (length / speed)));
00665
00666 if (mAction != WALK)
00667 setAction(WALK);
00668
00669
00670 int direction = 0;
00671 const float dx = std::abs(dir.x);
00672 const float dy = std::abs(dir.y);
00673 if (dx > dy)
00674 direction |= (dir.x > 0) ? RIGHT : LEFT;
00675 else
00676 direction |= (dir.y > 0) ? DOWN : UP;
00677 setDirection(direction);
00678 }
00679 else if (!mPath.empty()) {
00680
00681
00682 mPath.pop_front();
00683 } else if (mAction == WALK) {
00684 setAction(STAND);
00685 }
00686 #else
00687
00688 setPosition(mX * 32 + 16 + getXOffset(),
00689 mY * 32 + 32 + getYOffset());
00690 #endif
00691
00692 if (mEmotion != 0)
00693 {
00694 mEmotionTime--;
00695 if (mEmotionTime == 0)
00696 mEmotion = 0;
00697 }
00698
00699
00700 if (mUsedTargetCursor)
00701 mUsedTargetCursor->update(tick_time * 10);
00702
00703 for (int i = 0; i < VECTOREND_SPRITE; i++)
00704 {
00705 if (mSprites[i])
00706 mSprites[i]->update(tick_time * 10);
00707 }
00708
00709
00710 if (mMustResetParticles) {
00711 mMustResetParticles = false;
00712 for (std::set<int>::iterator it = mStatusEffects.begin();
00713 it != mStatusEffects.end(); it++) {
00714 const StatusEffect *effect = StatusEffect::getStatusEffect(*it, true);
00715 if (effect && effect->particleEffectIsPersistent())
00716 updateStatusEffect(*it, true);
00717 }
00718 }
00719
00720
00721 mChildParticleEffects.moveTo(mPos.x, mPos.y);
00722 }
00723
00724 void Being::draw(Graphics *graphics, int offsetX, int offsetY) const
00725 {
00726
00727
00728
00729 const int px = mPx + offsetX - 16;
00730 const int py = mPy + offsetY - 32;
00731
00732 if (mUsedTargetCursor)
00733 mUsedTargetCursor->draw(graphics, px, py);
00734
00735 for (int i = 0; i < VECTOREND_SPRITE; i++)
00736 {
00737 if (mSprites[i])
00738 {
00739 mSprites[i]->draw(graphics, px, py);
00740 }
00741 }
00742 }
00743
00744 void Being::drawEmotion(Graphics *graphics, int offsetX, int offsetY)
00745 {
00746 if (!mEmotion)
00747 return;
00748
00749 const int px = mPx - offsetX - 16;
00750 const int py = mPy - offsetY - 64 - 32;
00751 const int emotionIndex = mEmotion - 1;
00752
00753 if (emotionIndex >= 0 && emotionIndex <= EmoteDB::getLast())
00754 EmoteDB::getAnimation(emotionIndex)->draw(graphics, px, py);
00755 }
00756
00757 void Being::drawSpeech(int offsetX, int offsetY)
00758 {
00759 const int px = mPx - offsetX;
00760 const int py = mPy - offsetY;
00761 const int speech = (int) config.getValue("speech", NAME_IN_BUBBLE);
00762
00763
00764 if (mSpeechTime == 0)
00765 {
00766 if (mSpeechBubble->isVisible())
00767 mSpeechBubble->setVisible(false);
00768 }
00769 else if (mSpeechTime > 0 && (speech == NAME_IN_BUBBLE ||
00770 speech == NO_NAME_IN_BUBBLE))
00771 {
00772 const bool showName = (speech == NAME_IN_BUBBLE);
00773
00774 if (mText)
00775 {
00776 delete mText;
00777 mText = NULL;
00778 }
00779
00780 mSpeechBubble->setCaption(showName ? mName : "", mNameColor);
00781
00782 mSpeechBubble->setText(mSpeech, showName);
00783 mSpeechBubble->setPosition(px - (mSpeechBubble->getWidth() / 2),
00784 py - getHeight() - (mSpeechBubble->getHeight()));
00785 mSpeechBubble->setVisible(true);
00786 }
00787 else if (mSpeechTime > 0 && speech == TEXT_OVERHEAD)
00788 {
00789 mSpeechBubble->setVisible(false);
00790
00791 if (! mText) {
00792 mText = new Text(mSpeech,
00793 mPx, mPy - getHeight(),
00794 gcn::Graphics::CENTER,
00795 &guiPalette->getColor(Palette::PARTICLE),
00796 true);
00797 }
00798 }
00799 else if (speech == NO_SPEECH)
00800 {
00801 mSpeechBubble->setVisible(false);
00802
00803 if (mText)
00804 delete mText;
00805
00806 mText = NULL;
00807 }
00808 }
00809
00810 Being::Type Being::getType() const
00811 {
00812 return UNKNOWN;
00813 }
00814
00815 void Being::setStatusEffectBlock(int offset, Uint16 newEffects)
00816 {
00817 for (int i = 0; i < STATUS_EFFECTS; i++) {
00818 int index = StatusEffect::blockEffectIndexToEffectIndex(offset + i);
00819
00820 if (index != -1)
00821 setStatusEffect(index, (newEffects & (1 << i)) > 0);
00822 }
00823 }
00824
00825 void Being::handleStatusEffect(StatusEffect *effect, int effectId)
00826 {
00827 if (!effect)
00828 return;
00829
00830
00831
00832
00833
00834
00835
00836 Particle *particle = effect->getParticle();
00837
00838 if (effectId >= 0)
00839 mStatusParticleEffects.setLocally(effectId, particle);
00840 else {
00841 mStunParticleEffects.clearLocally();
00842 if (particle)
00843 mStunParticleEffects.addLocally(particle);
00844 }
00845 }
00846
00847 void Being::updateStunMode(int oldMode, int newMode)
00848 {
00849 handleStatusEffect(StatusEffect::getStatusEffect(oldMode, false), -1);
00850 handleStatusEffect(StatusEffect::getStatusEffect(newMode, true), -1);
00851 }
00852
00853 void Being::updateStatusEffect(int index, bool newStatus)
00854 {
00855 handleStatusEffect(StatusEffect::getStatusEffect(index, newStatus), index);
00856 }
00857
00858 void Being::setStatusEffect(int index, bool active)
00859 {
00860 const bool wasActive = mStatusEffects.find(index) != mStatusEffects.end();
00861
00862 if (active != wasActive) {
00863 updateStatusEffect(index, active);
00864 if (active)
00865 mStatusEffects.insert(index);
00866 else
00867 mStatusEffects.erase(index);
00868 }
00869 }
00870
00871 #ifdef EATHENA_SUPPORT
00872 int Being::getOffset(char pos, char neg) const
00873 {
00874
00875 if (mAction != WALK || !(mDirection & (pos | neg)))
00876 return 0;
00877
00878 int offset = (get_elapsed_time(mWalkTime) * 32) / mWalkSpeed;
00879
00880
00881 offset -= 32;
00882 if (offset > 0)
00883 offset = 0;
00884
00885
00886 if (mDirection & pos)
00887 offset = -offset;
00888
00889 return offset;
00890 }
00891 #endif
00892
00893 int Being::getWidth() const
00894 {
00895 if (AnimatedSprite *base = mSprites[BASE_SPRITE])
00896 return std::max(base->getWidth(), DEFAULT_WIDTH);
00897 else
00898 return DEFAULT_WIDTH;
00899 }
00900
00901 int Being::getHeight() const
00902 {
00903 if (AnimatedSprite *base = mSprites[BASE_SPRITE])
00904 return std::max(base->getHeight(), DEFAULT_HEIGHT);
00905 else
00906 return DEFAULT_HEIGHT;
00907 }
00908
00909 void Being::setTargetAnimation(SimpleAnimation* animation)
00910 {
00911 mUsedTargetCursor = animation;
00912 mUsedTargetCursor->reset();
00913 }
00914
00915 struct EffectDescription {
00916 std::string mGFXEffect;
00917 std::string mSFXEffect;
00918 };
00919
00920 static EffectDescription *default_effect = NULL;
00921 static std::map<int, EffectDescription *> effects;
00922 static bool effects_initialized = false;
00923
00924 static EffectDescription *getEffectDescription(xmlNodePtr node, int *id)
00925 {
00926 EffectDescription *ed = new EffectDescription;
00927
00928 *id = atoi(XML::getProperty(node, "id", "-1").c_str());
00929 ed->mSFXEffect = XML::getProperty(node, "audio", "");
00930 ed->mGFXEffect = XML::getProperty(node, "particle", "");
00931
00932 return ed;
00933 }
00934
00935 static EffectDescription *getEffectDescription(int effectId)
00936 {
00937 if (!effects_initialized)
00938 {
00939 XML::Document doc(BEING_EFFECTS_FILE);
00940 xmlNodePtr root = doc.rootNode();
00941
00942 if (!root || !xmlStrEqual(root->name, BAD_CAST "being-effects"))
00943 {
00944 logger->log("Error loading being effects file: "
00945 BEING_EFFECTS_FILE);
00946 return NULL;
00947 }
00948
00949 for_each_xml_child_node(node, root)
00950 {
00951 int id;
00952
00953 if (xmlStrEqual(node->name, BAD_CAST "effect"))
00954 {
00955 EffectDescription *EffectDescription =
00956 getEffectDescription(node, &id);
00957 effects[id] = EffectDescription;
00958 } else if (xmlStrEqual(node->name, BAD_CAST "default"))
00959 {
00960 EffectDescription *EffectDescription =
00961 getEffectDescription(node, &id);
00962
00963 if (default_effect)
00964 delete default_effect;
00965
00966 default_effect = EffectDescription;
00967 }
00968 }
00969
00970 effects_initialized = true;
00971 }
00972
00973 EffectDescription *ed = effects[effectId];
00974
00975 if (!ed)
00976 return default_effect;
00977 else
00978 return ed;
00979 }
00980
00981 void Being::internalTriggerEffect(int effectId, bool sfx, bool gfx)
00982 {
00983 logger->log("Special effect #%d on %s", effectId,
00984 getId() == player_node->getId() ? "self" : "other");
00985
00986 EffectDescription *ed = getEffectDescription(effectId);
00987
00988 if (!ed) {
00989 logger->log("Unknown special effect and no default recorded");
00990 return;
00991 }
00992
00993 if (gfx && !ed->mGFXEffect.empty()) {
00994 Particle *selfFX;
00995
00996 selfFX = particleEngine->addEffect(ed->mGFXEffect, 0, 0);
00997 controlParticle(selfFX);
00998 }
00999
01000 if (sfx && !ed->mSFXEffect.empty()) {
01001 sound.playSfx(ed->mSFXEffect);
01002 }
01003 }
01004
01005 int Being::getHairStyleCount()
01006 {
01007 return mNumberOfHairstyles;
01008 }
01009
01010 int Being::getHairColorCount()
01011 {
01012 return mNumberOfHairColors;
01013 }
01014
01015 std::string Being::getHairColor(int index)
01016 {
01017 if (index < 0 || index >= mNumberOfHairColors)
01018 return "#000000";
01019
01020 return hairColors[index];
01021 }
01022
01023 void Being::load()
01024 {
01025
01026
01027 int hairstyles = 1;
01028
01029 while (ItemDB::get(-hairstyles).getSprite(GENDER_MALE) != "error.xml")
01030 hairstyles++;
01031
01032 mNumberOfHairstyles = hairstyles;
01033
01034 XML::Document doc(HAIR_FILE);
01035 xmlNodePtr root = doc.rootNode();
01036
01037
01038 hairColors.resize(1, "#000000");
01039
01040 if (!root || !xmlStrEqual(root->name, BAD_CAST "colors"))
01041 {
01042 logger->log("Error loading being hair configuration file");
01043 } else {
01044 for_each_xml_child_node(node, root)
01045 {
01046 if (xmlStrEqual(node->name, BAD_CAST "color"))
01047 {
01048 int index = atoi(XML::getProperty(node, "id", "-1").c_str());
01049 std::string value = XML::getProperty(node, "value", "");
01050
01051 if (index >= 0 && !value.empty()) {
01052 if (index >= mNumberOfHairColors) {
01053 mNumberOfHairColors = index + 1;
01054 hairColors.resize(mNumberOfHairColors, "#000000");
01055 }
01056 hairColors[index] = value;
01057 }
01058 }
01059 }
01060 }
01061 }