1 /***************************************************************************
2 creature.cpp - A class describing any creature
3 -------------------
4 begin : Sat May 3 2003
5 copyright : (C) 2003 by Gabor Torok
6 email : cctorok@yahoo.com
7 ***************************************************************************/
8
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18 #include "common/constants.h"
19 #include "creature.h"
20 #include "item.h"
21 #include "pathmanager.h"
22 #include "render/renderlib.h"
23 #include "session.h"
24 #include "shapepalette.h"
25 #include "events/event.h"
26 #include "events/potionexpirationevent.h"
27 #include "events/statemodexpirationevent.h"
28 #include "events/thirsthungerevent.h"
29 #include "sqbinding/sqbinding.h"
30 #include "debug.h"
31 #include "sound.h"
32
33 using namespace std;
34
35 //#define DEBUG_CREATURE_AI 1
36
37 //#define DEBUG_INVENTORY 1
38
39 #define PERCEPTION_DELTA 2000
40
41 bool loading = false;
42
43 //#define DEBUG_CAPABILITIES
44
45 #define MOVE_DELAY 7
46
47 // at this fps, the players step 1 square
48 #define FPS_ONE 10.0f
49
50 // how fast to turn
51 #define TURN_STEP_COUNT 5
52
53 // how far to move away when in the player's way
54 #define AWAY_DISTANCE 8
55
56 // how close to stay to the player
57 #define CLOSE_DISTANCE 8
58
59 /// Describes monster toughness in general.
60 struct MonsterToughness {
61 float minSkillBase, maxSkillBase;
62 float minHpMpBase, maxHpMpBase;
63 float armorMisfuction;
64 };
65
66 // goes from not very tough to tough
67 MonsterToughness monsterToughness[] = {
68 { .5f, 0.8f, .7f, 1, .33f },
69 { .75f, 0.9f, .75f, 1, .15f },
70 { .8f, 1, .75f, 1.25f, .5f }
71 };
72
73
Creature(Session * session,Character * character,char const * name,int sex,int character_model_info_index)74 Creature::Creature( Session *session, Character *character, char const* name, int sex, int character_model_info_index ) : RenderedCreature( session->getPreferences(), session->getShapePalette(), session->getMap() ) {
75 this->session = session;
76 this->character = character;
77 this->monster = NULL;
78 setName( name );
79 this->character_model_info_index = character_model_info_index;
80 this->model_name = session->getShapePalette()->getCharacterModelInfo( sex, character_model_info_index )->model_name;
81 this->skin_name = session->getShapePalette()->getCharacterModelInfo( sex, character_model_info_index )->skin_name;
82 this->originalSpeed = this->speed = 5; // start neutral speed
83 this->motion = Constants::MOTION_MOVE_TOWARDS;
84 this->armor = 0;
85 this->armorChanged = true;
86 this->bonusArmor = 0;
87 this->thirst = 10;
88 this->hunger = 10;
89 this->shape = session->getShapePalette()->getCreatureShape( model_name.c_str(), skin_name.c_str(), session->getShapePalette()->getCharacterModelInfo( sex, character_model_info_index )->scale );
90 this->sex = sex;
91 commonInit();
92 }
93
Creature(Session * session,Monster * monster,GLShape * shape,bool initMonster)94 Creature::Creature( Session *session, Monster *monster, GLShape *shape, bool initMonster ) : RenderedCreature( session->getPreferences(), session->getShapePalette(), session->getMap() ) {
95 this->session = session;
96 this->character = NULL;
97 this->monster = monster;
98 setName( monster->getDisplayName() );
99 this->model_name = monster->getModelName();
100 this->skin_name = monster->getSkinName();
101 this->originalSpeed = this->speed = monster->getSpeed();
102 this->motion = Constants::MOTION_LOITER;
103 this->armor = monster->getBaseArmor();
104 this->armorChanged = true;
105 this->bonusArmor = 0;
106 this->shape = shape;
107 this->sex = Constants::SEX_MALE;
108 commonInit();
109 this->level = monster->getLevel();
110 if ( initMonster ) monsterInit();
111 }
112
commonInit()113 void Creature::commonInit() {
114 this->summoner = NULL;
115 this->backpack = new Item( session, RpgItem::getItemByName( "Backpack" ), 1 );
116 this->backpack->setInventoryOf( this );
117 this->lastDecision = 0;
118 this->portrait.clear();
119
120 this->scripted = false;
121 this->scriptedAnim = MD2_STAND;
122 this->lastPerceptionCheck = 0;
123 this->boss = false;
124 this->savedMissionObjective = false;
125
126 this->backpackSorted = false;
127
128 this->causeOfDeath[0] = 0;
129 ( ( AnimatedShape* )shape )->setCreatureSpeed( speed );
130
131 for ( int i = 0; i < RpgItem::DAMAGE_TYPE_COUNT; i++ ) {
132 lastArmor[i] = lastArmorSkill[i] = lastDodgePenalty[i] = 0;
133 }
134 for ( int i = 0; i < 12; i++ ) quickSpell[ i ] = NULL;
135 this->lastMove = 0;
136 this->moveCount = 0;
137 this->x = this->y = this->z = 0;
138 this->dir = Constants::MOVE_UP;
139 this->formation = DIAMOND_FORMATION;
140 this->tx = this->ty = -1;
141 this->selX = this->selY = -1;
142 this->cantMoveCounter = 0;
143 this->pathManager = new PathManager( this );
144
145 this->preferredWeapon = -1;
146 for(int i = 0; i < Constants::EQUIP_LOCATION_COUNT; i++) {
147 equipped[i] = MAX_BACKPACK_SIZE;
148 }
149 for ( int i = 0; i < Skill::SKILL_COUNT; i++ ) {
150 skillBonus[i] = skillsUsed[i] = skillMod[i] = 0;
151 }
152 //this->stateMod = ( 1 << StateMod::dead ) - 1;
153 //this->protStateMod = ( 1 << StateMod::dead ) - 1;
154 this->stateMod = 0;
155 this->protStateMod = 0;
156 this->level = 1;
157 this->experience = 0;
158 this->hp = 0;
159 this->mp = 0;
160 this->startingHp = 0;
161 this->startingMp = 0;
162 this->ac = 0;
163 this->targetCreature = NULL;
164 this->targetX = this->targetY = this->targetZ = 0;
165 this->targetItem = NULL;
166 this->lastTick = 0;
167 this->lastTurn = 0;
168 this->facingDirection = Constants::MOVE_UP; // good init ?
169 this->failedToMoveWithinRangeAttemptCount = 0;
170 this->action = Constants::ACTION_NO_ACTION;
171 this->actionItem = NULL;
172 this->actionSpell = NULL;
173 this->actionSkill = NULL;
174 this->preActionTargetCreature = NULL;
175 this->angle = this->wantedAngle = this->angleStep = 0;
176 this->portraitTextureIndex = 0;
177 this->deityIndex = -1;
178 this->availableSkillMod = 0;
179 this->hasAvailableSkillPoints = false;
180
181 // Yes, monsters have backpack weight issues too
182 backpackWeight = 0.0f;
183 for ( int i = 0; i < backpack->getContainedItemCount(); i++ ) {
184 backpackWeight += backpack->getContainedItem( i )->getWeight();
185 }
186 this->money = this->level * Util::dice( 10 );
187 calculateExpOfNextLevel();
188 this->battle = new Battle( session, this );
189
190 lastEnchantDate.setDate( -1, -1, -1, -1, -1, -1 );
191
192 this->npcInfo = NULL;
193 this->mapChanged = false;
194 this->moving = false;
195
196 evalSpecialSkills();
197
198 }
199
~Creature()200 Creature::~Creature() {
201 portrait.clear();
202
203 // cancel this creature's events
204 Party* party = session->getParty();
205 if ( party != NULL ) party->getCalendar()->cancelEventsForCreature( this );
206
207 // now delete the creature
208 session->getGameAdapter()->removeBattle( battle );
209 delete battle;
210 delete pathManager;
211 // delete the md2/3 shape
212 ShapePalette* shapepal = session->getShapePalette();
213 if ( shapepal != NULL ) {
214 shapepal->decrementSkinRefCountAndDeleteShape( model_name.c_str(),
215 skin_name.c_str(),
216 shape,
217 monster );
218 }
219 // delete the backpack infos
220 for ( map<Item*, BackpackInfo*>::iterator e = invInfos.begin(); e != invInfos.end(); ++e ) {
221 BackpackInfo *info = e->second;
222 delete info;
223 }
224 delete backpack;
225 }
226
227 /// Changes a character-type creature's profession, and applies the effects.
228
changeProfession(Character * c)229 void Creature::changeProfession( Character *c ) {
230 enum { MSG_SIZE = 120 };
231 char message[ MSG_SIZE ];
232
233 // boost skills
234 for ( int i = 0; i < Skill::SKILL_COUNT; i++ ) {
235 int maxValue = c->getSkill( i );
236 if ( maxValue > 0 ) {
237 int oldValue = character->getSkill( i );
238 int newValue = getSkill( i ) + ( oldValue > 0 ? maxValue - oldValue : maxValue );
239 setSkill( i, newValue );
240
241 snprintf( message, MSG_SIZE, _( "%1$s's skill in %2$s has increased." ), getName(), Skill::skills[ i ]->getDisplayName() );
242 session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_STATS );
243 }
244 }
245
246 // remove forbidden items
247 for( int i = 0; i < Constants::EQUIP_LOCATION_COUNT; i++ ) {
248 if ( equipped[i] < MAX_BACKPACK_SIZE ) {
249 Item *item = backpack->getContainedItem( equipped[i] );
250 if ( !c->canEquip( item->getRpgItem() ) ) {
251 doff( equipped[i] );
252 snprintf( message, MSG_SIZE, _( "%1$s is not allowed to equip %2$s." ), getName(), item->getName() );
253 session->getGameAdapter()->writeLogMessage( message );
254 }
255 }
256 }
257
258 // add capabilities?
259
260
261 this->character = c;
262 setHp();
263 setMp();
264 }
265
save()266 CreatureInfo *Creature::save() {
267 CreatureInfo *info = new CreatureInfo;
268 info->version = PERSIST_VERSION;
269 strncpy( ( char* )info->name, getName(), 254 );
270 info->name[254] = 0;
271 if ( isMonster() || isNpc() ) {
272 strcpy( ( char* )info->character_name, "" );
273 strcpy( ( char* )info->monster_name, monster->getType() );
274 info->character_model_info_index = 0;
275 info->npcInfo = ( isNpc() && getNpcInfo() ? getNpcInfo()->save() : NULL );
276 } else {
277 strcpy( ( char* )info->character_name, character->getName() );
278 strcpy( ( char* )info->monster_name, "" );
279 info->character_model_info_index = character_model_info_index;
280 info->npcInfo = NULL;
281 }
282 info->deityIndex = deityIndex;
283 info->hp = hp;
284 info->mp = mp;
285 info->exp = experience;
286 info->level = level;
287 info->money = money;
288 info->stateMod = stateMod;
289 info->protStateMod = protStateMod;
290 info->x = toint( x );
291 info->y = toint( y );
292 info->z = toint( z );
293 info->dir = dir;
294 info->speed = originalSpeed;
295 info->motion = motion;
296 info->sex = sex;
297 info->armor = 0;
298 info->bonusArmor = bonusArmor;
299 //info->bonusArmor = 0;
300 info->thirst = thirst;
301 info->hunger = hunger;
302 info->availableSkillPoints = availableSkillMod;
303 for ( int i = 0; i < Skill::SKILL_COUNT; i++ ) {
304 info->skills[i] = skills[i];
305 info->skillBonus[i] = skillBonus[i];
306 info->skillsUsed[i] = skillsUsed[i];
307 info->skillMod[i] = skillMod[i];
308 }
309 info->portraitTextureIndex = portraitTextureIndex;
310
311 // backpack
312 info->backpack_count = backpack->getContainedItemCount();
313 for ( int i = 0; i < backpack->getContainedItemCount(); i++ ) {
314 info->backpack[i] = backpack->getContainedItem( i )->save();
315 }
316 for(int i = 0; i < Constants::EQUIP_LOCATION_COUNT; i++) {
317 info->equipped[i] = equipped[i];
318 }
319
320 // spells
321 int count = 0;
322 for ( int i = 0; i < MagicSchool::getMagicSchoolCount(); i++ ) {
323 MagicSchool *school = MagicSchool::getMagicSchool( i );
324 for ( int t = 0; t < school->getSpellCount(); t++ ) {
325 Spell *spell = school->getSpell( t );
326 if ( isSpellMemorized( spell ) ) {
327 strcpy( ( char* )info->spell_name[count++], spell->getName() );
328 }
329 }
330 }
331 info->spell_count = count;
332
333 for ( int i = 0; i < 12; i++ ) {
334 strcpy( ( char* )info->quick_spell[ i ],
335 ( getQuickSpell( i ) ? getQuickSpell( i )->getName() : "" ) );
336 }
337
338 info->boss = ( Uint8 )boss;
339 info->mission = ( Uint8 )( session->getCurrentMission() && session->getCurrentMission()->isMissionCreature( this ) ? 1 : 0 );
340
341 return info;
342 }
343
load(Session * session,CreatureInfo * info)344 Creature *Creature::load( Session *session, CreatureInfo *info ) {
345 Creature *creature = NULL;
346
347 if ( !strlen( ( char* )info->character_name ) ) {
348
349 Monster *monster = Monster::getMonsterByName( ( char* )info->monster_name );
350 if ( !monster ) {
351 cerr << "Error: can't find monster: " << ( char* )info->monster_name << endl;
352 return NULL;
353 }
354 GLShape *shape = session->getShapePalette()->
355 getCreatureShape( monster->getModelName(),
356 monster->getSkinName(),
357 monster->getScale(),
358 monster );
359 creature = session->newCreature( monster, shape, true );
360 creature->setName( ( char* )info->name ); // important for npc-s
361 if ( info->npcInfo ) {
362 NpcInfo *npcInfo = NpcInfo::load( info->npcInfo );
363 if ( npcInfo ) creature->setNpcInfo( npcInfo );
364 }
365
366 // fixme: throw away this code when saving stats_mods and calendar events is implemented
367 // for now, set a monsters stat mods as declared in creatures.txt
368 creature->stateMod = monster->getStartingStateMod();
369 } else {
370 creature = new Creature( session,
371 Characters::getByName( ( char* )info->character_name ),
372 ( char* )info->name,
373 info->sex,
374 info->character_model_info_index );
375 }
376
377 // don't recalculate skills
378 // NOTE: don't call return until loading=false.
379 loading = true;
380
381 // cerr << "*** LOAD: creature=" << info->name << endl;
382 creature->setDeityIndex( info->deityIndex );
383 creature->setHp( info->hp );
384 creature->setMp( info->mp );
385 creature->setExp( info->exp );
386 creature->setLevel( info->level );
387 creature->setMoney( info->money );
388 creature->moveTo( info->x, info->y, info->z );
389 creature->setDir( info->dir );
390 //creature->setSpeed( info->speed );
391 //creature->setMotion( info->motion );
392 //creature->setArmor( info->armor );
393
394 // info->bonusArmor: can't be used until calendar is also persisted
395 //creature->setBonusArmor( info->bonusArmor );
396
397 creature->setThirst( info->thirst );
398 creature->setHunger( info->hunger );
399 creature->setAvailableSkillMod( info->availableSkillPoints );
400
401 for ( int i = 0; i < Skill::SKILL_COUNT; i++ ) {
402 creature->skills[i] = info->skills[i];
403 // Don't set skillBonus: it's reconstructed via the backpack.
404 //creature->skillBonus[i] = info->skillBonus[i];
405 // Don't set skillUsed: it's not used.
406 //creature->skillsUsed[i] = info->skillsUsed[i];
407 creature->skillMod[i] = info->skillMod[i];
408 }
409
410 // stateMod and protStateMod not useful until calendar is also persisted
411 //creature->stateMod = info->stateMod;
412 //creature->protStateMod = info->protStateMod;
413 // these two don't req. events:
414 if ( info->stateMod & ( 1 << StateMod::dead ) ) creature->setStateMod( StateMod::dead, true );
415 //if(info->stateMod & (1 << Constants::leveled)) creature->setStateMod(Constants::leveled, true);
416
417 // backpack
418 //creature->backpack_count = info->backpack_count;
419 for ( int i = 0; i < static_cast<int>( info->backpack_count ); i++ ) {
420 Item *item = Item::load( session, info->backpack[i] );
421 if ( item ) {
422 if( !creature->addToBackpack( item ) ) {
423 cerr << "Warning: could not add item to backpack: " << item->getName() << endl;
424 }
425 }
426 }
427 for(int i = 0; i < Constants::EQUIP_LOCATION_COUNT; i++) {
428 if ( info->equipped[i] < MAX_BACKPACK_SIZE ) {
429 creature->equipFromBackpack( info->equipped[i], i );
430 } else {
431 creature->equipped[i] = info->equipped[i];
432 }
433 }
434
435 creature->portraitTextureIndex = info->portraitTextureIndex;
436 if ( creature->portraitTextureIndex >= session->getShapePalette()->getPortraitCount( creature->getSex() ) )
437 creature->portraitTextureIndex = session->getShapePalette()->getPortraitCount( creature->getSex() ) - 1;
438
439 // spells
440 for ( int i = 0; i < static_cast<int>( info->spell_count ); i++ ) {
441 Spell *spell = Spell::getSpellByName( ( char* )info->spell_name[i] );
442 creature->addSpell( spell );
443 }
444
445 for ( int i = 0; i < 12; i++ ) {
446 if ( strlen( ( char* )info->quick_spell[ i ] ) ) {
447 Spell *spell = Spell::getSpellByName( ( char* )info->quick_spell[ i ] );
448 if ( spell ) creature->setQuickSpell( i, spell );
449 else {
450 SpecialSkill *special = SpecialSkill::findByName( ( char* )info->quick_spell[ i ], false );
451 if ( special ) creature->setQuickSpell( i, special );
452 else {
453 // it's an item. Find it
454 for ( int t = 0; t < creature->getBackpackContentsCount(); t++ ) {
455 Item *item = creature->getBackpackItem( t );
456 if ( !strcmp( item->getName(), ( char* )info->quick_spell[ i ] ) ) {
457 creature->setQuickSpell( i, ( Storable* )item );
458 break;
459 }
460 }
461 }
462 }
463 }
464 }
465
466 creature->setBoss( info->boss != 0 );
467 creature->setSavedMissionObjective( info->mission != 0 );
468 if ( creature->isSavedMissionObjective() ) {
469 cerr << "*********************************" << endl;
470 cerr << "Loaded mission creature:" << creature->getName() << endl;
471 cerr << "*********************************" << endl;
472 }
473
474 creature->calculateExpOfNextLevel();
475
476 creature->evalSpecialSkills();
477
478 // recalculate skills from now on
479 loading = false;
480
481 return creature;
482 }
483
484 /// Returns the UNLOCALIZED name of a character/monster/NPC.
485
getType()486 char const* Creature::getType() {
487 return( monster ? monster->getType() : character->getName() );
488 }
489
490 /// Gets the number of experience points required to level up.
491
calculateExpOfNextLevel()492 void Creature::calculateExpOfNextLevel() {
493 if ( !( isPartyMember() || isWanderingHero() ) ) return;
494 expOfNextLevel = 0;
495 for ( int i = 0; i < level; i++ ) {
496 expOfNextLevel += ( ( i + 1 ) * character->getLevelProgression() );
497 }
498 }
499
500 /// Selects (with a 1/10 chance) a random new movement direction.
501
switchDirection(bool force)502 void Creature::switchDirection( bool force ) {
503 int n = Util::dice( 10 );
504 if ( n == 0 || force ) {
505 int dir = Util::dice( 4 );
506 switch ( dir ) {
507 case 0: setDir( Constants::MOVE_UP ); break;
508 case 1: setDir( Constants::MOVE_DOWN ); break;
509 case 2: setDir( Constants::MOVE_LEFT ); break;
510 case 3: setDir( Constants::MOVE_RIGHT ); break;
511 }
512 }
513 }
514
515 /// Moves the creature 1 step into the specified direction.
516
move(Uint16 dir)517 bool Creature::move( Uint16 dir ) {
518 //if(character) return false;
519
520 // is monster (or npc) doing something else?
521 if ( ( ( AnimatedShape* )getShape() )->getCurrentAnimation() != MD2_RUN ) return false;
522
523 switchDirection( false );
524
525 // a hack for runaway creatures
526 if ( !( x > 10 && x < MAP_WIDTH - 10 &&
527 y > 10 && y < MAP_DEPTH - 10 ) ) {
528 if ( monster ) cerr << "hack for " << getName() << endl;
529 switchDirection( true );
530 return false;
531 }
532
533 GLfloat nx = x;
534 GLfloat ny = y;
535 GLfloat nz = z;
536 GLfloat step = getStep();
537 switch ( dir ) {
538 case Constants::MOVE_UP:
539 ny = y - step;
540 break;
541 case Constants::MOVE_DOWN:
542 ny = y + step;
543 break;
544 case Constants::MOVE_LEFT:
545 nx = x - step;
546 break;
547 case Constants::MOVE_RIGHT:
548 nx = x + step;
549 break;
550 }
551 setFacingDirection( dir );
552
553 if ( !session->getMap()->moveCreature( toint( x ), toint( y ), toint( z ),
554 toint( nx ), toint( ny ), toint( nz ), this ) ) {
555 ( ( AnimatedShape* )shape )->setDir( dir );
556 if ( session->getMap()->getHasWater() &&
557 !( toint( x ) == toint( nx ) &&
558 toint( y ) == toint( ny ) ) ) {
559 session->getMap()->startEffect( toint( getX() + getShape()->getWidth() / 2 ),
560 toint( getY() - getShape()->getDepth() / 2 ), 0,
561 Constants::EFFECT_RIPPLE, ( Constants::DAMAGE_DURATION * 4 ),
562 getShape()->getWidth(), getShape()->getDepth() );
563 }
564 moveTo( nx, ny, nz );
565 setDir( dir );
566 return true;
567 } else {
568 switchDirection( true );
569 return false;
570 }
571 }
572
573 /// Searches for a creature target within the specified range.
574
setTargetCreature(Creature * c,bool findPath,float range)575 void Creature::setTargetCreature( Creature *c, bool findPath, float range ) {
576 targetCreature = c;
577 if ( findPath ) {
578 if ( !setSelCreature( c , range, false ) ) {
579 // FIXME: should mark target somehow. Path alg. cannot reach it; blocked by something.
580 // Keep the target creature anyway.
581 if ( session->getPreferences()->isBattleTurnBased() ) {
582
583 session->getGameAdapter()->writeLogMessage( _( "Can't find path to target. Sorry!" ), Constants::MSGTYPE_SYSTEM );
584 session->getGameAdapter()->setCursorMode( Constants::CURSOR_FORBIDDEN );
585 }
586 }
587 }
588 }
589
590 /// Makes the creature follow another creature. Returns true for success.
591
follow(Creature * leader)592 bool Creature::follow( Creature *leader ) {
593 float dist = getDistance( leader );
594 if ( dist < CLOSE_DISTANCE ) {
595 if ( cantMoveCounter > 5 ) {
596 cantMoveCounter = 0;
597 } else {
598 return true;
599 }
600 }
601 //speed = FAST_SPEED;
602 // Creature *player = session->getParty()->getPlayer();
603 //bool found = pathManager->findPathToCreature( leader, player, session->getMap());
604 return setSelCreature( leader, 1 );
605 }
606
607 /// Sets where to move the creature. Returns true if the move is possible, false otherwise.
608
setSelXY(int x,int y,bool cancelIfNotPossible)609 bool Creature::setSelXY( int x, int y, bool cancelIfNotPossible ) {
610 bool ignoreParty = session->getParty()->getPlayer() == this && !session->getGameAdapter()->inTurnBasedCombat();
611 int oldSelX = selX;
612 int oldSelY = selY;
613 int oldtx = tx;
614 int oldty = ty;
615
616 selX = x;
617 selY = y;
618 if ( x < 0 || y < 0 ) return true; //this is often used to deselect
619
620 setMotion( Constants::MOTION_MOVE_TOWARDS );
621
622 // find the path
623 tx = selX;
624 ty = selY;
625 // Does the path lead to the destination?
626 bool ret = pathManager->findPath( selX, selY,
627 session->getParty()->getPlayer(),
628 session->getMap(),
629 ignoreParty );
630
631 /**
632 * For pc-s cancel the move.
633 */
634 if ( !ret && character && cancelIfNotPossible ) {
635 pathManager->clearPath();
636 selX = oldSelX;
637 selY = oldSelY;
638 tx = oldtx;
639 ty = oldty;
640 } else {
641 //make the selected location equal the end of our path
642 Location last = pathManager->getEndOfPath();
643 selX = last.x;
644 selY = last.y;
645 }
646
647 // FIXME: when to play sound?
648 if ( ret && session->getParty()->getPlayer() == this ) {
649 // play command sound
650 if ( 0 == Util::dice( session->getPreferences()->getSoundFreq() ) &&
651 !getStateMod( StateMod::dead ) ) {
652 //session->playSound(getCharacter()->getRandomSound(Constants::SOUND_TYPE_COMMAND));
653 int panning = session->getMap()->getPanningFromMapXY( this->x, this->y );
654 playCharacterSound( GameAdapter::COMMAND_SOUND, panning );
655 }
656 }
657
658 return ret;
659 }
660
661 /// Use this instead of setSelXY when targetting creatures so that it will check all locations occupied by large creatures.
662
setSelCreature(Creature * creature,float range,bool cancelIfNotPossible)663 bool Creature::setSelCreature( Creature* creature, float range, bool cancelIfNotPossible ) {
664 bool ignoreParty = session->getParty()->getPlayer() == this && !session->getGameAdapter()->inTurnBasedCombat();
665 int oldSelX = selX;
666 int oldSelY = selY;
667 int oldtx = tx;
668 int oldty = ty;
669
670 selX = toint( creature->getX() + creature->getShape()->getWidth() / 2.0f );
671 selY = toint( creature->getY() + creature->getShape()->getDepth() / 2.0f );
672 Creature * oldTarget = targetCreature;
673
674 targetCreature = creature;
675
676 setMotion( Constants::MOTION_MOVE_TOWARDS );
677 tx = ty = -1;
678
679 // find the path
680 tx = selX;
681 ty = selY;
682 // Does the path lead close enough to the destination?
683 // bool ret = pathManager->findPathToCreature( creature, session->getParty()->getPlayer(), session->getMap(), range, ignoreParty );
684 bool ret = pathManager->findPathToCreature( creature, this, session->getMap(), range, ignoreParty );
685
686 /**
687 * For pc-s cancel the move.
688 */
689 if ( !ret && session->getParty()->isPartyMember(this) && cancelIfNotPossible ) {
690 pathManager->clearPath();
691 selX = oldSelX;
692 selY = oldSelY;
693 tx = oldtx;
694 ty = oldty;
695 targetCreature = oldTarget;
696 }
697
698 // FIXME: when to play sound?
699 if ( ret && session->getParty()->getPlayer() == this ) {
700 // play command sound
701 if ( creature->getX() > -1 &&
702 0 == Util::dice( session->getPreferences()->getSoundFreq() ) &&
703 !getStateMod( StateMod::dead ) ) {
704 //session->playSound(getCharacter()->getRandomSound(Constants::SOUND_TYPE_COMMAND));
705 int panning = session->getMap()->getPanningFromMapXY( this->x, this->y );
706 playCharacterSound( GameAdapter::COMMAND_SOUND, panning );
707 }
708 }
709 return ret;
710 }
711
moveToLocator()712 Location *Creature::moveToLocator() {
713 Location *pos = NULL;
714
715 //we either have a target we want to reach, or we are wandering around
716 if ( selX > -1 || getMotion() == Constants::MOTION_LOITER ) {
717 // take a step
718 pos = takeAStepOnPath();
719
720 // did we step on a trap?
721 evalTrap();
722
723 // if we've no more steps
724 if ( pathManager->atEndOfPath() ) {
725 stopMoving();
726 setMotion( Constants::MOTION_STAND );
727 } else if ( pos ) {
728 cantMoveCounter++;
729 if ( isPartyMember() ) {
730 pathManager->moveNPCsOffPath( this, session->getMap() );
731 }
732 if ( cantMoveCounter > 5 ) {
733 stopMoving();
734 setMotion( Constants::MOTION_STAND );
735 cantMoveCounter = 0;
736 }
737 } else if ( !pos ) {
738 cantMoveCounter = 0;
739 setMoving( true );
740 }
741
742 } else {
743
744 if ( !( getMotion() == Constants::MOTION_LOITER || getMotion() == Constants::MOTION_STAND ) ) {
745 #if PATH_DEBUG
746 cerr << "Creature stuck: " << getName() << endl;
747 #endif
748 stopMoving();
749 setMotion( Constants::MOTION_STAND );
750 }
751
752 }
753
754 return pos;
755 }
756
757 /// Move the creature one step along its path. Handles blocking objects.
758
759 /// Returns the blocking shape or NULL
760 /// if move is possible.
761
takeAStepOnPath()762 Location *Creature::takeAStepOnPath() {
763 Location *position = NULL;
764 int a = ( ( AnimatedShape* )getShape() )->getCurrentAnimation();
765
766 if ( !pathManager->atEndOfPath() && a == MD2_RUN ) { //a != MD2_TAUNT ) {
767
768 // take a step on the bestPath
769 Location location = pathManager->getNextStepOnPath();
770
771 GLfloat newX = getX();
772 GLfloat newY = getY();
773
774 int cx = toint( newX );
775 int cy = toint( newY ); //current x,y
776
777 GLfloat step = getStep();
778 float targetX = static_cast<float>( location.x );
779 float targetY = static_cast<float>( location.y );
780
781 //get the direction to the target location
782 float diffX = targetX - newX; //distance between creature's (continuous) location and the target (discrete) location
783 float diffY = targetY - newY;
784 //get the x and y values for a step-length vector in the direction of diffX,diffY
785 //float dist = sqrt(diffX*diffX + diffY*diffY); //distance to location
786 float dist = Constants::distance( newX, newY, 0, 0, targetX, targetY, 0, 0 ); //distance to location
787 //if(dist < step) step = dist; //if the step is too great, we slow ourselves to avoid overstepping
788 if ( dist != 0.0f ) { // thee shall not divide with zero
789 float stepX = ( diffX * step ) / dist;
790 float stepY = ( diffY * step ) / dist;
791
792 newY += stepY;
793 newX += stepX;
794
795 int nx = toint( newX );
796 int ny = toint( newY );
797
798 position = session->getMap()->
799 moveCreature( cx, cy, toint( getZ() ),
800 nx, ny, toint( getZ() ),
801 this );
802
803 if ( position && cx != location.x && cy != location.y && ( ( cx != nx && cy == ny ) || ( cx == nx && cy != ny ) ) ) {
804 #if PATH_DEBUG
805 cerr << "Popping: " << this->getName() << endl;
806 #endif
807 //we are blocked at our next step, are moving diagonally, and did not complete the diagonal move
808 newX = targetX;
809 newY = targetY; //we just "pop" to the target location
810 nx = toint( newX );
811 ny = toint( newY );
812 position = session->getMap()->
813 moveCreature( cx, cy, toint( getZ() ),
814 nx, ny, toint( getZ() ),
815 this );
816 }
817 }
818
819 if ( !position ) {
820 computeAngle( newX, newY );
821 showWaterEffect( newX, newY );
822 moveTo( newX, newY, getZ() );
823 if ( toint( newX ) == location.x && toint( newY ) == location.y ) {
824 pathManager->incrementPositionOnPath();
825 }
826 } else {
827 #if PATH_DEBUG
828 cerr << "Blocked, stopping: " << this->getName() << endl;
829 #endif
830 }
831
832 }
833
834 return position;
835 }
836
837 /// Computes the angle the creature should assume to reach newX, nexY.
838
computeAngle(GLfloat newX,GLfloat newY)839 void Creature::computeAngle( GLfloat newX, GLfloat newY ) {
840 GLfloat a = Util::getAngle( newX, newY, 1, 1,
841 getX(), getY(), 1, 1 );
842
843 if ( pathManager->atStartOfPath() || a != wantedAngle ) {
844 wantedAngle = a;
845 GLfloat diff = Util::diffAngle( a, angle );
846 angleStep = diff / static_cast<float>( TURN_STEP_COUNT );
847 }
848
849 if ( fabs( angle - wantedAngle ) > 2.0f ) {
850 GLfloat diff = Util::diffAngle( wantedAngle, angle );
851 if ( fabs( diff ) < angleStep ) {
852 angle = wantedAngle;
853 } else {
854 angle += angleStep;
855 }
856 if ( angle < 0.0f ) angle = 360.0f + angle;
857 if ( angle >= 360.0f ) angle -= 360.0f;
858 } else {
859 angle = wantedAngle;
860 }
861
862 ( ( AnimatedShape* )shape )->setAngle( angle + 180.0f );
863 }
864
showWaterEffect(GLfloat newX,GLfloat newY)865 void Creature::showWaterEffect( GLfloat newX, GLfloat newY ) {
866 if ( session->getMap()->getHasWater() &&
867 !( toint( getX() ) == toint( newX ) &&
868 toint( getY() ) == toint( newY ) ) ) {
869 session->getMap()->startEffect( toint( getX() + getShape()->getWidth() / 2 ),
870 toint( getY() - getShape()->getDepth() / 2 ), 0,
871 Constants::EFFECT_RIPPLE,
872 ( Constants::DAMAGE_DURATION * 4 ),
873 getShape()->getWidth(), getShape()->getDepth() );
874 }
875 }
876
877 /// Stops movement.
878
stopMoving()879 void Creature::stopMoving() {
880 cantMoveCounter = 0;
881 pathManager->clearPath();
882 selX = selY = -1;
883 speed = originalSpeed;
884 getShape()->setCurrentAnimation( MD2_STAND );
885 if ( session->getParty()->getPlayer() == this ) session->getSound()->stopFootsteps();
886 }
887
888 /// Plays the footstep sound.
889
890 Uint32 lastFootstepTime = 0;
playFootstep()891 void Creature::playFootstep() {
892 Uint32 now = SDL_GetTicks();
893 if ( now - lastFootstepTime > ( Uint32 ) ( session->getPreferences()->getGameSpeedTicks() * 4 ) && getMotion() != Constants::MOTION_STAND ) {
894 int panning = session->getMap()->getPanningFromMapXY( this->x, this->y );
895 session->getSound()->startFootsteps( session->getAmbientSoundName(), session->getGameAdapter()->getCurrentDepth(), panning );
896 lastFootstepTime = now;
897 }
898 }
899
900 /// Checks whether the creature has any moves left on its path.
901
anyMovesLeft()902 bool Creature::anyMovesLeft() {
903 return( selX > -1 && !pathManager->atEndOfPath() );
904 }
905
906
907 /// Returns the weight the creature can carry before being overloaded.
908
getMaxBackpackWeight()909 float Creature::getMaxBackpackWeight() {
910 return static_cast<float>( getSkill( Skill::POWER ) ) * 2.5f;
911 }
912
pickUpOnMap(RenderedItem * item)913 void Creature::pickUpOnMap( RenderedItem *item ) {
914 addToBackpack( ( Item* )item );
915 }
916
917 /// Adds an item to the creature's backpack.
918
addToBackpack(Item * item,int itemX,int itemY)919 bool Creature::addToBackpack( Item *item, int itemX, int itemY ) {
920 BackpackInfo *info = getBackpackInfo( item, true );
921 if ( backpack->addContainedItem( item, itemX, itemY ) ) {
922
923 info->equipIndex = -1;
924 info->backpackIndex = backpack->getContainedItemCount() - 1;
925
926 backpackWeight += item->getWeight();
927
928 if ( backpackWeight > getMaxBackpackWeight() ) {
929 if ( isPartyMember() ) {
930 char msg[80];
931 snprintf( msg, 80, _( "%s is overloaded." ), getName() );
932 session->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_STATS );
933 }
934 setStateMod( StateMod::overloaded, true );
935 }
936
937 // check if the mission is over
938 if ( isPartyMember() &&
939 session->getCurrentMission() &&
940 session->getCurrentMission()->itemFound( item ) ) {
941 session->getGameAdapter()->missionCompleted();
942 }
943
944 #ifdef DEBUG_INVENTORY
945 if( this == session->getParty()->getPlayer() ) {
946 cerr << "Creature::addToBackpack: " << item->getName() << endl;
947 debugBackpack();
948 }
949 #endif
950
951 return true;
952 } else {
953 cerr << "*** error: unable to add to inventory of creature=" << getName() << " item=" << item->getName() << endl;
954 return false;
955 }
956 }
957
958 /// Returns the backpack index and equip index of an item.
959
getBackpackInfo(Item * item,bool createIfMissing)960 BackpackInfo *Creature::getBackpackInfo( Item *item, bool createIfMissing ) {
961 if ( invInfos.find( item ) == invInfos.end() ) {
962 if ( createIfMissing ) {
963 BackpackInfo *info = new BackpackInfo();
964 invInfos[ item ] = info;
965 return info;
966 } else {
967 return NULL;
968 }
969 } else {
970 return invInfos[ item ];
971 }
972 }
973
974 /// Returns the backpack index of an item.
975
findInBackpack(Item * item)976 int Creature::findInBackpack( Item *item ) {
977 #ifdef DEBUG_INVENTORY
978 if( this == session->getParty()->getPlayer() ) {
979 cerr << "findInBackpack: item=" << item->getName() << " address=" << item << endl;
980 debugBackpack();
981 }
982 #endif
983 BackpackInfo *info = getBackpackInfo( item );
984 return( info ? info->backpackIndex : -1 );
985 /*
986 for(int i = 0; i < backpack_count; i++) {
987 Item *invItem = backpack[i];
988 if(item == invItem) return i;
989 }
990 return -1;
991 */
992 }
993
debugBackpack()994 void Creature::debugBackpack() {
995 cerr << "**************************************" << endl;
996 cerr << "backpack size=" << backpack->getContainedItemCount() << endl;
997 for( int i = 0; i < backpack->getContainedItemCount(); i++ ) {
998 cerr << "\titem: " << backpack->getContainedItem( i )->getName() << " address=" << backpack->getContainedItem( i ) << endl;
999 }
1000 cerr << "InvInfos size=" << invInfos.size() << endl;
1001 for( map<Item*, BackpackInfo*>::iterator e = invInfos.begin(); e != invInfos.end(); ++e ) {
1002 Item *item = e->first;
1003 BackpackInfo *bpi = e->second;
1004 cerr << "\titem: " << item->getName() << " address=" << item << " backpack info: " << bpi->backpackIndex << "," << bpi->equipIndex << endl;
1005 }
1006 cerr << "Equipped: " << endl;
1007 for(int i = 0; i < Constants::EQUIP_LOCATION_COUNT; i++) {
1008 if(equipped[i] < MAX_BACKPACK_SIZE) {
1009 cerr << "\tat: " << i << " location: " << (1 << i) << " index: " << equipped[i] << " item: " << getBackpackItem(equipped[i])->getName() << " address: " << getBackpackItem(equipped[i]) << endl;
1010 }
1011 }
1012 cerr << "**************************************" << endl;
1013 }
1014
1015 /// Removes an item from the backpack at index.
1016
removeFromBackpack(int backpackIndex)1017 Item *Creature::removeFromBackpack( int backpackIndex ) {
1018 #ifdef DEBUG_INVENTORY
1019 if( this == session->getParty()->getPlayer() ) {
1020 cerr << "Creature::removeFromBackpack: " << backpackIndex << endl;
1021 if( this == session->getParty()->getPlayer() ) debugBackpack();
1022 }
1023 #endif
1024
1025 Item *item = NULL;
1026 if ( backpackIndex < backpack->getContainedItemCount() ) {
1027 // drop item if carrying it
1028 doff( backpackIndex );
1029
1030 // drop from backpack
1031 item = backpack->getContainedItem( backpackIndex );
1032
1033 BackpackInfo *info = getBackpackInfo( item );
1034 invInfos.erase( item );
1035 delete info;
1036
1037 // remove it from quickspell slot
1038 for ( int i = 0; i < 12; i++ ) {
1039 Storable *storable = getQuickSpell( i );
1040 if ( storable ) {
1041 if ( storable->getStorableType() == Storable::ITEM_STORABLE ) {
1042 if ( ( Item* )storable == item ) {
1043 setQuickSpell( i, NULL );
1044 }
1045 }
1046 }
1047 }
1048
1049 backpackWeight -= item->getWeight();
1050 if ( getStateMod( StateMod::overloaded ) && backpackWeight < getMaxBackpackWeight() ) {
1051 if ( isPartyMember() ) {
1052 char msg[80];
1053 snprintf( msg, 80, _( "%s is not overloaded anymore." ), getName() );
1054 session->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_STATS );
1055 }
1056 setStateMod( StateMod::overloaded, false );
1057 }
1058
1059 // remove it from the backpack
1060 backpack->removeContainedItem( item );
1061
1062 // update the backpack infos of items higher than the removed item... (HACK!)
1063 for ( int i = backpackIndex; i < backpack->getContainedItemCount(); i++ ) {
1064 BackpackInfo *info = getBackpackInfo( backpack->getContainedItem( i ) );
1065 info->backpackIndex--;
1066 }
1067 // adjust equipped indexes too
1068 for(int i = 0; i < Constants::EQUIP_LOCATION_COUNT; i++) {
1069 if ( equipped[i] > backpackIndex && equipped[i] < MAX_BACKPACK_SIZE ) {
1070 equipped[i]--;
1071 }
1072 }
1073 recalcAggregateValues();
1074 }
1075 return item;
1076 }
1077
1078 /// returns true if ate/drank item completely and false else
1079
eatDrink(int backpackIndex)1080 bool Creature::eatDrink( int backpackIndex ) {
1081 return eatDrink( getBackpackItem( backpackIndex ) );
1082 }
1083
eatDrink(Item * item)1084 bool Creature::eatDrink( Item *item ) {
1085 enum {MSG_SIZE = 500 };
1086 char msg[MSG_SIZE];
1087 char buff[200];
1088 RpgItem * rpgItem = item->getRpgItem();
1089
1090 int type = rpgItem->getType();
1091 //weight = item->getWeight();
1092 int level = item->getLevel();
1093 if ( type == RpgItem::FOOD ) {
1094 if ( getHunger() == 10 ) {
1095 snprintf( msg, MSG_SIZE, _( "%s is not hungry at the moment." ), getName() );
1096 session->getGameAdapter()->writeLogMessage( msg );
1097 return false;
1098 }
1099
1100 // TODO : the quality member of rpgItem should indicate if the
1101 // food is totally healthy or roten or partially roten etc...
1102 // We eat the item and it gives us "level" hunger points back
1103 setHunger( getHunger() + level );
1104 strcpy( buff, rpgItem->getShortDesc() );
1105 buff[0] = tolower( buff[0] );
1106 snprintf( msg, MSG_SIZE, _( "%1$s eats %2$s." ), getName(), buff );
1107 session->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_PLAYERITEM );
1108 bool b = item->decrementCharges();
1109 if ( b ) {
1110 snprintf( msg, MSG_SIZE, _( "%s is used up." ), item->getItemName() );
1111 session->getGameAdapter()->writeLogMessage( msg );
1112 }
1113 return b;
1114 } else if ( type == RpgItem::DRINK ) {
1115 if ( getThirst() == 10 ) {
1116 snprintf( msg, MSG_SIZE, _( "%s is not thirsty at the moment." ), getName() );
1117 session->getGameAdapter()->writeLogMessage( msg );
1118 return false;
1119 }
1120 setThirst( getThirst() + level );
1121 strcpy( buff, rpgItem->getShortDesc() );
1122 buff[0] = tolower( buff[0] );
1123 snprintf( msg, MSG_SIZE, _( "%1$s drinks %2$s." ), getName(), buff );
1124 session->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_PLAYERITEM );
1125 // TODO : according to the alcool rate set drunk state or not
1126 bool b = item->decrementCharges();
1127 if ( b ) {
1128 snprintf( msg, MSG_SIZE, _( "%s is used up." ), item->getItemName() );
1129 session->getGameAdapter()->writeLogMessage( msg );
1130 }
1131 return b;
1132 } else if ( type == RpgItem::POTION ) {
1133 // It's a potion
1134 // Even if not thirsty, character will always drink a potion
1135 strcpy( buff, rpgItem->getShortDesc() );
1136 buff[0] = tolower( buff[0] );
1137 setThirst( getThirst() + level );
1138 snprintf( msg, MSG_SIZE, _( "%1$s drinks from %2$s." ), getName(), buff );
1139 session->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_PLAYERITEM );
1140 usePotion( item );
1141 bool b = item->decrementCharges();
1142 if ( b ) {
1143 snprintf( msg, MSG_SIZE, _( "%s is used up." ), item->getItemName() );
1144 session->getGameAdapter()->writeLogMessage( msg );
1145 }
1146 return b;
1147 } else {
1148 session->getGameAdapter()->writeLogMessage( _( "You cannot eat or drink that!" ) );
1149 return false;
1150 }
1151 }
1152
1153 /// Uses a potion.
1154
usePotion(Item * item)1155 void Creature::usePotion( Item *item ) {
1156 // nothing to do?
1157 if ( item->getRpgItem()->getPotionSkill() == -1 ) return;
1158
1159 int n;
1160 enum {MSG_SIZE = 255 };
1161 char msg[ MSG_SIZE ];
1162
1163 int skill = item->getRpgItem()->getPotionSkill();
1164 if ( skill < 0 ) {
1165 switch ( -skill - 2 ) {
1166 case Constants::HP:
1167 n = item->getRpgItem()->getPotionPower() + item->getLevel();
1168 if ( n + getHp() > getMaxHp() )
1169 n = getMaxHp() - getHp();
1170 setHp( getHp() + n );
1171 snprintf( msg, MSG_SIZE, _( "%s heals %d points." ), getName(), n );
1172 session->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_STATS );
1173 startEffect( Constants::EFFECT_SWIRL, ( Constants::DAMAGE_DURATION * 4 ) );
1174 return;
1175 case Constants::MP:
1176 n = item->getRpgItem()->getPotionPower() + item->getLevel();
1177 if ( n + getMp() > getMaxMp() )
1178 n = getMaxMp() - getMp();
1179 setMp( getMp() + n );
1180 snprintf( msg, MSG_SIZE, _( "%s receives %d magic points." ), getName(), n );
1181 session->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_STATS );
1182 startEffect( Constants::EFFECT_SWIRL, ( Constants::DAMAGE_DURATION * 4 ) );
1183 return;
1184 case Constants::AC: {
1185 bonusArmor += item->getRpgItem()->getPotionPower();
1186 recalcAggregateValues();
1187 snprintf( msg, MSG_SIZE, _( "%s feels impervious to damage!" ), getName() );
1188 session->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_STATS );
1189 startEffect( Constants::EFFECT_SWIRL, ( Constants::DAMAGE_DURATION * 4 ) );
1190
1191 // add calendar event to remove armor bonus
1192 // (format : sec, min, hours, days, months, years)
1193 Date d( 0, item->getRpgItem()->getPotionTime() + item->getLevel(), 0, 0, 0, 0 );
1194 Event *e =
1195 new PotionExpirationEvent( session->getParty()->getCalendar()->getCurrentDate(),
1196 d, this, item, session, 1 );
1197 session->getParty()->getCalendar()->scheduleEvent( ( Event* )e ); // It's important to cast!!
1198 }
1199 return;
1200 default:
1201 cerr << "Implement me! (other potion skill boost)" << endl;
1202 return;
1203 }
1204 } else {
1205 skillBonus[skill] += item->getRpgItem()->getPotionPower();
1206 // recalcAggregateValues();
1207 snprintf( msg, MSG_SIZE, _( "%s feels at peace." ), getName() );
1208 session->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_STATS );
1209 startEffect( Constants::EFFECT_SWIRL, ( Constants::DAMAGE_DURATION * 4 ) );
1210
1211 // add calendar event to remove armor bonus
1212 // (format : sec, min, hours, days, months, years)
1213 Date d( 0, item->getRpgItem()->getPotionTime() + item->getLevel(), 0, 0, 0, 0 );
1214 Event *e = new PotionExpirationEvent( session->getParty()->getCalendar()->getCurrentDate(), d, this, item, session, 1 );
1215 session->getParty()->getCalendar()->scheduleEvent( ( Event* )e ); // It's important to cast!!
1216 }
1217 }
1218
1219 /// Set the creature's next action (carried out at begin of next battle turn).
1220
setAction(int action,Item * item,Spell * spell,SpecialSkill * skill)1221 void Creature::setAction( int action, Item *item, Spell *spell, SpecialSkill *skill ) {
1222 this->action = action;
1223 this->actionItem = item;
1224 this->actionSpell = spell;
1225 this->actionSkill = skill;
1226 preActionTargetCreature = getTargetCreature();
1227 // zero the clock
1228 setLastTurn( 0 );
1229
1230 enum {MSG_SIZE = 80 };
1231 char msg[ MSG_SIZE ];
1232 switch ( action ) {
1233 case Constants::ACTION_EAT_DRINK:
1234 this->battle->invalidate();
1235 snprintf( msg, MSG_SIZE, _( "%1$s will consume %2$s." ), getName(), item->getItemName() );
1236 break;
1237 case Constants::ACTION_CAST_SPELL:
1238 this->battle->invalidate();
1239 snprintf( msg, MSG_SIZE, _( "%1$s will cast %2$s." ), getName(), spell->getDisplayName() );
1240 break;
1241 case Constants::ACTION_SPECIAL:
1242 this->battle->invalidate();
1243 snprintf( msg, MSG_SIZE, _( "%1$s will use capability %2$s." ), getName(), skill->getDisplayName() );
1244 break;
1245 case Constants::ACTION_NO_ACTION:
1246 // no-op
1247 preActionTargetCreature = NULL;
1248 strcpy( msg, "" );
1249 break;
1250 default:
1251 cerr << "*** Error: unknown action " << action << endl;
1252 return;
1253 }
1254
1255 if ( strlen( msg ) ) {
1256 if ( session->getParty()->isPartyMember(this) ) {
1257 session->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_PLAYERBATTLE );
1258 } else {
1259 session->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_NPCBATTLE );
1260 }
1261 }
1262 }
1263
1264 /// equip or doff if already equipped
1265
equipFromBackpack(int backpackIndex,int equipIndexHint)1266 void Creature::equipFromBackpack( int backpackIndex, int equipIndexHint ) {
1267 this->battle->invalidate();
1268 // doff
1269 if ( doff( backpackIndex ) ) return;
1270 // don
1271 // FIXME: take into account: two-handed weapons, min skill req-s., etc.
1272 Item *item = getBackpackItem( backpackIndex );
1273 #ifdef DEBUG_INVENTORY
1274 cerr << "item at pos " << backpackIndex << " item=" << item << endl;
1275 debugBackpack();
1276 #endif
1277
1278 int place = -1;
1279 vector<int> places;
1280 for(int i = 0; i < Constants::EQUIP_LOCATION_COUNT; i++) {
1281 // if the slot is empty and the item can be worn here
1282 if ( item->getRpgItem()->getEquip() & ( 1 << i ) &&
1283 equipped[i] == MAX_BACKPACK_SIZE ) {
1284 if ( i == equipIndexHint ) {
1285 place = i;
1286 break;
1287 }
1288 places.push_back( i );
1289 }
1290 }
1291 if ( place == -1 && !places.empty() ) {
1292 place = places[ Util::dice( places.size() ) ];
1293 }
1294 if ( place > -1 ) {
1295
1296 BackpackInfo *info = getBackpackInfo( item );
1297 info->equipIndex = place;
1298
1299 equipped[ place ] = backpackIndex;
1300
1301 // once worn, show if it's cursed
1302 item->setShowCursed( true );
1303
1304 // handle magic attrib settings
1305 if ( item->isMagicItem() ) {
1306
1307 // increase skill bonuses
1308 map<int, int> *m = item->getSkillBonusMap();
1309 for ( map<int, int>::iterator e = m->begin(); e != m->end(); ++e ) {
1310 int skill = e->first;
1311 int bonus = e->second;
1312 setSkillBonus( skill, getSkillBonus( skill ) + bonus );
1313 }
1314 // if armor, enhance magic resistance
1315 if ( !item->getRpgItem()->isWeapon() && item->getSchool() ) {
1316 int skill = item->getSchool()->getResistSkill();
1317 setSkillBonus( skill, getSkillBonus( skill ) + item->getMagicResistance() );
1318 }
1319
1320 }
1321
1322 // recalc current weapon, and the state mods
1323 recalcAggregateValues();
1324
1325 // call script
1326 if ( isPartyMember() ) session->getSquirrel()->callItemEvent( this, item, "equipItem" );
1327
1328 return;
1329 }
1330 }
1331
1332 /// Unequips an item.
1333
doff(int backpackIndex)1334 int Creature::doff( int backpackIndex ) {
1335 // doff
1336 for(int i = 0; i < Constants::EQUIP_LOCATION_COUNT; i++) {
1337 if ( equipped[i] == backpackIndex ) {
1338 Item *item = getBackpackItem( backpackIndex );
1339 equipped[i] = MAX_BACKPACK_SIZE;
1340 BackpackInfo *info = getBackpackInfo( item );
1341 info->equipIndex = -1;
1342
1343 // handle magic attrib settings
1344 if ( item->isMagicItem() ) {
1345
1346 // unset the good attributes
1347 for ( int k = 0; k < StateMod::STATE_MOD_COUNT; k++ ) {
1348 if ( item->isStateModSet( k ) ) {
1349 this->setStateMod( k, false );
1350 }
1351 }
1352 // unset the protected attributes
1353 for ( int k = 0; k < StateMod::STATE_MOD_COUNT; k++ ) {
1354 if ( item->isStateModProtected( k ) ) {
1355 this->setProtectedStateMod( k, false );
1356 }
1357 }
1358 // decrease skill bonus
1359 map<int, int> *m = item->getSkillBonusMap();
1360 for ( map<int, int>::iterator e = m->begin(); e != m->end(); ++e ) {
1361 int skill = e->first;
1362 int bonus = e->second;
1363 setSkillBonus( skill, getSkillBonus( skill ) - bonus );
1364 }
1365 // if armor, reduce magic resistance
1366 if ( !item->getRpgItem()->isWeapon() && item->getSchool() ) {
1367 int skill = item->getSchool()->getResistSkill();
1368 setSkillBonus( skill, getSkillBonus( skill ) - item->getMagicResistance() );
1369 }
1370
1371 }
1372
1373 // recalc current weapon, and the state mods
1374 recalcAggregateValues();
1375
1376 // call script
1377 if ( isPartyMember() ) session->getSquirrel()->callItemEvent( this, item, "doffItem" );
1378
1379 return 1;
1380 }
1381 }
1382 return 0;
1383 }
1384
1385
1386 /// Get the item at an equip index. (What is at equipped location?)
1387 /// The parameter is an int from 0 - EQUIP_LOCATION_COUNT
getEquippedItemByIndex(int equipIndex)1388 Item *Creature::getEquippedItemByIndex( int equipIndex ) {
1389 int n = equipped[equipIndex];
1390 if ( n < MAX_BACKPACK_SIZE ) {
1391 return getBackpackItem( n );
1392 }
1393 return NULL;
1394 }
1395
1396
1397 /// Get the item at an equip index. (What is at equipped location?)
1398 /// The parameter is a power of 2 (see constants for EQUIP_LOCATION values
getEquippedItem(int equipLocation)1399 Item *Creature::getEquippedItem( int equipLocation ) {
1400
1401 // find out which power of 2 it is
1402 int equipIndex = Constants::getLocationIndex( equipLocation );
1403
1404 return getEquippedItemByIndex( equipIndex );
1405 }
1406
1407 /// Returns whether the item at an equip index is a weapon.
1408 /// The parameter is a power of 2 (see constants for EQUIP_LOCATION values
1409
isEquippedWeapon(int equipLocation)1410 bool Creature::isEquippedWeapon( int equipLocation ) {
1411 Item *item = getEquippedItem( equipLocation );
1412 return( item && item->getRpgItem()->isWeapon() );
1413 }
1414
1415
1416 /// Returns whether the creature has an item currently equipped.
1417
isEquipped(Item * item)1418 bool Creature::isEquipped( Item *item ) {
1419 BackpackInfo *info = getBackpackInfo( item );
1420 return( info && info->equipIndex > -1 );
1421 /*
1422 for(int i = 0; i < Constants::EQUIP_LOCATION_COUNT; i++) {
1423 if(equipped[i] < MAX_BACKPACK_SIZE &&
1424 backpack[ equipped[i] ] == item ) return true;
1425 }
1426 return false;
1427 */
1428 }
1429
1430 /// Returns whether an item at an backpack location is currently equipped somewhere.
1431
isEquipped(int backpackIndex)1432 bool Creature::isEquipped( int backpackIndex ) {
1433 if ( backpackIndex < 0 || backpackIndex >= backpack->getContainedItemCount() ) return false;
1434 return isEquipped( backpack->getContainedItem( backpackIndex ) );
1435 /*
1436 for(int i = 0; i < Constants::EQUIP_LOCATION_COUNT; i++) {
1437 if( equipped[i] == backpackIndex ) return true;
1438 }
1439 return false;
1440 */
1441 }
1442
1443 /// Unequips cursed items.
1444
removeCursedItems()1445 bool Creature::removeCursedItems() {
1446 bool found = false;
1447 for(int i = 0; i < Constants::EQUIP_LOCATION_COUNT; i++) {
1448 if ( equipped[i] < MAX_BACKPACK_SIZE && backpack->getContainedItem( equipped[i] )->isCursed() ) {
1449 found = true;
1450 // not the most efficient way to do this, but it works...
1451 doff( equipped[i] );
1452 }
1453 }
1454 return found;
1455 }
1456
1457 /// Gets the equip index of the item stored at an backpack index. (Where is the item worn?)
1458
getEquippedIndex(int backpackIndex)1459 int Creature::getEquippedIndex( int backpackIndex ) {
1460 if ( backpackIndex < 0 || backpackIndex >= backpack->getContainedItemCount() ) return -1;
1461 BackpackInfo *info = getBackpackInfo( backpack->getContainedItem( backpackIndex ) );
1462 return info->equipIndex;
1463 /*
1464 for(int i = 0; i < Constants::EQUIP_LOCATION_COUNT; i++) {
1465 if(equipped[i] == index) return i;
1466 }
1467 return -1;
1468 */
1469 }
1470
1471 /// Returns whether an item is worn in the backpack (also recurses containers).
1472
isItemInBackpack(Item * item)1473 bool Creature::isItemInBackpack( Item *item ) {
1474 // -=K=-: reverting that back; carried container contents get deleted in Session::cleanUpAfterMission otherwise
1475 // return( getBackpackInfo( item ) ? true : false );
1476
1477 for ( int i = 0; i < backpack->getContainedItemCount(); i++ ) {
1478 if ( backpack->getContainedItem( i ) == item || ( backpack->getContainedItem( i )->getRpgItem()->getType() == RpgItem::CONTAINER &&
1479 backpack->getContainedItem( i )->isContainedItem( item ) ) )
1480 return true;
1481 }
1482 return false;
1483
1484 }
1485
1486 /// Calculates the aggregate values based on equipped items.
1487
recalcAggregateValues()1488 void Creature::recalcAggregateValues() {
1489 armorChanged = true;
1490
1491 // try to select a new preferred weapon if needed.
1492 if ( preferredWeapon == -1 || !isEquippedWeapon( preferredWeapon ) ) {
1493 int values[] = {
1494 Constants::EQUIP_LOCATION_LEFT_HAND,
1495 Constants::EQUIP_LOCATION_RIGHT_HAND,
1496 Constants::EQUIP_LOCATION_WEAPON_RANGED,
1497 -1
1498 };
1499 preferredWeapon = -1;
1500 for ( int i = 0; values[ i ] > -1; i++ ) {
1501 if ( isEquippedWeapon( values[ i ] ) ) {
1502 preferredWeapon = values[ i ];
1503 break;
1504 }
1505 }
1506 }
1507
1508 for(int i = 0; i < Constants::EQUIP_LOCATION_COUNT; i++) {
1509 Item *item = getEquippedItemByIndex( i );
1510 // handle magic attrib settings
1511 if ( item != NULL && item->isMagicItem() ) {
1512
1513 // set the good attributes
1514 for ( int k = 0; k < StateMod::STATE_MOD_COUNT; k++ ) {
1515 if ( item->isStateModSet( k ) ) {
1516 this->setStateMod( k, true );
1517 }
1518 }
1519 // set the protected attributes
1520 for ( int k = 0; k < StateMod::STATE_MOD_COUNT; k++ ) {
1521 if ( item->isStateModProtected( k ) ) {
1522 this->setProtectedStateMod( k, true );
1523 }
1524 }
1525 // refresh map for invisibility, etc.
1526 session->getMap()->refresh();
1527 }
1528 }
1529 }
1530
1531 /// Selects the next equipped weapon as the active weapon.
1532
nextPreferredWeapon()1533 bool Creature::nextPreferredWeapon() {
1534 #ifdef DEBUG_INVENTORY
1535 cerr << "nextPreferredWeapon" << endl;
1536 debugBackpack();
1537 #endif
1538 int pos = preferredWeapon;
1539 for ( int i = 0; i < 4; i++ ) {
1540 switch ( pos ) {
1541 case Constants::EQUIP_LOCATION_LEFT_HAND: pos = Constants::EQUIP_LOCATION_RIGHT_HAND; break;
1542 case Constants::EQUIP_LOCATION_RIGHT_HAND: pos = Constants::EQUIP_LOCATION_WEAPON_RANGED; break;
1543 case Constants::EQUIP_LOCATION_WEAPON_RANGED: pos = -1; break;
1544 case -1: pos = Constants::EQUIP_LOCATION_LEFT_HAND; break;
1545 }
1546 #ifdef DEBUG_INVENTORY
1547 if( this == session->getParty()->getPlayer() ) {
1548 if( pos != -1 ) {
1549 cerr << "\tchecking pos=" << pos << " equipped=" << getEquippedItem( pos ) << " weapon: " << isEquippedWeapon( pos ) << endl;
1550 }
1551 }
1552 #endif
1553 if ( pos == -1 || isEquippedWeapon( pos ) ) {
1554 preferredWeapon = pos;
1555 return true;
1556 }
1557 }
1558 preferredWeapon = -1;
1559 return false;
1560 }
1561
1562 /// Returns an equipped weapon with an action radius >= dist, or NULL otherwise.
1563
getBestWeapon(float dist,bool callScript)1564 Item *Creature::getBestWeapon( float dist, bool callScript ) {
1565
1566 Item *ret = NULL;
1567
1568 // for TB combat for players, respect the current weapon
1569 if ( session->getPreferences()->isBattleTurnBased() && isPartyMember() ) {
1570 ret = ( preferredWeapon > -1 ? getEquippedItem( preferredWeapon ) : NULL );
1571 } else {
1572 int location[] = {
1573 Constants::EQUIP_LOCATION_RIGHT_HAND,
1574 Constants::EQUIP_LOCATION_LEFT_HAND,
1575 Constants::EQUIP_LOCATION_WEAPON_RANGED,
1576 -1
1577 };
1578 for ( int i = 0; location[i] > -1; i++ ) {
1579 Item *item = getEquippedItem( location[i] );
1580 if ( item &&
1581 item->getRpgItem()->isWeapon() &&
1582 item->getRange() >= dist ) {
1583 ret = item;
1584 break;
1585 }
1586 }
1587 }
1588 if ( isPartyMember() && ret && callScript && SQUIRREL_ENABLED ) {
1589 session->getSquirrel()->callItemEvent( this, ret, "startBattleWithItem" );
1590 }
1591
1592 return ret;
1593 }
1594
1595 /// Returns the initiative for a battle round, the higher, the faster the attack.
1596
getInitiative(int * max)1597 int Creature::getInitiative( int *max ) {
1598 float n = ( getSkill( Skill::SPEED ) + ( getSkill( Skill::LUCK ) / 5.0f ) );
1599 if ( max ) *max = toint( n );
1600 return toint( Util::roll( 0.0f, n ) );
1601 }
1602
1603 /// Returns the number of projectiles that can be launched simultaneously.
1604
1605 /// it is a function of speed, level, and weapon skill
1606 /// this method returns a number from 1-10
1607
getMaxProjectileCount(Item * item)1608 int Creature::getMaxProjectileCount( Item *item ) {
1609 int n = static_cast<int>( static_cast<double>( getSkill( Skill::SPEED ) + ( getLevel() * 10 ) +
1610 getSkill( item->getRpgItem()->getDamageSkill() ) ) / 30.0f );
1611 if ( n <= 0 )
1612 n = 1;
1613 return n;
1614 }
1615
1616 /// Returns the projectiles that have been fired by the creature.
1617
getProjectiles()1618 vector<RenderedProjectile*> *Creature::getProjectiles() {
1619 map<RenderedCreature*, vector<RenderedProjectile*>*> *m = RenderedProjectile::getProjectileMap();
1620 return( m->find( this ) == m->end() ? NULL : ( *m )[ ( RenderedCreature* )this ] );
1621 }
1622
1623 /// Take some damage and show a nice damage effect. Return true if the creature is killed.
1624
takeDamage(float damage,int effect_type,GLuint delay)1625 bool Creature::takeDamage( float damage,
1626 int effect_type,
1627 GLuint delay ) {
1628
1629 int intDamage = toint( damage );
1630 addRecentDamage( intDamage );
1631
1632 hp -= intDamage;
1633 // if creature dies start effect at its location
1634 if ( hp > 0 ) {
1635 startEffect( effect_type );
1636 int pain = Util::dice( 3 );
1637 getShape()->setCurrentAnimation( pain == 0 ? static_cast<int>( MD2_PAIN1 ) : ( pain == 1 ? static_cast<int>( MD2_PAIN2 ) : static_cast<int>( MD2_PAIN3 ) ) );
1638 } else if ( effect_type != Constants::EFFECT_GLOW ) {
1639 session->getMap()->startEffect( toint( getX() ), toint( getY() - this->getShape()->getDepth() + 1 ), toint( getZ() ),
1640 effect_type, ( Constants::DAMAGE_DURATION * 4 ),
1641 getShape()->getWidth(), getShape()->getDepth(), delay );
1642 }
1643
1644 // creature death here so it can be used from script
1645 if ( hp <= 0 ) {
1646 if ( !( ( isMonster() && MONSTER_IMORTALITY ) || ( isPartyMember() && GOD_MODE ) ) ) {
1647 session->creatureDeath( this );
1648 }
1649 return true;
1650 } else {
1651 return false;
1652 }
1653 }
1654
1655 /// Raises the creature from the dead.
1656
resurrect(int rx,int ry)1657 void Creature::resurrect( int rx, int ry ) {
1658 // remove all state mod effects
1659 for ( int i = 0; i < StateMod::STATE_MOD_COUNT; i++ ) {
1660 setStateMod( i, false );
1661 }
1662 if ( getThirst() < 5 ) setThirst( 5 );
1663 if ( getHunger() < 5 ) setHunger( 5 );
1664
1665 setHp( Util::pickOne( 1, 3 ) );
1666
1667 findPlace( rx, ry );
1668
1669 startEffect( Constants::EFFECT_TELEPORT, ( Constants::DAMAGE_DURATION * 4 ) );
1670
1671 char msg[120];
1672 snprintf( msg, 120, _( "%s is raised from the dead!" ), getName() );
1673 session->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_STATS );
1674 }
1675
1676 /// Gives experience points for a creature kill.
1677
1678 /// add exp after killing a creature
1679 /// only called for characters
1680
addExperience(Creature * creature_killed)1681 int Creature::addExperience( Creature *creature_killed ) {
1682 int n = ( creature_killed->level + 1 ) * 25;
1683 // extra for killing higher level creatures
1684 int bonus = ( creature_killed->level - getLevel() );
1685 if ( bonus > 0 ) n += bonus * 10;
1686 return addExperienceWithMessage( n );
1687 }
1688
1689 /// Gives experience points.
1690
1691 /// Add n exp points. Only called for characters
1692 /// Note that n can be a negative number. (eg.: failure to steal)
1693
addExperience(int delta)1694 int Creature::addExperience( int delta ) {
1695 int n = delta;
1696 experience += n;
1697 if ( experience < 0 ) {
1698 n = experience;
1699 experience = 0;
1700 }
1701
1702 // level up?
1703 if ( experience >= expOfNextLevel ) {
1704 level++;
1705 hp += getStartingHp();
1706 mp += getStartingMp();
1707 calculateExpOfNextLevel();
1708 setAvailableSkillMod( getAvailableSkillMod() + character->getSkillBonus() );
1709 char message[255];
1710 snprintf( message, 255, _( " %s levels up!" ), getName() );
1711 session->getGameAdapter()->startTextEffect( message );
1712 session->getGameAdapter()->refreshBackpackUI();
1713 }
1714
1715 evalSpecialSkills();
1716
1717 return n;
1718 }
1719
1720 /// Gives experience points and adds an appropriate message to the log scroller.
1721
1722 /// Add experience and show message in map window. Also shows
1723 /// message if creature leveled up. Use generally for party
1724 /// characters only.
1725
addExperienceWithMessage(int exp)1726 int Creature::addExperienceWithMessage( int exp ) {
1727 int n = 0;
1728 if ( !getStateMod( StateMod::dead ) ) {
1729 enum { MSG_SIZE = 120 };
1730 char message[ MSG_SIZE ];
1731 int oldLevel = level;
1732 n = addExperience( exp );
1733 if ( n > 0 ) {
1734 snprintf( message, MSG_SIZE, _( "%s gains %d experience points." ), getName(), n );
1735 session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_STATS );
1736 if ( oldLevel != level ) {
1737 snprintf( message, MSG_SIZE, _( "%s gains a level!" ), getName() );
1738 session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_MISSION );
1739 }
1740 } else if ( n < 0 ) {
1741 snprintf( message, MSG_SIZE, _( "%s looses %d experience points!" ), getName(), -n );
1742 session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_STATS );
1743 }
1744 }
1745 return n;
1746 }
1747
1748 /// Adds money after a creature kill
1749
addMoney(Creature * creature_killed)1750 int Creature::addMoney( Creature *creature_killed ) {
1751 int n = creature_killed->level - getLevel();
1752 if ( n < 1 ) n = 1;
1753 // fixme: use creature_killed->getMonster()->getMoney() instead of 100.0f
1754 long delta = ( long )n * Util::dice( 50 );
1755 money += delta;
1756 return delta;
1757 }
1758
1759
1760 /// Returns the angle between the creature and its target.
1761
getTargetAngle()1762 float Creature::getTargetAngle() {
1763 //if(!targetCreature) return -1.0f;
1764 if ( !targetCreature ) return angle;
1765 return Util::getAngle( getX(), getY(), getShape()->getWidth(), getShape()->getDepth(),
1766 getTargetCreature()->getX(), getTargetCreature()->getY(),
1767 getTargetCreature()->getShape()->getWidth(),
1768 getTargetCreature()->getShape()->getHeight() );
1769 }
1770
1771 /// Returns whether the creature knows a specific spell.
1772
1773 // FIXME: O(n) but there aren't that many spells...
isSpellMemorized(Spell * spell)1774 bool Creature::isSpellMemorized( Spell *spell ) {
1775 for ( int i = 0; i < static_cast<int>( spells.size() ); i++ ) {
1776 if ( spells[i] == spell ) return true;
1777 }
1778 return false;
1779 }
1780
1781 /// Adds a spell to the creature.
1782
1783 /// FIXME: O(n) but there aren't that many spells...
1784 /// return true if spell was added, false if creature already had this spell
1785
addSpell(Spell * spell)1786 bool Creature::addSpell( Spell *spell ) {
1787 for ( vector<Spell*>::iterator e = spells.begin(); e != spells.end(); ++e ) {
1788 Spell *thisSpell = *e;
1789 if ( thisSpell == spell ) return false;
1790 }
1791 spells.push_back( spell );
1792 return true;
1793 }
1794
1795 // this assumes that hasTarget() was called first.
isTargetValid()1796 bool Creature::isTargetValid() {
1797 // is it a non-creature target? (item or location)
1798 if ( !getTargetCreature() ) return true;
1799 if ( getTargetCreature()->getStateMod( StateMod::dead ) ) return false;
1800 // when attacking, attack the opposite kind (unless possessed)
1801 // however, you can cast spells on anyone
1802 if ( getAction() == Constants::ACTION_NO_ACTION &&
1803 !canAttack( getTargetCreature() ) ) return false;
1804 return true;
1805 }
1806
1807 /// Returns whether the creature can attack the other creature, and the mouse cursor the game should display.
1808
canAttack(RenderedCreature * creature,int * cursor)1809 bool Creature::canAttack( RenderedCreature *creature, int *cursor ) {
1810 // when attacking, attack the opposite kind (unless possessed)
1811 bool ret;
1812 if ( isMonster() ) {
1813 if ( !getStateMod( StateMod::possessed ) ) {
1814 ret = ( ( !creature->isMonster() && !creature->getStateMod( StateMod::possessed ) ) ||
1815 ( creature->isMonster() && creature->getStateMod( StateMod::possessed ) ) );
1816 } else {
1817 ret = ( ( !creature->isMonster() && creature->getStateMod( StateMod::possessed ) ) ||
1818 ( creature->isMonster() && !creature->getStateMod( StateMod::possessed ) ) );
1819 }
1820 } else {
1821 if ( !getStateMod( StateMod::possessed ) ) {
1822 ret = ( ( creature->isMonster() && !creature->getStateMod( StateMod::possessed ) ) ||
1823 ( !creature->isMonster() && creature->getStateMod( StateMod::possessed ) ) );
1824 } else {
1825 ret = ( ( !creature->isMonster() && !creature->getStateMod( StateMod::possessed ) ) ||
1826 ( creature->isMonster() && creature->getStateMod( StateMod::possessed ) ) );
1827 }
1828 }
1829 if ( ret && cursor ) {
1830 float dist = getDistanceToTarget( creature );
1831 Item *item = getBestWeapon( dist );
1832 if ( dist <= getBattle()->calculateRange( item ) ) {
1833 *cursor = ( !item ? Constants::CURSOR_NORMAL :
1834 ( item->getRpgItem()->isRangedWeapon() ?
1835 Constants::CURSOR_RANGED :
1836 Constants::CURSOR_ATTACK ) );
1837 } else {
1838 *cursor = Constants::CURSOR_MOVE;
1839 }
1840 }
1841 return ret;
1842 }
1843
1844 /// Cancels the creature's target selection.
1845
cancelTarget()1846 void Creature::cancelTarget() {
1847 setTargetCreature( NULL );
1848 setTargetItem( 0, 0, 0, NULL );
1849 if ( preActionTargetCreature ) setTargetCreature( preActionTargetCreature );
1850 preActionTargetCreature = NULL;
1851 setAction( Constants::ACTION_NO_ACTION );
1852 if ( !isPartyMember() && !scripted ) {
1853 setMotion( Constants::MOTION_LOITER );
1854 pathManager->findWanderingPath( CREATURE_LOITERING_RADIUS, session->getParty()->getPlayer(), session->getMap() );
1855 }
1856 }
1857
1858 /// Does the item's prerequisite apply to this creature?
1859
isWithPrereq(Item * item)1860 bool Creature::isWithPrereq ( Item *item ) {
1861 // If the item is non-magical or used up, get out.
1862 if ( !item->isMagicItem() || ( item->getCurrentCharges() < 1 ) ) return false;
1863
1864 // Is it a potion?
1865 if ( item->getRpgItem()->getPotionSkill() < 0 ) {
1866 int potionSkill = ( ( item->getRpgItem()->getPotionSkill() < 0 ) ? ( -item->getRpgItem()->getPotionSkill() - 2 ) : 0 ) ;
1867 float remainingHP = getHp() / ( getMaxHp() ? getMaxHp() : 1 );
1868 float remainingMP = getMp() / ( getMaxMp() ? getMaxMp() : 1 );
1869 if ( ( potionSkill == Constants::HP ) && ( remainingHP <= LOW_HP ) ) return true;
1870 if ( ( potionSkill == Constants::MP ) && ( ( remainingMP <= LOW_MP ) && getMaxMp() ) ) return true;
1871 }
1872
1873 // Is it an item holding a spell?
1874 if ( item->getSpell() ) {
1875 if ( isWithPrereq( item->getSpell() ) ) return true;
1876 }
1877
1878 return false;
1879 }
1880
1881 /// Does the spell's prerequisite apply to this creature?
1882
isWithPrereq(Spell * spell)1883 bool Creature::isWithPrereq( Spell *spell ) {
1884 if ( spell->isStateModPrereqAPotionSkill() ) {
1885 switch ( spell->getStateModPrereq() ) {
1886 case Constants::HP:
1887 //cerr << "\tisWithPrereq: " << getName() << " max hp=" << getMaxHp() << " hp=" << getHp() << endl;
1888 return( getHp() <= static_cast<int>( static_cast<float>( getMaxHp() ) * LOW_HP ) );
1889 case Constants::MP:
1890 return( getMp() <= static_cast<int>( static_cast<float>( getMaxMp() ) * LOW_MP ) );
1891 case Constants::AC:
1892 /*
1893 FIXME: Even if needed only cast it 1 out of 4 times.
1894 Really need some AI here to remember if the spell helped or not. (or some way
1895 to predict if casting a spell will help.) Otherwise the monster keeps casting
1896 Body of Stone to no effect.
1897 Also: HIGH_AC should not be hard-coded...
1898 */
1899 float armor, dodgePenalty;
1900 getArmor( &armor, &dodgePenalty, 0 );
1901 return( armor >= HIGH_AC ? false
1902 : ( Util::dice( 4 ) == 0 ? true
1903 : false ) );
1904 default: return false;
1905 }
1906 } else {
1907 return getStateMod( spell->getStateModPrereq() );
1908 }
1909 }
1910
1911 /// Finds the closest possible target creature for a spell.
1912
findClosestTargetWithPrereq(Spell * spell)1913 Creature *Creature::findClosestTargetWithPrereq( Spell *spell ) {
1914
1915 // is it self?
1916 if ( isWithPrereq( spell ) ) return this;
1917
1918 // who are the possible targets?
1919 vector<Creature*> possibleTargets;
1920 if ( getStateMod( StateMod::possessed ) ) {
1921 if ( !isMonster() ) {
1922 for ( int i = 0; i < session->getParty()->getPartySize(); i++ ) {
1923 if ( !session->getParty()->getParty( i )->getStateMod( StateMod::dead ) && session->getMap()->isLocationVisible( toint( session->getParty()->getParty( i )->getX() ), toint( session->getParty()->getParty( i )->getY() ) ) && session->getMap()->isLocationInLight( toint( session->getParty()->getParty( i )->getX() ), toint( session->getParty()->getParty( i )->getY() ), session->getParty()->getParty( i )->getShape() ) && session->getParty()->getParty( i )->isWithPrereq( spell ) )
1924 possibleTargets.push_back( session->getParty()->getParty( i ) );
1925 }
1926 }
1927 for ( int i = 0; i < session->getCreatureCount(); i++ ) {
1928 if ( ( !isMonster() ? session->getCreature( i )->isMonster() : ( session->getCreature( i )->isNpc() || session->getCreature( i )->isWanderingHero() ) ) && !session->getCreature( i )->getStateMod( StateMod::dead ) && session->getMap()->isLocationVisible( toint( session->getCreature( i )->getX() ), toint( session->getCreature( i )->getY() ) ) && session->getMap()->isLocationInLight( toint( session->getCreature( i )->getX() ), toint( session->getCreature( i )->getY() ), session->getCreature( i )->getShape() ) && session->getCreature( i )->isWithPrereq( spell ) )
1929 possibleTargets.push_back( session->getCreature( i ) );
1930 }
1931 } else {
1932 for ( int i = 0; i < session->getCreatureCount(); i++ ) {
1933 if ( ( isMonster() ? session->getCreature( i )->isMonster() : ( session->getCreature( i )->isNpc() || session->getCreature( i )->isWanderingHero() ) ) && !session->getCreature( i )->getStateMod( StateMod::dead ) && session->getMap()->isLocationVisible( toint( session->getCreature( i )->getX() ), toint( session->getCreature( i )->getY() ) ) && session->getMap()->isLocationInLight( toint( session->getCreature( i )->getX() ), toint( session->getCreature( i )->getY() ), session->getCreature( i )->getShape() ) && session->getCreature( i )->isWithPrereq( spell ) )
1934 possibleTargets.push_back( session->getCreature( i ) );
1935 }
1936 }
1937
1938 // find the closest one that is closer than 20 spaces away.
1939 Creature *closest = NULL;
1940 float closestDist = 0;
1941 for ( int i = 0; i < static_cast<int>( possibleTargets.size() ); i++ ) {
1942 Creature *p = possibleTargets[ i ];
1943 float dist =
1944 Constants::distance( getX(), getY(),
1945 getShape()->getWidth(), getShape()->getDepth(),
1946 p->getX(), p->getY(),
1947 p->getShape()->getWidth(),
1948 p->getShape()->getDepth() );
1949 if ( !closest || dist < closestDist ) {
1950 closest = p;
1951 closestDist = dist;
1952 }
1953 }
1954 return( closest && closestDist < (float)CREATURE_SIGHT_RADIUS ? closest : NULL );
1955 }
1956
1957 /// Basic NPC/monster AI.
1958
decideAction()1959 void Creature::decideAction() {
1960 Uint32 now = SDL_GetTicks();
1961 if( now - lastDecision < AI_DECISION_INTERVAL ) return;
1962 lastDecision = now;
1963
1964 if ( scripted ) {
1965 getShape()->setCurrentAnimation( getScriptedAnimation() );
1966 return;
1967 }
1968
1969 // override from squirrel
1970 if( SQUIRREL_ENABLED ) {
1971 HSQOBJECT *cref = session->getSquirrel()->getCreatureRef( this );
1972 if ( cref ) {
1973 bool result;
1974 session->getSquirrel()->callBoolMethod( "decideAction", cref, &result );
1975 if ( result ) return;
1976 }
1977 }
1978
1979 // This is the AI's decision matrix. On every decision cycle, it is walked
1980 // top to bottom and collects the max weights between all rows (states) that
1981 // currently apply to the situation. The result is an array of
1982 // (AI_ACTION_COUNT) weights which are then normalized. A dice is thrown
1983 // against random indices until the roll is <= the weight saved there. The
1984 // associated action is then executed.
1985
1986 // For the sake of consistency, the sum of the weights in a row should
1987 // always be 1.
1988
1989 // Actions (from left to right):
1990 // AtkClosest, Cast, AreaCast, Heal, ACCast, AtkRandom, StartLoiter, StopMoving, GoOn
1991 float decisionMatrix[ AI_STATE_COUNT ][ AI_ACTION_COUNT ] = {
1992 { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.25f, 0.0f, 0.75f }, // Hanging around, no enemies
1993 { 0.8f, 0.0f, 0.0f, 0.0f, 0.0f, 0.2f, 0.0f, 0.0f, 0.0f }, // Hanging around, enemies near
1994 { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.2f, 0.8f }, // Loitering, no enemies
1995 { 0.8f, 0.0f, 0.0f, 0.0f, 0.0f, 0.2f, 0.0f, 0.0f, 0.0f }, // Loitering, enemies near
1996 { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.2f, 0.8f, 0.0f }, // Loitering, end of path
1997 { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.2f, 0.0f, 0.0f, 0.8f }, // Moving towards target
1998 { 0.0f, 0.3f, 0.1f, 0.0f, 0.0f, 0.2f, 0.0f, 0.0f, 0.4f }, // Engaging target
1999 { 0.0f, 0.0f, 0.0f, 0.8f, 0.0f, 0.2f, 0.0f, 0.0f, 0.0f }, // Low HP
2000 { 0.0f, 0.35f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.65f }, // Target has low HP
2001 { 0.0f, 0.0f, 0.0f, 0.8f, 0.0f, 0.1f, 0.0f, 0.0f, 0.1f }, // Low MP
2002 { 0.0f, 0.35f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.65f }, // Target has low MP
2003 { 0.0f, 0.0f, 0.0f, 0.0f, 0.25f, 0.0f, 0.0f, 0.0f, 0.75f }, // No nice AC boost
2004 { 0.0f, 0.1f, 0.5f, 0.0f, 0.2f, 0.2f, 0.0f, 0.0f, 0.0f }, // Surrounded (min. 3 attackers)
2005 { 0.0f, 0.4f, 0.25f, 0.0f, 0.2f, 0.15f, 0.0f, 0.0f, 0.0f }, // Friendlies outnumbered by enemy
2006 { 0.0f, 0.2f, 0.15f, 0.0f, 0.1f, 0.2f, 0.0f, 0.0f, 0.35f } // Friendlies outnumbering enemy
2007 };
2008
2009 float decisionWeights[ AI_ACTION_COUNT ];
2010 for ( int i = 0; i < AI_ACTION_COUNT; i++ ) {
2011 decisionWeights[ i ] = 0.0f;
2012 }
2013
2014 // STEP 1: Collect the weights of the active states.
2015
2016 // Collect the standing and loitering states.
2017
2018 Creature *closestTarget = getClosestTarget();
2019
2020 if ( ( getMotion() == Constants::MOTION_STAND ) && !closestTarget ) {
2021 for ( int i = 0; i < AI_ACTION_COUNT; i++ ) {
2022 if ( decisionMatrix[ AI_STATE_STANDING_NO_ENEMY ][ i ] > decisionWeights[ i ] ) decisionWeights[ i ] = decisionMatrix[ AI_STATE_STANDING_NO_ENEMY ][ i ];
2023 }
2024 } else if ( ( getMotion() == Constants::MOTION_STAND ) && closestTarget && !hasTarget() ) {
2025 for ( int i = 0; i < AI_ACTION_COUNT; i++ ) {
2026 if ( decisionMatrix[ AI_STATE_STANDING_ENEMY_AROUND ][ i ] > decisionWeights[ i ] ) decisionWeights[ i ] = decisionMatrix[ AI_STATE_STANDING_ENEMY_AROUND ][ i ];
2027 }
2028 } else if ( ( getMotion() == Constants::MOTION_LOITER ) && !closestTarget && !getPathManager()->atEndOfPath() ) {
2029 for ( int i = 0; i < AI_ACTION_COUNT; i++ ) {
2030 if ( decisionMatrix[ AI_STATE_LOITERING_NO_ENEMY ][ i ] > decisionWeights[ i ] ) decisionWeights[ i ] = decisionMatrix[ AI_STATE_LOITERING_NO_ENEMY ][ i ];
2031 }
2032 } else if ( ( getMotion() == Constants::MOTION_LOITER ) && closestTarget && !hasTarget() ) {
2033 for ( int i = 0; i < AI_ACTION_COUNT; i++ ) {
2034 if ( decisionMatrix[ AI_STATE_LOITERING_ENEMY_AROUND ][ i ] > decisionWeights[ i ] ) decisionWeights[ i ] = decisionMatrix[ AI_STATE_LOITERING_ENEMY_AROUND ][ i ];
2035 }
2036 } else if ( ( getMotion() == Constants::MOTION_LOITER ) && getPathManager()->atEndOfPath() ) {
2037 for ( int i = 0; i < AI_ACTION_COUNT; i++ ) {
2038 if ( decisionMatrix[ AI_STATE_LOITERING_END_OF_PATH ][ i ] > decisionWeights[ i ] ) decisionWeights[ i ] = decisionMatrix[ AI_STATE_LOITERING_END_OF_PATH ][ i ];
2039 }
2040 } else if ( ( getMotion() == Constants::MOTION_MOVE_TOWARDS ) && hasTarget() ) {
2041 for ( int i = 0; i < AI_ACTION_COUNT; i++ ) {
2042 if ( decisionMatrix[ AI_STATE_MOVING_TOWARDS_ENEMY ][ i ] > decisionWeights[ i ] ) decisionWeights[ i ] = decisionMatrix[ AI_STATE_MOVING_TOWARDS_ENEMY ][ i ];
2043 }
2044 } else if ( ( getMotion() == Constants::MOTION_STAND ) && hasTarget() ) {
2045 for ( int i = 0; i < AI_ACTION_COUNT; i++ ) {
2046 if ( decisionMatrix[ AI_STATE_ENGAGING_ENEMY ][ i ] > decisionWeights[ i ] ) decisionWeights[ i ] = decisionMatrix[ AI_STATE_ENGAGING_ENEMY ][ i ];
2047 }
2048 }
2049
2050 // Collect the HP/MP states.
2051
2052 float remainingHP = getHp() / ( getMaxHp() ? getMaxHp() : 1 );
2053
2054 if ( remainingHP <= LOW_HP ) {
2055 for ( int i = 0; i < AI_ACTION_COUNT; i++ ) {
2056 if ( decisionMatrix[ AI_STATE_LOW_HP ][ i ] > decisionWeights[ i ] ) decisionWeights[ i ] = decisionMatrix[ AI_STATE_LOW_HP ][ i ];
2057 }
2058 }
2059
2060 if ( getTargetCreature() ) {
2061 remainingHP = getTargetCreature()->getHp() / ( getTargetCreature()->getMaxHp() ? getTargetCreature()->getMaxHp() : 1 );
2062
2063 if ( remainingHP <= LOW_HP ) {
2064 for ( int i = 0; i < AI_ACTION_COUNT; i++ ) {
2065 if ( decisionMatrix[ AI_STATE_ENEMY_LOW_HP ][ i ] > decisionWeights[ i ] ) decisionWeights[ i ] = decisionMatrix[ AI_STATE_ENEMY_LOW_HP ][ i ];
2066 }
2067 }
2068 }
2069
2070 float remainingMP = getMp() / ( getMaxMp() ? getMaxMp() : 1 );
2071
2072 if ( ( remainingMP <= LOW_MP ) && getMaxMp() ) {
2073 for ( int i = 0; i < AI_ACTION_COUNT; i++ ) {
2074 if ( decisionMatrix[ AI_STATE_LOW_MP ][ i ] > decisionWeights[ i ] ) decisionWeights[ i ] = decisionMatrix[ AI_STATE_LOW_MP ][ i ];
2075 }
2076 }
2077
2078 if ( getTargetCreature() ) {
2079 remainingMP = getTargetCreature()->getMp() / ( getTargetCreature()->getMaxMp() ? getTargetCreature()->getMaxMp() : 1 );
2080
2081 if ( ( remainingMP <= LOW_MP ) && getTargetCreature()->getMaxMp() ) {
2082 for ( int i = 0; i < AI_ACTION_COUNT; i++ ) {
2083 if ( decisionMatrix[ AI_STATE_ENEMY_LOW_MP ][ i ] > decisionWeights[ i ] ) decisionWeights[ i ] = decisionMatrix[ AI_STATE_ENEMY_LOW_MP ][ i ];
2084 }
2085 }
2086 }
2087
2088 // Collect the "I love to pimp my armor class" state.
2089
2090 float armor, dodgePenalty;
2091 getArmor( &armor, &dodgePenalty, 0 );
2092
2093 if ( armor < HIGH_AC ) {
2094 for ( int i = 0; i < AI_ACTION_COUNT; i++ ) {
2095 if ( decisionMatrix[ AI_STATE_AC_NEEDS_PIMPING ][ i ] > decisionWeights[ i ] ) decisionWeights[ i ] = decisionMatrix[ AI_STATE_AC_NEEDS_PIMPING ][ i ];
2096 }
2097 }
2098
2099 // Get information for the last 3 states.
2100
2101 Creature *c;
2102 int numAttackers = 0;
2103 int numFriendlies = 0;
2104 int numFoes = 0;
2105 float dist;
2106
2107 for ( int i = 0; i < session->getCreatureCount(); i++ ) {
2108 c = session->getCreature( i );
2109
2110 if ( c->getTargetCreature() == this ) numAttackers++;
2111
2112 dist = Constants::distance( getX(), getY(), getShape()->getWidth(), getShape()->getDepth(), c->getX(), c->getY(), c->getShape()->getWidth(), c->getShape()->getDepth() );
2113
2114 if ( dist <= CREATURE_SIGHT_RADIUS ) {
2115 if ( isFriendly( c ) ) {
2116 numFriendlies++;
2117 } else {
2118 numFoes++;
2119 }
2120 }
2121
2122 }
2123
2124 // Collect the "surrounded" state.
2125
2126 if ( numAttackers > 2 ) {
2127 for ( int i = 0; i < AI_ACTION_COUNT; i++ ) {
2128 if ( decisionMatrix[ AI_STATE_SURROUNDED ][ i ] > decisionWeights[ i ] ) decisionWeights[ i ] = decisionMatrix[ AI_STATE_SURROUNDED ][ i ];
2129 }
2130 }
2131
2132 // Collect the "outnumbered" states.
2133
2134 if ( numFoes > ( numFriendlies * 2 ) ) {
2135 for ( int i = 0; i < AI_ACTION_COUNT; i++ ) {
2136 if ( decisionMatrix[ AI_STATE_OUTNUMBERED ][ i ] > decisionWeights[ i ] ) decisionWeights[ i ] = decisionMatrix[ AI_STATE_OUTNUMBERED ][ i ];
2137 }
2138 } else if ( numFriendlies > ( numFoes * 2 ) ) {
2139 for ( int i = 0; i < AI_ACTION_COUNT; i++ ) {
2140 if ( decisionMatrix[ AI_STATE_FEW_ENEMIES ][ i ] > decisionWeights[ i ] ) decisionWeights[ i ] = decisionMatrix[ AI_STATE_FEW_ENEMIES ][ i ];
2141 }
2142 }
2143
2144 // STEP 2: Process the accumulated weights.
2145
2146 // Normalize the collected weights so their sum is 1.
2147
2148 float weightSum = 0.0f;
2149
2150 for ( int i = 0; i < AI_ACTION_COUNT; i++ ) {
2151 weightSum += decisionWeights[ i ];
2152 }
2153 for ( int i = 0; i < AI_ACTION_COUNT; i++ ) {
2154 decisionWeights[ i ] /= weightSum;
2155 }
2156
2157 // TODO:Shift the weights towards their average for chaotic creatures.
2158
2159 // STEP 3: Determine the action to do.
2160
2161 int action;
2162
2163 // Throw a dice against randomly picked nonzero weights.
2164 // NOTE: This will result in an endless loop if all weights are zero!
2165
2166 while ( true ) {
2167 action = Util::pickOne( 0, AI_ACTION_COUNT - 1 );
2168 if ( decisionWeights[ action ] > 0.0f ) {
2169 if ( Util::roll( 0.0f, 1.0f ) <= decisionWeights[ action ] ) break;
2170 }
2171 }
2172
2173 #ifdef DEBUG_CREATURE_AI
2174 cerr << getName() << " action=" << action << endl;
2175 #endif
2176
2177 switch ( action ) {
2178 case AI_ACTION_ATTACK_CLOSEST_ENEMY:
2179 attackClosestTarget();
2180 break;
2181 case AI_ACTION_CAST_ATTACK_SPELL:
2182 castOffensiveSpell();
2183 break;
2184 case AI_ACTION_CAST_AREA_SPELL:
2185 castAreaSpell();
2186 break;
2187 case AI_ACTION_HEAL:
2188 if ( !castHealingSpell() ) useMagicItem();
2189 break;
2190 case AI_ACTION_CAST_AC_SPELL:
2191 castACSpell();
2192 break;
2193 case AI_ACTION_ATTACK_RANDOM_ENEMY:
2194 attackRandomTarget();
2195 break;
2196 case AI_ACTION_START_LOITERING:
2197 pathManager->findWanderingPath( CREATURE_LOITERING_RADIUS, session->getParty()->getPlayer(), session->getMap() );
2198 setMotion( Constants::MOTION_LOITER );
2199 break;
2200 case AI_ACTION_STOP_MOVING:
2201 setMotion( Constants::MOTION_STAND );
2202 stopMoving();
2203 break;
2204 case AI_ACTION_GO_ON: // GoOn (a no-op!)
2205 break;
2206 }
2207
2208 }
2209
2210 /// Returns the closest suitable battle target, NULL if no target found.
2211
getClosestTarget()2212 Creature *Creature::getClosestTarget() {
2213 Creature *p;
2214 bool possessed = getStateMod( StateMod::possessed );
2215
2216 // Select a suitable target.
2217 if ( isMonster() ) {
2218 p = ( possessed ? session->getClosestMonster ( toint ( getX() ), toint ( getY() ), getShape()->getWidth(), getShape()->getDepth(), CREATURE_SIGHT_RADIUS ) : session->getClosestGoodGuy ( toint ( getX() ), toint ( getY() ), getShape()->getWidth(), getShape()->getDepth(), CREATURE_SIGHT_RADIUS ) );
2219 } else {
2220 p = ( possessed ? session->getClosestGoodGuy ( toint ( getX() ), toint ( getY() ), getShape()->getWidth(), getShape()->getDepth(), CREATURE_SIGHT_RADIUS ) : session->getClosestMonster ( toint ( getX() ), toint ( getY() ), getShape()->getWidth(), getShape()->getDepth(), CREATURE_SIGHT_RADIUS ) );
2221 }
2222
2223 return p;
2224 }
2225
2226 /// Returns a random suitable battle target, NULL if no target found.
2227
getRandomTarget()2228 Creature *Creature::getRandomTarget() {
2229 Creature *p;
2230 bool possessed = getStateMod( StateMod::possessed );
2231
2232 // Select a suitable target.
2233 if ( isMonster() ) {
2234 p = ( possessed ? session->getRandomNearbyMonster ( toint ( getX() ), toint ( getY() ), getShape()->getWidth(), getShape()->getDepth(), CREATURE_SIGHT_RADIUS ) : session->getRandomNearbyGoodGuy ( toint ( getX() ), toint ( getY() ), getShape()->getWidth(), getShape()->getDepth(), CREATURE_SIGHT_RADIUS ) );
2235 } else {
2236 p = ( possessed ? session->getRandomNearbyGoodGuy ( toint ( getX() ), toint ( getY() ), getShape()->getWidth(), getShape()->getDepth(), CREATURE_SIGHT_RADIUS ) : session->getRandomNearbyMonster ( toint ( getX() ), toint ( getY() ), getShape()->getWidth(), getShape()->getDepth(), CREATURE_SIGHT_RADIUS ) );
2237 }
2238
2239 return p;
2240 }
2241
2242 /// Makes the creature attack the closest suitable target. Returns true if target found.
2243
attackClosestTarget()2244 bool Creature::attackClosestTarget() {
2245
2246 Creature *p = getClosestTarget();
2247
2248 if ( p ) {
2249 // attack with item
2250 setTargetCreature ( p );
2251 }
2252
2253 return ( p != NULL );
2254 }
2255
2256 /// Makes the creature attack a random suitable target. Returns true if target found.
2257
attackRandomTarget()2258 bool Creature::attackRandomTarget() {
2259 Creature *p = getRandomTarget();
2260
2261 if ( p ) {
2262 // attack with item
2263 setTargetCreature ( p );
2264 }
2265
2266 return ( p != NULL );
2267 }
2268
2269 /// Try to heal someone; returns true if someone was found.
2270
castHealingSpell(bool checkOnly)2271 bool Creature::castHealingSpell( bool checkOnly ) {
2272 vector<Spell*> healingSpells;
2273 Spell *spell;
2274
2275 for ( int i = 0; i < getSpellCount(); i++ ) {
2276 spell = getSpell ( i );
2277 if ( spell->isFriendly() && ( spell->getMp() < getMp() ) && spell->hasStateModPrereq() && ( findClosestTargetWithPrereq( spell ) != NULL ) && ( spell->isStateModPrereqAPotionSkill() ? ( spell->getStateModPrereq() != Constants::AC ) : true ) ) {
2278 healingSpells.push_back ( spell );
2279 }
2280 }
2281
2282 // We can't cast a healing spell, exit
2283 if ( healingSpells.empty() ) return false;
2284
2285 if ( !checkOnly ) {
2286 spell = healingSpells[ Util::pickOne ( 0, healingSpells.size() - 1 ) ];
2287 setAction ( Constants::ACTION_CAST_SPELL, NULL, spell );
2288 setTargetCreature ( findClosestTargetWithPrereq( spell ) );
2289 setMotion( Constants::MOTION_MOVE_TOWARDS );
2290 }
2291
2292 return true;
2293 }
2294
2295 /// Tries to cast an offensive spell onto the targetted or nearest suitable creature.
2296
castOffensiveSpell(bool checkOnly)2297 bool Creature::castOffensiveSpell( bool checkOnly ) {
2298 vector<Spell*> offensiveSpells;
2299 Spell *spell;
2300
2301 // Gather spells that are suitable for auto-casting.
2302
2303 for ( int i = 0; i < getSpellCount(); i++ ) {
2304 spell = getSpell ( i );
2305 if ( !spell->isFriendly() && ( spell->getMp() < getMp() ) && spell->isCreatureTargetAllowed() && ( spell->hasStateModPrereq() ? findClosestTargetWithPrereq( spell ) != NULL : getClosestTarget() != NULL ) ) {
2306 offensiveSpells.push_back ( spell );
2307 }
2308 }
2309
2310 // We can't cast an offensive spell, exit
2311 if ( offensiveSpells.empty() ) return false;
2312
2313 if ( !checkOnly ) {
2314 spell = offensiveSpells[ Util::pickOne ( 0, offensiveSpells.size() - 1 ) ];
2315 setAction ( Constants::ACTION_CAST_SPELL, NULL, spell );
2316 if ( spell->hasStateModPrereq() ) setTargetCreature( findClosestTargetWithPrereq( spell ) ); else setTargetCreature( getClosestTarget() );
2317 setMotion( Constants::MOTION_MOVE_TOWARDS );
2318 }
2319
2320 return true;
2321 }
2322
2323 /// Tries to cast an area-affecting spell near the targetted or nearest suitable creature.
2324
castAreaSpell(bool checkOnly)2325 bool Creature::castAreaSpell( bool checkOnly ) {
2326 vector<Spell*> areaSpells;
2327 Spell *spell;
2328
2329 // Gather spells that are suitable for auto-casting.
2330
2331 for ( int i = 0; i < getSpellCount(); i++ ) {
2332 spell = getSpell ( i );
2333 if ( !spell->isFriendly() && ( spell->getMp() < getMp() ) && spell->isLocationTargetAllowed() && ( spell->hasStateModPrereq() ? findClosestTargetWithPrereq( spell ) != NULL : getClosestTarget() != NULL ) ) {
2334 areaSpells.push_back ( spell );
2335 }
2336 }
2337
2338 // We can't cast an offensive spell, exit
2339 if ( areaSpells.empty() ) return false;
2340
2341 if ( !checkOnly ) {
2342 spell = areaSpells[ Util::pickOne ( 0, areaSpells.size() - 1 ) ];
2343 setAction ( Constants::ACTION_CAST_SPELL, NULL, spell );
2344 if ( spell->hasStateModPrereq() ) setTargetCreature( findClosestTargetWithPrereq( spell ) ); else setTargetCreature( getClosestTarget() );
2345 setMotion( Constants::MOTION_MOVE_TOWARDS );
2346 }
2347
2348 return true;
2349 }
2350
2351 /// Try to raise someone's AC using a spell; returns true if someone was found.
2352
castACSpell(bool checkOnly)2353 bool Creature::castACSpell( bool checkOnly ) {
2354 vector<Spell*> acSpells;
2355 Spell *spell;
2356
2357 for ( int i = 0; i < getSpellCount(); i++ ) {
2358 spell = getSpell ( i );
2359 if ( spell->isFriendly() && ( spell->getMp() < getMp() ) && spell->hasStateModPrereq() && ( findClosestTargetWithPrereq( spell ) != NULL ) && ( spell->isStateModPrereqAPotionSkill() ? ( spell->getStateModPrereq() == Constants::AC ) : false ) ) {
2360 acSpells.push_back ( spell );
2361 }
2362 }
2363
2364 // We can't cast an AC raising spell, exit
2365 if ( acSpells.empty() ) return false;
2366
2367 if ( !checkOnly ) {
2368 spell = acSpells[ Util::pickOne ( 0, acSpells.size() - 1 ) ];
2369 setAction ( Constants::ACTION_CAST_SPELL, NULL, spell );
2370 setTargetCreature ( findClosestTargetWithPrereq( spell ) );
2371 setMotion( Constants::MOTION_MOVE_TOWARDS );
2372 }
2373
2374 return true;
2375 }
2376
2377 /// Tries to use a healing magical item from the backpack on oneself.
2378
useMagicItem(bool checkOnly)2379 bool Creature::useMagicItem( bool checkOnly ) {
2380
2381 // If there are no magical items in the backpack, get outta here.
2382 if ( !backpack->getContainedItemCount() || !backpack->getContainsMagicItem() ) return false;
2383
2384 vector<Item*> usefulItems;
2385 Item *item;
2386
2387 // Search the backpack for potions and items with healing spells.
2388 for ( int i = 0; i < backpack->getContainedItemCount(); i++ ) {
2389 item = backpack->getContainedItem( i );
2390 if ( isWithPrereq( item ) ) {
2391 usefulItems.push_back( item );
2392 }
2393 }
2394
2395 // If no items found, leave.
2396 if ( usefulItems.empty() ) return false;
2397
2398 if ( !checkOnly ) {
2399 item = usefulItems [ Util::pickOne( 0, usefulItems.size() - 1 ) ];
2400 setAction( ( ( item->getRpgItem()->getPotionSkill() < 0 ) ? Constants::ACTION_EAT_DRINK : Constants::ACTION_CAST_SPELL ), item, item->getSpell() );
2401 }
2402
2403 return true;
2404 }
2405
2406 /// Returns the distance to the selected target spot.
2407
getDistanceToSel()2408 float Creature::getDistanceToSel() {
2409 if ( selX > -1 && selY > -1 ) {
2410 return Constants::distance( getX(), getY(), getShape()->getWidth(), getShape()->getDepth(), selX, selY, 1, 1 );
2411 }
2412
2413 return 0;
2414 }
2415
2416 /// Returns the distance to another creature.
2417
getDistance(RenderedCreature * other)2418 float Creature::getDistance( RenderedCreature *other ) {
2419 return Constants::distance( getX(), getY(),
2420 getShape()->getWidth(), getShape()->getDepth(),
2421 other->getX(),
2422 other->getY(),
2423 other->getShape()->getWidth(),
2424 other->getShape()->getDepth() );
2425 }
2426
2427 /// Returns the distance to a creature or if not given, the selected target.
2428
getDistanceToTarget(RenderedCreature * creature)2429 float Creature::getDistanceToTarget( RenderedCreature *creature ) {
2430 if ( creature ) return getDistance( creature );
2431
2432 if ( !hasTarget() ) return 0.0f;
2433 if ( getTargetCreature() ) {
2434 return getDistance( getTargetCreature() );
2435 } else if ( getTargetX() || getTargetY() ) {
2436 if ( getTargetItem() ) {
2437 return Constants::distance( getX(), getY(),
2438 getShape()->getWidth(), getShape()->getDepth(),
2439 getTargetX(), getTargetY(),
2440 getTargetItem()->getShape()->getWidth(),
2441 getTargetItem()->getShape()->getDepth() );
2442 } else {
2443 return Constants::distance( getX(), getY(),
2444 getShape()->getWidth(), getShape()->getDepth(),
2445 getTargetX(), getTargetY(), 1, 1 );
2446 }
2447 } else {
2448 return 0.0f;
2449 }
2450 }
2451
2452 /// Sets the experience required for the character to level up.
2453
setExp()2454 void Creature::setExp() {
2455 if ( !( isPartyMember() || isWanderingHero() ) ) return;
2456 expOfNextLevel = 0;
2457 for ( int i = 0; i < level - 1; i++ ) {
2458 expOfNextLevel += ( ( i + 1 ) * character->getLevelProgression() );
2459 }
2460 }
2461
2462 /// Calculates how far the creature has moved since the last frame.
2463
getStep()2464 GLfloat Creature::getStep() {
2465 GLfloat fps = session->getGameAdapter()->getFps();
2466 GLfloat div = FPS_ONE + static_cast<float>( ( 4 - session->getPreferences()->getGameSpeedLevel() ) * 3.0f );
2467 if ( fps < div ) return 0.8f;
2468 GLfloat step = 1.0f / ( fps / div );
2469 if ( pathManager->getSpeed() <= 0 ) {
2470 step *= 1.0f - ( 1.0f / 10.0f );
2471 } else if ( pathManager->getSpeed() >= 10 ) {
2472 step *= 1.0f - ( 9.9f / 10.0f );
2473 } else {
2474 step *= ( 1.0f - ( ( GLfloat )( pathManager->getSpeed() ) / 10.0f ) );
2475 }
2476 return step;
2477 }
2478
2479 /// Returns a brief description of the creature.
2480
getDetailedDescription(std::string & s)2481 void Creature::getDetailedDescription( std::string& s ) {
2482 char tempdesc[256] = {0};
2483
2484 int alignmentPercent = (int)( getAlignment() * 100 );
2485 char alignmentDesc[32];
2486
2487 if ( alignmentPercent < 20 ) {
2488 snprintf( alignmentDesc, 32, _( "Utterly chaotic" ) );
2489 } else if ( alignmentPercent >= 20 && alignmentPercent < 40 ) {
2490 snprintf( alignmentDesc, 32, _( "Chaotic" ) );
2491 } else if ( alignmentPercent >= 40 && alignmentPercent < 60 ) {
2492 snprintf( alignmentDesc, 32, _( "Neutral" ) );
2493 } else if ( alignmentPercent >= 60 && alignmentPercent < 80 ) {
2494 snprintf( alignmentDesc, 32, _( "Lawful" ) );
2495 } else if ( alignmentPercent >= 80 ) {
2496 snprintf( alignmentDesc, 32, _( "Overly lawful" ) );
2497 }
2498
2499 snprintf( tempdesc, 255, _( "%s (L:%d HP:%d/%d MP:%d/%d AL:%d%%(%s))" ), _( getName() ), getLevel(), getHp(), getMaxHp(), getMp(), getMaxMp(), alignmentPercent, alignmentDesc );
2500
2501 s = tempdesc;
2502
2503 if ( session->getCurrentMission() && session->getCurrentMission()->isMissionCreature( this ) ) {
2504 s += _( " *Mission*" );
2505 }
2506 if ( boss ) {
2507 s += _( " *Boss*" );
2508 }
2509
2510 }
2511
2512 /// Draws the creature.
2513
draw()2514 void Creature::draw() {
2515 getShape()->draw();
2516 }
2517
2518 /// Adds additional NPC-only info.
2519
setNpcInfo(NpcInfo * npcInfo)2520 void Creature::setNpcInfo( NpcInfo *npcInfo ) {
2521 this->npcInfo = npcInfo;
2522 setName( npcInfo->name );
2523
2524 // for merchants, re-create backpack with the correct types
2525 if ( npcInfo->type == Constants::NPC_TYPE_MERCHANT ) {
2526 // drop everything beyond the basic backpack
2527 for ( int i = getMonster()->getStartingItemCount(); i < this->getBackpackContentsCount(); i++ ) {
2528 this->removeFromBackpack( getMonster()->getStartingItemCount() );
2529 }
2530
2531 std::vector<int> types( npcInfo->getSubtype()->size() + 1 );
2532 int typeCount = 0;
2533 for ( set<int>::iterator e = npcInfo->getSubtype()->begin(); e != npcInfo->getSubtype()->end(); ++e ) {
2534 types[ typeCount++ ] = *e;
2535 }
2536
2537 // equip merchants at the party's level
2538 int level = ( session->getParty() ?
2539 toint( ( static_cast<float>( session->getParty()->getTotalLevel() ) ) / ( static_cast<float>( session->getParty()->getPartySize() ) ) ) :
2540 getLevel() );
2541 if ( level < 0 ) level = 1;
2542
2543 // add some loot
2544 int nn = Util::pickOne( 3, 7 );
2545 //cerr << "Adding loot:" << nn << endl;
2546 for ( int i = 0; i < nn; i++ ) {
2547 Item *loot;
2548 if ( npcInfo->isSubtype( RpgItem::SCROLL ) ) {
2549 Spell *spell = MagicSchool::getRandomSpell( level );
2550 loot = session->newItem( RpgItem::getItemByName( "Scroll" ),
2551 level,
2552 spell );
2553 } else {
2554 loot =
2555 session->newItem(
2556 RpgItem::getRandomItemFromTypes( session->getGameAdapter()->
2557 getCurrentDepth(),
2558 &types[0], typeCount ),
2559 level );
2560 }
2561 //cerr << "\t" << loot->getRpgItem()->getName() << endl;
2562 // make it contain all items, no matter what size
2563 addToBackpack( loot );
2564 }
2565 }
2566 }
2567
2568 /// Sets up the special capabilities of the creature.
2569
evalSpecialSkills()2570 void Creature::evalSpecialSkills() {
2571
2572 if( !( isPartyMember() || isWanderingHero() ) || !SQUIRREL_ENABLED ) return;
2573
2574 set<SpecialSkill*> oldSpecialSkills;
2575 for ( int t = 0; t < SpecialSkill::getSpecialSkillCount(); t++ ) {
2576 SpecialSkill *ss = SpecialSkill::getSpecialSkill( t );
2577 if ( specialSkills.find( ss ) != specialSkills.end() ) {
2578 oldSpecialSkills.insert( ss );
2579 }
2580 }
2581 enum {TMP_SIZE = 120};
2582 char tmp[TMP_SIZE];
2583 specialSkills.clear();
2584 specialSkillNames.clear();
2585 HSQOBJECT *param = session->getSquirrel()->getCreatureRef( this );
2586 if ( param ) {
2587 bool result;
2588 for ( int t = 0; t < SpecialSkill::getSpecialSkillCount(); t++ ) {
2589 SpecialSkill *ss = SpecialSkill::getSpecialSkill( t );
2590 session->getSquirrel()->
2591 callBoolMethod( ss->getSquirrelFunctionPrereq(),
2592 param,
2593 &result );
2594 if ( result ) {
2595 specialSkills.insert( ss );
2596 string skillName = ss->getName();
2597 specialSkillNames.insert( skillName );
2598 if ( oldSpecialSkills.find( ss ) == oldSpecialSkills.end() ) {
2599 if ( session->getParty()->isPartyMember( this ) ) {
2600 snprintf( tmp, TMP_SIZE, _( "%1$s gains the %2$s special ability!" ), getName(), ss->getDisplayName() );
2601 session->getGameAdapter()->writeLogMessage( tmp, Constants::MSGTYPE_STATS );
2602 }
2603 }
2604 }
2605 }
2606 }
2607 for ( int t = 0; t < SpecialSkill::getSpecialSkillCount(); t++ ) {
2608 SpecialSkill *ss = SpecialSkill::getSpecialSkill( t );
2609 if ( specialSkills.find( ss ) == specialSkills.end() &&
2610 oldSpecialSkills.find( ss ) != oldSpecialSkills.end() ) {
2611 if ( session->getParty()->isPartyMember( this ) ) {
2612 snprintf( tmp, TMP_SIZE, _( "%1$s looses the %2$s special ability!" ), getName(), ss->getDisplayName() );
2613 session->getGameAdapter()->writeLogMessage( tmp, Constants::MSGTYPE_STATS );
2614 }
2615 }
2616 }
2617 }
2618
2619 /// Sets the value of a skill.
2620
setSkill(int index,int value)2621 void Creature::setSkill( int index, int value ) {
2622 int oldValue = getSkill( index );
2623 skills[index] = ( value < 0 ? 0 : value > 100 ? 100 : value );
2624 skillChanged( index, oldValue, getSkill( index ) );
2625 evalSpecialSkills();
2626 session->getParty()->recomputeMaxSkills();
2627 }
2628
2629 /// Sets the additional skill bonus (on top of the base value).
2630
setSkillBonus(int index,int value)2631 void Creature::setSkillBonus( int index, int value ) {
2632 int oldValue = getSkill( index );
2633 skillBonus[index] = value;
2634 skillChanged( index, oldValue, getSkill( index ) );
2635 session->getParty()->recomputeMaxSkills();
2636 }
2637
2638 /// Sets the not yet applied skill amount.
2639
setSkillMod(int index,int value)2640 void Creature::setSkillMod( int index, int value ) {
2641 int oldValue = getSkill( index );
2642 skillMod[ index ] = ( value < 0 ? 0 : value );
2643 skillChanged( index, oldValue, getSkill( index ) );
2644 session->getParty()->recomputeMaxSkills();
2645 }
2646
2647 /// Recalculates skills when stats change.
2648
skillChanged(int index,int oldValue,int newValue)2649 void Creature::skillChanged( int index, int oldValue, int newValue ) {
2650 // while loading don't update skill values.
2651 if ( loading ) return;
2652
2653 if ( Skill::skills[ index ]->getGroup()->isStat() && character ) {
2654 for ( int i = 0; i < Skill::SKILL_COUNT; i++ ) {
2655 int oldPrereq = 0;
2656 int newPrereq = 0;
2657 for ( int t = 0; t < Skill::skills[i]->getPreReqStatCount(); t++ ) {
2658 int statIndex = Skill::skills[i]->getPreReqStat( t )->getIndex();
2659 if ( statIndex == index ) {
2660 oldPrereq += oldValue;
2661 newPrereq += newValue;
2662 } else {
2663 oldPrereq += getSkill( statIndex );
2664 newPrereq += getSkill( statIndex );
2665 }
2666 }
2667 oldPrereq = static_cast<int>( ( oldPrereq / static_cast<float>( Skill::skills[i]->getPreReqStatCount() ) ) *
2668 static_cast<float>( Skill::skills[i]->getPreReqMultiplier() ) );
2669 newPrereq = static_cast<int>( ( newPrereq / static_cast<float>( Skill::skills[i]->getPreReqStatCount() ) ) *
2670 static_cast<float>( Skill::skills[i]->getPreReqMultiplier() ) );
2671 if ( oldPrereq != newPrereq ) {
2672 setSkill( i, getSkill( i ) + ( newPrereq - oldPrereq ) );
2673 armorChanged = true;
2674 }
2675 }
2676 }
2677 }
2678
2679 /// Applies the skill point based changes to skills.
2680
applySkillMods()2681 void Creature::applySkillMods() {
2682 for ( int i = 0; i < Skill::SKILL_COUNT; i++ ) {
2683 if ( skillMod[ i ] > 0 ) {
2684 setSkill( i, skills[ i ] + skillMod[ i ] );
2685 skillMod[ i ] = 0;
2686 }
2687 }
2688 availableSkillMod = 0;
2689 hasAvailableSkillPoints = false;
2690 }
2691
2692 /// Sets the active state of a state mod.
2693
setStateMod(int mod,bool setting)2694 void Creature::setStateMod( int mod, bool setting ) {
2695 if ( setting ) stateMod |= ( 1 << mod );
2696 else stateMod &= ( ( GLuint )0xffff - ( GLuint )( 1 << mod ) );
2697 evalSpecialSkills();
2698 }
2699
2700 /// Sets the "protected" state of a state mod (not influenceable by spells etc.)
2701
setProtectedStateMod(int mod,bool setting)2702 void Creature::setProtectedStateMod( int mod, bool setting ) {
2703 if ( setting ) protStateMod |= ( 1 << mod );
2704 else protStateMod &= ( ( GLuint )0xffff - ( GLuint )( 1 << mod ) );
2705 evalSpecialSkills();
2706 }
2707
2708 /// Applies the effects of recurring (periodic) special capabilities.
2709
applyRecurringSpecialSkills()2710 void Creature::applyRecurringSpecialSkills() {
2711 for ( int t = 0; t < SpecialSkill::getSpecialSkillCount(); t++ ) {
2712 SpecialSkill *skill = SpecialSkill::getSpecialSkill( t );
2713 if ( skill->getType() == SpecialSkill::SKILL_TYPE_RECURRING &&
2714 hasSpecialSkill( skill ) ) {
2715 useSpecialSkill( skill, false );
2716 }
2717 }
2718 }
2719
2720 /// Applies the effects of automatic special capabilities.
applyAutomaticSpecialSkills(int event,char * varName,float varValue)2721 float Creature::applyAutomaticSpecialSkills( int event, char *varName, float varValue ) {
2722 if( !( isPartyMember() || isWanderingHero() ) || !SQUIRREL_ENABLED ) return varValue;
2723
2724 #ifdef DEBUG_CAPABILITIES
2725 cerr << "Using automatic capabilities for event type: " << event << endl;
2726 #endif
2727 for ( int t = 0; t < SpecialSkill::getSpecialSkillCount(); t++ ) {
2728 SpecialSkill *skill = SpecialSkill::getSpecialSkill( t );
2729 if ( skill->getEvent() == event &&
2730 skill->getType() == SpecialSkill::SKILL_TYPE_AUTOMATIC &&
2731 hasSpecialSkill( skill ) ) {
2732 #ifdef DEBUG_CAPABILITIES
2733 cerr << "\tusing capability: " << skill->getDisplayName() <<
2734 " and setting var: " <<
2735 varName << "=" << varValue << endl;
2736 #endif
2737 session->getSquirrel()->setGlobalVariable( varName, varValue );
2738 useSpecialSkill( skill, false );
2739 varValue = session->getSquirrel()->getGlobalVariable( varName );
2740 #ifdef DEBUG_CAPABILITIES
2741 cerr << "\t\tgot back " << varValue << endl;
2742 #endif
2743 }
2744 }
2745 #ifdef DEBUG_CAPABILITIES
2746 cerr << "final value=" << varValue << " ===============================" << endl;
2747 #endif
2748 return varValue;
2749 }
2750
2751 /// Uses a special capability.
2752
useSpecialSkill(SpecialSkill * specialSkill,bool manualOnly)2753 char *Creature::useSpecialSkill( SpecialSkill *specialSkill, bool manualOnly ) {
2754 if( monster || !SQUIRREL_ENABLED ) return NULL;
2755
2756 if ( !hasSpecialSkill( specialSkill ) ) {
2757 return Constants::getMessage( Constants::UNMET_CAPABILITY_PREREQ_ERROR );
2758 } else if ( manualOnly &&
2759 specialSkill->getType() !=
2760 SpecialSkill::SKILL_TYPE_MANUAL ) {
2761 return Constants::getMessage( Constants::CANNOT_USE_AUTO_CAPABILITY_ERROR );
2762 }
2763 HSQOBJECT *param = session->getSquirrel()->getCreatureRef( this );
2764 if ( param ) {
2765 session->getSquirrel()->
2766 callNoArgMethod( specialSkill->getSquirrelFunctionAction(),
2767 param );
2768 return NULL;
2769 } else {
2770 cerr << "*** Error: can't find squarrel reference for creature: " << getName() << endl;
2771 return NULL;
2772 }
2773 }
2774
2775
2776
2777
2778
2779
2780 /**
2781 * ============================================================
2782 * ============================================================
2783 *
2784 * New battle system calls
2785 *
2786 * damage=attack_roll - ac
2787 * attack_roll=(item_action + item_level) % skill
2788 * ac=(armor_total + avg_armor_level) % skill
2789 * skill=avg. of ability skill (power,coord, etc.), item skill, luck
2790 *
2791 * ============================================================
2792 * ============================================================
2793 *
2794 * Fixme:
2795 * -currently, extra attacks are not used. (use SPEED skill to eval?)
2796 * -magic item armor
2797 * -automatic capabilities
2798 *
2799 * move here from battle.cpp:
2800 * -critical hits (2x,3x,damage,etc.)
2801 * -conditions modifiers
2802 *
2803 * ** Look in old methods and battle.cpp for more info
2804 */
2805
2806 // 1 item level equals this many character levels in combat
2807 #define ITEM_LEVEL_DIVISOR 8.0f
2808
2809 // base weapon damage of an attack with bare hands
2810 #define HAND_ATTACK_DAMAGE Dice(1,4,0)
2811
2812 /// Returns the creature's chance to dogde the specified attack.
2813
getDodge(Creature * attacker,Item * weapon)2814 float Creature::getDodge( Creature *attacker, Item *weapon ) {
2815 // the target's dodge if affected by angle of attack
2816 bool inFOV =
2817 Util::isInFOV( getX(), getY(), getTargetAngle(),
2818 attacker->getX(), attacker->getY() );
2819
2820 // the target's dodge
2821 float armor, dodgePenalty;
2822 getArmor( &armor, &dodgePenalty,
2823 weapon ? weapon->getRpgItem()->getDamageType() : 0,
2824 weapon );
2825 float dodge = getSkill( Skill::DODGE_ATTACK ) - dodgePenalty;
2826 if ( !inFOV ) {
2827 dodge /= 2.0f;
2828 if ( getCharacter() ) {
2829 session->getGameAdapter()->writeLogMessage( _( "...Attack from blind-spot!" ), Constants::MSGTYPE_PLAYERBATTLE );
2830 } else {
2831 session->getGameAdapter()->writeLogMessage( _( "...Attack from blind-spot!" ), Constants::MSGTYPE_NPCBATTLE );
2832 }
2833 }
2834 return dodge;
2835 }
2836
2837 /// Returns the chance the creature's armor deflects the damage from the specified attack.
2838
getArmor(float * armorP,float * dodgePenaltyP,int damageType,Item * vsWeapon)2839 float Creature::getArmor( float *armorP, float *dodgePenaltyP,
2840 int damageType, Item *vsWeapon ) {
2841 calcArmor( damageType, &armor, dodgePenaltyP, ( vsWeapon ? true : false ) );
2842
2843 // negative feedback: for monsters only, allow hits now and then
2844 // -=K=-: the sentence in if seems screwed up
2845 if ( monster &&
2846 ( Util::mt_rand() <
2847 monsterToughness[ session->getPreferences()->getMonsterToughness() ].
2848 armorMisfuction ) ) {
2849 // 3.0f * rand() / RAND_MAX < 1.0f ) {
2850 armor = Util::roll( 0.0f, armor / 2.0f );
2851 } else if( !monster && SQUIRREL_ENABLED ) {
2852 // apply any armor enhancing capabilities
2853 if ( vsWeapon ) {
2854 session->getSquirrel()->setCurrentWeapon( vsWeapon );
2855 armor = applyAutomaticSpecialSkills( SpecialSkill::SKILL_EVENT_ARMOR,
2856 "armor", armor );
2857 }
2858 }
2859
2860 *armorP = armor;
2861
2862 return armor;
2863 }
2864
2865 /// Calculates armor and dodge penalty.
2866
calcArmor(int damageType,float * armorP,float * dodgePenaltyP,bool callScript)2867 void Creature::calcArmor( int damageType,
2868 float *armorP,
2869 float *dodgePenaltyP,
2870 bool callScript ) {
2871 if ( armorChanged ) {
2872 for ( int t = 0; t < RpgItem::DAMAGE_TYPE_COUNT; t++ ) {
2873 lastArmor[ t ] = ( monster ? monster->getBaseArmor() : 0 );
2874 lastDodgePenalty[ t ] = 0;
2875 int armorCount = 0;
2876 for(int i = 0; i < Constants::EQUIP_LOCATION_COUNT; i++) {
2877 if ( equipped[i] != MAX_BACKPACK_SIZE ) {
2878 Item *item = backpack->getContainedItem( equipped[ i ] );
2879 if ( item->getRpgItem()->getType() == RpgItem::ARMOR ||
2880 ( item->isMagicItem() && item->getBonus() > 0 && !item->getRpgItem()->isWeapon() ) ) {
2881
2882 int n = ( item->getRpgItem()->getType() == RpgItem::ARMOR ?
2883 item->getRpgItem()->getDefense( t ) :
2884 item->getBonus() );
2885
2886 if ( callScript && !monster && SQUIRREL_ENABLED ) {
2887 session->getSquirrel()->setGlobalVariable( "armor", lastArmor[ t ] );
2888 session->getSquirrel()->callItemEvent( this, item, "useItemInDefense" );
2889 lastArmor[ t ] = session->getSquirrel()->getGlobalVariable( "armor" );
2890 }
2891 lastArmor[ t ] += n;
2892 lastDodgePenalty[ t ] += item->getRpgItem()->getDodgePenalty();
2893
2894 // item's level has a small influence.
2895 lastArmor[ t ] += item->getLevel() / 8;
2896
2897 // apply the armor influence... it uses the first
2898 // influence slot (AP_INFLUENCE)
2899 lastArmor[ t ] +=
2900 getInfluenceBonus( item, AP_INFLUENCE,
2901 ( callScript ? "CTH" : NULL ) );
2902
2903 armorCount++;
2904 }
2905 }
2906 }
2907 lastArmor[ t ] += bonusArmor;
2908 if ( lastArmor[ t ] < 0 ) lastArmor[ t ] = 0;
2909 }
2910 armorChanged = false;
2911 }
2912
2913 *armorP = lastArmor[ damageType ];
2914 *dodgePenaltyP = lastDodgePenalty[ damageType ];
2915 }
2916
2917 #define MAX_RANDOM_DAMAGE 2.0f
2918
power(float base,int e)2919 float power( float base, int e ) {
2920 float n = 1;
2921 for ( int i = 0; i < e; i++ ) {
2922 n *= base;
2923 }
2924 return n;
2925 }
2926
getInfluenceBonus(Item * weapon,int influenceType,char const * debugMessage)2927 float Creature::getInfluenceBonus( Item *weapon,
2928 int influenceType,
2929 char const* debugMessage ) {
2930
2931 if ( !weapon ) return 0;
2932
2933 float bonus = 0;
2934 for ( int i = 0; i < Skill::SKILL_COUNT; i++ ) {
2935 WeaponInfluence *minInfluence = weapon->getRpgItem()->getWeaponInfluence( i, influenceType, MIN_INFLUENCE );
2936 WeaponInfluence *maxInfluence = weapon->getRpgItem()->getWeaponInfluence( i, influenceType, MAX_INFLUENCE );
2937
2938 float n = 0;
2939 int value = getSkill( i );
2940 if ( minInfluence->limit > -1 && minInfluence->limit > value ) {
2941 switch ( minInfluence->type ) {
2942 case 'E' :
2943 // exponential malus
2944 n = -power( minInfluence->base, minInfluence->limit - value );
2945 break;
2946 case 'L' :
2947 // linear
2948 n = -( minInfluence->limit - value ) * minInfluence->base;
2949 break;
2950 default:
2951 cerr << "*** Error: unknown influence type for item: " << weapon->getRpgItem()->getDisplayName() << endl;
2952 }
2953 } else if ( maxInfluence->limit > -1 && maxInfluence->limit < value ) {
2954 switch ( maxInfluence->type ) {
2955 case 'E' :
2956 // exponential bonus
2957 n = power( maxInfluence->base, value - maxInfluence->limit );
2958 break;
2959 case 'L' :
2960 // linear bonus
2961 n = ( value - maxInfluence->limit ) * maxInfluence->base;
2962 break;
2963 default:
2964 cerr << "*** Error: unknown influence type for item: " << weapon->getRpgItem()->getDisplayName() << endl;
2965 }
2966 }
2967
2968 if ( n != 0 && debugMessage &&
2969 session->getPreferences()->getCombatInfoDetail() > 0 ) {
2970 char message[120];
2971 snprintf( message, 120, "...%s %s:%s %d-%d %s %d, %s=%.2f",
2972 debugMessage,
2973 _( "skill" ),
2974 Skill::skills[i]->getDisplayName(),
2975 minInfluence->limit,
2976 maxInfluence->limit,
2977 _( "vs." ),
2978 value,
2979 _( "bonus" ),
2980 n );
2981 session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_SYSTEM );
2982 }
2983
2984 bonus += n;
2985 }
2986 return bonus;
2987 }
2988
2989 /// Returns the chance to hit with a weapon, as well as the skill used for the weapon.
2990
getCth(Item * weapon,float * cth,float * skill,bool showDebug)2991 void Creature::getCth( Item *weapon, float *cth, float *skill, bool showDebug ) {
2992 // the attacker's skill
2993 *skill = getSkill( weapon ?
2994 weapon->getRpgItem()->getDamageSkill() :
2995 Skill::HAND_TO_HAND_COMBAT );
2996
2997 // The max cth is closer to the skill to avoid a lot of misses
2998 // This is ok, since dodge is subtracted from it anyway.
2999 float maxCth = *skill * 1.5f;
3000 if ( maxCth > 100 ) maxCth = 100;
3001 if ( maxCth < 40 ) maxCth = 40;
3002
3003 // roll chance to hit (CTH)
3004 *cth = Util::roll( 0.0f, maxCth );
3005
3006 // item's level has a small influence
3007 if ( weapon ) *skill += weapon->getLevel() / 2;
3008
3009 // Apply COORDINATION influence to skill
3010 // (As opposed to subtracting it from cth. This is b/c
3011 // skill is shown in the characterInfo as the cth.)
3012 *skill += getInfluenceBonus( weapon, CTH_INFLUENCE,
3013 ( showDebug ? "CTH" : NULL ) );
3014 if ( *skill < 0 ) *skill = 0;
3015
3016 if ( showDebug && session->getPreferences()->getCombatInfoDetail() > 0 ) {
3017 char message[120];
3018 snprintf( message, 120, "...%s:%.2f (%s:%.2f) %s %s:%.2f",
3019 _( "CTH" ),
3020 *cth,
3021 _( "max" ),
3022 maxCth,
3023 _( "vs." ),
3024 _( "skill" ),
3025 *skill );
3026 session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_SYSTEM );
3027 }
3028 }
3029
3030 /// Returns the chance to successfully attack with a weapon, as well as the min and max damage.
3031
getAttack(Item * weapon,float * maxP,float * minP,bool callScript)3032 float Creature::getAttack( Item *weapon,
3033 float *maxP,
3034 float *minP,
3035 bool callScript ) {
3036
3037 float power;
3038 if ( weapon && weapon->getRpgItem()->isRangedWeapon() ) {
3039 power = getSkill( Skill::POWER ) / 2.0f +
3040 getSkill( Skill::COORDINATION ) / 2.0f;
3041 } else {
3042 power = getSkill( Skill::POWER );
3043 }
3044
3045 // the min/max power value
3046 float minPower, maxPower;
3047 if ( power < 10 ) {
3048 // 1d10
3049 minPower = 1; maxPower = 10;
3050 } else if ( power < 15 ) {
3051 // 2d10
3052 minPower = 2; maxPower = 20;
3053 } else {
3054 // 3d10
3055 minPower = 3; maxPower = 30;
3056 }
3057
3058 // What percent of power is given by weapon?
3059 // (For unarmed combat it's a coordination bonus.)
3060 float damagePercent = ( weapon ?
3061 weapon->getRpgItem()->getDamage() :
3062 getSkill( Skill::COORDINATION ) +
3063 getSkill( Skill::POWER ) );
3064
3065 // item's level has a small influence
3066 if ( weapon ) damagePercent += weapon->getLevel() / 2;
3067
3068 // apply POWER influence
3069 damagePercent += getInfluenceBonus( weapon, DAM_INFLUENCE,
3070 ( callScript ? "DAM" : NULL ) );
3071 if ( damagePercent < 0 ) damagePercent = 0;
3072
3073 if ( minP ) {
3074 *minP = ( minPower / 100.0f ) * damagePercent;
3075 }
3076 if ( maxP ) {
3077 *maxP = ( maxPower / 100.0f ) * damagePercent;
3078 }
3079
3080 // roll the power
3081 float roll = Util::roll( minPower, maxPower );
3082
3083 // take the weapon's skill % of the max power
3084 roll = ( roll / 100.0f ) * damagePercent;
3085
3086 // apply damage enhancing capabilities
3087 if ( callScript && isPartyMember() && SQUIRREL_ENABLED ) {
3088 session->getSquirrel()->setCurrentWeapon( weapon );
3089 roll = applyAutomaticSpecialSkills( SpecialSkill::SKILL_EVENT_DAMAGE,
3090 "damage", roll );
3091 if ( weapon )
3092 session->getSquirrel()->setGlobalVariable( "damage", roll );
3093 session->getSquirrel()->callItemEvent( this, weapon, "useItemInAttack" );
3094 roll = session->getSquirrel()->getGlobalVariable( "damage" );
3095 }
3096
3097 return roll;
3098 }
3099
3100 /// The chance to parry a successful attack before it hits armor.
3101
getParry(Item ** parryItem)3102 float Creature::getParry( Item **parryItem ) {
3103 int location[] = {
3104 Constants::EQUIP_LOCATION_RIGHT_HAND,
3105 Constants::EQUIP_LOCATION_LEFT_HAND,
3106 Constants::EQUIP_LOCATION_WEAPON_RANGED,
3107 -1
3108 };
3109 float ret = 0;
3110 float maxParry = 0;
3111 for ( int i = 0; location[i] > -1; i++ ) {
3112 Item *item = getEquippedItem( location[i] );
3113 if ( !item ) continue;
3114 if ( item->getRpgItem()->getDefenseSkill() == Skill::SHIELD_DEFEND ) {
3115 // parry using a shield: use shield skill to parry
3116 maxParry = getSkill( item->getRpgItem()->getDefenseSkill() );
3117 } else if ( item->getRpgItem()->getParry() > 0 ) {
3118 // parry using a weapon: get max parry skill amount (% of weapon skill)
3119 maxParry =
3120 ( getSkill( item->getRpgItem()->getDamageSkill() ) / 100.0f ) *
3121 item->getRpgItem()->getParry();
3122
3123 // use the item's CTH skill to modify parry also
3124 maxParry += getInfluenceBonus( item, CTH_INFLUENCE, "PARRY" );
3125 if ( maxParry < 0 ) maxParry = 0;
3126
3127 } else {
3128 // no parry with this hand
3129 continue;
3130 }
3131 // roll to parry
3132 float parry = Util::roll( 0.0f, maxParry );
3133 // select the highest value
3134 if ( ret == 0 || ret < parry ) {
3135 ret = parry;
3136 *parryItem = item;
3137 }
3138 }
3139 return ret;
3140 }
3141
3142 /// Modifies the attack roll according to the attacked creature's active state mods.
3143
getDefenderStateModPercent(bool magical)3144 float Creature::getDefenderStateModPercent( bool magical ) {
3145 /*
3146 apply state_mods:
3147 (Done here so it's used for spells too)
3148
3149 blessed,
3150 empowered,
3151 enraged,
3152 ac_protected,
3153 magic_protected,
3154
3155 drunk,
3156
3157 poisoned,
3158 cursed,
3159 possessed,
3160 blinded,
3161 charmed,
3162 changed,
3163 overloaded,
3164 */
3165 float delta = 0.0f;
3166 if ( getStateMod( StateMod::blessed ) ) {
3167 delta += Util::roll( 0.0f, 10.0f );
3168 }
3169 if ( getStateMod( StateMod::empowered ) ) {
3170 delta += Util::roll( 5.0f, 15.0f );
3171 }
3172 if ( getStateMod( StateMod::enraged ) ) {
3173 delta += Util::roll( 8.0f, 18.0f );
3174 }
3175 if ( getStateMod( StateMod::drunk ) ) {
3176 delta += Util::roll( -7.0f, 7.0f );
3177 }
3178 if ( getStateMod( StateMod::cursed ) ) {
3179 delta -= Util::roll( 5.0f, 15.0f );
3180 }
3181 if ( getStateMod( StateMod::blinded ) ) {
3182 delta -= Util::roll( 0.0f, 10.0f );
3183 }
3184 if ( !magical && getTargetCreature()->getStateMod( StateMod::ac_protected ) ) {
3185 delta -= Util::roll( 0.0f, 7.0f );
3186 }
3187 if ( magical && getTargetCreature()->getStateMod( StateMod::magic_protected ) ) {
3188 delta -= ( 7.0f * Util::mt_rand() );
3189 }
3190 if ( getTargetCreature()->getStateMod( StateMod::blessed ) ) {
3191 delta -= Util::roll( 0.0f, 5.0f );
3192 }
3193 if ( getTargetCreature()->getStateMod( StateMod::cursed ) ) {
3194 delta += Util::roll( 0.0f, 5.0f );
3195 }
3196 if ( getTargetCreature()->getStateMod( StateMod::overloaded ) ) {
3197 delta += Util::roll( 0.0f, 2.0f );
3198 }
3199 if ( getTargetCreature()->getStateMod( StateMod::blinded ) ) {
3200 delta += Util::roll( 0.0f, 2.0f );
3201 }
3202 if ( getTargetCreature()->getStateMod( StateMod::invisible ) ) {
3203 delta -= Util::roll( 0.0f, 10.0f );
3204 }
3205 return delta;
3206 }
3207
3208 /// Modifies the attack roll according to the creature's active state mods.
3209
getAttackerStateModPercent()3210 float Creature::getAttackerStateModPercent() {
3211 /*
3212 apply state_mods:
3213 blessed,
3214 empowered,
3215 enraged,
3216 ac_protected,
3217 magic_protected,
3218
3219 drunk,
3220
3221 poisoned,
3222 cursed,
3223 possessed,
3224 blinded,
3225 charmed,
3226 changed,
3227 overloaded,
3228 */
3229 float delta = 0.0f;
3230 if ( getStateMod( StateMod::blessed ) ) {
3231 delta += Util::roll( 0.0f, 15.0f );
3232 }
3233 if ( getStateMod( StateMod::empowered ) ) {
3234 delta += Util::roll( 10.0f, 25.0f );
3235 }
3236 if ( getStateMod( StateMod::enraged ) ) {
3237 delta -= Util::roll( 0.0f, 10.0f );
3238 }
3239 if ( getStateMod( StateMod::drunk ) ) {
3240 delta += Util::roll( -15.0f, 15.0f );
3241 }
3242 if ( getStateMod( StateMod::cursed ) ) {
3243 delta -= Util::roll( 10.0f, 25.0f );
3244 }
3245 if ( getStateMod( StateMod::blinded ) ) {
3246 delta -= Util::roll( 0.0f, 15.0f );
3247 }
3248 if ( getStateMod( StateMod::overloaded ) ) {
3249 delta -= Util::roll( 0.0f, 10.0f );
3250 }
3251 if ( getStateMod( StateMod::invisible ) ) {
3252 delta += Util::roll( 5.0f, 10.0f );
3253 }
3254 return delta;
3255 }
3256
3257 /// Returns a semi-random amount of magical damage for an item.
3258
rollMagicDamagePercent(Item * item)3259 float Creature::rollMagicDamagePercent( Item *item ) {
3260 float itemLevel = ( item->getLevel() - 1 ) / ITEM_LEVEL_DIVISOR;
3261 return item->rollMagicDamage() + itemLevel;
3262 }
3263
3264 /// Returns the number of action points the creature receives at each battle turn.
3265
getMaxAP()3266 float Creature::getMaxAP( ) {
3267 return( static_cast<float>( getSkill( Skill::COORDINATION ) ) + static_cast<float>( getSkill( Skill::SPEED ) ) ) / 2.0f;
3268 }
3269
3270 /// Returns the attacks per round possible with item.
3271
getAttacksPerRound(Item * item)3272 float Creature::getAttacksPerRound( Item *item ) {
3273 return( getMaxAP() / getWeaponAPCost( item, false ) );
3274 }
3275
3276 /// Returns the action point cost of using a specific item.
3277
getWeaponAPCost(Item * item,bool showDebug)3278 float Creature::getWeaponAPCost( Item *item, bool showDebug ) {
3279 float baseAP = ( item ?
3280 item->getRpgItem()->getAP() :
3281 Constants::HAND_WEAPON_SPEED );
3282 // never show debug (called a lot)
3283 // Apply a max 3pt influence to weapon AP
3284 baseAP -= getInfluenceBonus( item, AP_INFLUENCE, NULL );
3285 // can't be free..
3286 if ( baseAP < 1 ) baseAP = 1;
3287 return baseAP;
3288 }
3289
3290 /// Checks whether an item can be equipped, returns error message or NULL if successful.
3291
canEquipItem(Item * item,bool interactive)3292 char *Creature::canEquipItem( Item *item, bool interactive ) {
3293
3294 // check item tags to see if this item can be equipped.
3295 if ( character ) {
3296 if ( !character->canEquip( item->getRpgItem() ) ) {
3297 return Constants::getMessage( Constants::ITEM_ACL_VIOLATION );
3298 }
3299 }
3300
3301 // check the level
3302 if ( getLevel() < item->getLevel() ) {
3303 return Constants::getMessage( Constants::ITEM_LEVEL_VIOLATION );
3304 }
3305
3306 // two handed weapon violations
3307 if( item->getRpgItem()->getEquip() & Constants::EQUIP_LOCATION_LEFT_HAND ||
3308 item->getRpgItem()->getEquip() & Constants::EQUIP_LOCATION_RIGHT_HAND ) {
3309 Item *leftHandWeapon = getEquippedItem( Constants::EQUIP_LOCATION_LEFT_HAND );
3310 Item *rightHandWeapon = getEquippedItem( Constants::EQUIP_LOCATION_RIGHT_HAND );
3311 bool bothHandsFree = !( leftHandWeapon || rightHandWeapon );
3312 bool holdsTwoHandedWeapon =
3313 ( ( leftHandWeapon && leftHandWeapon->getRpgItem()->getTwoHanded() == RpgItem::ONLY_TWO_HANDED ) ||
3314 ( rightHandWeapon && rightHandWeapon->getRpgItem()->getTwoHanded() == RpgItem::ONLY_TWO_HANDED ) );
3315
3316 if ( holdsTwoHandedWeapon ||
3317 ( !bothHandsFree &&
3318 item->getRpgItem()->getTwoHanded() == RpgItem::ONLY_TWO_HANDED ) ) {
3319 if ( interactive ) {
3320 return Constants::getMessage( Constants::ITEM_TWO_HANDED_VIOLATION );
3321 }
3322 }
3323 }
3324 return NULL;
3325 }
3326
3327 /// Sets character info (if not monster/NPC).
3328
setCharacter(Character * c)3329 void Creature::setCharacter( Character *c ) {
3330 assert( isPartyMember() || isWanderingHero() );
3331 character = c;
3332 }
3333
3334 /// Plays a character sound of the specified type.
3335
playCharacterSound(int soundType,int panning)3336 void Creature::playCharacterSound( int soundType, int panning ) {
3337 if ( !monster )
3338 session->getSound()->playCharacterSound( model_name.c_str(), soundType, panning );
3339 }
3340
3341 /// Does a roll against a skill, optionally with a luck modifier.
3342
rollSkill(int skill,float luckDiv)3343 bool Creature::rollSkill( int skill, float luckDiv ) {
3344 float f = static_cast<float>( getSkill( skill ) );
3345 if ( luckDiv > 0 )
3346 f += static_cast<float>( getSkill( Skill::LUCK ) ) / luckDiv;
3347 return( Util::roll( 0.0f, 100.0f ) <= f );
3348 }
3349
3350 /// Does a secret door discovery roll, returns true if successful.
3351
3352 #define SECRET_DOOR_ATTEMPT_INTERVAL 5000
rollSecretDoor(Location * pos)3353 bool Creature::rollSecretDoor( Location *pos ) {
3354 if ( secretDoorAttempts.find( pos ) != secretDoorAttempts.end() ) {
3355 Uint32 lastTime = secretDoorAttempts[ pos ];
3356 if ( SDL_GetTicks() - lastTime < SECRET_DOOR_ATTEMPT_INTERVAL ) return false;
3357 }
3358 bool ret = rollSkill( Skill::FIND_SECRET_DOOR, 4.0f );
3359 if ( !ret ) {
3360 secretDoorAttempts[ pos ] = SDL_GetTicks();
3361 }
3362 return ret;
3363 }
3364
resetSecretDoorAttempts()3365 void Creature::resetSecretDoorAttempts() {
3366 secretDoorAttempts.clear();
3367 }
3368
3369 /// Unused.
3370
3371 #define TRAP_FIND_ATTEMPT_INTERVAL 500
rollTrapFind(Trap * trap)3372 bool Creature::rollTrapFind( Trap *trap ) {
3373 if ( trapFindAttempts.find( trap ) != trapFindAttempts.end() ) {
3374 Uint32 lastTime = trapFindAttempts[ trap ];
3375 if ( SDL_GetTicks() - lastTime < TRAP_FIND_ATTEMPT_INTERVAL ) return false;
3376 }
3377 bool ret = rollSkill( Skill::FIND_TRAP, 0.5f ); // traps are easy to notice
3378 if ( !ret ) {
3379 trapFindAttempts[ trap ] = SDL_GetTicks();
3380 }
3381 return ret;
3382 }
3383
resetTrapFindAttempts()3384 void Creature::resetTrapFindAttempts() {
3385 trapFindAttempts.clear();
3386 }
3387
3388 /// Does a perception roll, discovers secret doors and traps if successful.
3389
rollPerception()3390 void Creature::rollPerception() {
3391
3392 Uint32 now = SDL_GetTicks();
3393 if ( now - lastPerceptionCheck < PERCEPTION_DELTA ) return;
3394 lastPerceptionCheck = now;
3395
3396 // find traps
3397 set<Uint8> *trapsShown = session->getMap()->getTrapsShown();
3398 for ( set<Uint8>::iterator e = trapsShown->begin(); e != trapsShown->end(); ++e ) {
3399 Uint8 trapIndex = *e;
3400 Trap *trap = session->getMap()->getTrapLoc( trapIndex );
3401 if ( trap->discovered == false ) {
3402 float dist = Constants::distance( getX(), getY(), getShape()->getWidth(), getShape()->getDepth(),
3403 trap->r.x, trap->r.y, trap->r.w, trap->r.h );
3404 if ( dist < 10 && !session->getMap()->isWallBetween( toint( getX() ), toint( getY() ), 0, trap->r.x, trap->r.y, 0 ) ) {
3405 trap->discovered = rollSkill( Skill::FIND_TRAP, 0.5f ); // traps are easy to notice
3406 if ( trap->discovered ) {
3407 char message[ 120 ];
3408 snprintf( message, 120, _( "%s notices a trap!" ), getName() );
3409 int panning = session->getMap()->getPanningFromMapXY( trap->r.x, trap->r.y );
3410 session->getSound()->playSound( "notice-trap", panning );
3411 session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_MISSION );
3412 addExperienceWithMessage( 50 );
3413 setMotion( Constants::MOTION_STAND );
3414 stopMoving();
3415 }
3416 }
3417 }
3418 }
3419
3420 // find secret doors
3421 for ( int xx = toint( getX() ) - 10; xx < toint( getX() ) + 10; xx++ ) {
3422 for ( int yy = toint( getY() ) - 10; yy < toint( getY() ) + 10; yy++ ) {
3423 Location *pos = session->getMap()->getLocation( xx, yy, 0 );
3424 if ( pos && session->getMap()->isSecretDoor( pos ) && !session->getMap()->isSecretDoorDetected( pos ) ) {
3425 if ( rollSkill( Skill::FIND_SECRET_DOOR, 4.0f ) ) {
3426 session->getMap()->setSecretDoorDetected( pos );
3427 char message[ 120 ];
3428 snprintf( message, 120, _( "%s notices a secret door!" ), getName() );
3429 int panning = session->getMap()->getPanningFromMapXY( pos->x, pos->y );
3430 session->getSound()->playSound( "notice-trap", panning );
3431 session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_MISSION );
3432 addExperienceWithMessage( 50 );
3433 }
3434 }
3435 }
3436 }
3437 }
3438
3439 /// Checks whether the creature has stepped into a trap and handles the effects.
3440
evalTrap()3441 void Creature::evalTrap() {
3442 int trapIndex = session->getMap()->getTrapAtLoc( toint( getX() ), toint( getY() ) );
3443 if ( trapIndex != -1 ) {
3444 Trap *trap = session->getMap()->getTrapLoc( trapIndex );
3445 if ( trap->enabled ) {
3446 trap->discovered = true;
3447 trap->enabled = false;
3448 int damage = static_cast<int>( Util::getRandomSum( 10, session->getCurrentMission()->getLevel() ) );
3449 char message[ 120 ];
3450 snprintf( message, 120, _( "%1$s blunders into a trap and takes %2$d points of damage!" ), getName(), damage );
3451 int panning = session->getMap()->getPanningFromMapXY( trap->r.x, trap->r.y );
3452 session->getSound()->playSound( "trigger-trap", panning );
3453 if ( getCharacter() ) {
3454 session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERDAMAGE );
3455 } else {
3456 session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCDAMAGE );
3457 }
3458 takeDamage( damage );
3459 }
3460 }
3461 }
3462
3463 /// Handles trap disarming.
3464
disableTrap(Trap * trap)3465 void Creature::disableTrap( Trap *trap ) {
3466 if ( trap->enabled ) {
3467 trap->discovered = true;
3468 trap->enabled = false;
3469 enum { MSG_SIZE = 120 };
3470 char message[ MSG_SIZE ];
3471 snprintf( message, MSG_SIZE, _( "%s attempts to disable the trap:" ), getName() );
3472 session->getGameAdapter()->writeLogMessage( message );
3473 bool ret = rollSkill( Skill::FIND_TRAP, 5.0f );
3474 if ( ret ) {
3475 session->getGameAdapter()->writeLogMessage( _( " and succeeds!" ), Constants::MSGTYPE_MISSION );
3476 int panning = session->getMap()->getPanningFromMapXY( trap->r.x, trap->r.y );
3477 session->getSound()->playSound( "disarm-trap", panning );
3478 int exp = static_cast<int>( Util::getRandomSum( 50, session->getCurrentMission()->getLevel() ) );
3479 addExperienceWithMessage( exp );
3480 } else {
3481 int damage = static_cast<int>( Util::getRandomSum( 10, session->getCurrentMission()->getLevel() ) );
3482 char message[ MSG_SIZE ];
3483 snprintf( message, MSG_SIZE, _( " and fails! %1$s takes %2$d points of damage!" ), getName(), damage );
3484 int panning = session->getMap()->getPanningFromMapXY( trap->r.x, trap->r.y );
3485 session->getSound()->playSound( "trigger-trap", panning );
3486 session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_FAILURE );
3487 takeDamage( damage );
3488 }
3489 } else {
3490 session->getGameAdapter()->writeLogMessage( _( "This trap is already disabled." ) );
3491 }
3492 }
3493
3494 /// Sets the motion type (stand, run, loiter around...) for the creature.
3495
setMotion(int motion)3496 void Creature::setMotion( int motion ) {
3497 this->motion = motion;
3498 }
3499
3500 /// Will this creature stay visible in movie mode?
3501
setScripted(bool b)3502 void Creature::setScripted( bool b ) {
3503 this->scripted = b;
3504 if ( scripted ) stopMoving();
3505 }
3506
3507 /// Draws the creature's portrait in movie mode.
3508
drawMoviePortrait(int width,int height)3509 void Creature::drawMoviePortrait( int width, int height ) {
3510 // todo: should be next power of 2 after width/height (maybe cap-ed at 256)
3511 int textureSizeW = 128;
3512 int textureSizeH = 128;
3513
3514 if ( !portrait.isSpecified() ) {
3515 if ( getCharacter() != NULL ) {
3516 getSession()->getShapePalette()->getPortraitTexture( getSex(), getPortraitTextureIndex() ).glBind();
3517 } else {
3518 getMonster()->getPortraitTexture().glBind();
3519 }
3520
3521 portrait.createAlpha( session->getShapePalette()->getNamedTexture( "conv_filter" ),
3522 getCharacter() ? getSession()->getShapePalette()->getPortraitTexture( getSex(), getPortraitTextureIndex() ) :
3523 getMonster()->getPortraitTexture(), 128, 128, width, height );
3524 }
3525
3526 glDisable( GL_DEPTH_TEST );
3527 glDisable( GL_CULL_FACE );
3528 glEnable( GL_TEXTURE_2D );
3529 // glTranslatef( x, y, 0 );
3530
3531 glEnable( GL_BLEND );
3532 // glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
3533 glBlendFunc( Scourge::blendA, Scourge::blendB );
3534 portrait.glBind();
3535 glColor4f( 1, 1, 1, 1 );
3536
3537 glBegin( GL_TRIANGLE_STRIP );
3538 glTexCoord2f( 0, 1 );
3539 glVertex3f( 0, 0, 0 );
3540 glTexCoord2f( 1, 1 );
3541 glVertex3f( textureSizeW, 0, 0 );
3542 glTexCoord2f( 0, 0 );
3543 glVertex3f( 0, textureSizeH, 0 );
3544 glTexCoord2f( 1, 0 );
3545 glVertex3f( textureSizeW, textureSizeH, 0 );
3546 glEnd();
3547
3548 session->getShapePalette()->getNamedTexture( "conv" ).glBind();
3549 glColor4f( 1, 1, 1, 1 );
3550
3551 glPushMatrix();
3552 glTranslatef( -10, -20, 0 );
3553
3554 glBegin( GL_TRIANGLE_STRIP );
3555 glTexCoord2f( 0, 0 );
3556 glVertex3f( 0, 0, 0 );
3557 glTexCoord2f( 1, 0 );
3558 glVertex3f( width + 20, 0, 0 );
3559 glTexCoord2f( 0, 1 );
3560 glVertex3f( 0, height + 40, 0 );
3561 glTexCoord2f( 1, 1 );
3562 glVertex3f( width + 20, height + 40, 0 );
3563 glEnd();
3564 glPopMatrix();
3565
3566 glDisable( GL_BLEND );
3567 glEnable( GL_DEPTH_TEST );
3568
3569 //glDisable( GL_ALPHA_TEST );
3570 //glDisable(GL_TEXTURE_2D);
3571 //glEnable( GL_CULL_FACE );
3572 }
3573
3574 /// Draws the creature's portrait, if it exists, else it draws a little 3D view of the creature.
3575
drawPortrait(int width,int height,bool inFrame)3576 void Creature::drawPortrait( int width, int height, bool inFrame ) {
3577 if ( getCharacter() || ( getMonster() && getMonster()->getPortraitTexture().isSpecified() ) ) {
3578 //glEnable( GL_ALPHA_TEST );
3579 //glAlphaFunc( GL_EQUAL, 0xff );
3580 glEnable( GL_TEXTURE_2D );
3581 glPushMatrix();
3582 // glTranslatef( x, y, 0 );
3583 if (getCharacter() != NULL) {
3584 getSession()->getShapePalette()->getPortraitTexture( getSex(), getPortraitTextureIndex() ).glBind();
3585 } else {
3586 getMonster()->getPortraitTexture().glBind();
3587 }
3588
3589 glColor4f( 1, 1, 1, 1 );
3590
3591
3592 glBegin( GL_TRIANGLE_STRIP );
3593 glTexCoord2f( 0, 0 );
3594 glVertex3f( 0, 0, 0 );
3595 glTexCoord2f( 1, 0 );
3596 glVertex3f( width, 0, 0 );
3597 glTexCoord2f( 0, 1 );
3598 glVertex3f( 0, height, 0 );
3599 glTexCoord2f( 1, 1 );
3600 glVertex3f( width, height, 0 );
3601 glEnd();
3602 glPopMatrix();
3603
3604 //glDisable( GL_ALPHA_TEST );
3605 glDisable( GL_TEXTURE_2D );
3606 } else if ( getMonster() ) {
3607
3608 glPushAttrib( GL_CURRENT_BIT | GL_ENABLE_BIT | GL_DEPTH_BUFFER_BIT );
3609 glEnable( GL_DEPTH_TEST );
3610
3611 Texture* textureGroup = session->getMap()->getShapes()->getCurrentTheme()->getTextureGroup( WallTheme::themeRefName[ WallTheme::THEME_REF_WALL ] );
3612 Texture texture = textureGroup[ GLShape::FRONT_SIDE ];
3613
3614 glPushMatrix();
3615 glEnable( GL_TEXTURE_2D );
3616 glColor4f( 1, 1, 1, 1 );
3617 texture.glBind();
3618
3619 glBegin( GL_TRIANGLE_STRIP );
3620 glTexCoord2f( 0, 0 );
3621 glVertex2i( 20, 0 );
3622 glTexCoord2f( 1, 0 );
3623 glVertex2i( 170, 0 );
3624 glTexCoord2f( 0, 1 );
3625 glVertex2i( 20, 150 );
3626 glTexCoord2f( 1, 1 );
3627 glVertex2i( 170, 150 );
3628 glEnd();
3629 glDisable( GL_TEXTURE_2D );
3630 glPopMatrix();
3631
3632 shape = getShape();
3633 shape->setCurrentAnimation( MD2_STAND, true );
3634
3635 // seems to have no effect...
3636 ((AnimatedShape*)shape)->setAlpha( 180.0f );
3637
3638 glPushMatrix();
3639 glTranslatef( 135, 190, 100 );
3640 glRotatef( 90, 1, 0, 0 );
3641 glRotatef( 180, 0, 0, 1 );
3642 glScalef( 2.0f, 2.0f, 2.0f );
3643 glColor4f( 1, 1, 1, 1 );
3644 shape->draw();
3645 glPopMatrix();
3646 glScalef( 1, 1, 1 );
3647
3648 glPopAttrib();
3649 }
3650 }
3651
3652 /// The item at a specified backpack index.
getBackpackItem(int backpackIndex)3653 Item *Creature::getBackpackItem( int backpackIndex ) {
3654 return backpack->getContainedItem( backpackIndex );
3655 }
3656 /// Number of items carried in backpack.
getBackpackContentsCount()3657 int Creature::getBackpackContentsCount() {
3658 return backpack->getContainedItemCount();
3659 }
3660
3661
3662
3663
3664
3665 /// Returns the max hit points of the creature.
3666
getMaxHp()3667 int Creature::getMaxHp() {
3668 return getStartingHp() * getLevel();
3669 }
3670
3671 /// Returns the max magic points of the creature.
3672
getMaxMp()3673 int Creature::getMaxMp() {
3674 return getStartingMp() * getLevel();
3675 }
3676
3677 /// Sets the full amount of hit points.
3678
setHp()3679 void Creature::setHp() {
3680 int total = getLevel() * getStartingHp();
3681 hp = Util::pickOne( (int)( total * 0.75f ), total );
3682 }
3683
3684 /// Sets the full amount of magic points.
3685
setMp()3686 void Creature::setMp() {
3687 int total = getLevel() * getStartingMp();
3688 mp = Util::pickOne( (int)( total * 0.75f ), total );
3689 }
3690
getStartingHp()3691 int Creature::getStartingHp() {
3692 return getCharacter() ? getCharacter()->getStartingHp() : startingHp;
3693 }
3694
getStartingMp()3695 int Creature::getStartingMp() {
3696 return getCharacter() ? getCharacter()->getStartingMp() : startingMp;
3697 }
3698
monsterInit()3699 void Creature::monsterInit() {
3700
3701 setLevel( monster->getLevel() );
3702
3703
3704 //cerr << "------------------------------------" << endl << "Creature: " << monster->getType() << endl;
3705 for ( int i = 0; i < Skill::SKILL_COUNT; i++ ) {
3706
3707 //int n = Creature::rollStartingSkill( scourge->getSession(), LEVEL, i );
3708 int n;
3709 if ( Skill::skills[i]->getGroup()->isStat() ) {
3710 MonsterToughness *mt = &( monsterToughness[ session->getPreferences()->getMonsterToughness() ] );
3711 n = static_cast<int>( 20.0f * Util::roll( mt->minSkillBase, mt->maxSkillBase ) );
3712 } else {
3713 // create the starting value as a function of the stats
3714 n = 0;
3715 for ( int t = 0; t < Skill::skills[i]->getPreReqStatCount(); t++ ) {
3716 int index = Skill::skills[i]->getPreReqStat( t )->getIndex();
3717 n += getSkill( index );
3718 }
3719 n = static_cast<int>( ( n / static_cast<float>( Skill::skills[i]->getPreReqStatCount() ) ) * static_cast<float>( Skill::skills[i]->getPreReqMultiplier() ) );
3720 }
3721
3722 // special: adjust magic resistance... makes game too hard otherwise
3723 if ( i == Skill::RESIST_AWARENESS_MAGIC ||
3724 i == Skill::RESIST_CONFRONTATION_MAGIC ||
3725 i == Skill::RESIST_DECEIT_MAGIC ||
3726 i == Skill::RESIST_HISTORY_MAGIC ||
3727 i == Skill::RESIST_LIFE_AND_DEATH_MAGIC ||
3728 i == Skill::RESIST_NATURE_MAGIC ) {
3729 n /= 2;
3730 }
3731
3732 // apply monster settings
3733 int minSkill = monster->getSkillLevel( Skill::skills[i]->getName() );
3734 if ( minSkill > n ) n = minSkill;
3735
3736 setSkill( i, n );
3737 //cerr << "\t" << Skill::skills[ i ]->getName() << "=" << getSkill( i ) << endl;
3738
3739 stateMod = monster->getStartingStateMod();
3740 }
3741
3742 // equip starting backpack
3743 for ( int i = 0; i < getMonster()->getStartingItemCount(); i++ ) {
3744 int itemLevel = getMonster()->getLevel() - Util::dice( 2 );
3745 if ( itemLevel < 1 ) itemLevel = 1;
3746 Item *item = session->newItem( getMonster()->getStartingItem( i ), itemLevel );
3747 if( addToBackpack( item ) ) {
3748 equipFromBackpack( backpack->getContainedItemCount() - 1 );
3749 }
3750 }
3751
3752 // add some loot
3753 int nn = Util::pickOne( 3, 7 );
3754 //cerr << "Adding loot:" << nn << endl;
3755 for ( int i = 0; i < nn; i++ ) {
3756 Item *loot;
3757 if ( 0 == Util::dice( 10 ) ) {
3758 Spell *spell = MagicSchool::getRandomSpell( getLevel() );
3759 loot = session->newItem( RpgItem::getItemByName( "Scroll" ),
3760 getLevel(),
3761 spell );
3762 } else {
3763 loot = session->newItem( RpgItem::getRandomItem( session->getGameAdapter()->getCurrentDepth() ),
3764 getLevel() );
3765 }
3766 //cerr << "\t" << loot->getRpgItem()->getName() << endl;
3767 // make it contain all items, no matter what size
3768 addToBackpack( loot );
3769 }
3770
3771 // add spells
3772 for ( int i = 0; i < getMonster()->getStartingSpellCount(); i++ ) {
3773 addSpell( getMonster()->getStartingSpell( i ) );
3774 }
3775
3776 // add some hp and mp
3777 if( monster->isNpc() ) {
3778 // npc-s are initialized to be similar to pc-s
3779 startingHp = monster->getHp();
3780 startingMp = monster->getMp();
3781 setHp();
3782 setMp();
3783 } else {
3784 // monsters initialization
3785 MonsterToughness mt = monsterToughness[ session->getPreferences()->getMonsterToughness() ];
3786
3787 startingHp = monster->getHp();
3788 float n = static_cast<float>( startingHp * level );
3789 hp = static_cast<int>( n * Util::roll( mt.minHpMpBase, mt.maxHpMpBase ) );
3790
3791 startingMp = monster->getMp();
3792 n = static_cast<float>( startingMp * level );
3793 mp = static_cast<int>( n * Util::roll( mt.minHpMpBase, mt.maxHpMpBase ) );
3794 }
3795 }
3796
summonCreature(bool friendly)3797 Creature *Creature::summonCreature( bool friendly ) {
3798 Creature *creature = NULL;
3799 if( (int)(getSummoned()->size()) >= getMaxSummonedCreatures() ) {
3800 session->getGameAdapter()->writeLogMessage( _( "You may not summon any more creatures." ), Constants::MSGTYPE_STATS );
3801 } else {
3802 Monster *monster = Monster::getRandomMonster( Util::pickOne( (int)( getLevel() * 0.5f ), (int)( getLevel() * 0.75f ) ) );
3803 if( monster ) {
3804 cerr << "*** summoning monster: " << monster->getType() << endl;
3805 creature = doSummon( monster, toint( getX() ), toint( getY() ), 0, 0, friendly );
3806 } else {
3807 cerr << "*** no monster summoned." << endl;
3808 }
3809 }
3810 return creature;
3811 }
3812
doSummon(Monster * monster,int cx,int cy,int ex,int ey,bool friendly)3813 Creature *Creature::doSummon( Monster *monster, int cx, int cy, int ex, int ey, bool friendly ) {
3814 GLShape *shape = session->getShapePalette()->
3815 getCreatureShape( monster->getModelName(),
3816 monster->getSkinName(),
3817 monster->getScale(),
3818 monster );
3819 Creature *creature = session->newCreature( monster, shape );
3820
3821 // try to place on map
3822 bool placed;
3823 if( ex <= 0 ) {
3824 cerr << "*** summon via findPlace: pos=" << cx << "," << cy << endl;
3825 int x, y;
3826 placed = creature->findPlace( cx, cy, &x, &y );
3827 if( placed ) {
3828 cerr << "\t*** final pos=" << x << "," << y << endl;
3829 }
3830 } else {
3831 cerr << "*** summon via findPlaceBounded: region: " << cx << "," << cy << "-" << ex << "," << ey << endl;
3832 placed = creature->findPlaceBounded( cx, cy, ex, ey );
3833 }
3834 if( !placed ) {
3835 cerr << "*** warning: unable to place summoned creature." << endl;
3836 return NULL;
3837 }
3838
3839 addSummoned( creature );
3840 creature->setSummoner( this );
3841
3842 // monster summons friendly or pc summons friendly: possess it
3843 if( isMonster() != friendly ) {
3844 // maybe make a new state mod for 'summonned'? Maybe not... all the battle code to update... argh
3845 creature->setStateMod( StateMod::possessed, true );
3846 }
3847
3848 // register with squirrel
3849 session->getSquirrel()->registerCreature( creature );
3850 for ( int i = 0; i < creature->getBackpackContentsCount(); i++ ) {
3851 session->getSquirrel()->registerItem( creature->getBackpackItem( i ) );
3852 }
3853
3854 creature->cancelTarget();
3855 creature->startEffect( Constants::EFFECT_TELEPORT, ( Constants::DAMAGE_DURATION * 4 ) );
3856
3857 char message[200];
3858 snprintf( message, 200, _( "%s calls for help: a %s appears!" ), getName(), monster->getType() );
3859 session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERMAGIC );
3860
3861 return creature;
3862 }
3863
dismissSummonedCreatures()3864 void Creature::dismissSummonedCreatures() {
3865 while( summoned.size() > 0 ) {
3866 Creature *c = summoned[0];
3867 c->dismissSummonedCreature();
3868 }
3869 }
3870
dismissSummonedCreature()3871 void Creature::dismissSummonedCreature() {
3872 if( isSummoned() ) {
3873 // remove from summoner's list
3874 for( vector<Creature*>::iterator e = getSummoner()->getSummoned()->begin(); e != getSummoner()->getSummoned()->end(); ++e ) {
3875 Creature *c = *e;
3876 if( c == this ) {
3877 getSummoner()->getSummoned()->erase( e );
3878 break;
3879 }
3880 }
3881 setSummoner( NULL );
3882
3883 // remove from the map; the object will be cleaned up at the end of the mission
3884 session->getMap()->removeCreature( toint( getX() ), toint( getY() ), toint( getZ() ) );
3885 setStateMod( StateMod::dead, true );
3886 // cancel target, otherwise segfaults on resurrection
3887 cancelTarget();
3888
3889 session->getMap()->startEffect( toint( getX() ), toint( getY() ), toint( getZ() ),
3890 Constants::EFFECT_DUST, ( Constants::DAMAGE_DURATION * 4 ) );
3891
3892 char message[200];
3893 snprintf( message, 200, _( "%s turns to vapors and disappears!" ), getName() );
3894 session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERMAGIC );
3895 }
3896 }
3897
3898 /// Returns the alignment of the creature as a float between 0.0 (fully chaotic) and 1.0 (totally lawful).
3899
getAlignment()3900 float Creature::getAlignment() {
3901 int totalSpellCount, chaoticSpellCount, lawfulSpellCount;
3902 int totalSkillPointCount, chaoticSkillPointCount, lawfulSkillPointCount;
3903 float chaoticness, lawfulness;
3904 Spell *spell;
3905 Skill *skill;
3906
3907 totalSpellCount = getSpellCount();
3908
3909 if ( totalSpellCount == 0 ) {
3910 // If the creature has no spells, determine alignment by the distribution of certain stats.
3911 totalSkillPointCount = chaoticSkillPointCount = lawfulSkillPointCount = 0;
3912 string skillName;
3913
3914 for ( int i = 0; i < Skill::SKILL_COUNT; i++ ) {
3915 skill = Skill::skills[ i ];
3916 skillName = skill->getName();
3917
3918 // Evaluate only basic stats and resistances.
3919 if ( skill->getGroup()->isStat() || ( skillName.compare( 0, 7, "RESIST_" ) == 0 ) ) {
3920 totalSkillPointCount += getSkill( i, false );
3921
3922 if ( skill->getAlignment() == ALIGNMENT_CHAOTIC ) {
3923 chaoticSkillPointCount += getSkill( i, false );
3924 } else if ( skill->getAlignment() == ALIGNMENT_LAWFUL ) {
3925 lawfulSkillPointCount += getSkill( i, false );
3926 }
3927
3928 }
3929
3930 }
3931
3932 chaoticness = (float)chaoticSkillPointCount / (float)totalSkillPointCount;
3933 lawfulness = (float)lawfulSkillPointCount / (float)totalSkillPointCount;
3934
3935 } else {
3936 // If the creature has spells, determine alignment by their selection.
3937 chaoticSpellCount = lawfulSpellCount = 0;
3938
3939 for ( int i = 0; i < totalSpellCount; i++ ) {
3940 spell = getSpell( i );
3941
3942 if ( spell->getSchool()->getBaseAlignment() == ALIGNMENT_CHAOTIC ) {
3943 chaoticSpellCount++;
3944 } else if ( spell->getSchool()->getBaseAlignment() == ALIGNMENT_LAWFUL ) {
3945 lawfulSpellCount++;
3946 }
3947
3948 }
3949
3950 // We don't need a neutralness value because it is already implied by the math done here.
3951 chaoticness = (float)chaoticSpellCount / (float)totalSpellCount;
3952 lawfulness = (float)lawfulSpellCount / (float)totalSpellCount;
3953 }
3954
3955 return ( ( -chaoticness + lawfulness ) + 1 ) / 2;
3956 }
3957