3 * Iter Vehemens ad Necem (IVAN)
4 * Copyright (C) Timo Kiviluoto
5 * Released under the GNU General
8 * See LICENSING which should be included
9 * along with this file for more details
12 /* Compiled through charset.cpp */
15 #include <sys/types.h>
43 /* These statedata structs contain functions and values used for handling
44 * states. Remember to update them. All normal states must have
45 * PrintBeginMessage and PrintEndMessage functions and a Description string.
46 * BeginHandler, EndHandler, Handler (called each tick) and IsAllowed are
47 * optional, enter zero if the state doesn't need one. If the SECRET flag
48 * is set, Description is not shown in the panel without magical means.
49 * You can also set some source (SRC_*) and duration (DUR_*) flags, which
50 * control whether the state can be randomly activated in certain situations.
51 * These flags can be found in ivandef.h. RANDOMIZABLE sets all source
52 * & duration flags at once. */
54 const char *Description
;
56 void (character::*PrintBeginMessage
) () const;
57 void (character::*PrintEndMessage
) () const;
58 void (character::*BeginHandler
) ();
59 void (character::*EndHandler
) ();
60 void (character::*Handler
) ();
61 truth (character::*IsAllowed
) () const;
62 void (character::*SituationDangerModifier
) (double &) const;
66 //WARNING! state count and state order MUST be synced with "define.dat"
67 const statedata StateData
[STATES
] =
75 &character::EndPolymorph
,
81 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
82 &character::PrintBeginHasteMessage
,
83 &character::PrintEndHasteMessage
,
91 RANDOMIZABLE
&~SRC_GOOD
,
92 &character::PrintBeginSlowMessage
,
93 &character::PrintEndSlowMessage
,
101 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
|SRC_GOOD
),
102 &character::PrintBeginPolymorphControlMessage
,
103 &character::PrintEndPolymorphControlMessage
,
112 &character::PrintBeginLifeSaveMessage
,
113 &character::PrintEndLifeSaveMessage
,
121 SECRET
|SRC_FOUNTAIN
|SRC_CONFUSE_READ
|DUR_FLAGS
,
122 &character::PrintBeginLycanthropyMessage
,
123 &character::PrintEndLycanthropyMessage
,
126 &character::LycanthropyHandler
,
128 &character::LycanthropySituationDangerModifier
131 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
132 &character::PrintBeginInvisibilityMessage
,
133 &character::PrintEndInvisibilityMessage
,
134 &character::BeginInvisibility
,
135 &character::EndInvisibility
,
141 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
142 &character::PrintBeginInfraVisionMessage
,
143 &character::PrintEndInfraVisionMessage
,
144 &character::BeginInfraVision
,
145 &character::EndInfraVision
,
151 RANDOMIZABLE
&~SRC_EVIL
,
152 &character::PrintBeginESPMessage
,
153 &character::PrintEndESPMessage
,
154 &character::BeginESP
,
162 &character::PrintBeginPoisonedMessage
,
163 &character::PrintEndPoisonedMessage
,
166 &character::PoisonedHandler
,
167 &character::CanBePoisoned
,
168 &character::PoisonedSituationDangerModifier
171 SECRET
|(RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_GOOD
)),
172 &character::PrintBeginTeleportMessage
,
173 &character::PrintEndTeleportMessage
,
176 &character::TeleportHandler
,
181 SECRET
|(RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_GOOD
)),
182 &character::PrintBeginPolymorphMessage
,
183 &character::PrintEndPolymorphMessage
,
186 &character::PolymorphHandler
,
188 &character::PolymorphingSituationDangerModifier
191 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
192 &character::PrintBeginTeleportControlMessage
,
193 &character::PrintEndTeleportControlMessage
,
202 &character::PrintBeginPanicMessage
,
203 &character::PrintEndPanicMessage
,
204 &character::BeginPanic
,
205 &character::EndPanic
,
207 &character::CanPanic
,
208 &character::PanicSituationDangerModifier
211 SECRET
|(RANDOMIZABLE
&~(DUR_PERMANENT
|SRC_GOOD
)),
212 &character::PrintBeginConfuseMessage
,
213 &character::PrintEndConfuseMessage
,
217 &character::CanBeConfused
,
218 &character::ConfusedSituationDangerModifier
221 SECRET
|(RANDOMIZABLE
&~DUR_TEMPORARY
),
222 &character::PrintBeginParasitizedMessage
,
223 &character::PrintEndParasitizedMessage
,
226 &character::ParasitizedHandler
,
227 &character::CanBeParasitized
,
228 &character::ParasitizedSituationDangerModifier
232 &character::PrintBeginSearchingMessage
,
233 &character::PrintEndSearchingMessage
,
236 &character::SearchingHandler
,
241 SECRET
|(RANDOMIZABLE
&~(SRC_GOOD
|SRC_EVIL
)),
242 &character::PrintBeginGasImmunityMessage
,
243 &character::PrintEndGasImmunityMessage
,
251 RANDOMIZABLE
&~SRC_EVIL
,
252 &character::PrintBeginLevitationMessage
,
253 &character::PrintEndLevitationMessage
,
255 &character::EndLevitation
,
261 SECRET
|(RANDOMIZABLE
&~DUR_TEMPORARY
),
262 &character::PrintBeginLeprosyMessage
,
263 &character::PrintEndLeprosyMessage
,
264 &character::BeginLeprosy
,
265 &character::EndLeprosy
,
266 &character::LeprosyHandler
,
268 &character::LeprosySituationDangerModifier
271 SRC_FOUNTAIN
|SRC_CONFUSE_READ
|DUR_FLAGS
,
272 &character::PrintBeginHiccupsMessage
,
273 &character::PrintEndHiccupsMessage
,
276 &character::HiccupsHandler
,
278 &character::HiccupsSituationDangerModifier
281 DUR_FLAGS
, //perhaps no fountain, no secret and no confuse read either: SECRET|SRC_FOUNTAIN|SRC_CONFUSE_READ|
282 &character::PrintBeginVampirismMessage
,
283 &character::PrintEndVampirismMessage
,
286 &character::VampirismHandler
,
288 &character::VampirismSituationDangerModifier
292 &character::PrintBeginSwimmingMessage
,
293 &character::PrintEndSwimmingMessage
,
294 &character::BeginSwimming
, &character::EndSwimming
,
300 SECRET
|(RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
)),
301 &character::PrintBeginDetectMessage
,
302 &character::PrintEndDetectMessage
,
305 &character::DetectHandler
,
311 &character::PrintBeginEtherealityMessage
,
312 &character::PrintEndEtherealityMessage
,
313 &character::BeginEthereality
, &character::EndEthereality
,
319 RANDOMIZABLE
&~SRC_EVIL
,
320 &character::PrintBeginFearlessMessage
,
321 &character::PrintEndFearlessMessage
,
322 &character::BeginFearless
,
323 &character::EndFearless
,
330 &character::PrintBeginPolymorphLockMessage
,
331 &character::PrintEndPolymorphLockMessage
,
334 &character::PolymorphLockHandler
,
339 SECRET
|(RANDOMIZABLE
&~SRC_EVIL
),
340 &character::PrintBeginRegenerationMessage
,
341 &character::PrintEndRegenerationMessage
,
349 SECRET
|(RANDOMIZABLE
&~SRC_EVIL
),
350 &character::PrintBeginDiseaseImmunityMessage
,
351 &character::PrintEndDiseaseImmunityMessage
,
360 &character::PrintBeginTeleportLockMessage
,
361 &character::PrintEndTeleportLockMessage
,
364 &character::TeleportLockHandler
,
371 characterprototype::characterprototype (const characterprototype
*Base
, characterspawner Spawner
,
372 charactercloner Cloner
, cchar
*ClassID
)
378 Index
= protocontainer
<character
>::Add(this);
382 void character::CreateInitialEquipment (int SpecialFlags
) { AddToInventory(DataBase
->Inventory
, SpecialFlags
); }
383 void character::EditAP (sLong What
) { AP
= Limit
<sLong
>(AP
+What
, -12000, 1200); }
384 int character::GetRandomStepperBodyPart () const { return TORSO_INDEX
; }
385 void character::GainIntrinsic (sLong What
) { BeginTemporaryState(What
, PERMANENT
); }
386 truth
character::IsUsingArms () const { return GetAttackStyle() & USE_ARMS
; }
387 truth
character::IsUsingLegs () const { return GetAttackStyle() & USE_LEGS
; }
388 truth
character::IsUsingHead () const { return GetAttackStyle() & USE_HEAD
; }
389 void character::CalculateAllowedWeaponSkillCategories () { AllowedWeaponSkillCategories
= MARTIAL_SKILL_CATEGORIES
; }
390 festring
character::GetBeVerb () const { return IsPlayer() ? CONST_S("are") : CONST_S("is"); }
391 void character::SetEndurance (int What
) { BaseExperience
[ENDURANCE
] = What
* EXP_MULTIPLIER
; }
392 void character::SetPerception (int What
) { BaseExperience
[PERCEPTION
] = What
* EXP_MULTIPLIER
; }
393 void character::SetIntelligence (int What
) { BaseExperience
[INTELLIGENCE
] = What
* EXP_MULTIPLIER
; }
394 void character::SetWisdom (int What
) { BaseExperience
[WISDOM
] = What
* EXP_MULTIPLIER
; }
395 void character::SetWillPower (int What
) { BaseExperience
[WILL_POWER
] = What
* EXP_MULTIPLIER
; }
396 void character::SetCharisma (int What
) { BaseExperience
[CHARISMA
] = What
* EXP_MULTIPLIER
; }
397 void character::SetMana (int What
) { BaseExperience
[MANA
] = What
* EXP_MULTIPLIER
; }
398 truth
character::IsOnGround () const { return MotherEntity
&& MotherEntity
->IsOnGround(); }
399 truth
character::LeftOversAreUnique () const { return GetArticleMode() || AssignedName
.GetSize(); }
400 truth
character::HomeDataIsValid () const { return (HomeData
&& HomeData
->Level
== GetLSquareUnder()->GetLevelIndex() && HomeData
->Dungeon
== GetLSquareUnder()->GetDungeonIndex()); }
401 void character::SetHomePos (v2 Pos
) { HomeData
->Pos
= Pos
; }
402 cchar
*character::FirstPersonUnarmedHitVerb () const { return "hit"; }
403 cchar
*character::FirstPersonCriticalUnarmedHitVerb () const { return "critically hit"; }
404 cchar
*character::ThirdPersonUnarmedHitVerb () const { return "hits"; }
405 cchar
*character::ThirdPersonCriticalUnarmedHitVerb () const { return "critically hits"; }
406 cchar
*character::FirstPersonKickVerb () const { return "kick"; }
407 cchar
*character::FirstPersonCriticalKickVerb () const { return "critically kick"; }
408 cchar
*character::ThirdPersonKickVerb () const { return "kicks"; }
409 cchar
*character::ThirdPersonCriticalKickVerb () const { return "critically kicks"; }
410 cchar
*character::FirstPersonBiteVerb () const { return "bite"; }
411 cchar
*character::FirstPersonCriticalBiteVerb () const { return "critically bite"; }
412 cchar
*character::ThirdPersonBiteVerb () const { return "bites"; }
413 cchar
*character::ThirdPersonCriticalBiteVerb () const { return "critically bites"; }
414 cchar
*character::UnarmedHitNoun () const { return "attack"; }
415 cchar
*character::KickNoun () const { return "kick"; }
416 cchar
*character::BiteNoun () const { return "attack"; }
417 cchar
*character::GetEquipmentName (int) const { return ""; }
418 const std::list
<feuLong
> &character::GetOriginalBodyPartID (int I
) const { return OriginalBodyPartID
[I
]; }
419 square
*character::GetNeighbourSquare (int I
) const { return GetSquareUnder()->GetNeighbourSquare(I
); }
420 lsquare
*character::GetNeighbourLSquare (int I
) const { return static_cast<lsquare
*>(GetSquareUnder())->GetNeighbourLSquare(I
); }
421 wsquare
*character::GetNeighbourWSquare (int I
) const { return static_cast<wsquare
*>(GetSquareUnder())->GetNeighbourWSquare(I
); }
422 god
*character::GetMasterGod () const { return game::GetGod(GetConfig()); }
423 col16
character::GetBodyPartColorA (int, truth
) const { return GetSkinColor(); }
424 col16
character::GetBodyPartColorB (int, truth
) const { return GetTorsoMainColor(); }
425 col16
character::GetBodyPartColorC (int, truth
) const { return GetBeltColor(); } // sorry...
426 col16
character::GetBodyPartColorD (int, truth
) const { return GetTorsoSpecialColor(); }
427 int character::GetRandomApplyBodyPart () const { return TORSO_INDEX
; }
428 truth
character::IsPet () const { return GetTeam()->GetID() == PLAYER_TEAM
; }
429 character
* character::GetLeader () const { return GetTeam()->GetLeader(); }
430 festring
character::GetZombieDescription () const { return " of "+GetName(INDEFINITE
); }
431 truth
character::BodyPartCanBeSevered (int I
) const { return I
; }
432 truth
character::HasBeenSeen () const { return DataBase
->Flags
& HAS_BEEN_SEEN
; }
433 truth
character::IsTemporary () const { return GetTorso()->GetLifeExpectancy(); }
434 cchar
*character::GetNormalDeathMessage () const { return "killed @k"; }
437 truth
character::IsHomeLevel (level
*lvl
) const {
438 if (!lvl
) return false;
440 const fearray
<festring
> &hlist
= DataBase
->HomeLevel
;
441 if (hlist
.Size
== 0) return false;
443 for (uInt f
= 0; f
< hlist
.Size
; ++f
) { if (hlist
[f
] == "*") return true; }
444 // taken from unique characters code
445 cfestring
*tag
= lvl
->GetLevelScript()->GetTag();
446 if (!tag
) return false; // not a possible home level
447 // check for home level
448 for (uInt f
= 0; f
< hlist
.Size
; ++f
) { if (hlist
[f
] == *tag
) return true; }
453 truth
character::MustBeRemovedFromBone () const {
454 if (IsUnique() && !CanBeGenerated()) return true;
456 const fearray
<festring
> &hlist
= DataBase
->HomeLevel
;
457 if (hlist
.Size
== 0) return false;
459 for (uInt f
= 0; f
< hlist
.Size
; ++f
) { if (hlist
[f
] == "*") return true; }
460 // taken from unique characters code
461 if (!IsEnabled() || GetTeam()->GetID() != DataBase
->NaturalTeam
) return true;
462 return IsHomeLevel(GetLevel());
466 int character::GetMoveType () const {// return (!StateIsActivated(LEVITATION) ? DataBase->MoveType : DataBase->MoveType | FLY); }
468 (!StateIsActivated(LEVITATION
) ? DataBase
->MoveType
: DataBase
->MoveType
|FLY
)|
469 (!StateIsActivated(ETHEREAL_MOVING
) ? DataBase
->MoveType
: DataBase
->MoveType
|ETHEREAL
)|
470 (!StateIsActivated(SWIMMING
) ? DataBase
->MoveType
: DataBase
->MoveType
|WALK
|SWIM
)|
476 int characterdatabase::*ExpPtr
[ATTRIBUTES
] = {
477 &characterdatabase::DefaultEndurance
,
478 &characterdatabase::DefaultPerception
,
479 &characterdatabase::DefaultIntelligence
,
480 &characterdatabase::DefaultWisdom
,
481 &characterdatabase::DefaultWillPower
,
482 &characterdatabase::DefaultCharisma
,
483 &characterdatabase::DefaultMana
,
484 &characterdatabase::DefaultArmStrength
,
485 &characterdatabase::DefaultLegStrength
,
486 &characterdatabase::DefaultDexterity
,
487 &characterdatabase::DefaultAgility
491 contentscript
<item
> characterdatabase::*EquipmentDataPtr
[EQUIPMENT_DATAS
] = {
492 &characterdatabase::Helmet
,
493 &characterdatabase::Amulet
,
494 &characterdatabase::Cloak
,
495 &characterdatabase::BodyArmor
,
496 &characterdatabase::Belt
,
497 &characterdatabase::RightWielded
,
498 &characterdatabase::LeftWielded
,
499 &characterdatabase::RightRing
,
500 &characterdatabase::LeftRing
,
501 &characterdatabase::RightGauntlet
,
502 &characterdatabase::LeftGauntlet
,
503 &characterdatabase::RightBoot
,
504 &characterdatabase::LeftBoot
508 character::character (ccharacter
&Char
) :
509 entity(Char
), id(Char
), NP(Char
.NP
), AP(Char
.AP
),
510 TemporaryState(Char
.TemporaryState
&~POLYMORPHED
),
511 Team(Char
.Team
), GoingTo(ERROR_V2
), Money(0),
512 AssignedName(Char
.AssignedName
), Action(0),
513 DataBase(Char
.DataBase
), MotherEntity(0),
514 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
515 AllowedWeaponSkillCategories(Char
.AllowedWeaponSkillCategories
),
516 BodyParts(Char
.BodyParts
),
517 RegenerationCounter(Char
.RegenerationCounter
),
518 SquaresUnder(Char
.SquaresUnder
), LastAcidMsgMin(0),
519 Stamina(Char
.Stamina
), MaxStamina(Char
.MaxStamina
),
520 BlocksSinceLastTurn(0), GenerationDanger(Char
.GenerationDanger
),
521 CommandFlags(Char
.CommandFlags
), WarnFlags(0),
522 ScienceTalks(Char
.ScienceTalks
), TrapData(0), CounterToMindWormHatch(0)
526 Flags
|= C_INITIALIZING
|C_IN_NO_MSG_MODE
;
527 Stack
= new stack(0, this, HIDDEN
);
528 for (c
= 0; c
< STATES
; ++c
) TemporaryStateCounter
[c
] = Char
.TemporaryStateCounter
[c
];
529 if (Team
) Team
->Add(this);
530 for (c
= 0; c
< BASE_ATTRIBUTES
; ++c
) BaseExperience
[c
] = Char
.BaseExperience
[c
];
531 BodyPartSlot
= new bodypartslot
[BodyParts
];
532 OriginalBodyPartID
= new std::list
<feuLong
>[BodyParts
];
533 CWeaponSkill
= new cweaponskill
[AllowedWeaponSkillCategories
];
534 SquareUnder
= new square
*[SquaresUnder
];
535 if (SquaresUnder
== 1) *SquareUnder
= 0; else memset(SquareUnder
, 0, SquaresUnder
*sizeof(square
*));
536 for (c
= 0; c
< BodyParts
; ++c
) {
537 BodyPartSlot
[c
].SetMaster(this);
538 bodypart
*CharBodyPart
= Char
.GetBodyPart(c
);
539 OriginalBodyPartID
[c
] = Char
.OriginalBodyPartID
[c
];
541 bodypart
*BodyPart
= static_cast<bodypart
*>(CharBodyPart
->Duplicate());
542 SetBodyPart(c
, BodyPart
);
543 BodyPart
->CalculateEmitation();
546 for (c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) CWeaponSkill
[c
] = Char
.CWeaponSkill
[c
];
547 HomeData
= Char
.HomeData
? new homedata(*Char
.HomeData
) : 0;
548 ID
= game::CreateNewCharacterID(this);
552 character::character () :
553 entity(HAS_BE
), NP(50000), AP(0), TemporaryState(0), Team(0),
554 GoingTo(ERROR_V2
), Money(0), Action(0), MotherEntity(0),
555 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
556 RegenerationCounter(0), HomeData(0), LastAcidMsgMin(0),
557 BlocksSinceLastTurn(0), GenerationDanger(DEFAULT_GENERATION_DANGER
),
558 WarnFlags(0), ScienceTalks(0), TrapData(0), CounterToMindWormHatch(0)
560 Stack
= new stack(0, this, HIDDEN
);
564 character::~character () {
565 if (Action
) delete Action
;
566 if (Team
) Team
->Remove(this);
568 for (int c
= 0; c
< BodyParts
; ++c
) delete GetBodyPart(c
);
569 delete [] BodyPartSlot
;
570 delete [] OriginalBodyPartID
;
571 delete PolymorphBackup
;
572 delete [] SquareUnder
;
573 delete [] CWeaponSkill
;
575 deleteList(TrapData
);
576 game::RemoveCharacterID(ID
);
580 void character::Hunger () {
581 auto bst
= GetBurdenState();
582 if (bst
== OVER_LOADED
|| bst
== STRESSED
) {
584 EditExperience(LEG_STRENGTH
, 150, 1 << 2);
585 EditExperience(AGILITY
, -50, 1 << 2);
586 } else if (bst
== BURDENED
) {
588 EditExperience(LEG_STRENGTH
, 75, 1 << 1);
589 EditExperience(AGILITY
, -25, 1 << 1);
590 } else if (bst
== UNBURDENED
) {
594 auto hst
= GetHungerState();
595 if (hst
== STARVING
) {
596 EditExperience(ARM_STRENGTH
, -75, 1 << 3);
597 EditExperience(LEG_STRENGTH
, -75, 1 << 3);
598 } else if (hst
== VERY_HUNGRY
) {
599 EditExperience(ARM_STRENGTH
, -50, 1 << 2);
600 EditExperience(LEG_STRENGTH
, -50, 1 << 2);
601 } else if (hst
== HUNGRY
) {
602 EditExperience(ARM_STRENGTH
, -25, 1 << 1);
603 EditExperience(LEG_STRENGTH
, -25, 1 << 1);
604 } else if (hst
== SATIATED
) {
605 EditExperience(AGILITY
, -25, 1 << 1);
606 } else if (hst
== BLOATED
) {
607 EditExperience(AGILITY
, -50, 1 << 2);
608 } else if (hst
== OVER_FED
) {
609 EditExperience(AGILITY
, -75, 1 << 3);
612 CheckStarvationDeath(CONST_S("starved to death"));
616 int character::TakeHit (character
*Enemy
, item
*Weapon
, bodypart
*EnemyBodyPart
, v2 HitPos
, double Damage
,
617 double ToHitValue
, int Success
, int Type
, int GivenDir
, truth Critical
, truth ForceHit
)
620 game::ClearEventData();
621 game::mActor
= Enemy
;
622 game::mResult
= DID_NO_DAMAGE
;
623 if (game::RunOnCharEvent(this, CONST_S("take_hit"))) { game::ClearEventData(); return game::mResult
; }
624 game::ClearEventData();
625 int Dir
= Type
== BITE_ATTACK
? YOURSELF
: GivenDir
;
626 double DodgeValue
= GetDodgeValue();
627 if (!Enemy
->IsPlayer() && GetAttackWisdomLimit() != NO_LIMIT
) Enemy
->EditExperience(WISDOM
, 75, 1 << 13);
628 if (!Enemy
->CanBeSeenBy(this)) ToHitValue
*= 2;
629 if (!CanBeSeenBy(Enemy
)) DodgeValue
*= 2;
630 if (Enemy
->StateIsActivated(CONFUSED
)) ToHitValue
*= 0.75;
632 switch (Enemy
->GetTirednessState()) {
638 switch (GetTirednessState()) {
646 if (!IsRetreating()) SetGoingTo(Enemy
->GetPos());
647 else SetGoingTo(GetPos()-((Enemy
->GetPos()-GetPos())<<4));
648 if (!Enemy
->IsRetreating()) Enemy
->SetGoingTo(GetPos());
649 else Enemy
->SetGoingTo(Enemy
->GetPos()-((GetPos()-Enemy
->GetPos())<<4));
652 /* Effectively, the average chance to hit is 100% / (DV/THV + 1). */
653 if (RAND() % int(100+ToHitValue
/DodgeValue
*(100+Success
)) < 100 && !Critical
&& !ForceHit
) {
654 Enemy
->AddMissMessage(this);
655 EditExperience(AGILITY
, 150, 1 << 7);
656 EditExperience(PERCEPTION
, 75, 1 << 7);
657 if (Enemy
->CanBeSeenByPlayer())
658 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
660 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
664 int TrueDamage
= int(Damage
*(100+Success
)/100)+(RAND()%3 ? 1 : 0);
666 TrueDamage
+= TrueDamage
>> 1;
670 int BodyPart
= ChooseBodyPartToReceiveHit(ToHitValue
, DodgeValue
);
672 if (Type
== UNARMED_ATTACK
) Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalUnarmedHitVerb(), Enemy
->ThirdPersonCriticalUnarmedHitVerb(), BodyPart
);
673 else if (Type
== WEAPON_ATTACK
) Enemy
->AddWeaponHitMessage(this, Weapon
, BodyPart
, true);
674 else if (Type
== KICK_ATTACK
) Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalKickVerb(), Enemy
->ThirdPersonCriticalKickVerb(), BodyPart
);
675 else if (Type
== BITE_ATTACK
) Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalBiteVerb(), Enemy
->ThirdPersonCriticalBiteVerb(), BodyPart
);
677 if (Type
== UNARMED_ATTACK
) Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonUnarmedHitVerb(), Enemy
->ThirdPersonUnarmedHitVerb(), BodyPart
);
678 else if (Type
== WEAPON_ATTACK
) Enemy
->AddWeaponHitMessage(this, Weapon
, BodyPart
, false);
679 else if (Type
== KICK_ATTACK
) Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonKickVerb(), Enemy
->ThirdPersonKickVerb(), BodyPart
);
680 else if (Type
== BITE_ATTACK
) Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonBiteVerb(), Enemy
->ThirdPersonBiteVerb(), BodyPart
);
683 if (!Critical
&& TrueDamage
&& Enemy
->AttackIsBlockable(Type
)) {
684 TrueDamage
= CheckForBlock(Enemy
, Weapon
, ToHitValue
, TrueDamage
, Success
, Type
);
685 if (!TrueDamage
|| (Weapon
&& !Weapon
->Exists())) {
686 if (Enemy
->CanBeSeenByPlayer())
687 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
689 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
694 int WeaponSkillHits
= CalculateWeaponSkillHits(Enemy
);
695 int DoneDamage
= ReceiveBodyPartDamage(Enemy
, TrueDamage
, PHYSICAL_DAMAGE
, BodyPart
, Dir
, false, Critical
, true, Type
== BITE_ATTACK
&& Enemy
->BiteCapturesBodyPart());
696 truth Succeeded
= (GetBodyPart(BodyPart
) && HitEffect(Enemy
, Weapon
, HitPos
, Type
, BodyPart
, Dir
, !DoneDamage
, Critical
, DoneDamage
)) || DoneDamage
;
697 if (Succeeded
) Enemy
->WeaponSkillHit(Weapon
, Type
, WeaponSkillHits
);
700 if (Weapon
->Exists() && DoneDamage
< TrueDamage
) Weapon
->ReceiveDamage(Enemy
, TrueDamage
-DoneDamage
, PHYSICAL_DAMAGE
);
701 if (Weapon
->Exists() && DoneDamage
&& SpillsBlood() && GetBodyPart(BodyPart
) &&
702 (GetBodyPart(BodyPart
)->IsAlive() || GetBodyPart(BodyPart
)->GetMainMaterial()->IsLiquid()))
703 Weapon
->SpillFluid(0, CreateBlood(15+RAND()%15));
706 if (Enemy
->AttackIsBlockable(Type
)) SpecialBodyDefenceEffect(Enemy
, EnemyBodyPart
, Type
);
709 if (Enemy
->CanBeSeenByPlayer())
710 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
712 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
714 return DID_NO_DAMAGE
;
717 if (CheckDeath(GetNormalDeathMessage(), Enemy
, Enemy
->IsPlayer() ? FORCE_MSG
: 0)) return HAS_DIED
;
719 if (Enemy
->CanBeSeenByPlayer())
720 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
722 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
728 struct svpriorityelement
{
729 svpriorityelement (int BodyPart
, int StrengthValue
) : BodyPart(BodyPart
), StrengthValue(StrengthValue
) {}
730 bool operator < (const svpriorityelement
&AnotherPair
) const { return StrengthValue
> AnotherPair
.StrengthValue
; }
736 int character::ChooseBodyPartToReceiveHit (double ToHitValue
, double DodgeValue
) {
737 if (BodyParts
== 1) return 0;
738 std::priority_queue
<svpriorityelement
> SVQueue
;
739 for (int c
= 0; c
< BodyParts
; ++c
) {
740 bodypart
*BodyPart
= GetBodyPart(c
);
741 if (BodyPart
&& (BodyPart
->GetHP() != 1 || BodyPart
->CanBeSevered(PHYSICAL_DAMAGE
)))
742 SVQueue
.push(svpriorityelement(c
, ModifyBodyPartHitPreference(c
, BodyPart
->GetStrengthValue()+BodyPart
->GetHP())));
744 while (SVQueue
.size()) {
745 svpriorityelement E
= SVQueue
.top();
746 int ToHitPercentage
= int(GLOBAL_WEAK_BODYPART_HIT_MODIFIER
*ToHitValue
*GetBodyPart(E
.BodyPart
)->GetBodyPartVolume()/(DodgeValue
*GetBodyVolume()));
747 ToHitPercentage
= ModifyBodyPartToHitChance(E
.BodyPart
, ToHitPercentage
);
748 if (ToHitPercentage
< 1) ToHitPercentage
= 1;
749 else if (ToHitPercentage
> 95) ToHitPercentage
= 95;
750 if (ToHitPercentage
> RAND()%100) return E
.BodyPart
;
757 void character::Be () {
758 if (game::ForceJumpToPlayerBe()) {
759 if (!IsPlayer()) return;
760 game::SetForceJumpToPlayerBe(false);
762 truth ForceBe
= HP
!= MaxHP
|| AllowSpoil();
763 for (int c
= 0; c
< BodyParts
; ++c
) {
764 bodypart
*BodyPart
= GetBodyPart(c
);
765 if (BodyPart
&& (ForceBe
|| BodyPart
->NeedsBe())) BodyPart
->Be();
768 if (!IsEnabled()) return;
769 if (GetTeam() == PLAYER
->GetTeam()) {
770 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) {
771 if (CWeaponSkill
[c
].Tick() && IsPlayer()) CWeaponSkill
[c
].AddLevelDownMessage(c
);
776 if (GetHungerState() == STARVING
&& !(RAND()%50)) LoseConsciousness(250+RAND_N(250), true);
777 if (!Action
|| Action
->AllowFoodConsumption()) Hunger();
779 if (Stamina
!= MaxStamina
) RegenerateStamina();
780 if (HP
!= MaxHP
|| StateIsActivated(REGENERATION
)) Regenerate();
781 if (Action
&& AP
>= 1000) ActionAutoTermination();
782 if (Action
&& AP
>= 1000) {
784 if (!IsEnabled()) return;
786 EditAP(GetStateAPGain(100));
790 SpecialTurnHandler();
791 BlocksSinceLastTurn
= 0;
793 static int Timer
= 0;
795 if (ivanconfig::GetAutoSaveInterval() && !GetAction() && ++Timer
>= ivanconfig::GetAutoSaveInterval()) {
796 //fprintf(stderr, "autosaving..."); fflush(stderr);
797 game::Save(game::GetAutoSaveFileName());
798 //fprintf(stderr, "done\n"); fflush(stderr);
801 game::CalculateNextDanger();
802 if (!StateIsActivated(POLYMORPHED
)) game::UpdatePlayerAttributeAverage();
803 if (!game::IsInWilderness()) Search(GetAttribute(PERCEPTION
));
807 if (Action
->ShowEnvironment()) {
808 static int Counter
= 0;
809 if (++Counter
== 10) {
810 game::DrawEverything();
814 msgsystem::ThyMessagesAreNowOld();
815 if (Action
->IsVoluntary() && READ_KEY()) Action
->Terminate(false);
818 if (!Action
&& !game::IsInWilderness()) GetAICommand();
824 void character::Move (v2 MoveTo
, truth TeleportMove
, truth Run
) {
825 if (!IsEnabled()) return;
826 /* Test whether the player is stuck to something */
827 if (!TeleportMove
&& !TryToUnStickTraps(MoveTo
-GetPos())) return;
828 if (Run
&& !IsPlayer() && TorsoIsAlive() &&
829 (Stamina
<= 10000/Max(GetAttribute(LEG_STRENGTH
), 1) || (!StateIsActivated(PANIC
) && Stamina
< MaxStamina
>>2))) {
833 if (GetBurdenState() != OVER_LOADED
|| TeleportMove
) {
834 lsquare
*OldSquareUnder
[MAX_SQUARES_UNDER
];
836 if (!game::IsInWilderness()) {
839 area
*ca
= GetSquareUnder()->GetArea();
841 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
842 v2 np
= GetPos()+game::GetMoveVector(f
);
844 if (np
.X
>= 0 && np
.Y
>= 0 && np
.X
< ca
->GetXSize() && np
.Y
< ca
->GetYSize()) {
845 lsquare
*sq
= static_cast<lsquare
*>(ca
->GetSquare(np
.X
, np
.Y
));
852 for (int c
= 0; c
< GetSquaresUnder(); ++c
) OldSquareUnder
[c
] = GetLSquareUnder(c
);
857 /* Multitiled creatures should behave differently, maybe? */
859 int ED
= GetSquareUnder()->GetEntryDifficulty(), Base
= 10000;
861 EditAP(-GetMoveAPRequirement(ED
)>>1);
863 EditExperience(AGILITY
, 125, ED
<<7);
865 auto hst
= GetHungerState();
866 if (hst
== SATIATED
) Base
= 11000;
867 else if (hst
== BLOATED
) Base
= 12500;
868 else if (hst
== OVER_FED
) Base
= 15000;
870 EditStamina(-Base
/Max(GetAttribute(LEG_STRENGTH
), 1), true);
872 int ED
= GetSquareUnder()->GetEntryDifficulty();
874 EditAP(-GetMoveAPRequirement(ED
));
876 EditExperience(AGILITY
, 75, ED
<<7);
879 if (IsPlayer()) ShowNewPosInfo();
880 if (IsPlayer() && !game::IsInWilderness()) GetStackUnder()->SetSteppedOn(true);
881 if (!game::IsInWilderness()) SignalStepFrom(OldSquareUnder
);
884 cchar
*CrawlVerb
= StateIsActivated(LEVITATION
) ? "float" : "crawl";
886 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb
);
893 void character::GetAICommand () {
894 SeekLeader(GetLeader());
895 if (FollowLeader(GetLeader())) return;
896 if (CheckForEnemies(true, true, true)) return;
897 if (CheckForUsefulItemsOnGround()) return;
898 if (CheckForDoors()) return;
899 if (CheckSadism()) return;
900 if (MoveRandomly()) return;
905 truth
character::MoveTowardsTarget (truth Run
) {
910 if (!Route
.empty()) {
913 } else TPos
= GoingTo
;
915 MoveTo
[0] = v2(0, 0);
916 MoveTo
[1] = v2(0, 0);
917 MoveTo
[2] = v2(0, 0);
919 if (TPos
.X
< Pos
.X
) {
920 if (TPos
.Y
< Pos
.Y
) {
921 MoveTo
[0] = v2(-1, -1);
922 MoveTo
[1] = v2(-1, 0);
923 MoveTo
[2] = v2( 0, -1);
924 } else if (TPos
.Y
== Pos
.Y
) {
925 MoveTo
[0] = v2(-1, 0);
926 MoveTo
[1] = v2(-1, -1);
927 MoveTo
[2] = v2(-1, 1);
928 } else if (TPos
.Y
> Pos
.Y
) {
929 MoveTo
[0] = v2(-1, 1);
930 MoveTo
[1] = v2(-1, 0);
931 MoveTo
[2] = v2( 0, 1);
933 } else if (TPos
.X
== Pos
.X
) {
934 if (TPos
.Y
< Pos
.Y
) {
935 MoveTo
[0] = v2( 0, -1);
936 MoveTo
[1] = v2(-1, -1);
937 MoveTo
[2] = v2( 1, -1);
938 } else if (TPos
.Y
== Pos
.Y
) {
941 } else if (TPos
.Y
> Pos
.Y
) {
942 MoveTo
[0] = v2( 0, 1);
943 MoveTo
[1] = v2(-1, 1);
944 MoveTo
[2] = v2( 1, 1);
946 } else if (TPos
.X
> Pos
.X
) {
947 if (TPos
.Y
< Pos
.Y
) {
948 MoveTo
[0] = v2(1, -1);
949 MoveTo
[1] = v2(1, 0);
950 MoveTo
[2] = v2(0, -1);
951 } else if (TPos
.Y
== Pos
.Y
) {
952 MoveTo
[0] = v2(1, 0);
953 MoveTo
[1] = v2(1, -1);
954 MoveTo
[2] = v2(1, 1);
955 } else if (TPos
.Y
> Pos
.Y
) {
956 MoveTo
[0] = v2(1, 1);
957 MoveTo
[1] = v2(1, 0);
958 MoveTo
[2] = v2(0, 1);
962 v2 ModifiedMoveTo
= ApplyStateModification(MoveTo
[0]);
964 if (TryMove(ModifiedMoveTo
, true, Run
)) return true;
966 int L
= (Pos
-TPos
).GetManhattanLength();
968 if (RAND()&1) Swap(MoveTo
[1], MoveTo
[2]);
970 if (Pos
.IsAdjacent(TPos
)) {
975 if ((Pos
+MoveTo
[1]-TPos
).GetManhattanLength() <= L
&& TryMove(ApplyStateModification(MoveTo
[1]), true, Run
)) return true;
976 if ((Pos
+MoveTo
[2]-TPos
).GetManhattanLength() <= L
&& TryMove(ApplyStateModification(MoveTo
[2]), true, Run
)) return true;
977 Illegal
.insert(Pos
+ModifiedMoveTo
);
978 if (CreateRoute()) return true;
983 int character::CalculateNewSquaresUnder (lsquare
**NewSquare
, v2 Pos
) const {
984 if (GetLevel()->IsValidPos(Pos
)) {
985 *NewSquare
= GetNearLSquare(Pos
);
992 truth
character::TryMove (v2 MoveVector
, truth Important
, truth Run
) {
993 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
994 character
*Pet
[MAX_SQUARES_UNDER
];
995 character
*Neutral
[MAX_SQUARES_UNDER
];
996 character
*Hostile
[MAX_SQUARES_UNDER
];
997 v2 PetPos
[MAX_SQUARES_UNDER
];
998 v2 NeutralPos
[MAX_SQUARES_UNDER
];
999 v2 HostilePos
[MAX_SQUARES_UNDER
];
1000 v2 MoveTo
= GetPos()+MoveVector
;
1001 int Direction
= game::GetDirectionForVector(MoveVector
);
1002 if (Direction
== DIR_ERROR
) ABORT("Direction fault.");
1003 if (!game::IsInWilderness()) {
1004 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, MoveTo
);
1009 for (int c
= 0; c
< Squares
; ++c
) {
1010 character
* Char
= MoveToSquare
[c
]->GetCharacter();
1011 if (Char
&& Char
!= this) {
1012 v2 Pos
= MoveToSquare
[c
]->GetPos();
1015 PetPos
[Pets
++] = Pos
;
1016 } else if (Char
->GetRelation(this) != HOSTILE
) {
1017 Neutral
[Neutrals
] = Char
;
1018 NeutralPos
[Neutrals
++] = Pos
;
1020 Hostile
[Hostiles
] = Char
;
1021 HostilePos
[Hostiles
++] = Pos
;
1026 if (Hostiles
== 1) return Hit(Hostile
[0], HostilePos
[0], Direction
);
1028 int Index
= RAND() % Hostiles
;
1029 return Hit(Hostile
[Index
], HostilePos
[Index
], Direction
);
1032 if (Neutrals
== 1) {
1033 if (!IsPlayer() && !Pets
&& Important
&& CanMoveOn(MoveToSquare
[0]))
1034 return HandleCharacterBlockingTheWay(Neutral
[0], NeutralPos
[0], Direction
);
1036 return IsPlayer() && Hit(Neutral
[0], NeutralPos
[0], Direction
);
1037 } else if (Neutrals
) {
1039 int Index
= RAND() % Neutrals
;
1040 return Hit(Neutral
[Index
], NeutralPos
[Index
], Direction
);
1046 for (int c
= 0; c
< Squares
; ++c
) if (MoveToSquare
[c
]->IsScary(this)) return false;
1050 if (IsPlayer() && !ivanconfig::GetBeNice() && Pet
[0]->IsMasochist() && HasSadistAttackMode() &&
1051 game::TruthQuestion("Do you want to punish " + Pet
[0]->GetObjectPronoun() + "?"))
1052 return Hit(Pet
[0], PetPos
[0], Direction
, SADIST_HIT
);
1054 return (Important
&& (CanMoveOn(MoveToSquare
[0]) ||
1055 (IsPlayer() && game::GoThroughWallsCheatIsActive())) && Displace(Pet
[0]));
1056 } else if (Pets
) return false;
1058 if ((CanMove() && CanMoveOn(MoveToSquare
[0])) || ((game::GoThroughWallsCheatIsActive() && IsPlayer()))) {
1059 Move(MoveTo
, false, Run
);
1060 if (IsEnabled() && GetPos() == GoingTo
) TerminateGoingTo();
1063 for (int c
= 0; c
< Squares
; ++c
) {
1064 olterrain
*Terrain
= MoveToSquare
[c
]->GetOLTerrain();
1065 if (Terrain
&& Terrain
->CanBeOpened()) {
1067 if (Terrain
->IsLocked()) {
1070 if (ivanconfig::GetKickDownDoors()) {
1071 if (game::TruthQuestion(CONST_S("Locked! Do you want to kick ")+Terrain
->GetName(DEFINITE
)+"?", true, game::GetMoveCommandKeyBetweenPoints(PLAYER
->GetPos(), MoveToSquare
[0]->GetPos()))) {
1072 Kick(MoveToSquare
[c
], Direction
);
1079 ADD_MESSAGE("The %s is locked.", Terrain
->GetNameSingular().CStr()); /* not sure if this is better than "the door is locked", but I guess it _might_ be slighltly better */
1081 } else if (Important
&& CheckKick()) {
1082 room
*Room
= MoveToSquare
[c
]->GetRoom();
1083 if (!Room
|| Room
->AllowKick(this, MoveToSquare
[c
])) {
1084 int HP
= Terrain
->GetHP();
1085 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s kicks %s.", CHAR_NAME(DEFINITE
), Terrain
->CHAR_NAME(DEFINITE
));
1086 Kick(MoveToSquare
[c
], Direction
);
1087 olterrain
*NewTerrain
= MoveToSquare
[c
]->GetOLTerrain();
1088 if (NewTerrain
== Terrain
&& Terrain
->GetHP() == HP
) { // BUG!
1089 Illegal
.insert(MoveTo
);
1095 } else { /* if (Terrain->IsLocked()) */
1096 /*if(!IsPlayer() || game::TruthQuestion(CONST_S("Do you want to open ")+Terrain->GetName(DEFINITE)+"?", false, game::GetMoveCommandKeyBetweenPoints(PLAYER->GetPos(), MoveToSquare[0]->GetPos()))) return MoveToSquare[c]->Open(this);*/
1097 /* Non-players always try to open it */
1098 if (!IsPlayer()) return MoveToSquare
[c
]->Open(this);
1099 if (game::TruthQuestion(CONST_S("Do you want to open ")+Terrain
->GetName(DEFINITE
)+"?", false, game::GetMoveCommandKeyBetweenPoints(PLAYER
->GetPos(), MoveToSquare
[0]->GetPos()))) {
1100 return MoveToSquare
[c
]->Open(this);
1103 } /* if (Terrain->IsLocked()) */
1104 } else { /* if (CanOpen()) */
1106 ADD_MESSAGE("This monster type cannot open doors.");
1110 Illegal
.insert(MoveTo
);
1111 return CreateRoute();
1113 } /* if (CanOpen()) */
1114 } /* if (Terrain && Terrain->CanBeOpened()) */
1119 if (IsPlayer() && !IsStuck() && GetLevel()->IsOnGround() && game::TruthQuestion(CONST_S("Do you want to leave ")+game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex())+"?")) {
1120 if (HasPetrussNut() && !HasGoldenEagleShirt()) {
1121 game::TextScreen(CONST_S("An undead and sinister voice greets you as you leave the city behind:\n\n\"MoRtAl! ThOu HaSt SlAuGtHeReD pEtRuS aNd PlEaSeD mE!\nfRoM tHiS dAy On, ThOu ArT tHe DeArEsT sErVaNt Of AlL eViL!\"\n\nYou are victorious!"));
1122 game::GetCurrentArea()->SendNewDrawRequest();
1123 game::DrawEverything();
1124 ShowAdventureInfo();
1125 festring Msg
= CONST_S("killed Petrus and became the Avatar of Chaos");
1126 PLAYER
->AddScoreEntry(Msg
, 3, false);
1130 if (game::TryTravel(WORLD_MAP
, WORLD_MAP
, game::GetCurrentDungeonIndex())) return true;
1135 /** No multitile support */
1136 if (CanMove() && GetArea()->IsValidPos(MoveTo
) && (CanMoveOn(GetNearWSquare(MoveTo
)) || game::GoThroughWallsCheatIsActive())) {
1137 if (!game::GoThroughWallsCheatIsActive()) {
1138 charactervector
&V
= game::GetWorldMap()->GetPlayerGroup();
1139 truth Discard
= false;
1140 for (uInt c
= 0; c
< V
.size(); ++c
) {
1141 if (!V
[c
]->CanMoveOn(GetNearWSquare(MoveTo
))) {
1143 ADD_MESSAGE("One or more of your team members cannot cross this terrain.");
1144 if (!game::TruthQuestion("Discard them?")) return false;
1147 if (Discard
) delete V
[c
];
1148 V
.erase(V
.begin() + c
--);
1152 Move(MoveTo
, false);
1161 void character::CreateCorpse (lsquare
*Square
) {
1162 if (!BodyPartsDisappearWhenSevered() && !game::AllBodyPartsVanish()) {
1163 corpse
*Corpse
= corpse::Spawn(0, NO_MATERIALS
);
1164 Corpse
->SetDeceased(this);
1165 Square
->AddItem(Corpse
);
1173 void character::Die (ccharacter
*Killer
, cfestring
&Msg
, feuLong DeathFlags
) {
1174 /* Note: This function musn't delete any objects, since one of these may be
1175 the one currently processed by pool::Be()! */
1176 if (!IsEnabled()) return;
1177 game::ClearEventData();
1178 game::mActor
= Killer
;
1179 if (game::RunOnCharEvent(this, CONST_S("die"))) { game::ClearEventData(); RemoveTraps(); return; }
1180 game::ClearEventData();
1183 ADD_MESSAGE("You die.");
1184 game::DrawEverything();
1185 if (game::TruthQuestion(CONST_S("Do you want to save screenshot?"), REQUIRES_ANSWER
)) {
1188 dir
<< ivanconfig::GetMyDir() << "/save";
1189 mkdir(dir
.CStr(), 0755);
1191 dir
<< getenv("HOME") << "/.ivan-save";
1192 mkdir(dir
.CStr(), 0755);
1194 dir
<< "/deathshots";
1195 mkdir(dir
.CStr(), 0755);
1197 time_t t
= time(NULL
);
1198 struct tm
*ts
= localtime(&t
);
1200 timestr
<< (int)(ts
->tm_year
%100);
1201 int t
= ts
->tm_mon
+1;
1202 if (t
< 10) timestr
<< '0'; timestr
<< t
;
1203 t
= ts
->tm_mday
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1205 t
= ts
->tm_hour
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1206 t
= ts
->tm_min
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1207 t
= ts
->tm_sec
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1211 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1212 festring ext
= ".png";
1214 festring ext
= ".bmp";
1216 festring fname
= dir
+"/deathshot_"+timestr
;
1217 if (inputfile::fileExists(fname
+ext
)) {
1218 for (int f
= 0; f
< 1000; f
++) {
1220 sprintf(buf
, "%03d", f
);
1221 festring fn
= fname
+buf
;
1222 if (!inputfile::fileExists(fn
+ext
)) {
1229 fprintf(stderr
, "deathshot: %s\n", fname
.CStr());
1230 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1231 DOUBLE_BUFFER
->SavePNG(fname
);
1233 DOUBLE_BUFFER
->SaveBMP(fname
);
1236 if (game::WizardModeIsActive()) {
1237 game::DrawEverything();
1238 if (!game::TruthQuestion(CONST_S("Do you want to do this, cheater?"), REQUIRES_ANSWER
)) {
1244 SetNP(SATIATED_LEVEL
);
1245 SendNewDrawRequest();
1249 } else if (CanBeSeenByPlayer() && !(DeathFlags
& DISALLOW_MSG
)) {
1250 ProcessAndAddMessage(GetDeathMessage());
1251 } else if (DeathFlags
& FORCE_MSG
) {
1252 ADD_MESSAGE("You sense the death of something.");
1255 if (!(DeathFlags
& FORBID_REINCARNATION
)) {
1256 if (StateIsActivated(LIFE_SAVED
) && CanMoveOn(!game::IsInWilderness() ? GetSquareUnder() : PLAYER
->GetSquareUnder())) {
1260 if (SpecialSaveLife()) return;
1261 } else if (StateIsActivated(LIFE_SAVED
)) {
1265 Flags
|= C_IN_NO_MSG_MODE
;
1266 character
*Ghost
= 0;
1268 game::RemoveSaves();
1269 if (!game::IsInWilderness()) {
1270 Ghost
= game::CreateGhost();
1275 square
*SquareUnder
[MAX_SQUARES_UNDER
];
1276 memset(SquareUnder
, 0, sizeof(SquareUnder
));
1278 if (IsPlayer() || !game::IsInWilderness()) {
1279 for (int c
= 0; c
< SquaresUnder
; ++c
) SquareUnder
[c
] = GetSquareUnder(c
);
1282 charactervector
& V
= game::GetWorldMap()->GetPlayerGroup();
1283 V
.erase(std::find(V
.begin(), V
.end(), this));
1285 //lsquare **LSquareUnder = reinterpret_cast<lsquare **>(SquareUnder); /* warning; wtf? */
1286 lsquare
*LSquareUnder
[MAX_SQUARES_UNDER
];
1287 memmove(LSquareUnder
, SquareUnder
, sizeof(SquareUnder
));
1289 if (!game::IsInWilderness()) {
1290 if (!StateIsActivated(POLYMORPHED
)) {
1291 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(this, Killer
, Msg
);
1292 if (!(DeathFlags
& DISALLOW_CORPSE
)) CreateCorpse(LSquareUnder
[0]); else SendToHell();
1294 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(GetPolymorphBackup(), Killer
, Msg
);
1295 GetPolymorphBackup()->CreateCorpse(LSquareUnder
[0]);
1296 GetPolymorphBackup()->Flags
&= ~C_POLYMORPHED
;
1297 SetPolymorphBackup(0);
1301 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(this, Killer
, Msg
);
1306 if (!game::IsInWilderness()) {
1307 for (int c
= 0; c
< GetSquaresUnder(); ++c
) LSquareUnder
[c
]->SetTemporaryEmitation(GetEmitation());
1309 ShowAdventureInfo();
1310 if (!game::IsInWilderness()) {
1311 for(int c
= 0; c
< GetSquaresUnder(); ++c
) LSquareUnder
[c
]->SetTemporaryEmitation(0);
1315 if (!game::IsInWilderness()) {
1316 if (GetSquaresUnder() == 1) {
1317 stack
*StackUnder
= LSquareUnder
[0]->GetStack();
1318 GetStack()->MoveItemsTo(StackUnder
);
1319 doforbodypartswithparam
<stack
*>()(this, &bodypart::DropEquipment
, StackUnder
);
1321 while (GetStack()->GetItems()) GetStack()->GetBottom()->MoveTo(LSquareUnder
[RAND_N(GetSquaresUnder())]->GetStack());
1322 for (int c
= 0; c
< BodyParts
; ++c
) {
1323 bodypart
*BodyPart
= GetBodyPart(c
);
1324 if (BodyPart
) BodyPart
->DropEquipment(LSquareUnder
[RAND_N(GetSquaresUnder())]->GetStack());
1329 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(0);
1331 Flags
&= ~C_IN_NO_MSG_MODE
;
1335 if (!game::IsInWilderness()) {
1336 Ghost
->PutTo(LSquareUnder
[0]->GetPos());
1340 game::TextScreen(CONST_S("Unfortunately you died."), ZERO_V2
, WHITE
, true, true, &game::ShowDeathSmiley
);
1346 void character::AddMissMessage (ccharacter
*Enemy
) const {
1348 if (Enemy
->IsPlayer()) Msg
= GetDescription(DEFINITE
)+" misses you!";
1349 else if (IsPlayer()) Msg
= CONST_S("You miss ")+Enemy
->GetDescription(DEFINITE
)+'!';
1350 else if (CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer()) Msg
= GetDescription(DEFINITE
)+" misses "+Enemy
->GetDescription(DEFINITE
)+'!';
1352 ADD_MESSAGE("%s", Msg
.CStr());
1356 void character::AddBlockMessage (ccharacter
*Enemy
, citem
*Blocker
, cfestring
&HitNoun
, truth Partial
) const {
1358 festring BlockVerb
= (Partial
? " to partially block the " : " to block the ")+HitNoun
;
1360 Msg
<< "You manage" << BlockVerb
<< " with your " << Blocker
->GetName(UNARTICLED
) << '!';
1361 } else if (Enemy
->IsPlayer() || Enemy
->CanBeSeenByPlayer()) {
1362 if (CanBeSeenByPlayer())
1363 Msg
<< GetName(DEFINITE
) << " manages" << BlockVerb
<< " with " << GetPossessivePronoun() << ' ' << Blocker
->GetName(UNARTICLED
) << '!';
1365 Msg
<< "Something manages" << BlockVerb
<< " with something!";
1369 ADD_MESSAGE("%s", Msg
.CStr());
1373 void character::AddPrimitiveHitMessage (ccharacter
*Enemy
, cfestring
&FirstPersonHitVerb
,
1374 cfestring
&ThirdPersonHitVerb
, int BodyPart
) const
1377 festring BodyPartDescription
;
1378 if (BodyPart
&& (Enemy
->CanBeSeenByPlayer() || Enemy
->IsPlayer()))
1379 BodyPartDescription
<< " in the " << Enemy
->GetBodyPartName(BodyPart
);
1380 if (Enemy
->IsPlayer())
1381 Msg
<< GetDescription(DEFINITE
) << ' ' << ThirdPersonHitVerb
<< " you" << BodyPartDescription
<< '!';
1382 else if (IsPlayer())
1383 Msg
<< "You " << FirstPersonHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
<< '!';
1384 else if (CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer())
1385 Msg
<< GetDescription(DEFINITE
) << ' ' << ThirdPersonHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) + BodyPartDescription
<< '!';
1388 ADD_MESSAGE("%s", Msg
.CStr());
1392 cchar
*const HitVerb
[] = { "strike", "slash", "stab" };
1393 cchar
*const HitVerb3rdPersonEnd
[] = { "s", "es", "s" };
1396 void character::AddWeaponHitMessage (ccharacter
*Enemy
, citem
*Weapon
, int BodyPart
, truth Critical
) const {
1398 festring BodyPartDescription
;
1400 if (BodyPart
&& (Enemy
->CanBeSeenByPlayer() || Enemy
->IsPlayer()))
1401 BodyPartDescription
<< " in the " << Enemy
->GetBodyPartName(BodyPart
);
1403 int FittingTypes
= 0;
1404 int DamageFlags
= Weapon
->GetDamageFlags();
1407 for (int c
= 0; c
< DAMAGE_TYPES
; ++c
) {
1408 if (1 << c
& DamageFlags
) {
1409 if (!FittingTypes
|| !RAND_N(FittingTypes
+1)) DamageType
= c
;
1414 if (!FittingTypes
) ABORT("No damage flags specified for %s!", Weapon
->CHAR_NAME(UNARTICLED
));
1416 festring NewHitVerb
= Critical
? " critically " : " ";
1417 NewHitVerb
<< HitVerb
[DamageType
];
1418 cchar
*const E
= HitVerb3rdPersonEnd
[DamageType
];
1420 if (Enemy
->IsPlayer()) {
1421 Msg
<< GetDescription(DEFINITE
) << NewHitVerb
<< E
<< " you" << BodyPartDescription
;
1422 if (CanBeSeenByPlayer()) Msg
<< " with " << GetPossessivePronoun() << ' ' << Weapon
->GetName(UNARTICLED
);
1424 } else if (IsPlayer()) {
1425 Msg
<< "You" << NewHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
<< '!';
1426 } else if(CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer()) {
1427 Msg
<< GetDescription(DEFINITE
) << NewHitVerb
<< E
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
;
1428 if (CanBeSeenByPlayer()) Msg
<< " with " << GetPossessivePronoun() << ' ' << Weapon
->GetName(UNARTICLED
);
1433 ADD_MESSAGE("%s", Msg
.CStr());
1437 item
*character::GeneralFindItem (ItemCheckerCB chk
) const {
1438 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1440 if (it
&& chk(it
)) return it
;
1446 static truth
isEncryptedScroll (item
*i
) { return i
->IsEncryptedScroll(); }
1447 truth
character::HasEncryptedScroll () const {
1448 if (GeneralFindItem(::isEncryptedScroll
)) return true;
1449 return combineequipmentpredicates()(this, &item::IsEncryptedScroll
, 1);
1453 static truth
isElpuriHead (item
*i
) { return i
->IsHeadOfElpuri(); }
1454 truth
character::HasHeadOfElpuri () const {
1455 if (GeneralFindItem(::isElpuriHead
)) return true;
1456 return combineequipmentpredicates()(this, &item::IsHeadOfElpuri
, 1);
1460 static truth
isPetrussNut (item
*i
) { return i
->IsPetrussNut(); }
1461 truth
character::HasPetrussNut () const {
1462 if (GeneralFindItem(::isPetrussNut
)) return true;
1463 return combineequipmentpredicates()(this, &item::IsPetrussNut
, 1);
1467 static truth
isGoldenEagleShirt (item
*i
) { return i
->IsGoldenEagleShirt(); }
1468 truth
character::HasGoldenEagleShirt () const {
1469 if (GeneralFindItem(::isGoldenEagleShirt
)) return true;
1470 return combineequipmentpredicates()(this, &item::IsGoldenEagleShirt
, 1);
1474 static truth
isShadowVeil (item
*i
) { return i
->IsShadowVeil(); }
1475 truth
character::HasShadowVeil () const {
1476 if (GeneralFindItem(::isShadowVeil
)) return true;
1477 return combineequipmentpredicates()(this, &item::IsShadowVeil
, 1);
1481 static truth
isLostRubyFlamingSword (item
*i
) { return i
->IsLostRubyFlamingSword(); }
1482 truth
character::HasLostRubyFlamingSword () const {
1483 if (GeneralFindItem(::isLostRubyFlamingSword
)) return true;
1484 return combineequipmentpredicates()(this, &item::IsLostRubyFlamingSword
, 1);
1488 truth
character::HasOmmelBlood () const {
1489 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
)
1490 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) return true;
1492 for (int c
= 0; c
< GetEquipments(); ++c
) {
1493 item
*Item
= GetEquipment(c
);
1495 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) return true;
1497 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1501 truth
character::HasCurdledBlood () const {
1502 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
)
1503 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) return true;
1505 for (int c
= 0; c
< GetEquipments(); ++c
) {
1506 item
*Item
= GetEquipment(c
);
1508 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) return true;
1510 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1514 truth
character::CurdleOmmelBlood () const {
1515 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1516 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) {
1517 i
->ChangeSecondaryMaterial(MAKE_MATERIAL(CURDLED_OMMEL_BLOOD
));
1522 for (int c
= 0; c
< GetEquipments(); ++c
) {
1523 item
*Item
= GetEquipment(c
);
1525 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) {
1526 Item
->ChangeSecondaryMaterial(MAKE_MATERIAL(CURDLED_OMMEL_BLOOD
));
1530 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1534 truth
character::RemoveCurdledOmmelBlood () {
1535 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1536 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) {
1537 (*i
)->RemoveFromSlot();
1543 for (int c
= 0; c
< GetEquipments(); ++c
) {
1544 item
*Item
= GetEquipment(c
);
1546 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) {
1547 Item
->RemoveFromSlot();
1556 int character::GeneralRemoveItem (ItemCheckerCB chk
, truth allItems
) {
1562 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1564 if (Item
&& chk(Item
)) {
1565 Item
->RemoveFromSlot();
1568 if (!allItems
) return cnt
;
1577 for (int c
= 0; c
< GetEquipments(); ++c
) {
1578 item
*Item
= GetEquipment(c
);
1579 if (Item
&& chk(Item
)) {
1580 Item
->RemoveFromSlot();
1583 if (!allItems
) return cnt
;
1593 //static truth isEncryptedScroll (item *i) { return i->IsEncryptedScroll(); }
1594 truth
character::RemoveEncryptedScroll () { return GeneralRemoveItem(::isEncryptedScroll
) != 0; }
1597 static truth
isMondedrPass (item
*i
) { return i
->IsMondedrPass(); }
1598 truth
character::RemoveMondedrPass () { return GeneralRemoveItem(::isMondedrPass
) != 0; }
1601 static truth
isRingOfThieves (item
*i
) { return i
->IsRingOfThieves(); }
1602 truth
character::RemoveRingOfThieves () { return GeneralRemoveItem(::isRingOfThieves
) != 0; }
1605 truth
character::RemoveShadowVeil () { return (GeneralRemoveItem(::isShadowVeil
) != 0); }
1608 truth
character::ReadItem (item
*ToBeRead
) {
1609 if (!ToBeRead
->CanBeRead(this)) {
1610 if (IsPlayer()) ADD_MESSAGE("You can't read this.");
1613 if (!GetLSquareUnder()->IsDark() || game::GetSeeWholeMapCheatMode()) {
1614 if (StateIsActivated(CONFUSED
) && !(RAND()&7)) {
1615 if (!ToBeRead
->IsDestroyable(this)) {
1616 ADD_MESSAGE("You read some words of %s and understand exactly nothing.", ToBeRead
->CHAR_NAME(DEFINITE
));
1618 ADD_MESSAGE("%s is very confusing. Or perhaps you are just too confused?", ToBeRead
->CHAR_NAME(DEFINITE
));
1619 ActivateRandomState(SRC_CONFUSE_READ
, 1000+RAND()%1500);
1620 ToBeRead
->RemoveFromSlot();
1621 ToBeRead
->SendToHell();
1626 if (ToBeRead
->Read(this)) {
1627 if (!game::WizardModeIsActive()) {
1628 /* This AP is used to take the stuff out of backpack */
1635 if (IsPlayer()) ADD_MESSAGE("It's too dark here to read.");
1640 void character::CalculateBurdenState () {
1641 int OldBurdenState
= BurdenState
;
1642 sLong SumOfMasses
= GetCarriedWeight();
1643 sLong CarryingStrengthUnits
= sLong(GetCarryingStrength())*2500;
1644 if (SumOfMasses
> (CarryingStrengthUnits
<< 1) + CarryingStrengthUnits
) BurdenState
= OVER_LOADED
;
1645 else if (SumOfMasses
> CarryingStrengthUnits
<< 1) BurdenState
= STRESSED
;
1646 else if (SumOfMasses
> CarryingStrengthUnits
) BurdenState
= BURDENED
;
1647 else BurdenState
= UNBURDENED
;
1648 if (!IsInitializing() && BurdenState
!= OldBurdenState
) CalculateBattleInfo();
1652 void character::Save (outputfile
&SaveFile
) const {
1653 SaveFile
<< (uShort
)GetType();
1654 Stack
->Save(SaveFile
);
1656 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) SaveFile
<< BaseExperience
[c
];
1658 SaveFile
<< ExpModifierMap
;
1659 SaveFile
<< NP
<< AP
<< Stamina
<< GenerationDanger
<< ScienceTalks
<< CounterToMindWormHatch
;
1660 SaveFile
<< TemporaryState
<< EquipmentState
<< Money
<< GoingTo
<< RegenerationCounter
<< Route
<< Illegal
;
1661 SaveFile
<< CurrentSweatMaterial
;
1662 SaveFile
.Put(!!IsEnabled());
1663 SaveFile
<< HomeData
<< BlocksSinceLastTurn
<< CommandFlags
;
1664 SaveFile
<< WarnFlags
<< (uShort
)Flags
;
1666 for (int c
= 0; c
< BodyParts
; ++c
) SaveFile
<< BodyPartSlot
[c
] << OriginalBodyPartID
[c
];
1668 SaveLinkedList(SaveFile
, TrapData
);
1671 for (int c
= 0; c
< STATES
; ++c
) SaveFile
<< TemporaryStateCounter
[c
];
1675 SaveFile
<< Team
->GetID(); // feuLong
1677 SaveFile
.Put(false);
1680 if (GetTeam() && GetTeam()->GetLeader() == this) SaveFile
.Put(true); else SaveFile
.Put(false);
1682 SaveFile
<< AssignedName
<< PolymorphBackup
;
1684 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) SaveFile
<< CWeaponSkill
[c
];
1686 SaveFile
<< (uShort
)GetConfig();
1690 void character::Load (inputfile
&SaveFile
) {
1692 Stack
->Load(SaveFile
);
1694 game::AddCharacterID(this, ID
);
1696 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) SaveFile
>> BaseExperience
[c
];
1698 SaveFile
>> ExpModifierMap
;
1699 SaveFile
>> NP
>> AP
>> Stamina
>> GenerationDanger
>> ScienceTalks
>> CounterToMindWormHatch
;
1700 SaveFile
>> TemporaryState
>> EquipmentState
>> Money
>> GoingTo
>> RegenerationCounter
>> Route
>> Illegal
;
1701 SaveFile
>> CurrentSweatMaterial
;
1703 if (!SaveFile
.Get()) Disable();
1705 SaveFile
>> HomeData
>> BlocksSinceLastTurn
>> CommandFlags
;
1706 SaveFile
>> WarnFlags
;
1707 WarnFlags
&= ~WARNED
;
1708 Flags
|= ReadType(uShort
, SaveFile
) & ~ENTITY_FLAGS
;
1710 for (int c
= 0; c
< BodyParts
; ++c
) {
1711 SaveFile
>> BodyPartSlot
[c
] >> OriginalBodyPartID
[c
];
1712 item
*BodyPart
= *BodyPartSlot
[c
];
1713 if (BodyPart
) BodyPart
->Disable();
1716 LoadLinkedList(SaveFile
, TrapData
);
1719 if (Action
) Action
->SetActor(this);
1721 for (int c
= 0; c
< STATES
; ++c
) SaveFile
>> TemporaryStateCounter
[c
];
1723 if (SaveFile
.Get()) SetTeam(game::GetTeam(ReadType(feuLong
, SaveFile
)));
1725 if (SaveFile
.Get()) GetTeam()->SetLeader(this);
1727 SaveFile
>> AssignedName
>> PolymorphBackup
;
1729 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) SaveFile
>> CWeaponSkill
[c
];
1731 databasecreator
<character
>::InstallDataBase(this, ReadType(uShort
, SaveFile
));
1733 if (IsEnabled() && !game::IsInWilderness()) {
1734 for (int c
= 1; c
< GetSquaresUnder(); ++c
) GetSquareUnder(c
)->SetCharacter(this);
1738 const fearray<festring> < = GetLevelTags();
1740 fprintf(stderr, "====\n");
1741 for (uInt f = 0; f < lt.Size; ++f) fprintf(stderr, " %u: [%s]\n", f, lt[f].CStr());
1747 truth
character::Engrave (cfestring
&What
) {
1748 GetLSquareUnder()->Engrave(What
);
1752 truth
character::MoveRandomly () {
1753 if (!IsEnabled()) return false;
1754 for (int c
= 0; c
< 10; ++c
) {
1755 v2 ToTry
= game::GetMoveVector(RAND()&7);
1756 if (GetLevel()->IsValidPos(GetPos()+ToTry
)) {
1757 lsquare
*Square
= GetNearLSquare(GetPos()+ToTry
);
1758 if (!Square
->IsDangerous(this) && !Square
->IsScary(this) && TryMove(ToTry
, false, false)) return true;
1765 truth
character::TestForPickup (item
*ToBeTested
) const {
1766 if (MakesBurdened(ToBeTested
->GetWeight()+GetCarriedWeight())) return false;
1771 void character::AddScoreEntry (cfestring
&Description
, double Multiplier
, truth AddEndLevel
) const {
1772 if (!game::WizardModeIsReallyActive()) {
1774 if (!HScore
.CheckVersion()) {
1775 if (game::Menu(0, v2(RES
.X
>> 1, RES
.Y
>> 1), CONST_S("The highscore version doesn't match.\rDo you want to erase previous records and start a new file?\rNote, if you answer no, the score of your current game will be lost!\r"), CONST_S("Yes\rNo\r"), LIGHT_GRAY
)) return;
1778 festring Desc
= game::GetPlayerName();
1779 Desc
<< ", " << Description
;
1780 if (AddEndLevel
) Desc
<< " in "+(game::IsInWilderness() ? "the world map" : game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex()));
1781 HScore
.Add(sLong(game::GetScore()*Multiplier
), Desc
);
1787 truth
character::CheckDeath (cfestring
&Msg
, ccharacter
*Murderer
, feuLong DeathFlags
) {
1788 if (!IsEnabled()) return true;
1789 if (game::IsSumoWrestling() && IsDead()) {
1790 game::EndSumoWrestling(!!IsPlayer());
1793 if (DeathFlags
& FORCE_DEATH
|| IsDead()) {
1794 if (Murderer
&& Murderer
->IsPlayer() && GetTeam()->GetKillEvilness()) game::DoEvilDeed(GetTeam()->GetKillEvilness());
1795 festring SpecifierMsg
;
1796 int SpecifierParts
= 0;
1797 if (GetPolymorphBackup()) {
1798 SpecifierMsg
<< " polymorphed into ";
1799 id::AddName(SpecifierMsg
, INDEFINITE
);
1802 if (!(DeathFlags
& IGNORE_TRAPS
) && IsStuck()) {
1803 if (SpecifierParts
++) SpecifierMsg
<< " and";
1804 SpecifierMsg
<< " caught in " << GetTrapDescription();
1806 if (GetAction() && !(DeathFlags
& IGNORE_UNCONSCIOUSNESS
&& GetAction()->IsUnconsciousness())) {
1807 festring ActionMsg
= GetAction()->GetDeathExplanation();
1808 if (!ActionMsg
.IsEmpty()) {
1809 if (SpecifierParts
> 1) {
1810 SpecifierMsg
= ActionMsg
<< ',' << SpecifierMsg
;
1812 if (SpecifierParts
) SpecifierMsg
<< " and";
1813 SpecifierMsg
<< ActionMsg
;
1818 festring NewMsg
= Msg
;
1819 if (Murderer
== this) {
1820 SEARCH_N_REPLACE(NewMsg
, "@bkp", CONST_S("by ") + GetPossessivePronoun(false) + " own");
1821 SEARCH_N_REPLACE(NewMsg
, "@bk", CONST_S("by ") + GetObjectPronoun(false) + "self");
1822 SEARCH_N_REPLACE(NewMsg
, "@k", GetObjectPronoun(false) + "self");
1824 SEARCH_N_REPLACE(NewMsg
, "@bkp", CONST_S("by ") + Murderer
->GetName(INDEFINITE
) + "'s");
1825 SEARCH_N_REPLACE(NewMsg
, "@bk", CONST_S("by ") + Murderer
->GetName(INDEFINITE
));
1826 SEARCH_N_REPLACE(NewMsg
, "@k", CONST_S("by ") + Murderer
->GetName(INDEFINITE
));
1828 if (SpecifierParts
) NewMsg
<< " while" << SpecifierMsg
;
1829 if (IsPlayer() && game::WizardModeIsActive()) ADD_MESSAGE("Death message: %s. Score: %d.", NewMsg
.CStr(), game::GetScore());
1830 Die(Murderer
, NewMsg
, DeathFlags
);
1837 truth
character::CheckStarvationDeath (cfestring
&Msg
) {
1838 if (GetNP() < 1 && UsesNutrition()) return CheckDeath(Msg
, 0, FORCE_DEATH
);
1843 void character::ThrowItem (int Direction
, item
*ToBeThrown
) {
1844 if (Direction
> 7) ABORT("Throw in TOO odd direction...");
1845 ToBeThrown
->Fly(this, Direction
, GetAttribute(ARM_STRENGTH
));
1849 void character::HasBeenHitByItem (character
*Thrower
, item
*Thingy
, int Damage
, double ToHitValue
, int Direction
) {
1850 if (IsPlayer()) ADD_MESSAGE("%s hits you.", Thingy
->CHAR_NAME(DEFINITE
));
1851 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s.", Thingy
->CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
));
1852 int BodyPart
= ChooseBodyPartToReceiveHit(ToHitValue
, DodgeValue
);
1853 int WeaponSkillHits
= Thrower
? CalculateWeaponSkillHits(Thrower
) : 0;
1854 int DoneDamage
= ReceiveBodyPartDamage(Thrower
, Damage
, PHYSICAL_DAMAGE
, BodyPart
, Direction
);
1855 truth Succeeded
= (GetBodyPart(BodyPart
) && HitEffect(Thrower
, Thingy
, Thingy
->GetPos(), THROW_ATTACK
, BodyPart
, Direction
, !DoneDamage
, false, DoneDamage
)) || DoneDamage
;
1856 if (Succeeded
&& Thrower
) Thrower
->WeaponSkillHit(Thingy
, THROW_ATTACK
, WeaponSkillHits
);
1857 festring DeathMsg
= CONST_S("killed by a flying ")+Thingy
->GetName(UNARTICLED
);
1858 if (CheckDeath(DeathMsg
, Thrower
)) return;
1860 if (Thrower
->CanBeSeenByPlayer())
1861 DeActivateVoluntaryAction(CONST_S("The attack of ")+Thrower
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
1863 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
1865 DeActivateVoluntaryAction(CONST_S("The hit interrupts you."));
1870 truth
character::DodgesFlyingItem (item
*Item
, double ToHitValue
) {
1871 return !Item
->EffectIsGood() && RAND() % int(100+ToHitValue
/DodgeValue
*100) < 100;
1875 void character::GetPlayerCommand () {
1877 truth HasActed
= false;
1879 game::DrawEverything();
1880 if (game::GetDangerFound() && !StateIsActivated(FEARLESS
)) {
1881 if (game::GetDangerFound() > 500.) {
1882 if (game::GetCausePanicFlag()) {
1883 game::SetCausePanicFlag(false);
1884 BeginTemporaryState(PANIC
, 500+RAND_N(500));
1886 game::AskForEscPress(CONST_S("You are horrified by your situation!"));
1887 } else if (ivanconfig::GetWarnAboutDanger()) {
1888 if (game::GetDangerFound() > 50.) game::AskForEscPress(CONST_S("You sense great danger!"));
1889 else game::AskForEscPress(CONST_S("You sense danger!"));
1891 game::SetDangerFound(0);
1893 game::SetIsInGetCommand(true);
1894 int Key
= GET_KEY();
1895 game::SetIsInGetCommand(false);
1896 if (Key
!= '+' && Key
!= '-' && Key
!= 'M') msgsystem::ThyMessagesAreNowOld(); // gum
1897 truth ValidKeyPressed
= false;
1899 for (int c
= 0; c
< DIRECTION_COMMAND_KEYS
; ++c
) {
1900 if (Key
== game::GetMoveCommandKey(c
)) {
1901 HasActed
= TryMove(ApplyStateModification(game::GetMoveVector(c
)), true, game::PlayerIsRunning());
1902 ValidKeyPressed
= true;
1906 if (!ValidKeyPressed
) {
1907 for (int c
= 0; (cmd
= commandsystem::GetCommand(c
)); ++c
) {
1909 /* Numpad aliases for most commonly used commands */
1910 if (Key
== KEY_DEL
&& cmd
->GetName() == "Eat") Key
= cmd
->GetKey();
1911 if (Key
== KEY_INS
&& cmd
->GetName() == "PickUp") Key
= cmd
->GetKey();
1912 if (Key
== KEY_PLUS
&& cmd
->GetName() == "EquipmentScreen") Key
= cmd
->GetKey();
1914 if (Key
== cmd
->GetKey()) {
1915 if (game::IsInWilderness() && !commandsystem::GetCommand(c
)->IsUsableInWilderness()) {
1916 ADD_MESSAGE("This function cannot be used while in wilderness.");
1917 } else if (!game::WizardModeIsActive() && commandsystem::GetCommand(c
)->IsWizardModeFunction()) {
1918 ADD_MESSAGE("Activate wizardmode to use this function.");
1920 HasActed
= commandsystem::GetCommand(c
)->GetLinkedFunction()(this);
1922 ValidKeyPressed
= true;
1927 if (!ValidKeyPressed
) ADD_MESSAGE("Unknown key. Press '?' for a list of commands.");
1929 game::IncreaseTurn();
1933 void character::Vomit (v2 Pos
, int Amount
, truth ShowMsg
) {
1934 if (!CanVomit()) return;
1936 if (IsPlayer()) ADD_MESSAGE("You vomit.");
1937 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s vomits.", CHAR_NAME(DEFINITE
));
1939 if (VomittingIsUnhealthy()) {
1940 EditExperience(ARM_STRENGTH
, -75, 1 << 9);
1941 EditExperience(LEG_STRENGTH
, -75, 1 << 9);
1944 EditNP(-2500-RAND()%2501);
1945 CheckStarvationDeath(CONST_S("vomited himself to death"));
1947 if (StateIsActivated(PARASITIZED
) && !(RAND() & 7)) {
1948 if (IsPlayer()) ADD_MESSAGE("You notice a dead broad tapeworm among your former stomach contents.");
1949 DeActivateTemporaryState(PARASITIZED
);
1951 if (!game::IsInWilderness()) {
1952 GetNearLSquare(Pos
)->ReceiveVomit(this, liquid::Spawn(GetVomitMaterial(), sLong(sqrt(GetBodyVolume())*Amount
/1000)));
1957 truth
character::Polymorph (character
*NewForm
, int Counter
) {
1958 if (!IsPolymorphable() || (!IsPlayer() && game::IsInWilderness())) {
1963 if (GetAction()) GetAction()->Terminate(false);
1964 NewForm
->SetAssignedName("");
1966 ADD_MESSAGE("Your body glows in a crimson light. You transform into %s!", NewForm
->CHAR_NAME(INDEFINITE
));
1967 else if (CanBeSeenByPlayer())
1968 ADD_MESSAGE("%s glows in a crimson light and %s transforms into %s!", CHAR_NAME(DEFINITE
), GetPersonalPronoun().CStr(), NewForm
->CHAR_NAME(INDEFINITE
));
1970 Flags
|= C_IN_NO_MSG_MODE
;
1971 NewForm
->Flags
|= C_IN_NO_MSG_MODE
;
1972 NewForm
->ChangeTeam(GetTeam());
1973 NewForm
->GenerationDanger
= GenerationDanger
;
1974 NewForm
->mOnEvents
= this->mOnEvents
;
1976 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(NewForm
);
1980 NewForm
->PutToOrNear(Pos
);
1981 NewForm
->SetAssignedName(GetAssignedName());
1982 NewForm
->ActivateTemporaryState(POLYMORPHED
);
1983 NewForm
->SetTemporaryStateCounter(POLYMORPHED
, Counter
);
1985 if (TemporaryStateIsActivated(POLYMORPHED
)) {
1986 NewForm
->SetPolymorphBackup(GetPolymorphBackup());
1987 SetPolymorphBackup(0);
1990 NewForm
->SetPolymorphBackup(this);
1991 Flags
|= C_POLYMORPHED
;
1995 GetStack()->MoveItemsTo(NewForm
->GetStack());
1996 NewForm
->SetMoney(GetMoney());
1997 DonateEquipmentTo(NewForm
);
1998 Flags
&= ~C_IN_NO_MSG_MODE
;
1999 NewForm
->Flags
&= ~C_IN_NO_MSG_MODE
;
2000 NewForm
->CalculateAll();
2004 game::SetPlayer(NewForm
);
2005 game::SendLOSUpdateRequest();
2009 NewForm
->TestWalkability();
2014 void character::BeKicked (character
*Kicker
, item
*Boot
, bodypart
*Leg
, v2 HitPos
, double KickDamage
,
2015 double ToHitValue
, int Success
, int Direction
, truth Critical
, truth ForceHit
)
2018 game::ClearEventData();
2019 game::mActor
= Kicker
;
2020 if (game::RunOnCharEvent(this, CONST_S("before_be_kicked"))) { game::ClearEventData(); return; }
2021 game::ClearEventData();
2023 auto hitres
= (TakeHit(Kicker
, Boot
, Leg
, HitPos
, KickDamage
, ToHitValue
, Success
, KICK_ATTACK
, Direction
, Critical
, ForceHit
));
2024 if (hitres
== HAS_HIT
|| hitres
== HAS_BLOCKED
|| hitres
== DID_NO_DAMAGE
) {
2025 if (IsEnabled() && !CheckBalance(KickDamage
)) {
2026 if (IsPlayer()) ADD_MESSAGE("The kick throws you off balance.");
2027 else if (Kicker
->IsPlayer()) ADD_MESSAGE("The kick throws %s off balance.", CHAR_DESCRIPTION(DEFINITE
));
2028 v2 FallToPos
= GetPos()+game::GetMoveVector(Direction
);
2029 FallTo(Kicker
, FallToPos
);
2035 /* Return true if still in balance */
2036 truth
character::CheckBalance (double KickDamage
) {
2037 return !CanMove() || IsStuck() || !KickDamage
|| (!IsFlying() && KickDamage
*5 < RAND()%GetSize());
2041 void character::FallTo (character
*GuiltyGuy
, v2 Where
) {
2043 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
2044 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, Where
);
2046 truth NoRoom
= false;
2047 for (int c
= 0; c
< Squares
; ++c
) {
2048 olterrain
*Terrain
= MoveToSquare
[c
]->GetOLTerrain();
2049 if (Terrain
&& !CanMoveOn(Terrain
)) { NoRoom
= true; break; }
2053 if (IsPlayer()) ADD_MESSAGE("You hit your head on the wall.");
2054 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s head on the wall.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
2056 ReceiveDamage(GuiltyGuy
, 1+RAND()%5, PHYSICAL_DAMAGE
, HEAD
);
2057 CheckDeath(CONST_S("killed by hitting a wall due to being kicked @bk"), GuiltyGuy
);
2059 if (IsFreeForMe(MoveToSquare
[0])) Move(Where
, true);
2060 // Place code that handles characters bouncing to each other here
2066 truth
character::CheckCannibalism (cmaterial
*What
) const {
2067 return GetTorso()->GetMainMaterial()->IsSameAs(What
);
2071 void character::StandIdleAI () {
2072 SeekLeader(GetLeader());
2073 if (CheckForEnemies(true, true, true)) return;
2074 if (CheckForUsefulItemsOnGround()) return;
2075 if (FollowLeader(GetLeader())) return;
2076 if (CheckForDoors()) return;
2077 if (MoveTowardsHomePos()) return;
2078 if (CheckSadism()) return;
2083 truth
character::LoseConsciousness (int Counter
, truth HungerFaint
) {
2084 if (!AllowUnconsciousness()) return false;
2085 action
*Action
= GetAction();
2087 if (HungerFaint
&& !Action
->AllowUnconsciousness()) return false;
2088 if (Action
->IsUnconsciousness()) {
2089 static_cast<unconsciousness
*>(Action
)->RaiseCounterTo(Counter
);
2092 Action
->Terminate(false);
2094 if (IsPlayer()) ADD_MESSAGE("You lose consciousness.");
2095 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s loses consciousness.", CHAR_NAME(DEFINITE
));
2096 unconsciousness
*Unconsciousness
= unconsciousness::Spawn(this);
2097 Unconsciousness
->SetCounter(Counter
);
2098 SetAction(Unconsciousness
);
2103 void character::DeActivateVoluntaryAction (cfestring
&Reason
) {
2104 if (GetAction() && GetAction()->IsVoluntary()) {
2106 if (Reason
.GetSize()) ADD_MESSAGE("%s", Reason
.CStr());
2107 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"?")) GetAction()->ActivateInDNDMode();
2108 else GetAction()->Terminate(false);
2110 GetAction()->Terminate(false);
2116 void character::ActionAutoTermination () {
2117 if (!GetAction() || !GetAction()->IsVoluntary() || GetAction()->InDNDMode()) return;
2119 for (int c
= 0; c
< game::GetTeams(); ++c
) {
2120 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
2121 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
2123 if (ch
->IsEnabled() && ch
->CanBeSeenBy(this, false, true) && (ch
->CanMove() || ch
->GetPos().IsAdjacent(Pos
)) && ch
->CanAttack()) {
2125 ADD_MESSAGE("%s seems to be hostile.", ch
->CHAR_NAME(DEFINITE
));
2126 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"?")) GetAction()->ActivateInDNDMode();
2127 else GetAction()->Terminate(false);
2129 GetAction()->Terminate(false);
2139 truth
character::CheckForEnemies (truth CheckDoors
, truth CheckGround
, truth MayMoveRandomly
, truth RunTowardsTarget
) {
2140 if (!IsEnabled()) return false;
2141 truth HostileCharsNear
= false;
2142 character
*NearestChar
= 0;
2143 sLong NearestDistance
= 0x7FFFFFFF;
2145 for (int c
= 0; c
< game::GetTeams(); ++c
) {
2146 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
2147 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
2149 if (ch
->IsEnabled() && GetAttribute(WISDOM
) < ch
->GetAttackWisdomLimit()) {
2150 sLong ThisDistance
= Max
<sLong
>(abs(ch
->GetPos().X
- Pos
.X
), abs(ch
->GetPos().Y
- Pos
.Y
));
2151 if (ThisDistance
<= GetLOSRangeSquare()) HostileCharsNear
= true;
2152 if ((ThisDistance
< NearestDistance
|| (ThisDistance
== NearestDistance
&& !(RAND() % 3))) &&
2153 ch
->CanBeSeenBy(this, false, IsGoingSomeWhere()) &&
2154 (!IsGoingSomeWhere() || HasClearRouteTo(ch
->GetPos()))) {
2156 NearestDistance
= ThisDistance
;
2164 if (GetAttribute(INTELLIGENCE
) >= 10 || IsSpy()) game::CallForAttention(GetPos(), 100);
2165 if (SpecialEnemySightedReaction(NearestChar
)) return true;
2166 if (IsExtraCoward() && !StateIsActivated(PANIC
) && NearestChar
->GetRelativeDanger(this) >= 0.5 && !StateIsActivated(FEARLESS
)) {
2167 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s sees %s.", CHAR_NAME(DEFINITE
), NearestChar
->CHAR_DESCRIPTION(DEFINITE
));
2168 BeginTemporaryState(PANIC
, 500+RAND()%500);
2170 if (!IsRetreating()) {
2171 if (CheckGround
&& NearestDistance
> 2 && CheckForUsefulItemsOnGround(false)) return true;
2172 SetGoingTo(NearestChar
->GetPos());
2174 SetGoingTo(Pos
-((NearestChar
->GetPos()-Pos
)<<4));
2176 return MoveTowardsTarget(true);
2178 character
*Leader
= GetLeader();
2179 if (Leader
== this) Leader
= 0;
2180 if (!Leader
&& IsGoingSomeWhere()) {
2181 if (!MoveTowardsTarget(RunTowardsTarget
)) {
2185 if (!IsEnabled()) return true;
2186 if (GetPos() == GoingTo
) TerminateGoingTo();
2190 if ((!Leader
|| (Leader
&& !IsGoingSomeWhere())) && HostileCharsNear
) {
2191 if (CheckDoors
&& CheckForDoors()) return true;
2192 if (CheckGround
&& CheckForUsefulItemsOnGround()) return true;
2193 if (MayMoveRandomly
&& MoveRandomly()) return true; // one has heard that an enemy is near but doesn't know where
2201 truth
character::CheckForDoors () {
2202 if (!CanOpen() || !IsEnabled()) return false;
2203 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2204 lsquare
*Square
= GetNeighbourLSquare(d
);
2205 if (Square
&& Square
->GetOLTerrain() && Square
->GetOLTerrain()->Open(this)) return true;
2211 truth
character::CheckForUsefulItemsOnGround (truth CheckFood
) {
2212 if (StateIsActivated(PANIC
) || !IsEnabled()) return false;
2213 itemvector ItemVector
;
2214 GetStackUnder()->FillItemVector(ItemVector
);
2215 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
2216 if (ItemVector
[c
]->CanBeSeenBy(this) && ItemVector
[c
]->IsPickable(this)) {
2217 if (!(CommandFlags
& DONT_CHANGE_EQUIPMENT
) && TryToEquip(ItemVector
[c
])) return true;
2218 if (CheckFood
&& UsesNutrition() && !CheckIfSatiated() && TryToConsume(ItemVector
[c
])) return true;
2219 if (IsRangedAttacker() && (ItemVector
[c
])->GetThrowItemTypes() && TryToAddToInventory(ItemVector
[c
])) return true;
2226 truth
character::TryToAddToInventory (item
*Item
) {
2227 if (!(GetBurdenState() > STRESSED
) || !CanUseEquipment() || Item
->GetSquaresUnder() != 1) return false;
2228 room
*Room
= GetRoom();
2229 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
2230 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up %s from the ground.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(INDEFINITE
));
2231 Item
->MoveTo(GetStack());
2239 truth
character::CheckInventoryForItemToThrow (item
*ToBeChecked
) {
2240 return (ToBeChecked
->GetThrowItemTypes() & GetWhatThrowItemTypesToThrow()) ? true : false; //hehe
2244 truth
character::CheckThrowItemOpportunity () {
2245 if (!IsRangedAttacker() || !CanThrow() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2246 //fprintf(stderr, "character::CheckThrowItemOpportunity...\n");
2248 // (1) - Acquire target as nearest enemy
2249 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2250 // (3) - check inventory for throwing weapon, select this weapon
2251 // (4) - throw item in direction where the enemy is
2253 //Check the visible area for hostiles
2254 int ThrowDirection
= 0;
2255 int TargetFound
= 0;
2258 int RangeMax
= GetLOSRange();
2259 int CandidateDirections
[7] = {0, 0, 0, 0, 0, 0, 0};
2260 int HostileFound
= 0;
2261 item
*ToBeThrown
= 0;
2262 level
*Level
= GetLevel();
2264 for (int r
= 1; r
<= RangeMax
; ++r
) {
2265 for (int dir
= 0; dir
< MDIR_STAND
; ++dir
) {
2268 case 0: TestPos
= v2(Pos
.X
-r
, Pos
.Y
-r
); break;
2269 case 1: TestPos
= v2(Pos
.X
, Pos
.Y
-r
); break;
2270 case 2: TestPos
= v2(Pos
.X
+r
, Pos
.Y
-r
); break;
2271 case 3: TestPos
= v2(Pos
.X
-r
, Pos
.Y
); break;
2272 case 4: TestPos
= v2(Pos
.X
+r
, Pos
.Y
); break;
2273 case 5: TestPos
= v2(Pos
.X
-r
, Pos
.Y
+r
); break;
2274 case 6: TestPos
= v2(Pos
.X
, Pos
.Y
+r
); break;
2275 case 7: TestPos
= v2(Pos
.X
+r
, Pos
.Y
+r
); break;
2277 if (Level
->IsValidPos(TestPos
)) {
2278 square
*TestSquare
= GetNearSquare(TestPos
);
2279 character
*Dude
= TestSquare
->GetCharacter();
2281 if (Dude
&& Dude
->IsEnabled() && Dude
->CanBeSeenBy(this, false, true)) {
2282 if (GetRelation(Dude
) != HOSTILE
) CandidateDirections
[dir
] = BLOCKED
;
2283 else if (GetRelation(Dude
) == HOSTILE
&& CandidateDirections
[dir
] != BLOCKED
) {
2284 //then load this candidate position direction into the vector of possible throw directions
2285 CandidateDirections
[dir
] = SUCCESS
;
2294 for (int dir
= 0; dir
< MDIR_STAND
; ++dir
) {
2295 if (CandidateDirections
[dir
] == SUCCESS
&& !TargetFound
) {
2296 ThrowDirection
= dir
;
2301 if (!TargetFound
) return false;
2305 //fprintf(stderr, "throw: has target.\n");
2306 // check inventory for throwing weapon
2307 itemvector ItemVector
;
2308 GetStack()->FillItemVector(ItemVector
);
2309 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
2310 if (ItemVector
[c
]->IsThrowingWeapon()) {
2311 ToBeThrown
= ItemVector
[c
];
2315 if (!ToBeThrown
) return false;
2316 //fprintf(stderr, "throw: has throwing weapon.\n");
2317 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s throws %s.", CHAR_NAME(DEFINITE
), ToBeThrown
->CHAR_NAME(INDEFINITE
));
2318 ThrowItem(ThrowDirection
, ToBeThrown
);
2319 EditExperience(ARM_STRENGTH
, 75, 1<<8);
2320 EditExperience(DEXTERITY
, 75, 1<<8);
2321 EditExperience(PERCEPTION
, 75, 1<<8);
2329 truth
character::CheckAIZapOpportunity () {
2330 if (/*!IsRangedAttacker() || */ !CanZap() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2332 // (1) - Acquire target as nearest enemy
2333 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2334 // (3) - check inventory for zappable item
2335 // (4) - zap item in direction where the enemy is
2336 //Check the rest of the visible area for hostiles
2339 int SensibleRange
= 5;
2340 int RangeMax
= GetLOSRange();
2341 if (RangeMax
< SensibleRange
) SensibleRange
= RangeMax
;
2342 int CandidateDirections
[7] = {0, 0, 0, 0, 0, 0, 0};
2343 int HostileFound
= 0;
2344 int ZapDirection
= 0;
2345 int TargetFound
= 0;
2346 item
*ToBeZapped
= 0;
2347 level
*Level
= GetLevel();
2349 for (int r
= 2; r
<= SensibleRange
; ++r
) {
2350 for (int dir
= 0; dir
< MDIR_STAND
; ++dir
) {
2352 case 0: TestPos
= v2(Pos
.X
-r
, Pos
.Y
-r
); break;
2353 case 1: TestPos
= v2(Pos
.X
, Pos
.Y
-r
); break;
2354 case 2: TestPos
= v2(Pos
.X
+r
, Pos
.Y
-r
); break;
2355 case 3: TestPos
= v2(Pos
.X
-r
, Pos
.Y
); break;
2356 case 4: TestPos
= v2(Pos
.X
+r
, Pos
.Y
); break;
2357 case 5: TestPos
= v2(Pos
.X
-r
, Pos
.Y
+r
); break;
2358 case 6: TestPos
= v2(Pos
.X
, Pos
.Y
+r
); break;
2359 case 7: TestPos
= v2(Pos
.X
+r
, Pos
.Y
+r
); break;
2361 if (Level
->IsValidPos(TestPos
)) {
2362 square
*TestSquare
= GetNearSquare(TestPos
);
2363 character
*Dude
= TestSquare
->GetCharacter();
2365 if (Dude
&& Dude
->IsEnabled() && Dude
->CanBeSeenBy(this, false, true)) {
2366 if (GetRelation(Dude
) != HOSTILE
) CandidateDirections
[dir
] = BLOCKED
;
2367 else if (GetRelation(Dude
) == HOSTILE
&& CandidateDirections
[dir
] != BLOCKED
) {
2368 //then load this candidate position direction into the vector of possible zap directions
2369 CandidateDirections
[dir
] = SUCCESS
;
2378 for (int dir
= 0; dir
< MDIR_STAND
; ++dir
) {
2379 if (CandidateDirections
[dir
] == SUCCESS
&& !TargetFound
) {
2385 if (!TargetFound
) return false;
2389 // check inventory for zappable item
2390 itemvector ItemVector
;
2391 GetStack()->FillItemVector(ItemVector
);
2392 for (unsigned int c
= 0; c
< ItemVector
.size(); ++c
) {
2393 if (ItemVector
[c
]->GetMinCharges() > 0 && ItemVector
[c
]->GetPrice()) {
2394 // bald-faced gum solution for choosing zappables that have shots left.
2395 // MinCharges needs to be replaced. Empty wands have zero price!
2396 ToBeZapped
= ItemVector
[c
];
2400 if (!ToBeZapped
) return false;
2401 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s zaps %s.", CHAR_NAME(DEFINITE
), ToBeZapped
->CHAR_NAME(INDEFINITE
));
2402 if (ToBeZapped
->Zap(this, GetPos(), ZapDirection
)) {
2403 EditAP(-100000/APBonus(GetAttribute(PERCEPTION
)));
2413 truth
character::FollowLeader (character
*Leader
) {
2414 if (!Leader
|| Leader
== this || !IsEnabled()) return false;
2415 if ((CommandFlags
&FOLLOW_LEADER
) && Leader
->CanBeSeenBy(this) && Leader
->SquareUnderCanBeSeenBy(this, true)) {
2416 v2 Distance
= GetPos()-GoingTo
;
2417 if (abs(Distance
.X
) <= 2 && abs(Distance
.Y
) <= 2) return false;
2418 return MoveTowardsTarget(false);
2420 if (IsGoingSomeWhere()) {
2421 if (!MoveTowardsTarget(true)) {
2431 void character::SeekLeader (ccharacter
*Leader
) {
2432 if (Leader
&& Leader
!= this) {
2433 if (Leader
->CanBeSeenBy(this) && (Leader
->SquareUnderCanBeSeenBy(this, true) || !IsGoingSomeWhere())) {
2434 if (CommandFlags
&FOLLOW_LEADER
) SetGoingTo(Leader
->GetPos());
2435 } else if (!IsGoingSomeWhere()) {
2436 team
*Team
= GetTeam();
2437 for (std::list
<character
*>::const_iterator i
= Team
->GetMember().begin(); i
!= Team
->GetMember().end(); ++i
) {
2439 if (ch
->IsEnabled() && ch
->GetID() != GetID() &&
2440 (CommandFlags
& FOLLOW_LEADER
) == (ch
->CommandFlags
& FOLLOW_LEADER
) && ch
->CanBeSeenBy(this)) {
2441 v2 Pos
= ch
->GetPos();
2442 v2 Distance
= GetPos()-Pos
;
2443 if (abs(Distance
.X
) > 2 && abs(Distance
.Y
) > 2) {
2454 int character::GetMoveEase () const {
2455 if (BurdenState
== OVER_LOADED
|| BurdenState
== STRESSED
) return 50;
2456 if (BurdenState
== BURDENED
) return 75;
2457 if (BurdenState
== UNBURDENED
) return 100;
2462 int character::GetLOSRange () const {
2463 if (!game::IsInWilderness()) return GetAttribute(PERCEPTION
)*GetLevel()->GetLOSModifier()/48;
2468 truth
character::Displace (character
*Who
, truth Forced
) {
2469 if (GetBurdenState() == OVER_LOADED
) {
2471 cchar
*CrawlVerb
= StateIsActivated(LEVITATION
) ? "float" : "crawl";
2472 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb
);
2479 double Danger
= GetRelativeDanger(Who
);
2480 int PriorityDifference
= Limit(GetDisplacePriority()-Who
->GetDisplacePriority(), -31, 31);
2482 if (IsPlayer()) ++PriorityDifference
;
2483 else if (Who
->IsPlayer()) --PriorityDifference
;
2485 if (PriorityDifference
>= 0) Danger
*= 1 << PriorityDifference
;
2486 else Danger
/= 1 << -PriorityDifference
;
2488 if (IsSmall() && Who
->IsSmall() &&
2489 (Forced
|| Danger
> 1.0 || !(Who
->IsPlayer() || Who
->IsBadPath(GetPos()))) &&
2490 !IsStuck() && !Who
->IsStuck() && (!Who
->GetAction() || Who
->GetAction()->TryDisplace()) &&
2491 CanMove() && Who
->CanMove() && Who
->CanMoveOn(GetLSquareUnder())) {
2492 if (IsPlayer()) ADD_MESSAGE("You displace %s!", Who
->CHAR_DESCRIPTION(DEFINITE
));
2493 else if (Who
->IsPlayer()) ADD_MESSAGE("%s displaces you!", CHAR_DESCRIPTION(DEFINITE
));
2494 else if (CanBeSeenByPlayer() || Who
->CanBeSeenByPlayer()) ADD_MESSAGE("%s displaces %s!", CHAR_DESCRIPTION(DEFINITE
), Who
->CHAR_DESCRIPTION(DEFINITE
));
2495 lsquare
*OldSquareUnder1
[MAX_SQUARES_UNDER
];
2496 lsquare
*OldSquareUnder2
[MAX_SQUARES_UNDER
];
2497 for (int c
= 0; c
< GetSquaresUnder(); ++c
) OldSquareUnder1
[c
] = GetLSquareUnder(c
);
2498 for (int c
= 0; c
< Who
->GetSquaresUnder(); ++c
) OldSquareUnder2
[c
] = Who
->GetLSquareUnder(c
);
2500 v2 WhoPos
= Who
->GetPos();
2505 EditAP(-GetMoveAPRequirement(GetSquareUnder()->GetEntryDifficulty()) - 500);
2506 EditNP(-12*GetSquareUnder()->GetEntryDifficulty());
2507 EditExperience(AGILITY
, 75, GetSquareUnder()->GetEntryDifficulty() << 7);
2508 if (IsPlayer()) ShowNewPosInfo();
2509 if (Who
->IsPlayer()) Who
->ShowNewPosInfo();
2510 SignalStepFrom(OldSquareUnder1
);
2511 Who
->SignalStepFrom(OldSquareUnder2
);
2515 ADD_MESSAGE("%s resists!", Who
->CHAR_DESCRIPTION(DEFINITE
));
2524 void character::SetNP (sLong What
) {
2525 int OldState
= GetHungerState();
2528 int NewState
= GetHungerState();
2529 if (NewState
== STARVING
&& OldState
> STARVING
) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
2530 else if (NewState
== VERY_HUNGRY
&& OldState
> VERY_HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting very hungry."));
2531 else if (NewState
== HUNGRY
&& OldState
> HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting hungry."));
2536 void character::ShowNewPosInfo () const {
2537 msgsystem::EnterBigMessageMode();
2540 if (ivanconfig::GetAutoCenterMap()) {
2541 game::UpdateCameraX();
2542 game::UpdateCameraY();
2544 if (Pos
.X
< game::GetCamera().X
+3 || Pos
.X
>= game::GetCamera().X
+game::GetScreenXSize()-3) game::UpdateCameraX();
2545 if (Pos
.Y
< game::GetCamera().Y
+3 || Pos
.Y
>= game::GetCamera().Y
+game::GetScreenYSize()-3) game::UpdateCameraY();
2548 game::SendLOSUpdateRequest();
2549 game::DrawEverythingNoBlit();
2552 if (!game::IsInWilderness()) {
2553 if (GetLSquareUnder()->IsDark() && !game::GetSeeWholeMapCheatMode()) ADD_MESSAGE("It's dark in here!");
2555 GetLSquareUnder()->ShowSmokeMessage();
2556 itemvectorvector PileVector
;
2557 GetStackUnder()->Pile(PileVector
, this, CENTER
);
2559 if (PileVector
.size()) {
2560 truth Feel
= !GetLSquareUnder()->IsTransparent() || GetLSquareUnder()->IsDark();
2562 if (PileVector
.size() == 1) {
2564 ADD_MESSAGE("You feel %s lying here.", PileVector
[0][0]->GetName(INDEFINITE
, PileVector
[0].size()).CStr());
2566 if (ivanconfig::GetShowFullItemDesc() && PileVector
[0][0]->AllowDetailedDescription()) {
2569 PileVector
[0][0]->AddInventoryEntry(PLAYER
, text
, PileVector
[0].size(), true);
2570 //fprintf(stderr, "invdsc : [%s]\n", text.CStr());
2571 ADD_MESSAGE("%s %s lying here.", text
.CStr(), PileVector
[0].size() == 1 ? "is" : "are");
2573 ADD_MESSAGE("%s %s lying here.", PileVector
[0][0]->GetName(INDEFINITE
, PileVector
[0].size()).CStr(), PileVector
[0].size() == 1 ? "is" : "are");
2576 fprintf(stderr, "description: [%s]\n", PileVector[0][0]->GetDescription(INDEFINITE).CStr());
2577 fprintf(stderr, "strength : [%s]\n", PileVector[0][0]->GetStrengthValueDescription());
2578 fprintf(stderr, "basetohit : [%s]\n", PileVector[0][0]->GetBaseToHitValueDescription());
2579 fprintf(stderr, "baseblock : [%s]\n", PileVector[0][0]->GetBaseBlockValueDescription());
2580 fprintf(stderr, "extdsc : [%s]\n", PileVector[0][0]->GetExtendedDescription().CStr());
2585 for (uInt c
= 0; c
< PileVector
.size(); ++c
) {
2586 if ((Items
+= PileVector
[c
].size()) > 3) break;
2589 if (Feel
) ADD_MESSAGE("You feel several items lying here.");
2590 else ADD_MESSAGE("Several items are lying here.");
2592 if (Feel
) ADD_MESSAGE("You feel a few items lying here.");
2593 else ADD_MESSAGE("A few items are lying here.");
2599 GetLSquareUnder()->GetSideItemDescription(SideItems
);
2601 if (!SideItems
.IsEmpty()) ADD_MESSAGE("There is %s.", SideItems
.CStr());
2603 if (GetLSquareUnder()->HasEngravings()) {
2604 if (CanRead()) ADD_MESSAGE("Something has been engraved here: \"%s\"", GetLSquareUnder()->GetEngraved());
2605 else ADD_MESSAGE("Something has been engraved here.");
2609 msgsystem::LeaveBigMessageMode();
2613 void character::Hostility (character
*Enemy
) {
2614 if (Enemy
== this || !Enemy
|| !Team
|| !Enemy
->Team
) return;
2615 if (Enemy
->IsMasochist() && GetRelation(Enemy
) == FRIEND
) return;
2616 if (!IsAlly(Enemy
)) {
2617 GetTeam()->Hostility(Enemy
->GetTeam());
2618 } else if (IsPlayer() && !Enemy
->IsPlayer()) {
2619 // I believe both may be players due to polymorph feature...
2620 if (Enemy
->CanBeSeenByPlayer()) ADD_MESSAGE("%s becomes enraged.", Enemy
->CHAR_NAME(DEFINITE
));
2621 Enemy
->ChangeTeam(game::GetTeam(BETRAYED_TEAM
));
2626 stack
*character::GetGiftStack () const {
2627 if (GetLSquareUnder()->GetRoomIndex() && !GetLSquareUnder()->GetRoom()->AllowDropGifts()) return GetStack();
2628 return GetStackUnder();
2632 truth
character::MoveRandomlyInRoom () {
2633 for (int c
= 0; c
< 10; ++c
) {
2634 v2 ToTry
= game::GetMoveVector(RAND()&7);
2635 if (GetLevel()->IsValidPos(GetPos()+ToTry
)) {
2636 lsquare
*Square
= GetNearLSquare(GetPos()+ToTry
);
2637 if (!Square
->IsDangerous(this) && !Square
->IsScary(this) &&
2638 (!Square
->GetOLTerrain() || !Square
->GetOLTerrain()->IsDoor()) &&
2639 TryMove(ToTry
, false, false)) return true;
2646 //#define dirlogf(...) do { fprintf(stderr, __VA_ARGS__); } while (0)
2647 #define dirlogf(...) ((void)0)
2650 static const int revDir
[MDIR_STAND
] = { MDIR_DOWN_RIGHT
, MDIR_DOWN
, MDIR_DOWN_LEFT
, MDIR_RIGHT
, MDIR_LEFT
, MDIR_UP_RIGHT
, MDIR_UP
, MDIR_UP_LEFT
};
2651 static const bool orthoDir
[MDIR_STAND
] = { false, true, false, true, true, false, true, false };
2654 // only for ortho moveDir
2655 static inline truth
IsDirExcluded (int moveDir
, int dir
) {
2656 if (moveDir
== dir
) return true;
2658 case MDIR_UP
: return (dir
== MDIR_UP_LEFT
|| dir
== MDIR_UP_RIGHT
);
2659 case MDIR_LEFT
: return (dir
== MDIR_UP_LEFT
|| dir
== MDIR_DOWN_LEFT
);
2660 case MDIR_RIGHT
: return (dir
== MDIR_UP_RIGHT
|| dir
== MDIR_DOWN_RIGHT
);
2661 case MDIR_DOWN
: return (dir
== MDIR_DOWN_LEFT
|| dir
== MDIR_DOWN_RIGHT
);
2667 truth
character::IsPassableSquare (int x
, int y
) const {
2668 if (x
>= 0 && y
>= 0) {
2669 area
*ca
= GetSquareUnder()->GetArea();
2672 if (x
>= ca
->GetXSize() || y
>= ca
->GetYSize()) return false;
2673 sq
= static_cast<lsquare
*>(ca
->GetSquare(x
, y
));
2674 return sq
&& CanMoveOn(sq
);
2680 void character::CountPossibleMoveDirs (cv2 pos
, int *odirs
, int *ndirs
, int exclideDir
) const {
2681 if (odirs
) *odirs
= 0;
2682 if (ndirs
) *ndirs
= 0;
2683 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2684 if (!IsDirExcluded(exclideDir
, f
)) {
2685 if (IsPassableSquare(pos
+game::GetMoveVector(f
))) {
2687 if (odirs
) ++(*odirs
);
2689 if (ndirs
) ++(*ndirs
);
2698 * in corridor (for orto-dirs):
2699 * count dirs excluding ortho-dir we going:
2700 * if there is one or less ortho-dirs and one or less non-ortho-dirs, we are in corridor
2702 // only for ortho-dirs
2703 truth
character::IsInCorridor (int x
, int y
, int moveDir
) const {
2706 dirlogf("IsInCorridor(%d,%d,%d)\n", x
, y
, moveDir
);
2708 moveDir
= (moveDir
>= 0 && moveDir
< MDIR_STAND
? revDir
[moveDir
] : -1);
2709 dirlogf(" reversedDir: %d\n", moveDir
);
2710 CountPossibleMoveDirs(v2(x
, y
), &od
, &nd
, moveDir
);
2711 dirlogf(" possibleDirs: (%d:%d)\n", od
, nd
);
2712 dirlogf(" IsInCorridor: %s\n", ((od
<= 1 && nd
<= 1) ? "yes" : "no"));
2713 return (od
<= 1 && nd
<= 1);
2717 cv2
character::GetDiagonalForDirs (int moveDir
, int newDir
) const {
2721 case MDIR_LEFT
: return game::GetMoveVector(MDIR_UP_LEFT
);
2722 case MDIR_RIGHT
: return game::GetMoveVector(MDIR_UP_RIGHT
);
2727 case MDIR_LEFT
: return game::GetMoveVector(MDIR_DOWN_LEFT
);
2728 case MDIR_RIGHT
: return game::GetMoveVector(MDIR_DOWN_RIGHT
);
2733 case MDIR_UP
: return game::GetMoveVector(MDIR_UP_LEFT
);
2734 case MDIR_DOWN
: return game::GetMoveVector(MDIR_DOWN_LEFT
);
2739 case MDIR_UP
: return game::GetMoveVector(MDIR_UP_RIGHT
);
2740 case MDIR_DOWN
: return game::GetMoveVector(MDIR_DOWN_RIGHT
);
2744 ABORT("wtf in character::GetDiagonalForDirs()");
2748 truth
character::IsInTunnelDeadEnd () const {
2751 CountPossibleMoveDirs(GetPos(), &od
, &nd
, -1);
2752 return (od
<= 1 && nd
== 0);
2757 * try to walk in the given dir
2758 * can do two steps without a turn and still in corridor?
2762 * go in non-ortho dir, set prevdir to last ortho-dir from corridor tracing
2764 // only for ortho-dirs; assume that the char is in corridor
2765 int character::CheckCorridorMove (v2
&moveVector
, cv2 pos
, int moveDir
, truth
*markAsTurn
) const {
2766 v2
ps1(pos
+(moveVector
= game::GetMoveVector(moveDir
)));
2768 if (markAsTurn
) *markAsTurn
= true;
2770 if (IsPassableSquare(ps1
)) {
2771 // we can do first step in the given dir
2772 // check if we will be in corridor after it
2773 dirlogf("CheckCorridorMove: can do first step\n");
2774 if (IsInCorridor(ps1
, moveDir
)) {
2775 // check second step
2776 v2
ps2(ps1
+moveVector
);
2777 dirlogf("CheckCorridorMove: still in corridor after the first step\n");
2778 if (IsPassableSquare(ps2
)) {
2779 // can do second step
2780 dirlogf("CheckCorridorMove: can do second step\n");
2783 // can't do second step; but we still in corridor, so we should make a turn
2784 int newDir
= -1; // direction to turn
2785 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2786 if (f
!= moveDir
&& orthoDir
[f
] && f
!= revDir
[moveDir
] && IsPassableSquare(ps1
+game::GetMoveVector(f
))) {
2791 dirlogf("CheckCorridorMove: can't do second step; moveDir=%d; newDir=%d\n", moveDir
, newDir
);
2793 // dead end, will stop
2794 //ABORT("wtd in character::CheckCorridorMove()");
2797 // we should do diagonal move
2798 moveVector
= GetDiagonalForDirs(moveDir
, newDir
);
2799 // if this is 'one-tile-turn', we should not change the direction to newDir
2800 if (IsPassableSquare(ps1
+game::GetMoveVector(newDir
)+game::GetMoveVector(moveDir
))) {
2801 // yes, this is 'one-tile-turn'
2802 dirlogf("CheckCorridorMove: one-tile-turn, don't change dir\n");
2810 * 'g'o right: should stop at '*', but it just goes right
2812 if (markAsTurn
) *markAsTurn
= IsInCorridor(ps1
+game::GetMoveVector(newDir
), newDir
);
2818 dirlogf("CheckCorridorMove: can do one or two steps; move forward\n");
2819 // can do one or two steps: check for T-junction
2820 // we should stop if we have more than two open dirs, or one of open dirs is not moveDir
2822 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2823 if (f
== revDir
[moveDir
]) continue; // skip "reverse dir" check
2824 v2
ps2(pos
+game::GetMoveVector(f
));
2825 if (IsPassableSquare(ps2
)) {
2827 if (dcount
> 2) return -1; // more than two open dirs, stop
2828 if (f
!= moveDir
) return -1; // one of open dirs is not moveDir
2831 // just move forward
2834 dirlogf("CheckCorridorMove: dead end\n");
2835 // can't go, assume invalid direction
2840 truth
character::IsDangerousSquare (v2 pos
) const {
2841 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
2842 auto Squares
= CalculateNewSquaresUnder(MoveToSquare
, pos
);
2843 for (decltype(Squares
) c
= 0; c
< Squares
; ++c
) {
2844 lsquare
*Square
= MoveToSquare
[c
];
2845 // check if someone is standing at the square
2846 if (Square
->GetCharacter() && GetTeam() != Square
->GetCharacter()->GetTeam() && Square
->GetCharacter()->CanBeSeenBy(this)) return true;
2847 if (Square
->IsDangerous(this)) {
2848 if (IsPlayer() && Square
->HasBeenSeen()) return true;
2849 if (Square
->CanBeSeenBy(this)) return true;
2856 void character::MarkAdjacentItemsAsSeen (v2 pos
) {
2857 lsquare
*sqlist
[MAX_SQUARES_UNDER
];
2858 for (int d
= 0; d
< MDIR_STAND
; ++d
) {
2859 auto np
= pos
+game::GetMoveVector(d
);
2860 if (!IsPassableSquare(np
)) continue;
2861 auto sqcount
= CalculateNewSquaresUnder(sqlist
, np
);
2862 for (int n
= 0; n
< sqcount
; ++n
) {
2863 lsquare
*sq
= sqlist
[n
];
2864 if ((IsPlayer() && sq
->HasBeenSeen()) || sq
->CanBeSeenBy(this)) {
2865 sq
->GetStack()->SetSteppedOn(true);
2872 void character::GoOn (go
*Go
, truth FirstStep
) {
2873 dirlogf("=== character::GoOn; dir=%d; pos=(%d,%d) ===\n", Go
->GetDirection(), GetPos().X
, GetPos().Y
);
2875 dirlogf("FirstStep\n");
2876 mPrevMoveDir
= Go
->GetDirection();
2877 Go
->SetIsWalkingInOpen(!IsInCorridor(Go
->GetDirection()));
2880 v2 MoveVector
= ApplyStateModification(game::GetMoveVector(Go
->GetDirection()));
2881 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
2882 lsquare
*MoveToSquare2
[MAX_SQUARES_UNDER
];
2883 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, GetPos()+MoveVector
);
2884 int moveDir
= game::MoveVectorToDirection(MoveVector
);
2886 if (Squares
== 0 || !CanMoveOn(MoveToSquare
[0])) {
2887 dirlogf("just can't move\n");
2888 Go
->Terminate(false);
2893 // first step: mark all adjacent items as seen
2894 MarkAdjacentItemsAsSeen(GetPos());
2898 // check for corridor<->open place
2899 if (!Go
->GetPrevWasTurn() && Go
->IsWalkingInOpen() != !IsInCorridor(GetPos(), moveDir
)) {
2900 dirlogf("moved to/from open place\n");
2901 Go
->Terminate(false);
2905 // check for room change
2906 uInt OldRoomIndex
= GetLSquareUnder()->GetRoomIndex();
2907 uInt CurrentRoomIndex
= MoveToSquare
[0]->GetRoomIndex();
2908 if (OldRoomIndex
&& (CurrentRoomIndex
!= OldRoomIndex
)) {
2909 // room about to be changed, stop here
2910 dirlogf("room about to be changed\n");
2911 Go
->Terminate(false);
2915 // stop near a dangerous square
2916 if (IsDangerousSquare(GetPos()+MoveVector
)) {
2917 dirlogf("sense the danger\n");
2918 Go
->Terminate(false);
2923 // if the state modified the direction, move and stop
2924 if (moveDir
!= Go
->GetDirection()) {
2925 dirlogf("move affected by state\n");
2926 if (TryMove(MoveVector
, true, game::PlayerIsRunning())) {
2927 game::DrawEverything();
2928 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2930 Go
->Terminate(false);
2934 truth doStop
= false, markAsTurn
= false;
2936 // continuous walking
2937 if (Go
->IsWalkingInOpen() || !orthoDir
[moveDir
]) {
2938 // walking in open space or diagonal walking
2939 v2
newPos(GetPos()+MoveVector
);
2940 int ood
, ond
, nod
, nnd
;
2942 * open: stop if # of possible dirs in next step != # of possible dirs in current step
2943 * (or next step is in corridor)
2945 dirlogf("open walking\n");
2946 if (IsInCorridor(newPos
, moveDir
)) {
2947 // trying to enter the corridor, stop right here
2948 dirlogf("entering the corridor\n");
2949 Go
->Terminate(false);
2952 CountPossibleMoveDirs(GetPos(), &ood
, &ond
);
2953 CountPossibleMoveDirs(newPos
, &nod
, &nnd
);
2954 if (ood
!= nod
|| ond
!= nnd
) {
2955 // # of directions to walk to changed, stop right here
2956 dirlogf("# of directions changed from (%d:%d) to (%d:%d)\n", ood
, ond
, nod
, nnd
);
2957 //Go->Terminate(false);
2961 // ok, we can do this move
2963 // ortho-walking thru the corridor
2964 int newDir
= CheckCorridorMove(MoveVector
, GetPos(), moveDir
, &markAsTurn
);
2966 // ah, something weird; stop right here
2967 Go
->Terminate(false);
2970 Go
->SetDirection(newDir
); // perform possible turn
2973 // stop near the dangerous square
2974 for (int mdv
= 0; mdv
< MDIR_STAND
; ++mdv
) {
2975 if (IsDangerousSquare(GetPos()+MoveVector
+game::GetMoveVector(mdv
))) {
2976 dirlogf(" danger!\n");
2977 Go
->Terminate(false);
2983 // now try to perform the move
2984 dirlogf("trying to make the move\n");
2986 square
*BeginSquare
= GetSquareUnder();
2987 uInt OldRoomIndex
= GetLSquareUnder()->GetRoomIndex();
2988 uInt CurrentRoomIndex
= MoveToSquare
[0]->GetRoomIndex();
2990 // stop on the square with something interesting
2993 area
*ca
= GetSquareUnder()->GetArea();
2994 v2 npos
= GetPos()+MoveVector
;
2995 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2996 v2 np
= npos
+game::GetMoveVector(f
);
2997 if (np
.X
>= 0 && np
.Y
>= 0 && np
.X
< ca
->GetXSize() && np
.Y
< ca
->GetYSize()) {
2998 lsquare
*sq
= static_cast<lsquare
*>(ca
->GetSquare(np
.X
, np
.Y
));
2999 if (IsPlayer() && !sq
->HasBeenSeen()) continue;
3000 //if (!sq->CanBeSeenBy(this)) continue;
3001 olterrain
*terra
= sq
->GetOLTerrain();
3003 dirlogf("** OK terra at %d; door: %s; seen: %s\n", f
, (terra
->IsDoor() ? "yes" : "no"), (sq
->IsGoSeen() ? "yes" : "no"));
3004 if (terra
->IsDoor()) {
3005 if (ivanconfig::GetStopOnSeenDoors() || !sq
->IsGoSeen()) {
3006 dirlogf(" *** stop near the door\n");
3017 for (int c
= 0; c
< Squares
; ++c
) {
3018 lsquare
*Square
= MoveToSquare
[c
];
3020 if (!Square
->HasBeenSeen()) continue;
3022 if (!Square
->CanBeSeenBy(this)) continue;
3024 if (Square
->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
3025 dirlogf(" stepped near something interesting\n");
3032 // check items in adjacent squares too, so diagonal move won't miss any
3034 for (int f
= 0; f
< MDIR_STAND
&& !doStop
; ++f
) {
3035 v2 np
= game::GetMoveVector(f
);
3036 if (np
== MoveVector
) continue; // this will be checked on the next move
3037 if (!IsPassableSquare(GetPos()+np
)) continue;
3038 int sq2
= CalculateNewSquaresUnder(MoveToSquare2
, GetPos()+np
);
3039 for (int c
= 0; c
< sq2
; ++c
) {
3040 lsquare
*Square
= MoveToSquare2
[c
];
3042 if (!Square
->HasBeenSeen()) continue;
3044 if (!Square
->CanBeSeenBy(this)) continue;
3046 if (Square
->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
3047 dirlogf(" stepped near something interesting\n");
3049 Go
->Terminate(false);
3057 Go
->SetPrevWasTurn(markAsTurn
&& MoveVector
.X
&& MoveVector
.Y
); // diagonal move?
3059 truth moveOk
= TryMove(MoveVector
, true, game::PlayerIsRunning());
3061 if (!moveOk
|| BeginSquare
== GetSquareUnder() || (CurrentRoomIndex
&& (OldRoomIndex
!= CurrentRoomIndex
))) {
3062 dirlogf(" stopped\n");
3064 game::DrawEverything();
3065 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
3067 Go
->Terminate(false);
3072 mPrevMoveDir
= Go
->GetDirection();
3073 Go
->SetIsWalkingInOpen(!IsInCorridor(moveDir
));
3076 game::DrawEverything();
3077 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
3078 if (doStop
) Go
->Terminate(false);
3082 void character::SetTeam (team
*What
) {
3088 void character::ChangeTeam (team
*What
) {
3089 if (Team
) Team
->Remove(this);
3091 SendNewDrawRequest();
3092 if (Team
) Team
->Add(this);
3096 truth
character::ChangeRandomAttribute (int HowMuch
) {
3097 for (int c
= 0; c
< 50; ++c
) {
3098 int AttribID
= RAND()%ATTRIBUTES
;
3099 if (EditAttribute(AttribID
, HowMuch
)) return true;
3105 int character::RandomizeReply (sLong
&Said
, int Replies
) {
3106 truth NotSaid
= false;
3107 for (int c
= 0; c
< Replies
; ++c
) {
3108 if (!(Said
& (1 << c
))) {
3113 if (!NotSaid
) Said
= 0;
3115 while (Said
& 1 << (ToSay
= RAND() % Replies
));
3121 void character::DisplayInfo (festring
&Msg
) {
3123 Msg
<< " You are " << GetStandVerb() << " here.";
3125 Msg
<< ' ' << GetName(INDEFINITE
).CapitalizeCopy() << " is " << GetStandVerb() << " here. " << GetPersonalPronoun().CapitalizeCopy();
3126 cchar
*Separator1
= GetAction() ? "," : " and";
3127 cchar
*Separator2
= " and";
3128 if (GetTeam() == PLAYER
->GetTeam()) {
3131 int Relation
= GetRelation(PLAYER
);
3132 if (Relation
== HOSTILE
) Msg
<< " is hostile";
3133 else if (Relation
== UNCARING
) {
3134 Msg
<< " does not care about you";
3135 Separator1
= Separator2
= " and is";
3137 Msg
<< " is friendly";
3140 if (StateIsActivated(PANIC
)) {
3141 Msg
<< Separator1
<< " panicked";
3142 Separator2
= " and";
3144 if (GetAction()) Msg
<< Separator2
<< ' ' << GetAction()->GetDescription();
3150 void character::TestWalkability () {
3151 if (!IsEnabled()) return;
3152 square
*SquareUnder
= !game::IsInWilderness() ? GetSquareUnder() : PLAYER
->GetSquareUnder();
3153 if (SquareUnder
->IsFatalToStay() && !CanMoveOn(SquareUnder
)) {
3154 truth Alive
= false;
3155 if (!game::IsInWilderness() || IsPlayer()) {
3156 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
3157 square
*Square
= GetNeighbourSquare(d
);
3158 if (Square
&& CanMoveOn(Square
) && IsFreeForMe(Square
)) {
3159 if (IsPlayer()) ADD_MESSAGE("%s.", SquareUnder
->SurviveMessage(this));
3160 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE
), SquareUnder
->MonsterSurviveMessage(this));
3161 Move(Square
->GetPos(), true); // actually, this shouldn't be a teleport move
3162 SquareUnder
->SurviveEffect(this);
3172 festring DeathMsg
= festring(SquareUnder
->DeathMessage(this));
3173 game::AskForEscPress(DeathMsg
+".");
3174 festring Msg
= SquareUnder
->ScoreEntry(this);
3175 PLAYER
->AddScoreEntry(Msg
);
3178 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE
), SquareUnder
->MonsterDeathVerb(this));
3179 Die(0, SquareUnder
->ScoreEntry(this), DISALLOW_MSG
);
3186 int character::GetSize () const {
3187 if (GetTorso()->GetSize() < 1) {
3188 fprintf(stderr
, "WARNING: character::GetSize() is %d for %s!\n", GetTorso()->GetSize(), GetNameSingular().CStr());
3191 return GetTorso()->GetSize();
3195 void character::SetMainMaterial (material
*NewMaterial
, int SpecialFlags
) {
3196 NewMaterial
->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
3197 GetBodyPart(0)->SetMainMaterial(NewMaterial
, SpecialFlags
);
3198 for (int c
= 1; c
< BodyParts
; ++c
) {
3199 NewMaterial
= NewMaterial
->SpawnMore(GetBodyPart(c
)->GetMainMaterial()->GetVolume());
3200 GetBodyPart(c
)->SetMainMaterial(NewMaterial
, SpecialFlags
);
3205 void character::ChangeMainMaterial (material
*NewMaterial
, int SpecialFlags
) {
3206 NewMaterial
->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
3207 GetBodyPart(0)->ChangeMainMaterial(NewMaterial
, SpecialFlags
);
3208 for (int c
= 1; c
< BodyParts
; ++c
) {
3209 NewMaterial
= NewMaterial
->SpawnMore(GetBodyPart(c
)->GetMainMaterial()->GetVolume());
3210 GetBodyPart(c
)->ChangeMainMaterial(NewMaterial
, SpecialFlags
);
3215 void character::SetSecondaryMaterial (material
*, int) {
3216 ABORT("Illegal character::SetSecondaryMaterial call!");
3220 void character::ChangeSecondaryMaterial (material
*, int) {
3221 ABORT("Illegal character::ChangeSecondaryMaterial call!");
3225 void character::TeleportRandomly (truth Intentional
) {
3226 v2 TelePos
= ERROR_V2
;
3227 if (StateIsActivated(TELEPORT_LOCK
)) { ADD_MESSAGE("You flicker for a second."); return; }
3228 if (StateIsActivated(TELEPORT_CONTROL
)) {
3230 v2 Input
= game::PositionQuestion(CONST_S("Where do you wish to teleport? [direction keys move cursor, space accepts]"), GetPos(), &game::TeleportHandler
, 0, false);
3231 if (Input
== ERROR_V2
) Input
= GetPos(); // esc pressed
3232 lsquare
*Square
= GetNearLSquare(Input
);
3233 if (CanMoveOn(Square
) || game::GoThroughWallsCheatIsActive()) {
3234 if (Square
->GetPos() == GetPos()) {
3235 ADD_MESSAGE("You disappear and reappear.");
3238 if (IsFreeForMe(Square
)) {
3239 if ((Input
-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
3240 EditExperience(INTELLIGENCE
, 100, 1 << 10);
3243 ADD_MESSAGE("You cannot concentrate yourself enough to control a teleport that far.");
3246 character
*C
= Square
->GetCharacter();
3247 if (C
) ADD_MESSAGE("For a moment you feel very much like %s.", C
->CHAR_NAME(INDEFINITE
));
3248 else ADD_MESSAGE("You feel that something weird has happened, but can't really tell what exactly.");
3251 ADD_MESSAGE("You feel like having been hit by something really hard from the inside.");
3253 } else if (!Intentional
) {
3254 if (IsGoingSomeWhere() && GetLevel()->IsValidPos(GoingTo
)) {
3255 v2 Where
= GetLevel()->GetNearestFreeSquare(this, GoingTo
);
3256 if (Where
!= ERROR_V2
&& (Where
-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
3257 EditExperience(INTELLIGENCE
, 100, 1 << 10);
3265 ADD_MESSAGE("A rainbow-colored whirlpool twists the existence around you. You are sucked through a tunnel piercing a myriad of surreal universes. Luckily you return to this dimension in one piece.");
3268 //if (TelePos != ERROR_V2) Move(TelePos, true);
3269 //else Move(GetLevel()->GetRandomSquare(this), true);
3270 //if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE));
3271 //if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3273 if (TelePos
== ERROR_V2
) TelePos
= GetLevel()->GetRandomSquare(this);
3275 room
*PossibleRoom
= game::GetCurrentLevel()->GetLSquare(TelePos
)->GetRoom();
3277 if (!PossibleRoom
) {
3278 //if it's outside of a room
3279 if (TelePos
!= ERROR_V2
) Move(TelePos
, true); else Move(GetLevel()->GetRandomSquare(this), true);
3280 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE
));
3281 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3282 } else if (PossibleRoom
&& PossibleRoom
->IsOKToTeleportInto()) {
3283 // If it's inside of a room, check whether a ward is active that might impede the player
3284 if (TelePos
!= ERROR_V2
) Move(TelePos
, true); else Move(GetLevel()->GetRandomSquare(this), true);
3285 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE
));
3286 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3289 ADD_MESSAGE("A mighty force blasts you back to where you were standing. A ward prevents you from teleporting.");
3291 game::GetCurrentLevel()->Explosion(this, CONST_S("killed by an explosion triggered when attempting to teleport into room protected by a ward"), PLAYER
->GetPos(), 300 >> 3, false);
3296 CONST_S("killed by an explosion triggered when attempting to teleport into room protected by a ward"),
3300 lsquare* Square = GetNearLSquare(GetPos());
3301 Square->DrawParticles(RED);
3302 Square->FireBall(Beam);*/
3307 void character::DoDetecting () {
3308 material
*TempMaterial
;
3311 festring Temp
= game::DefaultQuestion(CONST_S("What material do you want to detect?"), game::GetDefaultDetectMaterial());
3312 TempMaterial
= protosystem::CreateMaterial(Temp
);
3313 if (TempMaterial
) break;
3314 game::DrawEverythingNoBlit();
3317 level
*Level
= GetLevel();
3318 int Squares
= Level
->DetectMaterial(TempMaterial
);
3320 if (Squares
> GetAttribute(INTELLIGENCE
) * (25+RAND()%51)) {
3321 ADD_MESSAGE("An enormous burst of geographical information overwhelms your consciousness. Your mind cannot cope with it and your memories blur.");
3322 Level
->BlurMemory();
3323 BeginTemporaryState(CONFUSED
, 1000 + RAND() % 1000);
3324 EditExperience(INTELLIGENCE
, -100, 1 << 12);
3325 } else if (!Squares
) {
3326 ADD_MESSAGE("You feel a sudden urge to imagine the dark void of a starless night sky.");
3327 EditExperience(INTELLIGENCE
, 200, 1 << 12);
3329 ADD_MESSAGE("You feel attracted to all things made of %s.", TempMaterial
->GetName(false, false).CStr());
3330 game::PositionQuestion(CONST_S("Detecting material [direction keys move cursor, space exits]"), GetPos(), 0, 0, false);
3331 EditExperience(INTELLIGENCE
, 300, 1 << 12);
3334 delete TempMaterial
;
3335 Level
->CalculateLuminances();
3336 game::SendLOSUpdateRequest();
3340 void character::RestoreHP () {
3341 doforbodyparts()(this, &bodypart::FastRestoreHP
);
3346 void character::RestoreLivingHP () {
3348 for (int c
= 0; c
< BodyParts
; ++c
) {
3349 bodypart
*BodyPart
= GetBodyPart(c
);
3350 if (BodyPart
&& BodyPart
->CanRegenerate()) {
3351 BodyPart
->FastRestoreHP();
3352 HP
+= BodyPart
->GetHP();
3358 truth
character::AllowDamageTypeBloodSpill (int Type
) {
3359 if ((Type
&0xFFF) == PHYSICAL_DAMAGE
) return true;
3360 if ((Type
&0xFFF) == SOUND
) return true;
3361 if ((Type
&0xFFF) == ENERGY
) return true;
3363 if ((Type
&0xFFF) == ACID
) return false;
3364 if ((Type
&0xFFF) == FIRE
) return false;
3365 if ((Type
&0xFFF) == DRAIN
) return false;
3366 if ((Type
&0xFFF) == POISON
) return false;
3367 if ((Type
&0xFFF) == ELECTRICITY
) return false;
3368 if ((Type
&0xFFF) == MUSTARD_GAS_DAMAGE
) return false;
3369 if ((Type
&0xFFF) == PSI
) return false;
3371 ABORT("Unknown blood effect destroyed the dungeon!");
3376 /* Returns truly done damage */
3377 int character::ReceiveBodyPartDamage (character
*Damager
, int Damage
, int Type
, int BodyPartIndex
,
3378 int Direction
, truth PenetrateResistance
, truth Critical
, truth ShowNoDamageMsg
, truth CaptureBodyPart
)
3380 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
3381 if (!Damager
|| Damager
->AttackMayDamageArmor()) BodyPart
->DamageArmor(Damager
, Damage
, Type
);
3382 if (!PenetrateResistance
) {
3383 Damage
-= (BodyPart
->GetTotalResistance(Type
)>>1)+RAND()%((BodyPart
->GetTotalResistance(Type
)>>1)+1);
3385 if (int(Damage
) < 1) {
3389 if (ShowNoDamageMsg
) {
3390 if (IsPlayer()) ADD_MESSAGE("You are not hurt.");
3391 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is not hurt.", GetPersonalPronoun().CStr());
3397 if (Critical
&& AllowDamageTypeBloodSpill(Type
) && !game::IsInWilderness()) {
3398 BodyPart
->SpillBlood(2+(RAND()&1));
3399 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
3400 lsquare
*Square
= GetNeighbourLSquare(d
);
3401 if (Square
&& Square
->IsFlyable()) BodyPart
->SpillBlood(1, Square
->GetPos());
3405 if (BodyPart
->ReceiveDamage(Damager
, Damage
, Type
, Direction
) && BodyPartCanBeSevered(BodyPartIndex
)) {
3406 if (DamageTypeDestroysBodyPart(Type
)) {
3407 if (IsPlayer()) ADD_MESSAGE("Your %s is destroyed!", BodyPart
->GetBodyPartName().CStr());
3408 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is destroyed!", GetPossessivePronoun().CStr(), BodyPart
->GetBodyPartName().CStr());
3409 GetBodyPart(BodyPartIndex
)->DropEquipment();
3410 item
*Severed
= SevereBodyPart(BodyPartIndex
);
3411 if (Severed
) Severed
->DestroyBodyPart(!game::IsInWilderness() ? GetStackUnder() : GetStack());
3412 SendNewDrawRequest();
3413 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart destroyed!"));
3415 if (IsPlayer()) ADD_MESSAGE("Your %s is severed off!", BodyPart
->GetBodyPartName().CStr());
3416 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is severed off!", GetPossessivePronoun().CStr(), BodyPart
->GetBodyPartName().CStr());
3417 item
*Severed
= SevereBodyPart(BodyPartIndex
);
3418 SendNewDrawRequest();
3420 if (CaptureBodyPart
) {
3421 Damager
->GetLSquareUnder()->AddItem(Severed
);
3422 } else if (!game::IsInWilderness()) {
3423 /** No multi-tile humanoid support! */
3424 GetStackUnder()->AddItem(Severed
);
3425 if (Direction
!= YOURSELF
) Severed
->Fly(0, Direction
, Damage
);
3427 GetStack()->AddItem(Severed
);
3429 Severed
->DropEquipment();
3430 } else if (IsPlayer() || CanBeSeenByPlayer()) {
3431 ADD_MESSAGE("It vanishes.");
3433 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart severed!"));
3435 if (CanPanicFromSeveredBodyPart() && RAND()%100 < GetPanicLevel() && !StateIsActivated(PANIC
) && !IsDead() && !StateIsActivated(FEARLESS
)) {
3436 BeginTemporaryState(PANIC
, 1000+RAND()%1001);
3438 SpecialBodyPartSeverReaction();
3441 if (!IsDead()) CheckPanic(500);
3447 /* Returns 0 if bodypart disappears */
3448 item
*character::SevereBodyPart (int BodyPartIndex
, truth ForceDisappearance
, stack
*EquipmentDropStack
) {
3449 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
3450 if (StateIsActivated(LEPROSY
)) BodyPart
->GetMainMaterial()->SetIsInfectedByLeprosy(true);
3451 if (ForceDisappearance
|| BodyPartsDisappearWhenSevered() || StateIsActivated(POLYMORPHED
) || game::AllBodyPartsVanish()) {
3452 BodyPart
->DropEquipment(EquipmentDropStack
);
3453 BodyPart
->RemoveFromSlot();
3454 CalculateAttributeBonuses();
3455 CalculateBattleInfo();
3456 BodyPart
->SendToHell();
3457 SignalPossibleTransparencyChange();
3458 RemoveTraps(BodyPartIndex
);
3461 BodyPart
->SetOwnerDescription("of " + GetName(INDEFINITE
));
3462 BodyPart
->SetIsUnique(LeftOversAreUnique());
3463 UpdateBodyPartPicture(BodyPartIndex
, true);
3464 BodyPart
->RemoveFromSlot();
3465 BodyPart
->RandomizePosition();
3466 CalculateAttributeBonuses();
3467 CalculateBattleInfo();
3469 SignalPossibleTransparencyChange();
3470 RemoveTraps(BodyPartIndex
);
3475 /* The second int is actually TargetFlags, which is not used here, but seems to be used in humanoid::ReceiveDamage.
3476 * Returns true if the character really receives damage */
3477 truth
character::ReceiveDamage (character
*Damager
, int Damage
, int Type
, int, int Direction
,
3478 truth
, truth PenetrateArmor
, truth Critical
, truth ShowMsg
)
3480 truth Affected
= ReceiveBodyPartDamage(Damager
, Damage
, Type
, 0, Direction
, PenetrateArmor
, Critical
, ShowMsg
);
3481 if (DamageTypeAffectsInventory(Type
)) {
3482 for (int c
= 0; c
< GetEquipments(); ++c
) {
3483 item
*Equipment
= GetEquipment(c
);
3484 if (Equipment
) Equipment
->ReceiveDamage(Damager
, Damage
, Type
);
3486 GetStack()->ReceiveDamage(Damager
, Damage
, Type
);
3492 festring
character::GetDescription (int Case
) const {
3493 if (IsPlayer()) return CONST_S("you");
3494 if (CanBeSeenByPlayer()) return GetName(Case
);
3495 return CONST_S("something");
3499 festring
character::GetPersonalPronoun (truth PlayersView
) const {
3500 if (IsPlayer() && PlayersView
) return CONST_S("you");
3501 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3502 if (GetSex() == MALE
) return CONST_S("he");
3503 return CONST_S("she");
3507 festring
character::GetPossessivePronoun (truth PlayersView
) const {
3508 if (IsPlayer() && PlayersView
) return CONST_S("your");
3509 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("its");
3510 if (GetSex() == MALE
) return CONST_S("his");
3511 return CONST_S("her");
3515 festring
character::GetObjectPronoun (truth PlayersView
) const {
3516 if (IsPlayer() && PlayersView
) return CONST_S("you");
3517 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3518 if (GetSex() == MALE
) return CONST_S("him");
3519 return CONST_S("her");
3523 void character::AddName (festring
&String
, int Case
) const {
3524 if (AssignedName
.IsEmpty()) {
3525 id::AddName(String
, Case
);
3526 } else if (!(Case
& PLURAL
)) {
3527 if (!ShowClassDescription()) {
3528 String
<< AssignedName
;
3530 String
<< AssignedName
<< ' ';
3531 id::AddName(String
, (Case
|ARTICLE_BIT
)&~INDEFINE_BIT
);
3534 id::AddName(String
, Case
);
3535 String
<< " named " << AssignedName
;
3540 int character::GetHungerState () const {
3541 if (!UsesNutrition()) return NOT_HUNGRY
;
3542 if (GetNP() > OVER_FED_LEVEL
) return OVER_FED
;
3543 if (GetNP() > BLOATED_LEVEL
) return BLOATED
;
3544 if (GetNP() > SATIATED_LEVEL
) return SATIATED
;
3545 if (GetNP() > NOT_HUNGER_LEVEL
) return NOT_HUNGRY
;
3546 if (GetNP() > HUNGER_LEVEL
) return HUNGRY
;
3547 if (GetNP() > VERY_HUNGER_LEVEL
) return VERY_HUNGRY
;
3552 truth
character::CanConsume (material
*Material
) const {
3553 return GetConsumeFlags() & Material
->GetConsumeType();
3557 void character::SetTemporaryStateCounter (sLong State
, int What
) {
3558 for (int c
= 0; c
< STATES
; ++c
) {
3559 if ((1 << c
) & State
) TemporaryStateCounter
[c
] = What
;
3564 void character::EditTemporaryStateCounter (sLong State
, int What
) {
3565 for (int c
= 0; c
< STATES
; ++c
) {
3566 if ((1 << c
) & State
) TemporaryStateCounter
[c
] += What
;
3571 int character::GetTemporaryStateCounter (sLong State
) const {
3572 for (int c
= 0; c
< STATES
; ++c
) {
3573 if ((1 << c
) & State
) return TemporaryStateCounter
[c
];
3575 ABORT("Illegal GetTemporaryStateCounter request!");
3580 truth
character::CheckKick () const {
3582 if (IsPlayer()) ADD_MESSAGE("This race can't kick.");
3589 int character::GetResistance (int Type
) const {
3590 if ((Type
&0xFFF) == PHYSICAL_DAMAGE
) return 0;
3591 if ((Type
&0xFFF) == DRAIN
) return 0;
3592 if ((Type
&0xFFF) == MUSTARD_GAS_DAMAGE
) return 0;
3593 if ((Type
&0xFFF) == PSI
) return 0;
3595 if ((Type
&0xFFF) == ENERGY
) return GetEnergyResistance();
3596 if ((Type
&0xFFF) == FIRE
) return GetFireResistance();
3597 if ((Type
&0xFFF) == POISON
) return GetPoisonResistance();
3598 if ((Type
&0xFFF) == ELECTRICITY
) return GetElectricityResistance();
3599 if ((Type
&0xFFF) == ACID
) return GetAcidResistance();
3600 if ((Type
&0xFFF) == SOUND
) return GetSoundResistance();
3602 ABORT("Resistance lack detected!");
3607 void character::Regenerate () {
3609 if (StateIsActivated(REGENERATION
) && !(RAND()%3000)) {
3610 bodypart
*NewBodyPart
= GenerateRandomBodyPart();
3611 if (!NewBodyPart
) return;
3612 NewBodyPart
->SetHP(1);
3613 if (IsPlayer()) ADD_MESSAGE("You grow a new %s.", NewBodyPart
->GetBodyPartName().CStr());
3614 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), NewBodyPart
->GetBodyPartName().CStr());
3618 sLong RegenerationBonus
= 0;
3619 truth NoHealableBodyParts
= true;
3620 for (int c
= 0; c
< BodyParts
; ++c
) {
3621 bodypart
*BodyPart
= GetBodyPart(c
);
3622 if (BodyPart
&& BodyPart
->CanRegenerate()) {
3623 RegenerationBonus
+= BodyPart
->GetMaxHP();
3624 if (NoHealableBodyParts
&& BodyPart
->GetHP() < BodyPart
->GetMaxHP()) NoHealableBodyParts
= false;
3627 if (!RegenerationBonus
|| NoHealableBodyParts
) return;
3628 RegenerationBonus
*= (50+GetAttribute(ENDURANCE
));
3630 if (Action
&& Action
->IsRest()) {
3631 if (SquaresUnder
== 1) RegenerationBonus
*= GetSquareUnder()->GetRestModifier() << 1;
3633 int Lowest
= GetSquareUnder(0)->GetRestModifier();
3634 for (int c
= 1; c
< GetSquaresUnder(); ++c
) {
3635 int Mod
= GetSquareUnder(c
)->GetRestModifier();
3636 if (Mod
< Lowest
) Lowest
= Mod
;
3638 RegenerationBonus
*= Lowest
<< 1;
3642 RegenerationCounter
+= RegenerationBonus
;
3644 while (RegenerationCounter
> 1250000) {
3645 bodypart
*BodyPart
= HealHitPoint();
3646 if (!BodyPart
) break;
3647 EditNP(-Max(7500/MaxHP
, 1));
3648 RegenerationCounter
-= 1250000;
3649 int HP
= BodyPart
->GetHP();
3650 EditExperience(ENDURANCE
, Min(1000*BodyPart
->GetMaxHP()/(HP
*HP
), 300), 1000);
3655 void character::PrintInfo () const {
3656 felist
Info(CONST_S("Information about ")+GetName(DEFINITE
));
3657 for (int c
= 0; c
< GetEquipments(); ++c
) {
3658 item
*Equipment
= GetEquipment(c
);
3659 if ((EquipmentEasilyRecognized(c
) || game::WizardModeIsActive()) && Equipment
) {
3660 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
3661 Info
.AddEntry(festring(GetEquipmentName(c
))+": "+Equipment
->GetName(INDEFINITE
), LIGHT_GRAY
, 0, ImageKey
, true);
3664 if (Info
.IsEmpty()) {
3665 ADD_MESSAGE("There's nothing special to tell about %s.", CHAR_NAME(DEFINITE
));
3667 game::SetStandardListAttributes(Info
);
3668 Info
.SetEntryDrawer(game::ItemEntryDrawer
);
3671 game::ClearItemDrawVector();
3675 truth
character::TryToRiseFromTheDead () {
3676 for (int c
= 0; c
< BodyParts
; ++c
) {
3677 bodypart
*BodyPart
= GetBodyPart(c
);
3679 BodyPart
->ResetSpoiling();
3680 if (BodyPart
->CanRegenerate() || BodyPart
->GetHP() < 1) BodyPart
->SetHP(1);
3688 truth
character::RaiseTheDead (character
*) {
3689 truth Useful
= false;
3690 for (int c
= 0; c
< BodyParts
; ++c
) {
3691 bodypart
*BodyPart
= GetBodyPart(c
);
3692 if (!BodyPart
&& CanCreateBodyPart(c
)) {
3693 CreateBodyPart(c
)->SetHP(1);
3694 if (IsPlayer()) ADD_MESSAGE("Suddenly you grow a new %s.", GetBodyPartName(c
).CStr());
3695 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), GetBodyPartName(c
).CStr());
3697 } else if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < 1) {
3702 if (IsPlayer()) ADD_MESSAGE("You shudder.");
3703 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s shudders.", CHAR_NAME(DEFINITE
));
3709 void character::SetSize (int Size
) {
3710 for (int c
= 0; c
< BodyParts
; ++c
) {
3711 bodypart
*BodyPart
= GetBodyPart(c
);
3712 if (BodyPart
) BodyPart
->SetSize(GetBodyPartSize(c
, Size
));
3717 sLong
character::GetBodyPartSize (int I
, int TotalSize
) const {
3718 if (I
== TORSO_INDEX
) return TotalSize
;
3719 ABORT("Weird bodypart size request for a character!");
3724 sLong
character::GetBodyPartVolume (int I
) const {
3725 if (I
== TORSO_INDEX
) return GetTotalVolume();
3726 ABORT("Weird bodypart volume request for a character!");
3731 void character::CreateBodyParts (int SpecialFlags
) {
3732 for (int c
= 0; c
< BodyParts
; ++c
) if (CanCreateBodyPart(c
)) CreateBodyPart(c
, SpecialFlags
);
3736 void character::RestoreBodyParts () {
3737 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) CreateBodyPart(c
);
3741 void character::UpdatePictures () {
3742 if (!PictureUpdatesAreForbidden()) for (int c
= 0; c
< BodyParts
; ++c
) UpdateBodyPartPicture(c
, false);
3746 bodypart
*character::MakeBodyPart (int I
) const {
3747 if (I
== TORSO_INDEX
) return normaltorso::Spawn(0, NO_MATERIALS
);
3748 ABORT("Weird bodypart to make for a character!");
3753 bodypart
*character::CreateBodyPart (int I
, int SpecialFlags
) {
3754 bodypart
*BodyPart
= MakeBodyPart(I
);
3755 material
*Material
= CreateBodyPartMaterial(I
, GetBodyPartVolume(I
));
3756 BodyPart
->InitMaterials(Material
, false);
3757 BodyPart
->SetSize(GetBodyPartSize(I
, GetTotalSize()));
3758 BodyPart
->SetBloodMaterial(GetBloodMaterial());
3759 BodyPart
->SetNormalMaterial(Material
->GetConfig());
3761 SetBodyPart(I
, BodyPart
);
3762 BodyPart
->InitSpecialAttributes();
3763 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdateBodyPartPicture(I
, false);
3764 if (!IsInitializing()) {
3765 CalculateBattleInfo();
3766 SendNewDrawRequest();
3767 SignalPossibleTransparencyChange();
3773 v2
character::GetBodyPartBitmapPos (int I
, truth
) const {
3774 if (I
== TORSO_INDEX
) return GetTorsoBitmapPos();
3775 ABORT("Weird bodypart BitmapPos request for a character!");
3780 void character::UpdateBodyPartPicture (int I
, truth Severed
) {
3781 bodypart
*BP
= GetBodyPart(I
);
3783 BP
->SetBitmapPos(GetBodyPartBitmapPos(I
, Severed
));
3784 BP
->GetMainMaterial()->SetSkinColor(GetBodyPartColorA(I
, Severed
));
3785 BP
->GetMainMaterial()->SetSkinColorIsSparkling(GetBodyPartSparkleFlags(I
) & SPARKLING_A
);
3786 BP
->SetMaterialColorB(GetBodyPartColorB(I
, Severed
));
3787 BP
->SetMaterialColorC(GetBodyPartColorC(I
, Severed
));
3788 BP
->SetMaterialColorD(GetBodyPartColorD(I
, Severed
));
3789 BP
->SetSparkleFlags(GetBodyPartSparkleFlags(I
));
3790 BP
->SetSpecialFlags(GetSpecialBodyPartFlags(I
));
3791 BP
->SetWobbleData(GetBodyPartWobbleData(I
));
3792 BP
->UpdatePictures();
3797 void character::LoadDataBaseStats () {
3798 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) {
3799 BaseExperience
[c
] = DataBase
->NaturalExperience
[c
];
3800 if (BaseExperience
[c
]) LimitRef(BaseExperience
[c
], MIN_EXP
, MAX_EXP
);
3802 SetMoney(GetDefaultMoney());
3803 SetInitialSweatMaterial(GetSweatMaterial());
3804 const fearray
<sLong
> &Skills
= GetKnownCWeaponSkills();
3806 const fearray
<sLong
> &Hits
= GetCWeaponSkillHits();
3807 if (Hits
.Size
== 1) {
3808 for (uInt c
= 0; c
< Skills
.Size
; ++c
) {
3809 if (Skills
[c
] < AllowedWeaponSkillCategories
) CWeaponSkill
[Skills
[c
]].AddHit(Hits
[0]*100);
3811 } else if (Hits
.Size
== Skills
.Size
) {
3812 for (uInt c
= 0; c
< Skills
.Size
; ++c
) {
3813 if (Skills
[c
] < AllowedWeaponSkillCategories
) CWeaponSkill
[Skills
[c
]].AddHit(Hits
[c
]*100);
3816 ABORT("Illegal weapon skill hit array size detected!");
3822 character
*characterprototype::SpawnAndLoad (inputfile
&SaveFile
) const {
3823 character
*Char
= Spawner(0, LOAD
);
3824 Char
->Load(SaveFile
);
3825 Char
->CalculateAll();
3830 void character::Initialize (int NewConfig
, int SpecialFlags
) {
3831 Flags
|= C_INITIALIZING
|C_IN_NO_MSG_MODE
;
3832 CalculateBodyParts();
3833 CalculateAllowedWeaponSkillCategories();
3834 CalculateSquaresUnder();
3835 BodyPartSlot
= new bodypartslot
[BodyParts
];
3836 OriginalBodyPartID
= new std::list
<feuLong
>[BodyParts
];
3837 CWeaponSkill
= new cweaponskill
[AllowedWeaponSkillCategories
];
3838 SquareUnder
= new square
*[SquaresUnder
];
3840 if (SquaresUnder
== 1) *SquareUnder
= 0; else memset(SquareUnder
, 0, SquaresUnder
*sizeof(square
*));
3842 for (int c
= 0; c
< BodyParts
; ++c
) BodyPartSlot
[c
].SetMaster(this);
3844 if (!(SpecialFlags
& LOAD
)) {
3845 ID
= game::CreateNewCharacterID(this);
3846 databasecreator
<character
>::InstallDataBase(this, NewConfig
);
3847 LoadDataBaseStats();
3848 TemporaryState
|= GetClassStates();
3849 if (TemporaryState
) {
3850 for (int c
= 0; c
< STATES
; ++c
) if (TemporaryState
& (1 << c
)) TemporaryStateCounter
[c
] = PERMANENT
;
3853 CreateBodyParts(SpecialFlags
| NO_PIC_UPDATE
);
3854 InitSpecialAttributes();
3855 CommandFlags
= GetDefaultCommandFlags();
3857 if (GetAttribute(INTELLIGENCE
, false) < 8) CommandFlags
&= ~DONT_CONSUME_ANYTHING_VALUABLE
; // gum
3858 if (!GetDefaultName().IsEmpty()) SetAssignedName(GetDefaultName());
3861 if (!(SpecialFlags
& LOAD
)) PostConstruct();
3863 if (!(SpecialFlags
& LOAD
)) {
3864 if (!(SpecialFlags
& NO_EQUIPMENT
)) CreateInitialEquipment((SpecialFlags
& NO_EQUIPMENT_PIC_UPDATE
) >> 1);
3865 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdatePictures();
3871 Flags
&= ~(C_INITIALIZING
|C_IN_NO_MSG_MODE
);
3875 truth
character::TeleportNear (character
*Caller
) {
3876 v2 Where
= GetLevel()->GetNearestFreeSquare(this, Caller
->GetPos());
3877 if (Where
== ERROR_V2
) return false;
3883 void character::ReceiveHeal (sLong Amount
) {
3885 for (c
= 0; c
< Amount
/ 10; ++c
) if (!HealHitPoint()) break;
3887 if (RAND()%10 < Amount
) HealHitPoint();
3888 if (Amount
>= 250 || RAND()%250 < Amount
) {
3889 bodypart
*NewBodyPart
= GenerateRandomBodyPart();
3890 if (!NewBodyPart
) return;
3891 NewBodyPart
->SetHP(1);
3892 if (IsPlayer()) ADD_MESSAGE("You grow a new %s.", NewBodyPart
->GetBodyPartName().CStr());
3893 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), NewBodyPart
->GetBodyPartName().CStr());
3898 void character::AddHealingLiquidConsumeEndMessage () const {
3899 if (IsPlayer()) ADD_MESSAGE("You feel better.");
3900 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks healthier.", CHAR_NAME(DEFINITE
));
3904 void character::ReceiveSchoolFood (sLong SizeOfEffect
) {
3905 SizeOfEffect
+= RAND()%SizeOfEffect
;
3906 if (SizeOfEffect
>= 250) VomitAtRandomDirection(SizeOfEffect
);
3907 if (!(RAND() % 3) && SizeOfEffect
>= 500 && EditAttribute(ENDURANCE
, SizeOfEffect
/500)) {
3908 if (IsPlayer()) ADD_MESSAGE("You gain a little bit of toughness for surviving this stuff.");
3909 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks tougher.", CHAR_NAME(DEFINITE
));
3911 BeginTemporaryState(POISONED
, (SizeOfEffect
>>1));
3915 void character::AddSchoolFoodConsumeEndMessage () const {
3916 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff tasted like vomit and old mousepads.");
3920 void character::AddSchoolFoodHitMessage () const {
3921 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff feels like vomit and old mousepads.");
3925 void character::ReceiveNutrition (sLong SizeOfEffect
) {
3926 EditNP(SizeOfEffect
);
3930 void character::ReceiveOmmelUrine (sLong Amount
) {
3931 EditExperience(ARM_STRENGTH
, 500, Amount
<<4);
3932 EditExperience(LEG_STRENGTH
, 500, Amount
<<4);
3933 if (IsPlayer()) game::DoEvilDeed(Amount
/25);
3937 void character::ReceiveOmmelCerumen (sLong Amount
) {
3938 EditExperience(INTELLIGENCE
, 500, Amount
<< 5);
3939 EditExperience(WISDOM
, 500, Amount
<< 5);
3940 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3944 void character::ReceiveOmmelSweat (sLong Amount
) {
3945 EditExperience(AGILITY
, 500, Amount
<< 4);
3946 EditExperience(DEXTERITY
, 500, Amount
<< 4);
3948 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3952 void character::ReceiveOmmelTears (sLong Amount
) {
3953 EditExperience(PERCEPTION
, 500, Amount
<< 4);
3954 EditExperience(CHARISMA
, 500, Amount
<< 4);
3955 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3959 void character::ReceiveOmmelSnot (sLong Amount
) {
3960 EditExperience(ENDURANCE
, 500, Amount
<< 5);
3962 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3966 void character::ReceiveOmmelBone (sLong Amount
) {
3967 EditExperience(ARM_STRENGTH
, 500, Amount
<< 6);
3968 EditExperience(LEG_STRENGTH
, 500, Amount
<< 6);
3969 EditExperience(DEXTERITY
, 500, Amount
<< 6);
3970 EditExperience(AGILITY
, 500, Amount
<< 6);
3971 EditExperience(ENDURANCE
, 500, Amount
<< 6);
3972 EditExperience(PERCEPTION
, 500, Amount
<< 6);
3973 EditExperience(INTELLIGENCE
, 500, Amount
<< 6);
3974 EditExperience(WISDOM
, 500, Amount
<< 6);
3975 EditExperience(CHARISMA
, 500, Amount
<< 6);
3978 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3982 void character::AddOmmelConsumeEndMessage () const {
3983 if (IsPlayer()) ADD_MESSAGE("You feel a primitive force coursing through your veins.");
3984 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks more powerful.", CHAR_NAME(DEFINITE
));
3988 void character::ReceivePepsi (sLong Amount
) {
3989 ReceiveDamage(0, Amount
/ 100, POISON
, TORSO
);
3990 EditExperience(PERCEPTION
, Amount
, 1 << 14);
3991 if (CheckDeath(CONST_S("was poisoned by pepsi"), 0)) return;
3992 if (IsPlayer()) game::DoEvilDeed(Amount
/ 10);
3996 void character::AddPepsiConsumeEndMessage () const {
3997 if (IsPlayer()) ADD_MESSAGE("Urgh. You feel your guruism fading away.");
3998 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very lame.", CHAR_NAME(DEFINITE
));
4002 void character::ReceiveDarkness (sLong Amount
) {
4003 EditExperience(INTELLIGENCE
, -Amount
/ 5, 1 << 13);
4004 EditExperience(WISDOM
, -Amount
/ 5, 1 << 13);
4005 EditExperience(CHARISMA
, -Amount
/ 5, 1 << 13);
4006 if (IsPlayer()) game::DoEvilDeed(int(Amount
/ 50));
4010 void character::AddFrogFleshConsumeEndMessage () const {
4011 if (IsPlayer()) ADD_MESSAGE("Arg. You feel the fate of a navastater placed upon you...");
4012 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks like a navastater.", CHAR_NAME(DEFINITE
));
4016 void character::ReceiveKoboldFlesh (sLong
) {
4017 /* As it is commonly known, the possibility of fainting per 500 cubic
4018 centimeters of kobold flesh is exactly 5%. */
4019 if (!(RAND() % 20)) {
4020 if (IsPlayer()) ADD_MESSAGE("You lose control of your legs and fall down.");
4021 LoseConsciousness(250 + RAND_N(250));
4026 void character::AddKoboldFleshConsumeEndMessage () const {
4027 if (IsPlayer()) ADD_MESSAGE("This stuff tasted really funny.");
4031 void character::AddKoboldFleshHitMessage () const {
4032 if (IsPlayer()) ADD_MESSAGE("You feel very funny.");
4036 void character::AddBoneConsumeEndMessage () const {
4037 if (IsPlayer()) ADD_MESSAGE("You feel like a hippie.");
4038 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s barks happily.", CHAR_NAME(DEFINITE
)); // this suspects that nobody except dogs can eat bones
4041 truth
character::RawEditAttribute (double &Experience
, int Amount
) const {
4042 /* Check if the attribute is disabled for creature */
4043 if (!Experience
) return false;
4044 if ((Amount
< 0 && Experience
< 2 * EXP_MULTIPLIER
) || (Amount
> 0 && Experience
> 999 * EXP_MULTIPLIER
)) return false;
4045 Experience
+= Amount
* EXP_MULTIPLIER
;
4046 LimitRef
<double>(Experience
, MIN_EXP
, MAX_EXP
);
4051 void character::DrawPanel (truth AnimationDraw
) const {
4052 if (AnimationDraw
) { DrawStats(true); return; }
4053 igraph::BlitBackGround(v2(19 + (game::GetScreenXSize() << 4), 0), v2(RES
.X
- 19 - (game::GetScreenXSize() << 4), RES
.Y
));
4054 igraph::BlitBackGround(v2(16, 45 + (game::GetScreenYSize() << 4)), v2(game::GetScreenXSize() << 4, 9));
4055 FONT
->Printf(DOUBLE_BUFFER
, v2(16, 45 + (game::GetScreenYSize() << 4)), WHITE
, "%s", GetPanelName().CStr());
4056 game::UpdateAttributeMemory();
4057 int PanelPosX
= RES
.X
- 96;
4058 int PanelPosY
= DrawStats(false);
4059 PrintAttribute("End", ENDURANCE
, PanelPosX
, PanelPosY
++);
4060 PrintAttribute("Per", PERCEPTION
, PanelPosX
, PanelPosY
++);
4061 PrintAttribute("Int", INTELLIGENCE
, PanelPosX
, PanelPosY
++);
4062 PrintAttribute("Wis", WISDOM
, PanelPosX
, PanelPosY
++);
4063 PrintAttribute("Wil", WILL_POWER
, PanelPosX
, PanelPosY
++);
4064 PrintAttribute("Cha", CHARISMA
, PanelPosX
, PanelPosY
++);
4065 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Siz %d", GetSize());
4066 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), IsInBadCondition() ? RED
: WHITE
, "HP %d/%d", GetHP(), GetMaxHP());
4068 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Gold: %d", GetMoney());
4071 if (game::IsInWilderness())
4072 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Worldmap");
4074 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", game::GetCurrentDungeon()->GetShortLevelDescription(game::GetCurrentLevelIndex()).CapitalizeCopy().CStr());
4077 game::GetTime(Time
);
4078 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Day %d", Time
.Day
);
4079 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Time %d:%s%d", Time
.Hour
, Time
.Min
< 10 ? "0" : "", Time
.Min
);
4080 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Turn %d", game::GetTurn());
4085 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", festring(GetAction()->GetDescription()).CapitalizeCopy().CStr());
4088 //printf("========= STATES =========\n");
4089 for (int c
= 0; c
< STATES
; ++c
) {
4090 //printf(" %d: %s (%s)\n", c, StateData[c].Description, (StateIsActivated(1<<c) ? "TAN" : "ona"));
4091 if (!(StateData
[c
].Flags
& SECRET
) && StateIsActivated(1 << c
) && (1 << c
!= HASTE
|| !StateIsActivated(SLOW
)) && (1 << c
!= SLOW
|| !StateIsActivated(HASTE
))) {
4092 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), (1 << c
) & EquipmentState
|| TemporaryStateCounter
[c
] >= PERMANENT
? BLUE
: WHITE
, "%s", StateData
[c
].Description
);
4096 auto hst
= GetHungerState();
4097 if (hst
== STARVING
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Starving");
4098 else if (hst
== VERY_HUNGRY
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Very hungry");
4099 else if (hst
== HUNGRY
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), ORANGE
, "Hungry");
4100 else if (hst
== SATIATED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Satiated");
4101 else if (hst
== BLOATED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Bloated");
4102 else if (hst
== OVER_FED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Overfed!");
4104 auto bst
= GetBurdenState();
4105 if (bst
== OVER_LOADED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Overload!");
4106 else if (bst
== STRESSED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), ORANGE
, "Stressed");
4107 else if (bst
== BURDENED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Burdened");
4109 auto trst
= GetTirednessState();
4110 if (trst
== FAINTING
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Fainting");
4111 else if (trst
== EXHAUSTED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), ORANGE
, "Exhausted");
4113 if (game::PlayerIsRunning()) {
4114 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", GetRunDescriptionLine(0));
4115 cchar
*SecondLine
= GetRunDescriptionLine(1);
4116 if (strlen(SecondLine
)) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", SecondLine
);
4121 void character::CalculateDodgeValue () {
4122 DodgeValue
= 0.05 * GetMoveEase() * GetAttribute(AGILITY
) / sqrt(GetSize());
4123 if (IsFlying()) DodgeValue
*= 2;
4124 if (DodgeValue
< 1) DodgeValue
= 1;
4128 truth
character::DamageTypeAffectsInventory (int Type
) {
4129 if ((Type
&0xFFF) == SOUND
) return true;
4130 if ((Type
&0xFFF) == ENERGY
) return true;
4131 if ((Type
&0xFFF) == ACID
) return true;
4132 if ((Type
&0xFFF) == FIRE
) return true;
4133 if ((Type
&0xFFF) == ELECTRICITY
) return true;
4135 if ((Type
&0xFFF) == PHYSICAL_DAMAGE
) return false;
4136 if ((Type
&0xFFF) == POISON
) return false;
4137 if ((Type
&0xFFF) == DRAIN
) return false;
4138 if ((Type
&0xFFF) == MUSTARD_GAS_DAMAGE
) return false;
4139 if ((Type
&0xFFF) == PSI
) return false;
4141 ABORT("Unknown reaping effect destroyed dungeon!");
4146 int character::CheckForBlockWithArm (character
*Enemy
, item
*Weapon
, arm
*Arm
,
4147 double WeaponToHitValue
, int Damage
, int Success
, int Type
)
4149 int BlockStrength
= Arm
->GetBlockCapability();
4150 double BlockValue
= Arm
->GetBlockValue();
4151 if (BlockStrength
&& BlockValue
) {
4152 item
*Blocker
= Arm
->GetWielded();
4153 if (RAND() % int(100+WeaponToHitValue
/BlockValue
/(1<<BlocksSinceLastTurn
)*(100+Success
)) < 100) {
4154 int NewDamage
= BlockStrength
< Damage
? Damage
-BlockStrength
: 0;
4155 if (Type
== UNARMED_ATTACK
) AddBlockMessage(Enemy
, Blocker
, Enemy
->UnarmedHitNoun(), NewDamage
);
4156 else if (Type
== WEAPON_ATTACK
) AddBlockMessage(Enemy
, Blocker
, "attack", NewDamage
);
4157 else if (Type
== KICK_ATTACK
) AddBlockMessage(Enemy
, Blocker
, Enemy
->KickNoun(), NewDamage
);
4158 else if (Type
== BITE_ATTACK
) AddBlockMessage(Enemy
, Blocker
, Enemy
->BiteNoun(), NewDamage
);
4159 sLong Weight
= Blocker
->GetWeight();
4160 sLong StrExp
= Limit(15 * Weight
/ 200, 75, 300);
4161 sLong DexExp
= Weight
? Limit(75000 / Weight
, 75, 300) : 300;
4162 Arm
->EditExperience(ARM_STRENGTH
, StrExp
, 1 << 8);
4163 Arm
->EditExperience(DEXTERITY
, DexExp
, 1 << 8);
4164 EditStamina(-10000 / GetAttribute(ARM_STRENGTH
), false);
4165 if (Arm
->TwoHandWieldIsActive()) {
4166 arm
*PairArm
= Arm
->GetPairArm();
4167 PairArm
->EditExperience(ARM_STRENGTH
, StrExp
, 1 << 8);
4168 PairArm
->EditExperience(DEXTERITY
, DexExp
, 1 << 8);
4170 Blocker
->WeaponSkillHit(Enemy
->CalculateWeaponSkillHits(this));
4171 Blocker
->ReceiveDamage(this, Damage
, PHYSICAL_DAMAGE
);
4172 Blocker
->BlockEffect(this, Enemy
, Weapon
, Type
);
4173 if (Weapon
) Weapon
->ReceiveDamage(Enemy
, Damage
- NewDamage
, PHYSICAL_DAMAGE
);
4174 if (BlocksSinceLastTurn
< 16) ++BlocksSinceLastTurn
;
4182 sLong
character::GetStateAPGain (sLong BaseAPGain
) const {
4183 if (!StateIsActivated(HASTE
) == !StateIsActivated(SLOW
)) return BaseAPGain
;
4184 if (StateIsActivated(HASTE
)) return (BaseAPGain
* 5) >> 2;
4185 return (BaseAPGain
<< 2) / 5;
4189 void character::SignalEquipmentAdd (int EquipmentIndex
) {
4190 item
*Equipment
= GetEquipment(EquipmentIndex
);
4191 if (Equipment
->IsInCorrectSlot(EquipmentIndex
)) {
4192 sLong AddedStates
= Equipment
->GetGearStates();
4194 for (int c
= 0; c
< STATES
; ++c
) {
4195 if (AddedStates
& (1 << c
)) {
4196 if (!StateIsActivated(1 << c
)) {
4197 if (!IsInNoMsgMode()) (this->*StateData
[c
].PrintBeginMessage
)();
4198 EquipmentState
|= 1 << c
;
4199 if (StateData
[c
].BeginHandler
) (this->*StateData
[c
].BeginHandler
)();
4201 EquipmentState
|= 1 << c
;
4207 if (!IsInitializing() && Equipment
->IsInCorrectSlot(EquipmentIndex
)) ApplyEquipmentAttributeBonuses(Equipment
);
4211 void character::SignalEquipmentRemoval (int, citem
*Item
) {
4212 CalculateEquipmentState();
4213 if (CalculateAttributeBonuses()) CheckDeath(festring("lost ")+GetPossessivePronoun(false)+" vital "+Item
->GetName(INDEFINITE
));
4217 void character::CalculateEquipmentState () {
4218 sLong Back
= EquipmentState
;
4220 for (int c
= 0; c
< GetEquipments(); ++c
) {
4221 item
*Equipment
= GetEquipment(c
);
4222 if (Equipment
&& Equipment
->IsInCorrectSlot(c
)) EquipmentState
|= Equipment
->GetGearStates();
4224 for (int c
= 0; c
< STATES
; ++c
) {
4225 if (Back
& (1 << c
) && !StateIsActivated(1 << c
)) {
4226 if (StateData
[c
].EndHandler
) {
4227 (this->*StateData
[c
].EndHandler
)();
4228 if (!IsEnabled()) return;
4230 if (!IsInNoMsgMode()) (this->*StateData
[c
].PrintEndMessage
)();
4236 /* Counter = duration in ticks */
4237 void character::BeginTemporaryState (sLong State
, int Counter
) {
4238 if (!Counter
) return;
4240 if (State
== POLYMORPHED
) ABORT("No Polymorphing with BeginTemporaryState!");
4241 for (Index
= 0; Index
< STATES
; ++Index
) if (1 << Index
== State
) break;
4242 if (Index
== STATES
) ABORT("BeginTemporaryState works only when State == 2^n!");
4243 if (TemporaryStateIsActivated(State
)) {
4244 int OldCounter
= GetTemporaryStateCounter(State
);
4245 if (OldCounter
!= PERMANENT
) EditTemporaryStateCounter(State
, Max(Counter
, 50-OldCounter
));
4246 } else if (StateData
[Index
].IsAllowed
== 0 || (this->*StateData
[Index
].IsAllowed
)()) {
4247 SetTemporaryStateCounter(State
, Max(Counter
, 50));
4248 if (!EquipmentStateIsActivated(State
)) {
4249 if (!IsInNoMsgMode()) (this->*StateData
[Index
].PrintBeginMessage
)();
4250 ActivateTemporaryState(State
);
4251 if (StateData
[Index
].BeginHandler
) (this->*StateData
[Index
].BeginHandler
)();
4253 ActivateTemporaryState(State
);
4259 void character::HandleStates () {
4260 if (!TemporaryState
&& !EquipmentState
) return;
4261 for (int c
= 0; c
< STATES
; ++c
) {
4262 if (TemporaryState
& (1 << c
) && TemporaryStateCounter
[c
] != PERMANENT
) {
4263 if (!--TemporaryStateCounter
[c
]) {
4264 TemporaryState
&= ~(1 << c
);
4265 if (!(EquipmentState
& (1 << c
))) {
4266 if (StateData
[c
].EndHandler
) {
4267 (this->*StateData
[c
].EndHandler
)();
4268 if (!IsEnabled()) return;
4270 if (!TemporaryStateCounter
[c
]) (this->*StateData
[c
].PrintEndMessage
)();
4274 if (StateIsActivated(1 << c
)) {
4275 if (StateData
[c
].Handler
) (this->*StateData
[c
].Handler
)();
4277 if (!IsEnabled()) return;
4282 void character::PrintBeginPolymorphControlMessage () const {
4283 if (IsPlayer()) ADD_MESSAGE("You feel your mind has total control over your body.");
4287 void character::PrintEndPolymorphControlMessage () const {
4288 if (IsPlayer()) ADD_MESSAGE("You are somehow uncertain of your willpower.");
4292 void character::PrintBeginLifeSaveMessage () const {
4293 if (IsPlayer()) ADD_MESSAGE("You hear Hell's gates being locked just now.");
4297 void character::PrintEndLifeSaveMessage () const {
4298 if (IsPlayer()) ADD_MESSAGE("You feel the Afterlife is welcoming you once again.");
4302 void character::PrintBeginLycanthropyMessage () const {
4303 if (IsPlayer()) ADD_MESSAGE("You suddenly notice you've always loved full moons.");
4307 void character::PrintEndLycanthropyMessage () const {
4308 if (IsPlayer()) ADD_MESSAGE("You feel the wolf inside you has had enough of your bad habits.");
4312 void character::PrintBeginVampirismMessage () const {
4313 if (IsPlayer()) ADD_MESSAGE("You suddenly decide you have always hated garlic.");
4317 void character::PrintEndVampirismMessage () const {
4318 if (IsPlayer()) ADD_MESSAGE("You recall your delight of the morning sunshine back in New Attnam. You are a vampire no longer.");
4322 void character::PrintBeginInvisibilityMessage () const {
4323 if ((PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm()) || (PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5)) {
4324 if (IsPlayer()) ADD_MESSAGE("You seem somehow transparent.");
4325 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems somehow transparent.", CHAR_NAME(DEFINITE
));
4327 if (IsPlayer()) ADD_MESSAGE("You fade away.");
4328 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears!", CHAR_NAME(DEFINITE
));
4333 void character::PrintEndInvisibilityMessage () const {
4334 if ((PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm()) || (PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5)) {
4335 if (IsPlayer()) ADD_MESSAGE("Your notice your transparency has ended.");
4336 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The appearance of %s seems far more solid now.", CHAR_NAME(INDEFINITE
));
4338 if (IsPlayer()) ADD_MESSAGE("You reappear.");
4339 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s appears from nowhere!", CHAR_NAME(INDEFINITE
));
4344 void character::PrintBeginInfraVisionMessage () const {
4346 if (StateIsActivated(INVISIBLE
) && IsWarm() && !(StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5))
4347 ADD_MESSAGE("You reappear.");
4349 ADD_MESSAGE("You feel your perception being magically altered.");
4354 void character::PrintEndInfraVisionMessage () const {
4356 if (StateIsActivated(INVISIBLE
) && IsWarm() && !(StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5))
4357 ADD_MESSAGE("You disappear.");
4359 ADD_MESSAGE("You feel your perception returning to normal.");
4364 void character::PrintBeginESPMessage () const {
4365 if (IsPlayer()) ADD_MESSAGE("You suddenly feel like being only a tiny part of a great network of intelligent minds.");
4369 void character::PrintEndESPMessage () const {
4370 if (IsPlayer()) ADD_MESSAGE("You are filled with desire to be just yourself from now on.");
4374 void character::PrintBeginHasteMessage () const {
4375 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4376 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE
));
4380 void character::PrintEndHasteMessage () const {
4381 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4382 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE
));
4386 void character::PrintBeginSlowMessage () const {
4387 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4388 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE
));
4392 void character::PrintEndSlowMessage () const {
4393 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4394 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE
));
4398 void character::EndPolymorph () {
4399 ForceEndPolymorph();
4403 character
*character::ForceEndPolymorph () {
4405 ADD_MESSAGE("You return to your true form.");
4406 } else if (game::IsInWilderness()) {
4407 ActivateTemporaryState(POLYMORPHED
);
4408 SetTemporaryStateCounter(POLYMORPHED
, 10);
4409 return this; // fast gum solution, state ends when the player enters a dungeon
4411 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s returns to %s true form.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
4413 if (GetAction()) GetAction()->Terminate(false);
4417 character
*Char
= GetPolymorphBackup();
4418 Flags
|= C_IN_NO_MSG_MODE
;
4419 Char
->Flags
|= C_IN_NO_MSG_MODE
;
4420 Char
->PutToOrNear(Pos
);
4421 Char
->ChangeTeam(GetTeam());
4422 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(Char
);
4423 SetPolymorphBackup(0);
4425 Char
->Flags
&= ~C_POLYMORPHED
;
4426 GetStack()->MoveItemsTo(Char
->GetStack());
4427 DonateEquipmentTo(Char
);
4428 Char
->SetMoney(GetMoney());
4429 Flags
&= ~C_IN_NO_MSG_MODE
;
4430 Char
->Flags
&= ~C_IN_NO_MSG_MODE
;
4431 Char
->CalculateAll();
4432 Char
->SetAssignedName(GetAssignedName());
4435 game::SetPlayer(Char
);
4436 game::SendLOSUpdateRequest();
4439 Char
->TestWalkability();
4444 void character::LycanthropyHandler () {
4445 if (StateIsActivated(POLYMORPH_LOCK
)) return;
4446 if (GetType() == werewolfwolf::ProtoType
.GetIndex()) return;
4447 if (!(RAND() % 2000)) {
4448 if (StateIsActivated(POLYMORPH_CONTROL
) && (IsPlayer() ? !game::TruthQuestion(CONST_S("Do you wish to change into a werewolf?")) : false)) return;
4449 Polymorph(werewolfwolf::Spawn(), 1000 + RAND() % 2000);
4454 void character::SaveLife () {
4455 if (TemporaryStateIsActivated(LIFE_SAVED
)) {
4457 ADD_MESSAGE("But wait! You glow briefly red and seem to be in a better shape!");
4458 else if (CanBeSeenByPlayer())
4459 ADD_MESSAGE("But wait, suddenly %s glows briefly red and seems to be in a better shape!", GetPersonalPronoun().CStr());
4460 DeActivateTemporaryState(LIFE_SAVED
);
4462 item
*LifeSaver
= 0;
4463 for (int c
= 0; c
< GetEquipments(); ++c
) {
4464 item
*Equipment
= GetEquipment(c
);
4465 if (Equipment
&& Equipment
->IsInCorrectSlot(c
) && Equipment
->GetGearStates() & LIFE_SAVED
) LifeSaver
= Equipment
;
4467 if (!LifeSaver
) ABORT("The Universe can only kill you once!");
4469 ADD_MESSAGE("But wait! Your %s glows briefly red and disappears and you seem to be in a better shape!", LifeSaver
->CHAR_NAME(UNARTICLED
));
4470 else if (CanBeSeenByPlayer())
4471 ADD_MESSAGE("But wait, suddenly %s %s glows briefly red and disappears and %s seems to be in a better shape!", GetPossessivePronoun().CStr(), LifeSaver
->CHAR_NAME(UNARTICLED
), GetPersonalPronoun().CStr());
4472 LifeSaver
->RemoveFromSlot();
4473 LifeSaver
->SendToHell();
4476 if (IsPlayer()) game::AskForEscPress(CONST_S("Life saved!"));
4484 if (GetNP() < SATIATED_LEVEL
) SetNP(SATIATED_LEVEL
);
4486 SendNewDrawRequest();
4488 if (GetAction()) GetAction()->Terminate(false);
4492 character
*character::PolymorphRandomly (int MinDanger
, int MaxDanger
, int Time
) {
4493 character
*NewForm
= 0;
4494 if (StateIsActivated(POLYMORPH_LOCK
)) { ADD_MESSAGE("You feel uncertain about your body for a moment."); return NewForm
; }
4495 if (StateIsActivated(POLYMORPH_CONTROL
)) {
4497 if (!GetNewFormForPolymorphWithControl(NewForm
)) return NewForm
;
4499 NewForm
= protosystem::CreateMonster(MinDanger
*10, MaxDanger
*10, NO_EQUIPMENT
);
4502 NewForm
= protosystem::CreateMonster(MinDanger
, MaxDanger
, NO_EQUIPMENT
);
4504 Polymorph(NewForm
, Time
);
4509 /* In reality, the reading takes Time / (Intelligence * 10) turns */
4510 void character::StartReading (item
*Item
, sLong Time
) {
4511 study
*Read
= study::Spawn(this);
4512 Read
->SetLiteratureID(Item
->GetID());
4513 if (game::WizardModeIsActive()) Time
= 1;
4514 Read
->SetCounter(Time
);
4516 if (IsPlayer()) ADD_MESSAGE("You start reading %s.", Item
->CHAR_NAME(DEFINITE
));
4517 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s starts reading %s.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(DEFINITE
));
4521 /* Call when one makes something with his/her/its hands.
4522 * Difficulty of 5 takes about one turn, so it's the most common to use. */
4523 void character::DexterityAction (int Difficulty
) {
4524 EditAP(-20000 * Difficulty
/ APBonus(GetAttribute(DEXTERITY
)));
4525 EditExperience(DEXTERITY
, Difficulty
* 15, 1 << 7);
4529 /* If Theoretically != false, range is not a factor. */
4530 truth
character::CanBeSeenByPlayer (truth Theoretically
, truth IgnoreESP
) const {
4531 if (IsEnabled() && !game::IsGenerating() && (Theoretically
|| GetSquareUnder())) {
4532 truth MayBeESPSeen
= PLAYER
->IsEnabled() && !IgnoreESP
&& PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5;
4533 truth MayBeInfraSeen
= PLAYER
->IsEnabled() && PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm();
4534 truth Visible
= !StateIsActivated(INVISIBLE
) || MayBeESPSeen
|| MayBeInfraSeen
;
4535 if (game::IsInWilderness()) return Visible
;
4536 if (MayBeESPSeen
&& (Theoretically
|| GetDistanceSquareFrom(PLAYER
) <= PLAYER
->GetESPRangeSquare())) return true;
4537 if (!Visible
) return false;
4538 return (Theoretically
|| SquareUnderCanBeSeenByPlayer(MayBeInfraSeen
));
4544 truth
character::CanBeSeenBy (ccharacter
*Who
, truth Theoretically
, truth IgnoreESP
) const {
4545 if (Who
->IsPlayer()) return CanBeSeenByPlayer(Theoretically
, IgnoreESP
);
4546 if (IsEnabled() && !game::IsGenerating() && (Theoretically
|| GetSquareUnder())) {
4547 truth MayBeESPSeen
= Who
->IsEnabled() && !IgnoreESP
&& Who
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5;
4548 truth MayBeInfraSeen
= Who
->IsEnabled() && Who
->StateIsActivated(INFRA_VISION
) && IsWarm();
4549 truth Visible
= !StateIsActivated(INVISIBLE
) || MayBeESPSeen
|| MayBeInfraSeen
;
4550 if (game::IsInWilderness()) return Visible
;
4551 if (MayBeESPSeen
&& (Theoretically
|| GetDistanceSquareFrom(Who
) <= Who
->GetESPRangeSquare())) return true;
4552 if (!Visible
) return false;
4553 return (Theoretically
|| SquareUnderCanBeSeenBy(Who
, MayBeInfraSeen
));
4559 truth
character::SquareUnderCanBeSeenByPlayer (truth IgnoreDarkness
) const {
4560 if (!GetSquareUnder()) return false;
4561 int S1
= SquaresUnder
, S2
= PLAYER
->SquaresUnder
;
4562 if (S1
== 1 && S2
== 1) {
4563 if (GetSquareUnder()->CanBeSeenByPlayer(IgnoreDarkness
)) return true;
4564 if (IgnoreDarkness
) {
4565 int LOSRangeSquare
= PLAYER
->GetLOSRangeSquare();
4566 if ((GetPos() - PLAYER
->GetPos()).GetLengthSquare() <= LOSRangeSquare
) {
4567 eyecontroller::Map
= GetLevel()->GetMap();
4568 return mapmath
<eyecontroller
>::DoLine(PLAYER
->GetPos().X
, PLAYER
->GetPos().Y
, GetPos().X
, GetPos().Y
, SKIP_FIRST
);
4573 for (int c1
= 0; c1
< S1
; ++c1
) {
4574 lsquare
*Square
= GetLSquareUnder(c1
);
4575 if (Square
->CanBeSeenByPlayer(IgnoreDarkness
)) return true;
4576 else if (IgnoreDarkness
) {
4577 v2 Pos
= Square
->GetPos();
4578 int LOSRangeSquare
= PLAYER
->GetLOSRangeSquare();
4579 for (int c2
= 0; c2
< S2
; ++c2
) {
4580 v2 PlayerPos
= PLAYER
->GetPos(c2
);
4581 if ((Pos
-PlayerPos
).GetLengthSquare() <= LOSRangeSquare
) {
4582 eyecontroller::Map
= GetLevel()->GetMap();
4583 if (mapmath
<eyecontroller
>::DoLine(PlayerPos
.X
, PlayerPos
.Y
, Pos
.X
, Pos
.Y
, SKIP_FIRST
)) return true;
4593 truth
character::SquareUnderCanBeSeenBy (ccharacter
*Who
, truth IgnoreDarkness
) const {
4594 int S1
= SquaresUnder
, S2
= Who
->SquaresUnder
;
4595 int LOSRangeSquare
= Who
->GetLOSRangeSquare();
4596 if (S1
== 1 && S2
== 1) return GetSquareUnder()->CanBeSeenFrom(Who
->GetPos(), LOSRangeSquare
, IgnoreDarkness
);
4597 for (int c1
= 0; c1
< S1
; ++c1
) {
4598 lsquare
*Square
= GetLSquareUnder(c1
);
4599 for (int c2
= 0; c2
< S2
; ++c2
) if (Square
->CanBeSeenFrom(Who
->GetPos(c2
), LOSRangeSquare
, IgnoreDarkness
)) return true;
4605 int character::GetDistanceSquareFrom (ccharacter
*Who
) const {
4606 int S1
= SquaresUnder
, S2
= Who
->SquaresUnder
;
4607 if (S1
== 1 && S2
== 1) return (GetPos() - Who
->GetPos()).GetLengthSquare();
4608 v2
MinDist(0x7FFF, 0x7FFF);
4609 int MinLength
= 0xFFFF;
4610 for (int c1
= 0; c1
< S1
; ++c1
) {
4611 for (int c2
= 0; c2
< S2
; ++c2
) {
4612 v2 Dist
= GetPos(c1
)-Who
->GetPos(c2
);
4613 if (Dist
.X
< 0) Dist
.X
= -Dist
.X
;
4614 if (Dist
.Y
< 0) Dist
.Y
= -Dist
.Y
;
4615 if (Dist
.X
<= MinDist
.X
&& Dist
.Y
<= MinDist
.Y
) {
4617 MinLength
= Dist
.GetLengthSquare();
4618 } else if (Dist
.X
< MinDist
.X
|| Dist
.Y
< MinDist
.Y
) {
4619 int Length
= Dist
.GetLengthSquare();
4620 if (Length
< MinLength
) {
4631 void character::AttachBodyPart (bodypart
*BodyPart
) {
4632 SetBodyPart(BodyPart
->GetBodyPartIndex(), BodyPart
);
4633 if (!AllowSpoil()) BodyPart
->ResetSpoiling();
4634 BodyPart
->ResetPosition();
4635 BodyPart
->UpdatePictures();
4636 CalculateAttributeBonuses();
4637 CalculateBattleInfo();
4638 SendNewDrawRequest();
4639 SignalPossibleTransparencyChange();
4643 /* Returns true if the character has all bodyparts, false if not. */
4644 truth
character::HasAllBodyParts () const {
4645 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) return false;
4650 bodypart
*character::GenerateRandomBodyPart () {
4651 int NeededBodyPart
[MAX_BODYPARTS
];
4653 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) NeededBodyPart
[Index
++] = c
;
4654 return Index
? CreateBodyPart(NeededBodyPart
[RAND() % Index
]) : 0;
4658 /* Searches the character's Stack and if it find some bodyparts there that are the character's
4659 * old bodyparts returns a stackiterator to one of them (choosen in random).
4660 * If no fitting bodyparts are found the function returns 0 */
4661 bodypart
*character::FindRandomOwnBodyPart (truth AllowNonLiving
) const {
4662 itemvector LostAndFound
;
4663 for (int c
= 0; c
< BodyParts
; ++c
) {
4664 if (!GetBodyPart(c
)) {
4665 for (std::list
<feuLong
>::iterator i
= OriginalBodyPartID
[c
].begin(); i
!= OriginalBodyPartID
[c
].end(); ++i
) {
4666 bodypart
*Found
= static_cast<bodypart
*>(SearchForItem(*i
));
4667 if (Found
&& (AllowNonLiving
|| Found
->CanRegenerate())) LostAndFound
.push_back(Found
);
4671 if (LostAndFound
.empty()) return 0;
4672 return static_cast<bodypart
*>(LostAndFound
[RAND() % LostAndFound
.size()]);
4676 void character::PrintBeginPoisonedMessage () const {
4677 if (IsPlayer()) ADD_MESSAGE("You seem to be very ill.");
4678 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very ill.", CHAR_NAME(DEFINITE
));
4682 void character::PrintEndPoisonedMessage () const {
4683 if (IsPlayer()) ADD_MESSAGE("You feel better again.");
4684 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks better.", CHAR_NAME(DEFINITE
));
4688 void character::PoisonedHandler () {
4689 if (!(RAND() % 100)) VomitAtRandomDirection(500 + RAND_N(250));
4691 for (int Used
= 0; Used
< GetTemporaryStateCounter(POISONED
); Used
+= 100) if (!(RAND() % 100)) ++Damage
;
4693 ReceiveDamage(0, Damage
, POISON
, ALL
, 8, false, false, false, false);
4694 CheckDeath(CONST_S("died of acute poisoning"), 0);
4699 truth
character::IsWarm () const {
4700 return combinebodypartpredicates()(this, &bodypart::IsWarm
, 1);
4704 truth
character::IsWarmBlooded() const
4706 return combinebodypartpredicates()(this, &bodypart::IsWarmBlooded
, 1);
4710 void character::BeginInvisibility () {
4712 SendNewDrawRequest();
4713 SignalPossibleTransparencyChange();
4717 void character::BeginInfraVision () {
4718 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4722 void character::BeginESP () {
4723 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4727 void character::EndInvisibility () {
4729 SendNewDrawRequest();
4730 SignalPossibleTransparencyChange();
4734 void character::EndInfraVision () {
4735 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4739 void character::EndESP () {
4740 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4744 void character::Draw (blitdata
&BlitData
) const {
4745 col24 L
= BlitData
.Luminance
;
4746 if (PLAYER
->IsEnabled() &&
4747 ((PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5 &&
4748 (PLAYER
->GetPos() - GetPos()).GetLengthSquare() <= PLAYER
->GetESPRangeSquare()) ||
4749 (PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm())))
4750 BlitData
.Luminance
= ivanconfig::GetContrastLuminance();
4752 DrawBodyParts(BlitData
);
4753 BlitData
.Luminance
= ivanconfig::GetContrastLuminance();
4754 BlitData
.Src
.Y
= 16;
4755 cint SquareIndex
= BlitData
.CustomData
& SQUARE_INDEX_MASK
;
4757 if (GetTeam() == PLAYER
->GetTeam() && !IsPlayer() && SquareIndex
== GetTameSymbolSquareIndex()) {
4758 BlitData
.Src
.X
= 32;
4759 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4762 if (IsFlying() && SquareIndex
== GetFlySymbolSquareIndex()) {
4763 BlitData
.Src
.X
= 128;
4764 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4767 if (IsSwimming() && SquareIndex
== GetSwimmingSymbolSquareIndex()) {
4768 BlitData
.Src
.X
= 240;
4769 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4772 if (GetAction() && GetAction()->IsUnconsciousness() && SquareIndex
== GetUnconsciousSymbolSquareIndex()) {
4773 BlitData
.Src
.X
= 224;
4774 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4777 BlitData
.Src
.X
= BlitData
.Src
.Y
= 0;
4778 BlitData
.Luminance
= L
;
4782 void character::DrawBodyParts (blitdata
&BlitData
) const {
4783 GetTorso()->Draw(BlitData
);
4787 void character::PrintBeginTeleportMessage () const {
4788 if (IsPlayer()) ADD_MESSAGE("You feel jumpy.");
4792 void character::PrintEndTeleportMessage () const {
4793 if (IsPlayer()) ADD_MESSAGE("You suddenly realize you've always preferred walking to jumping.");
4797 void character::PrintBeginDetectMessage () const {
4798 if (IsPlayer()) ADD_MESSAGE("You feel curious about your surroundings.");
4802 void character::PrintEndDetectMessage () const {
4803 if (IsPlayer()) ADD_MESSAGE("You decide to rely on your intuition from now on.");
4807 void character::TeleportHandler () {
4808 if (!(RAND() % 1500) && !game::IsInWilderness()) {
4809 if (IsPlayer()) ADD_MESSAGE("You feel an urgent spatial relocation is now appropriate.");
4810 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears.", CHAR_NAME(DEFINITE
));
4816 void character::DetectHandler () {
4818 //the AI can't be asked position questions! So only the player can hav this state really :/ a bit daft of me
4819 if (!(RAND()%3000) && !game::IsInWilderness()) {
4820 ADD_MESSAGE("Your mind wanders in search of something.");
4821 DoDetecting(); //in fact, who knows what would happen if a dark frog had the detecting state?
4827 void character::PrintBeginPolymorphMessage () const {
4828 if (IsPlayer()) ADD_MESSAGE("An uncomfortable uncertainty of who you really are overwhelms you.");
4832 void character::PrintEndPolymorphMessage () const {
4833 if (IsPlayer()) ADD_MESSAGE("You feel you are you and no one else.");
4837 void character::PolymorphHandler () {
4838 if (!(RAND() % 1500)) PolymorphRandomly(1, 999999, 200 + RAND() % 800);
4841 void character::PrintBeginTeleportControlMessage () const {
4842 if (IsPlayer()) ADD_MESSAGE("You feel very controlled.");
4846 void character::PrintEndTeleportControlMessage () const {
4847 if (IsPlayer()) ADD_MESSAGE("You feel your control slipping.");
4851 void character::DisplayStethoscopeInfo (character
*) const {
4852 felist
Info(CONST_S("Information about ") + GetDescription(DEFINITE
));
4853 AddSpecialStethoscopeInfo(Info
);
4854 Info
.AddEntry(CONST_S("Endurance: ") + GetAttribute(ENDURANCE
), LIGHT_GRAY
);
4855 Info
.AddEntry(CONST_S("Perception: ") + GetAttribute(PERCEPTION
), LIGHT_GRAY
);
4856 Info
.AddEntry(CONST_S("Intelligence: ") + GetAttribute(INTELLIGENCE
), LIGHT_GRAY
);
4857 Info
.AddEntry(CONST_S("Wisdom: ") + GetAttribute(WISDOM
), LIGHT_GRAY
);
4858 //Info.AddEntry(CONST_S("Willpower: ") + GetAttribute(WILL_POWER), LIGHT_GRAY);
4859 Info
.AddEntry(CONST_S("Charisma: ") + GetAttribute(CHARISMA
), LIGHT_GRAY
);
4860 Info
.AddEntry(CONST_S("HP: ") + GetHP() + "/" + GetMaxHP(), IsInBadCondition() ? RED
: LIGHT_GRAY
);
4861 if (GetAction()) Info
.AddEntry(festring(GetAction()->GetDescription()).CapitalizeCopy(), LIGHT_GRAY
);
4862 for (int c
= 0; c
< STATES
; ++c
) {
4863 if (StateIsActivated(1 << c
) && (1 << c
!= HASTE
|| !StateIsActivated(SLOW
)) && (1 << c
!= SLOW
|| !StateIsActivated(HASTE
)))
4864 Info
.AddEntry(StateData
[c
].Description
, LIGHT_GRAY
);
4866 switch (GetTirednessState()) {
4867 case FAINTING
: Info
.AddEntry("Fainting", RED
); break;
4868 case EXHAUSTED
: Info
.AddEntry("Exhausted", LIGHT_GRAY
); break;
4870 game::SetStandardListAttributes(Info
);
4875 truth
character::CanUseStethoscope (truth PrintReason
) const {
4876 if (PrintReason
) ADD_MESSAGE("This type of monster can't use a stethoscope.");
4881 /* Effect used by at least Sophos.
4882 * NOTICE: Doesn't check for death! */
4883 void character::TeleportSomePartsAway (int NumberToTeleport
) {
4884 if (StateIsActivated(TELEPORT_LOCK
)) {
4885 if (IsPlayer()) ADD_MESSAGE("You feel very itchy for a moment.");
4888 for (int c
= 0; c
< NumberToTeleport
; ++c
) {
4889 int RandomBodyPart
= GetRandomNonVitalBodyPart();
4890 if (RandomBodyPart
== NONE_INDEX
) {
4891 for (; c
< NumberToTeleport
; ++c
) {
4892 GetTorso()->SetHP((GetTorso()->GetHP() << 2) / 5);
4893 sLong TorsosVolume
= GetTorso()->GetMainMaterial()->GetVolume() / 10;
4894 if (!TorsosVolume
) break;
4895 sLong Amount
= (RAND() % TorsosVolume
)+1;
4896 item
*Lump
= GetTorso()->GetMainMaterial()->CreateNaturalForm(Amount
);
4897 GetTorso()->GetMainMaterial()->EditVolume(-Amount
);
4898 Lump
->MoveTo(GetNearLSquare(GetLevel()->GetRandomSquare())->GetStack());
4899 if (IsPlayer()) ADD_MESSAGE("Parts of you teleport away.");
4900 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Parts of %s teleport away.", CHAR_NAME(DEFINITE
));
4903 item
*SeveredBodyPart
= SevereBodyPart(RandomBodyPart
);
4904 if (SeveredBodyPart
) {
4905 GetNearLSquare(GetLevel()->GetRandomSquare())->AddItem(SeveredBodyPart
);
4906 SeveredBodyPart
->DropEquipment();
4907 if (IsPlayer()) ADD_MESSAGE("Your %s teleports away.", GetBodyPartName(RandomBodyPart
).CStr());
4908 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s teleports away.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4910 if (IsPlayer()) ADD_MESSAGE("Your %s disappears.", GetBodyPartName(RandomBodyPart
).CStr());
4911 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s disappears.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4918 /* Returns an index of a random bodypart that is not vital. If no non-vital bodypart is found returns NONE_INDEX */
4919 int character::GetRandomNonVitalBodyPart () const {
4920 int OKBodyPart
[MAX_BODYPARTS
];
4921 int OKBodyParts
= 0;
4922 for (int c
= 0; c
< BodyParts
; ++c
) if (GetBodyPart(c
) && !BodyPartIsVital(c
)) OKBodyPart
[OKBodyParts
++] = c
;
4923 return OKBodyParts
? OKBodyPart
[RAND() % OKBodyParts
] : NONE_INDEX
;
4927 void character::CalculateVolumeAndWeight () {
4928 Volume
= Stack
->GetVolume();
4929 Weight
= Stack
->GetWeight();
4931 CarriedWeight
= Weight
;
4932 for (int c
= 0; c
< BodyParts
; ++c
) {
4933 bodypart
*BodyPart
= GetBodyPart(c
);
4935 BodyVolume
+= BodyPart
->GetBodyPartVolume();
4936 Volume
+= BodyPart
->GetVolume();
4937 CarriedWeight
+= BodyPart
->GetCarriedWeight();
4938 Weight
+= BodyPart
->GetWeight();
4944 void character::SignalVolumeAndWeightChange () {
4945 if (!IsInitializing()) {
4946 CalculateVolumeAndWeight();
4947 if (IsEnabled()) CalculateBurdenState();
4948 if (MotherEntity
) MotherEntity
->SignalVolumeAndWeightChange();
4953 void character::SignalEmitationIncrease (col24 EmitationUpdate
) {
4954 if (game::CompareLights(EmitationUpdate
, Emitation
) > 0) {
4955 game::CombineLights(Emitation
, EmitationUpdate
);
4956 if (MotherEntity
) MotherEntity
->SignalEmitationIncrease(EmitationUpdate
);
4957 else if (SquareUnder
[0] && !game::IsInWilderness()) {
4958 for(int c
= 0; c
< GetSquaresUnder(); ++c
) GetLSquareUnder()->SignalEmitationIncrease(EmitationUpdate
);
4964 void character::SignalEmitationDecrease (col24 EmitationUpdate
) {
4965 if (game::CompareLights(EmitationUpdate
, Emitation
) >= 0 && Emitation
) {
4966 col24 Backup
= Emitation
;
4967 CalculateEmitation();
4968 if (Backup
!= Emitation
) {
4969 if (MotherEntity
) MotherEntity
->SignalEmitationDecrease(EmitationUpdate
);
4970 else if (SquareUnder
[0] && !game::IsInWilderness()) {
4971 for (int c
= 0; c
< GetSquaresUnder(); ++c
) GetLSquareUnder(c
)->SignalEmitationDecrease(EmitationUpdate
);
4978 void character::CalculateEmitation () {
4979 Emitation
= GetBaseEmitation();
4980 for (int c
= 0; c
< BodyParts
; ++c
) {
4981 bodypart
*BodyPart
= GetBodyPart(c
);
4982 if (BodyPart
) game::CombineLights(Emitation
, BodyPart
->GetEmitation());
4984 game::CombineLights(Emitation
, Stack
->GetEmitation());
4988 void character::CalculateAll () {
4989 Flags
|= C_INITIALIZING
;
4990 CalculateAttributeBonuses();
4991 CalculateVolumeAndWeight();
4992 CalculateEmitation();
4993 CalculateBodyPartMaxHPs(0);
4994 CalculateMaxStamina();
4995 CalculateBurdenState();
4996 CalculateBattleInfo();
4997 Flags
&= ~C_INITIALIZING
;
5001 void character::CalculateHP () {
5002 HP
= sumbodypartproperties()(this, &bodypart::GetHP
);
5006 void character::CalculateMaxHP () {
5007 MaxHP
= sumbodypartproperties()(this, &bodypart::GetMaxHP
);
5011 void character::CalculateBodyPartMaxHPs (feuLong Flags
) {
5012 doforbodypartswithparam
<feuLong
>()(this, &bodypart::CalculateMaxHP
, Flags
);
5018 truth
character::EditAttribute (int Identifier
, int Value
) {
5019 if (Identifier
== ENDURANCE
&& UseMaterialAttributes()) return false;
5020 if (RawEditAttribute(BaseExperience
[Identifier
], Value
)) {
5021 if (!IsInitializing()) {
5022 if (Identifier
== LEG_STRENGTH
) CalculateBurdenState();
5023 else if (Identifier
== ENDURANCE
) CalculateBodyPartMaxHPs();
5024 else if (IsPlayer() && Identifier
== PERCEPTION
) game::SendLOSUpdateRequest();
5025 else if (IsPlayerKind() && (Identifier
== INTELLIGENCE
|| Identifier
== WISDOM
|| Identifier
== CHARISMA
)) UpdatePictures();
5026 CalculateBattleInfo();
5034 truth
character::ActivateRandomState (int Flags
, int Time
, sLong Seed
) {
5036 if (Seed
) femath::SetSeed(Seed
);
5037 sLong ToBeActivated
= GetRandomState(Flags
|DUR_TEMPORARY
);
5039 if (!ToBeActivated
) return false;
5040 BeginTemporaryState(ToBeActivated
, Time
);
5045 truth
character::GainRandomIntrinsic (int Flags
) {
5046 sLong ToBeActivated
= GetRandomState(Flags
|DUR_PERMANENT
);
5047 if (!ToBeActivated
) return false;
5048 GainIntrinsic(ToBeActivated
);
5053 /* Returns 0 if state not found */
5054 sLong
character::GetRandomState (int Flags
) const {
5055 sLong OKStates
[STATES
];
5056 int NumberOfOKStates
= 0;
5057 for (int c
= 0; c
< STATES
; ++c
) {
5058 if (StateData
[c
].Flags
& Flags
& DUR_FLAGS
&& StateData
[c
].Flags
& Flags
& SRC_FLAGS
) OKStates
[NumberOfOKStates
++] = 1 << c
;
5060 return NumberOfOKStates
? OKStates
[RAND() % NumberOfOKStates
] : 0;
5064 int characterprototype::CreateSpecialConfigurations (characterdatabase
**TempConfig
, int Configs
, int Level
) {
5065 if (Level
== 0 && TempConfig
[0]->CreateDivineConfigurations
) {
5066 Configs
= databasecreator
<character
>::CreateDivineConfigurations(this, TempConfig
, Configs
);
5068 if (Level
== 1 && TempConfig
[0]->CreateUndeadConfigurations
) {
5069 for (int c
= 1; c
< protocontainer
<character
>::GetSize(); ++c
) {
5070 const character::prototype
*Proto
= protocontainer
<character
>::GetProto(c
);
5071 if (!Proto
) continue; // missing character
5072 const character::database
*const *CharacterConfigData
= Proto
->GetConfigData();
5073 if (!CharacterConfigData
) ABORT("No database entry for character <%s>!", Proto
->GetClassID());
5074 const character::database
*const* End
= CharacterConfigData
+Proto
->GetConfigSize();
5075 for (++CharacterConfigData
; CharacterConfigData
!= End
; ++CharacterConfigData
) {
5076 const character::database
*CharacterDataBase
= *CharacterConfigData
;
5077 if (CharacterDataBase
->UndeadVersions
) {
5078 character::database
* ConfigDataBase
= new character::database(**TempConfig
);
5079 festring ucfgname
= "undead ";
5080 ucfgname
<< CharacterDataBase
->CfgStrName
;
5081 ConfigDataBase
->InitDefaults(this, (c
<< 8) | CharacterDataBase
->Config
, ucfgname
);
5082 ConfigDataBase
->PostFix
<< "of ";
5083 if (CharacterDataBase
->Adjective
.GetSize()) {
5084 if (CharacterDataBase
->UsesLongAdjectiveArticle
) ConfigDataBase
->PostFix
<< "an ";
5085 else ConfigDataBase
->PostFix
<< "a ";
5086 ConfigDataBase
->PostFix
<< CharacterDataBase
->Adjective
<< ' ';
5088 if (CharacterDataBase
->UsesLongArticle
) ConfigDataBase
->PostFix
<< "an ";
5089 else ConfigDataBase
->PostFix
<< "a ";
5091 ConfigDataBase
->PostFix
<< CharacterDataBase
->NameSingular
;
5092 if (CharacterDataBase
->PostFix
.GetSize()) ConfigDataBase
->PostFix
<< ' ' << CharacterDataBase
->PostFix
;
5093 int P1
= TempConfig
[0]->UndeadAttributeModifier
;
5094 int P2
= TempConfig
[0]->UndeadVolumeModifier
;
5096 for (c2
= 0; c2
< ATTRIBUTES
; ++c2
) ConfigDataBase
->*ExpPtr
[c2
] = CharacterDataBase
->*ExpPtr
[c2
] * P1
/ 100;
5097 for (c2
= 0; c2
< EQUIPMENT_DATAS
; ++c2
) ConfigDataBase
->*EquipmentDataPtr
[c2
] = contentscript
<item
>();
5098 ConfigDataBase
->DefaultIntelligence
= 5;
5099 ConfigDataBase
->DefaultWisdom
= 5;
5100 ConfigDataBase
->DefaultCharisma
= 5;
5101 ConfigDataBase
->TotalSize
= CharacterDataBase
->TotalSize
;
5102 ConfigDataBase
->Sex
= CharacterDataBase
->Sex
;
5103 ConfigDataBase
->AttributeBonus
= CharacterDataBase
->AttributeBonus
;
5104 ConfigDataBase
->TotalVolume
= CharacterDataBase
->TotalVolume
* P2
/ 100;
5105 if (TempConfig
[0]->UndeadCopyMaterials
) {
5106 ConfigDataBase
->HeadBitmapPos
= CharacterDataBase
->HeadBitmapPos
;
5107 ConfigDataBase
->HairColor
= CharacterDataBase
->HairColor
;
5108 ConfigDataBase
->EyeColor
= CharacterDataBase
->EyeColor
;
5109 ConfigDataBase
->CapColor
= CharacterDataBase
->CapColor
;
5110 ConfigDataBase
->FleshMaterial
= CharacterDataBase
->FleshMaterial
;
5111 ConfigDataBase
->BloodMaterial
= CharacterDataBase
->BloodMaterial
;
5112 ConfigDataBase
->VomitMaterial
= CharacterDataBase
->VomitMaterial
;
5113 ConfigDataBase
->SweatMaterial
= CharacterDataBase
->SweatMaterial
;
5115 ConfigDataBase
->KnownCWeaponSkills
= CharacterDataBase
->KnownCWeaponSkills
;
5116 ConfigDataBase
->CWeaponSkillHits
= CharacterDataBase
->CWeaponSkillHits
;
5117 ConfigDataBase
->PostProcess();
5118 TempConfig
[Configs
++] = ConfigDataBase
;
5123 if (Level
== 0 && TempConfig
[0]->CreateGolemMaterialConfigurations
) {
5124 for (int c
= 1; c
< protocontainer
<material
>::GetSize(); ++c
) {
5125 const material::prototype
* Proto
= protocontainer
<material
>::GetProto(c
);
5126 if (!Proto
) continue; // missing matherial
5127 const material::database
*const* MaterialConfigData
= Proto
->GetConfigData();
5128 const material::database
*const* End
= MaterialConfigData
+ Proto
->GetConfigSize();
5129 for (++MaterialConfigData
; MaterialConfigData
!= End
; ++MaterialConfigData
) {
5130 const material::database
* MaterialDataBase
= *MaterialConfigData
;
5131 if (MaterialDataBase
->CategoryFlags
& IS_GOLEM_MATERIAL
) {
5132 character::database
* ConfigDataBase
= new character::database(**TempConfig
);
5134 gcfgname
<< MaterialDataBase
->CfgStrName
;
5135 gcfgname
<< " golem";
5136 ConfigDataBase
->InitDefaults(this, MaterialDataBase
->Config
, gcfgname
);
5137 ConfigDataBase
->Adjective
= MaterialDataBase
->NameStem
;
5138 ConfigDataBase
->UsesLongAdjectiveArticle
= MaterialDataBase
->NameFlags
& USE_AN
;
5139 ConfigDataBase
->AttachedGod
= MaterialDataBase
->AttachedGod
;
5140 TempConfig
[Configs
++] = ConfigDataBase
;
5149 double character::GetTimeToDie (ccharacter
*Enemy
, int Damage
, double ToHitValue
, truth AttackIsBlockable
, truth UseMaxHP
) const {
5150 double DodgeValue
= GetDodgeValue();
5151 if (!Enemy
->CanBeSeenBy(this, true)) ToHitValue
*= 2;
5152 if (!CanBeSeenBy(Enemy
, true)) DodgeValue
*= 2;
5153 double MinHits
= 1000;
5155 for (int c
= 0; c
< BodyParts
; ++c
) {
5156 if (BodyPartIsVital(c
) && GetBodyPart(c
)) {
5157 double Hits
= GetBodyPart(c
)->GetTimeToDie(Damage
, ToHitValue
, DodgeValue
, AttackIsBlockable
, UseMaxHP
);
5158 if (First
) { MinHits
= Hits
; First
= false; } else MinHits
= 1/(1/MinHits
+1/Hits
);
5165 double character::GetRelativeDanger (ccharacter
*Enemy
, truth UseMaxHP
) const {
5166 double Danger
= Enemy
->GetTimeToKill(this, UseMaxHP
)/GetTimeToKill(Enemy
, UseMaxHP
);
5167 int EnemyAP
= Enemy
->GetMoveAPRequirement(1);
5168 int ThisAP
= GetMoveAPRequirement(1);
5169 if (EnemyAP
> ThisAP
) Danger
*= 1.25; else if (ThisAP
> EnemyAP
) Danger
*= 0.80;
5170 if (!Enemy
->CanBeSeenBy(this, true)) Danger
*= (Enemy
->IsPlayer() ? 0.2 : 0.5);
5171 if (!CanBeSeenBy(Enemy
, true)) Danger
*= (IsPlayer() ? 5.0 : 2.0);
5172 if (GetAttribute(INTELLIGENCE
) < 10 && !IsPlayer()) Danger
*= 0.80;
5173 if (Enemy
->GetAttribute(INTELLIGENCE
) < 10 && !Enemy
->IsPlayer()) Danger
*= 1.25;
5174 return Limit(Danger
, 0.001, 1000.0);
5178 festring
character::GetBodyPartName (int I
, truth Articled
) const {
5179 if (I
== TORSO_INDEX
) return Articled
? CONST_S("a torso") : CONST_S("torso");
5180 ABORT("Illegal character bodypart name request!");
5185 item
*character::SearchForItem (feuLong ID
) const {
5186 item
*Equipment
= findequipment
<feuLong
>()(this, &item::HasID
, ID
);
5187 if (Equipment
) return Equipment
;
5188 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (i
->GetID() == ID
) return *i
;
5193 truth
character::ContentsCanBeSeenBy (ccharacter
*Viewer
) const {
5194 return (Viewer
== this);
5198 truth
character::HitEffect (character
*Enemy
, item
* Weapon
, v2 HitPos
, int Type
, int BodyPartIndex
,
5199 int Direction
, truth BlockedByArmour
, truth Critical
, int DoneDamage
)
5201 if (Weapon
) return Weapon
->HitEffect(this, Enemy
, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5202 if (Type
== UNARMED_ATTACK
) return Enemy
->SpecialUnarmedEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5203 if (Type
== KICK_ATTACK
) return Enemy
->SpecialKickEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5204 if (Type
== BITE_ATTACK
) return Enemy
->SpecialBiteEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
, Critical
, DoneDamage
);
5209 void character::WeaponSkillHit (item
*Weapon
, int Type
, int Hits
) {
5211 if (Type
== UNARMED_ATTACK
) Category
= UNARMED
;
5212 else if (Type
== WEAPON_ATTACK
) { Weapon
->WeaponSkillHit(Hits
); return; }
5213 else if (Type
== KICK_ATTACK
) Category
= KICK
;
5214 else if (Type
== BITE_ATTACK
) Category
= BITE
;
5215 else if (Type
== THROW_ATTACK
) { if (!IsHumanoid()) return; Category
= Weapon
->GetWeaponCategory(); }
5216 else { ABORT("Illegal Type %d passed to character::WeaponSkillHit()!", Type
); return; }
5217 if (GetCWeaponSkill(Category
)->AddHit(Hits
)) {
5218 CalculateBattleInfo();
5219 if (IsPlayer()) GetCWeaponSkill(Category
)->AddLevelUpMessage(Category
);
5224 /* Returns 0 if character cannot be duplicated */
5225 character
*character::Duplicate (feuLong Flags
) {
5226 if (!(Flags
& IGNORE_PROHIBITIONS
) && !CanBeCloned()) return 0;
5227 character
*Char
= GetProtoType()->Clone(this);
5228 if (Flags
& MIRROR_IMAGE
) {
5229 DuplicateEquipment(Char
, Flags
& ~IGNORE_PROHIBITIONS
);
5230 Char
->SetLifeExpectancy(Flags
>> LE_BASE_SHIFT
& LE_BASE_RANGE
, Flags
>> LE_RAND_SHIFT
& LE_RAND_RANGE
);
5232 Char
->CalculateAll();
5233 Char
->CalculateEmitation();
5234 Char
->UpdatePictures();
5235 Char
->Flags
&= ~(C_INITIALIZING
|C_IN_NO_MSG_MODE
);
5240 truth
character::TryToEquip (item
*Item
) {
5241 if (!Item
->AllowEquip() || !CanUseEquipment() || GetAttribute(WISDOM
) >= Item
->GetWearWisdomLimit() || Item
->GetSquaresUnder() != 1) {
5245 for (int e
= 0; e
< GetEquipments(); ++e
) {
5246 if (GetBodyPartOfEquipment(e
) && EquipmentIsAllowed(e
)) {
5247 sorter Sorter
= EquipmentSorter(e
);
5248 if ((Sorter
== 0 || (Item
->*Sorter
)(this)) &&
5249 ((e
!= RIGHT_WIELDED_INDEX
&& e
!= LEFT_WIELDED_INDEX
) ||
5250 Item
->IsWeapon(this) || Item
->IsShield(this)) && AllowEquipment(Item
, e
))
5252 item
*OldEquipment
= GetEquipment(e
);
5253 if (BoundToUse(OldEquipment
, e
)) continue;
5254 lsquare
*LSquareUnder
= GetLSquareUnder();
5255 stack
*StackUnder
= LSquareUnder
->GetStack();
5256 msgsystem::DisableMessages();
5257 Flags
|= C_PICTURE_UPDATES_FORBIDDEN
;
5258 LSquareUnder
->Freeze();
5259 StackUnder
->Freeze();
5260 double Danger
= GetRelativeDanger(PLAYER
);
5261 if (OldEquipment
) OldEquipment
->RemoveFromSlot();
5262 Item
->RemoveFromSlot();
5263 SetEquipment(e
, Item
);
5264 double NewDanger
= GetRelativeDanger(PLAYER
);
5265 Item
->RemoveFromSlot();
5266 StackUnder
->AddItem(Item
);
5267 if (OldEquipment
) SetEquipment(e
, OldEquipment
);
5268 msgsystem::EnableMessages();
5269 Flags
&= ~C_PICTURE_UPDATES_FORBIDDEN
;
5270 LSquareUnder
->UnFreeze();
5271 StackUnder
->UnFreeze();
5273 if (NewDanger
> Danger
|| BoundToUse(Item
, e
)) {
5274 room
*Room
= GetRoom();
5275 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
5276 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s drops %s %s and equips %s instead.", CHAR_NAME(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, OldEquipment
->CHAR_NAME(UNARTICLED
), Item
->CHAR_NAME(INDEFINITE
));
5277 if (Room
) Room
->DropItem(this, OldEquipment
, 1);
5278 OldEquipment
->MoveTo(StackUnder
);
5279 Item
->RemoveFromSlot();
5280 SetEquipment(e
, Item
);
5286 if (NewDanger
> Danger
|| (NewDanger
== Danger
&& e
!= RIGHT_WIELDED_INDEX
&& e
!= LEFT_WIELDED_INDEX
) || BoundToUse(Item
, e
)) {
5287 room
*Room
= GetRoom();
5288 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
5289 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up and equips %s.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(INDEFINITE
));
5290 Item
->RemoveFromSlot();
5291 SetEquipment(e
, Item
);
5304 truth
character::TryToConsume (item
*Item
) {
5305 return Item
->CanBeEatenByAI(this) && ConsumeItem(Item
, Item
->GetConsumeMaterial(this)->GetConsumeVerb());
5309 void character::UpdateESPLOS () const {
5310 if (StateIsActivated(ESP
) && !game::IsInWilderness()) {
5311 for (int c
= 0; c
< game::GetTeams(); ++c
) {
5312 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
5313 const character
*ch
= *i
;
5314 if (ch
->IsEnabled()) ch
->SendNewDrawRequest();
5321 int character::GetCWeaponSkillLevel (citem
*Item
) const {
5322 if (Item
->GetWeaponCategory() < GetAllowedWeaponSkillCategories()) return GetCWeaponSkill(Item
->GetWeaponCategory())->GetLevel();
5327 void character::PrintBeginPanicMessage () const {
5328 if (IsPlayer()) ADD_MESSAGE("You panic!");
5329 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s panics.", CHAR_NAME(DEFINITE
));
5333 void character::PrintEndPanicMessage () const {
5334 if (IsPlayer()) ADD_MESSAGE("You finally calm down.");
5335 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s calms down.", CHAR_NAME(DEFINITE
));
5339 void character::CheckPanic (int Ticks
) {
5340 if (GetPanicLevel() > 1 && !StateIsActivated(PANIC
) && GetHP()*100 < RAND()%(GetPanicLevel()*GetMaxHP()<<1) && !StateIsActivated(FEARLESS
)) {
5341 BeginTemporaryState(PANIC
, ((Ticks
* 3) >> 2) + RAND() % ((Ticks
>> 1) + 1)); // 25% randomness to ticks...
5346 /* returns 0 if fails else the newly created character */
5347 character
*character::DuplicateToNearestSquare (character
*Cloner
, feuLong Flags
) {
5348 character
*NewlyCreated
= Duplicate(Flags
);
5349 if (!NewlyCreated
) return 0;
5350 if (Flags
& CHANGE_TEAM
&& Cloner
) NewlyCreated
->ChangeTeam(Cloner
->GetTeam());
5351 NewlyCreated
->PutNear(GetPos());
5352 return NewlyCreated
;
5356 void character::SignalSpoil (material
*m
) {
5357 if (GetMotherEntity()) GetMotherEntity()->SignalSpoil(m
);
5358 else Disappear(0, "spoil", &item::IsVeryCloseToSpoiling
);
5362 truth
character::CanHeal () const {
5363 for (int c
= 0; c
< BodyParts
; ++c
) {
5364 bodypart
*BodyPart
= GetBodyPart(c
);
5365 if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < BodyPart
->GetMaxHP()) return true;
5371 int character::GetRelation (ccharacter
*Who
) const {
5372 return GetTeam()->GetRelation(Who
->GetTeam());
5376 truth (item::*AffectTest
[BASE_ATTRIBUTES
])() const = {
5377 &item::AffectsEndurance
,
5378 &item::AffectsPerception
,
5379 &item::AffectsIntelligence
,
5380 &item::AffectsWisdom
,
5381 &item::AffectsWillPower
,
5382 &item::AffectsCharisma
,
5387 /* Returns nonzero if endurance has decreased and death may occur */
5388 truth
character::CalculateAttributeBonuses () {
5389 doforbodyparts()(this, &bodypart::CalculateAttributeBonuses
);
5390 int BackupBonus
[BASE_ATTRIBUTES
];
5391 int BackupCarryingBonus
= CarryingBonus
;
5394 for (c1
= 0; c1
< BASE_ATTRIBUTES
; ++c1
) {
5395 BackupBonus
[c1
] = AttributeBonus
[c1
];
5396 AttributeBonus
[c1
] = 0;
5398 for (c1
= 0; c1
< GetEquipments(); ++c1
) {
5399 item
*Equipment
= GetEquipment(c1
);
5400 if (!Equipment
|| !Equipment
->IsInCorrectSlot(c1
)) continue;
5401 for (int c2
= 0; c2
< BASE_ATTRIBUTES
; ++c2
) {
5402 if ((Equipment
->*AffectTest
[c2
])()) AttributeBonus
[c2
] += Equipment
->GetEnchantment();
5404 if (Equipment
->AffectsCarryingCapacity()) CarryingBonus
+= Equipment
->GetCarryingBonus();
5407 ApplySpecialAttributeBonuses();
5409 if (IsPlayer() && !IsInitializing() && AttributeBonus
[PERCEPTION
] != BackupBonus
[PERCEPTION
]) game::SendLOSUpdateRequest();
5410 if (IsPlayer() && !IsInitializing() && AttributeBonus
[INTELLIGENCE
] != BackupBonus
[INTELLIGENCE
]) UpdateESPLOS();
5412 if (!IsInitializing() && CarryingBonus
!= BackupCarryingBonus
) CalculateBurdenState();
5414 if (!IsInitializing() && AttributeBonus
[ENDURANCE
] != BackupBonus
[ENDURANCE
]) {
5415 CalculateBodyPartMaxHPs();
5416 CalculateMaxStamina();
5417 return AttributeBonus
[ENDURANCE
] < BackupBonus
[ENDURANCE
];
5424 void character::ApplyEquipmentAttributeBonuses (item
*Equipment
) {
5425 if (Equipment
->AffectsEndurance()) {
5426 AttributeBonus
[ENDURANCE
] += Equipment
->GetEnchantment();
5427 CalculateBodyPartMaxHPs();
5428 CalculateMaxStamina();
5430 if (Equipment
->AffectsPerception()) {
5431 AttributeBonus
[PERCEPTION
] += Equipment
->GetEnchantment();
5432 if (IsPlayer()) game::SendLOSUpdateRequest();
5434 if (Equipment
->AffectsIntelligence()) {
5435 AttributeBonus
[INTELLIGENCE
] += Equipment
->GetEnchantment();
5436 if (IsPlayer()) UpdateESPLOS();
5438 if (Equipment
->AffectsWisdom()) AttributeBonus
[WISDOM
] += Equipment
->GetEnchantment();
5439 if (Equipment
->AffectsWillPower()) AttributeBonus
[WILL_POWER
] += Equipment
->GetEnchantment();
5440 if (Equipment
->AffectsCharisma()) AttributeBonus
[CHARISMA
] += Equipment
->GetEnchantment();
5441 if (Equipment
->AffectsMana()) AttributeBonus
[MANA
] += Equipment
->GetEnchantment();
5442 if (Equipment
->AffectsCarryingCapacity()) {
5443 CarryingBonus
+= Equipment
->GetCarryingBonus();
5444 CalculateBurdenState();
5449 void character::ReceiveAntidote (sLong Amount
) {
5450 if (StateIsActivated(POISONED
)) {
5451 if (GetTemporaryStateCounter(POISONED
) > Amount
) {
5452 EditTemporaryStateCounter(POISONED
, -Amount
);
5455 if (IsPlayer()) ADD_MESSAGE("Aaaah... You feel much better.");
5456 Amount
-= GetTemporaryStateCounter(POISONED
);
5457 DeActivateTemporaryState(POISONED
);
5460 if ((Amount
>= 100 || RAND_N(100) < Amount
) && StateIsActivated(PARASITIZED
)) {
5461 if (IsPlayer()) ADD_MESSAGE("Something in your belly didn't seem to like this stuff.");
5462 DeActivateTemporaryState(PARASITIZED
);
5463 Amount
-= Min(100, Amount
);
5465 if ((Amount
>= 100 || RAND_N(100) < Amount
) && StateIsActivated(LEPROSY
)) {
5466 if (IsPlayer()) ADD_MESSAGE("You are not falling to pieces anymore.");
5467 DeActivateTemporaryState(LEPROSY
);
5468 Amount
-= Min(100, Amount
);
5473 void character::AddAntidoteConsumeEndMessage () const {
5474 if (StateIsActivated(POISONED
)) {
5475 // true only if the antidote didn't cure the poison completely
5476 if (IsPlayer()) ADD_MESSAGE("Your body processes the poison in your veins with rapid speed.");
5481 truth
character::IsDead () const {
5482 for (int c
= 0; c
< BodyParts
; ++c
) {
5483 bodypart
*BodyPart
= GetBodyPart(c
);
5484 if (BodyPartIsVital(c
) && (!BodyPart
|| BodyPart
->GetHP() < 1)) return true;
5490 void character::SignalSpoilLevelChange (material
*m
) {
5491 if (GetMotherEntity()) GetMotherEntity()->SignalSpoilLevelChange(m
); else UpdatePictures();
5495 void character::AddOriginalBodyPartID (int I
, feuLong What
) {
5496 if (std::find(OriginalBodyPartID
[I
].begin(), OriginalBodyPartID
[I
].end(), What
) == OriginalBodyPartID
[I
].end()) {
5497 OriginalBodyPartID
[I
].push_back(What
);
5498 if (OriginalBodyPartID
[I
].size() > 100) OriginalBodyPartID
[I
].erase(OriginalBodyPartID
[I
].begin());
5503 void character::AddToInventory (const fearray
<contentscript
<item
> > &ItemArray
, int SpecialFlags
) {
5504 for (uInt c1
= 0; c1
< ItemArray
.Size
; ++c1
) {
5505 if (ItemArray
[c1
].IsValid()) {
5506 const interval
*TimesPtr
= ItemArray
[c1
].GetTimes();
5507 int Times
= TimesPtr
? TimesPtr
->Randomize() : 1;
5508 for (int c2
= 0; c2
< Times
; ++c2
) {
5509 item
*Item
= ItemArray
[c1
].Instantiate(SpecialFlags
);
5511 Stack
->AddItem(Item
);
5512 Item
->SpecialGenerationHandler();
5520 truth
character::HasHadBodyPart (citem
*Item
) const {
5521 for (int c
= 0; c
< BodyParts
; ++c
)
5522 if (std::find(OriginalBodyPartID
[c
].begin(), OriginalBodyPartID
[c
].end(), Item
->GetID()) != OriginalBodyPartID
[c
].end())
5524 return GetPolymorphBackup() && GetPolymorphBackup()->HasHadBodyPart(Item
);
5528 festring
&character::ProcessMessage (festring
&Msg
) const {
5529 SEARCH_N_REPLACE(Msg
, "@nu", GetName(UNARTICLED
));
5530 SEARCH_N_REPLACE(Msg
, "@ni", GetName(INDEFINITE
));
5531 SEARCH_N_REPLACE(Msg
, "@nd", GetName(DEFINITE
));
5532 SEARCH_N_REPLACE(Msg
, "@du", GetDescription(UNARTICLED
));
5533 SEARCH_N_REPLACE(Msg
, "@di", GetDescription(INDEFINITE
));
5534 SEARCH_N_REPLACE(Msg
, "@dd", GetDescription(DEFINITE
));
5535 SEARCH_N_REPLACE(Msg
, "@pp", GetPersonalPronoun());
5536 SEARCH_N_REPLACE(Msg
, "@sp", GetPossessivePronoun());
5537 SEARCH_N_REPLACE(Msg
, "@op", GetObjectPronoun());
5538 SEARCH_N_REPLACE(Msg
, "@Nu", GetName(UNARTICLED
).CapitalizeCopy());
5539 SEARCH_N_REPLACE(Msg
, "@Ni", GetName(INDEFINITE
).CapitalizeCopy());
5540 SEARCH_N_REPLACE(Msg
, "@Nd", GetName(DEFINITE
).CapitalizeCopy());
5541 SEARCH_N_REPLACE(Msg
, "@Du", GetDescription(UNARTICLED
).CapitalizeCopy());
5542 SEARCH_N_REPLACE(Msg
, "@Di", GetDescription(INDEFINITE
).CapitalizeCopy());
5543 SEARCH_N_REPLACE(Msg
, "@Dd", GetDescription(DEFINITE
).CapitalizeCopy());
5544 SEARCH_N_REPLACE(Msg
, "@Pp", GetPersonalPronoun().CapitalizeCopy());
5545 SEARCH_N_REPLACE(Msg
, "@Sp", GetPossessivePronoun().CapitalizeCopy());
5546 SEARCH_N_REPLACE(Msg
, "@Op", GetObjectPronoun().CapitalizeCopy());
5547 SEARCH_N_REPLACE(Msg
, "@Gd", GetMasterGod()->GetName());
5552 void character::ProcessAndAddMessage (festring Msg
) const {
5553 ADD_MESSAGE("%s", ProcessMessage(Msg
).CStr());
5557 void character::BeTalkedTo () {
5559 if (GetRelation(PLAYER
) == HOSTILE
)
5560 ProcessAndAddMessage(GetHostileReplies()[RandomizeReply(Said
, GetHostileReplies().Size
)]);
5562 ProcessAndAddMessage(GetFriendlyReplies()[RandomizeReply(Said
, GetFriendlyReplies().Size
)]);
5566 truth
character::CheckZap () {
5568 ADD_MESSAGE("This monster type can't zap.");
5575 void character::DamageAllItems (character
*Damager
, int Damage
, int Type
) {
5576 GetStack()->ReceiveDamage(Damager
, Damage
, Type
);
5577 for (int c
= 0; c
< GetEquipments(); ++c
) {
5578 item
*Equipment
= GetEquipment(c
);
5579 if (Equipment
) Equipment
->ReceiveDamage(Damager
, Damage
, Type
);
5584 truth
character::Equips (citem
*Item
) const {
5585 return combineequipmentpredicateswithparam
<feuLong
>()(this, &item::HasID
, Item
->GetID(), 1);
5589 void character::PrintBeginConfuseMessage () const {
5590 if (IsPlayer()) ADD_MESSAGE("You feel quite happy.");
5594 void character::PrintEndConfuseMessage () const {
5595 if (IsPlayer()) ADD_MESSAGE("The world is boring again.");
5599 v2
character::ApplyStateModification (v2 TryDirection
) const {
5600 if (!StateIsActivated(CONFUSED
) || RAND() & 15 || game::IsInWilderness()) return TryDirection
;
5601 v2 To
= GetLevel()->GetFreeAdjacentSquare(this, GetPos(), true);
5602 if (To
== ERROR_V2
) return TryDirection
;
5604 if (To
!= TryDirection
&& IsPlayer()) ADD_MESSAGE("Whoa! You somehow don't manage to walk straight.");
5609 void character::AddConfuseHitMessage () const {
5610 if (IsPlayer()) ADD_MESSAGE("This stuff is confusing.");
5614 item
*character::SelectFromPossessions (cfestring
&Topic
, sorter Sorter
) {
5615 itemvector ReturnVector
;
5616 SelectFromPossessions(ReturnVector
, Topic
, NO_MULTI_SELECT
, Sorter
);
5617 return !ReturnVector
.empty() ? ReturnVector
[0] : 0;
5621 truth
character::SelectFromPossessions (itemvector
&ReturnVector
, cfestring
&Topic
, int Flags
, sorter Sorter
) {
5623 truth InventoryPossible
= GetStack()->SortedItems(this, Sorter
);
5624 if (InventoryPossible
) List
.AddEntry(CONST_S("choose from inventory"), LIGHT_GRAY
, 20, game::AddToItemDrawVector(itemvector()));
5629 for (c
= 0; c
< BodyParts
; ++c
) {
5630 bodypart
*BodyPart
= GetBodyPart(c
);
5631 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) {
5632 Item
.push_back(BodyPart
);
5634 BodyPart
->AddName(Entry
, UNARTICLED
);
5635 int ImageKey
= game::AddToItemDrawVector(itemvector(1, BodyPart
));
5636 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
5640 for (c
= 0; c
< GetEquipments(); ++c
) {
5641 item
*Equipment
= GetEquipment(c
);
5642 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) {
5643 Item
.push_back(Equipment
);
5644 Entry
= GetEquipmentName(c
);
5647 Equipment
->AddInventoryEntry(this, Entry
, 1, true);
5648 AddSpecialEquipmentInfo(Entry
, c
);
5649 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
5650 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
5655 game::SetStandardListAttributes(List
);
5656 List
.SetFlags(SELECTABLE
|DRAW_BACKGROUND_AFTERWARDS
);
5657 List
.SetEntryDrawer(game::ItemEntryDrawer
);
5658 game::DrawEverythingNoBlit();
5659 int Chosen
= List
.Draw();
5660 game::ClearItemDrawVector();
5661 if (Chosen
!= ESCAPED
) {
5662 if ((InventoryPossible
&& !Chosen
) || Chosen
& FELIST_ERROR_BIT
) {
5663 GetStack()->DrawContents(ReturnVector
, this, Topic
, Flags
, Sorter
);
5665 ReturnVector
.push_back(Item
[InventoryPossible
? Chosen
- 1 : Chosen
]);
5666 if (Flags
& SELECT_PAIR
&& ReturnVector
[0]->HandleInPairs()) {
5667 item
*PairEquipment
= GetPairEquipment(ReturnVector
[0]->GetEquipmentIndex());
5668 if (PairEquipment
&& PairEquipment
->CanBePiledWith(ReturnVector
[0], this)) ReturnVector
.push_back(PairEquipment
);
5673 if (!GetStack()->SortedItems(this, Sorter
)) return false;
5674 game::ClearItemDrawVector();
5675 GetStack()->DrawContents(ReturnVector
, this, Topic
, Flags
, Sorter
);
5681 truth
character::EquipsSomething (sorter Sorter
) {
5682 for (int c
= 0; c
< GetEquipments(); ++c
) {
5683 item
*Equipment
= GetEquipment(c
);
5684 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) return true;
5690 material
*character::CreateBodyPartMaterial (int, sLong Volume
) const {
5691 return MAKE_MATERIAL(GetFleshMaterial(), Volume
);
5695 truth
character::CheckTalk () {
5697 ADD_MESSAGE("This monster does not know the art of talking.");
5704 truth
character::MoveTowardsHomePos () {
5705 if (HomeDataIsValid() && IsEnabled()) {
5706 SetGoingTo(HomeData
->Pos
);
5707 return MoveTowardsTarget(false) || (!GetPos().IsAdjacent(HomeData
->Pos
) && MoveRandomly());
5713 int character::TryToChangeEquipment (stack
*MainStack
, stack
*SecStack
, int Chosen
) {
5714 if (!GetBodyPartOfEquipment(Chosen
)) {
5715 ADD_MESSAGE("Bodypart missing!");
5719 item
*OldEquipment
= GetEquipment(Chosen
);
5720 if (!IsPlayer() && BoundToUse(OldEquipment
, Chosen
)) {
5721 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE
), OldEquipment
->CHAR_NAME(DEFINITE
));
5724 if (OldEquipment
) OldEquipment
->MoveTo(MainStack
);
5726 sorter Sorter
= EquipmentSorter(Chosen
);
5727 if (!MainStack
->SortedItems(this, Sorter
) && (!SecStack
|| !SecStack
->SortedItems(this, Sorter
))) {
5728 ADD_MESSAGE("You haven't got any item that could be used for this purpose.");
5732 game::DrawEverythingNoBlit();
5733 itemvector ItemVector
;
5734 int Return
= MainStack
->DrawContents(ItemVector
, SecStack
, this,
5735 CONST_S("Choose ")+GetEquipmentName(Chosen
)+':',
5736 (SecStack
? CONST_S("Items in your inventory") : CONST_S("")),
5737 (SecStack
? festring(CONST_S("Items in ")+GetPossessivePronoun()+" inventory") : CONST_S("")),
5738 (SecStack
? festring(GetDescription(DEFINITE
)+" is "+GetVerbalBurdenState()) : CONST_S("")),
5739 GetVerbalBurdenStateColor(),
5740 NONE_AS_CHOICE
|NO_MULTI_SELECT
|SELECT_PAIR
|SKIP_FIRST_IF_NO_OLD
|SELECT_MOST_RECENT
,
5741 Sorter
, OldEquipment
);
5742 if (Return
== ESCAPED
) {
5744 OldEquipment
->RemoveFromSlot();
5745 SetEquipment(Chosen
, OldEquipment
);
5750 item
*Item
= (ItemVector
.empty() ? 0 : ItemVector
[0]);
5751 int otherChosen
= -1;
5754 if (!IsPlayer() && !AllowEquipment(Item
, Chosen
)) {
5755 ADD_MESSAGE("%s refuses to equip %s.", CHAR_DESCRIPTION(DEFINITE
), Item
->CHAR_NAME(DEFINITE
));
5758 if (ItemVector
[0]->HandleInPairs() && ItemVector
.size() > 1) {
5760 case RIGHT_GAUNTLET_INDEX
: otherChosen
= LEFT_GAUNTLET_INDEX
; break;
5761 case LEFT_GAUNTLET_INDEX
: otherChosen
= RIGHT_GAUNTLET_INDEX
; break;
5762 case RIGHT_BOOT_INDEX
: otherChosen
= LEFT_BOOT_INDEX
; break;
5763 case LEFT_BOOT_INDEX
: otherChosen
= RIGHT_BOOT_INDEX
; break;
5766 if (otherChosen
!= -1) {
5767 if (GetBodyPartOfEquipment(otherChosen
)) {
5768 if (!game::TruthQuestion("Do you want to wear both items?", NO
)) otherChosen
= -1;
5774 // wear/wield first item
5775 Item
->RemoveFromSlot();
5776 SetEquipment(Chosen
, Item
);
5777 if (CheckIfEquipmentIsNotUsable(Chosen
)) { Item
->MoveTo(MainStack
); Item
= 0; otherChosen
= -1; } // small bug?
5778 // wear/wield possible second item
5779 if (Item
&& otherChosen
!= -1 && ItemVector
[0]->HandleInPairs() && ItemVector
.size() > 1 && GetBodyPartOfEquipment(otherChosen
)) {
5780 item
*otherOld
= GetEquipment(otherChosen
);
5781 if (otherOld
&& !IsPlayer() && BoundToUse(otherOld
, otherChosen
)) {
5782 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE
), otherOld
->CHAR_NAME(DEFINITE
));
5784 } else if (otherOld
) {
5785 otherOld
->MoveTo(MainStack
);
5787 if (otherChosen
!= -1) {
5788 ItemVector
[1]->RemoveFromSlot();
5789 SetEquipment(otherChosen
, ItemVector
[1]);
5790 if (CheckIfEquipmentIsNotUsable(otherChosen
)) { ItemVector
[1]->MoveTo(MainStack
); otherChosen
= -1; } // small bug?
5795 return (Item
!= OldEquipment
? (otherChosen
!= -1 ? 2 : 1) : 0);
5799 void character::PrintBeginParasitizedMessage () const {
5800 if (IsPlayer()) ADD_MESSAGE("You feel you are no longer alone.");
5804 void character::PrintEndParasitizedMessage () const {
5805 if (IsPlayer()) ADD_MESSAGE("A feeling of long welcome emptiness overwhelms you.");
5809 void character::ParasitizedHandler () {
5811 if (!(RAND() % 250)) {
5812 if (IsPlayer()) ADD_MESSAGE("Ugh. You feel something violently carving its way through your intestines.");
5813 ReceiveDamage(0, 1, POISON
, TORSO
, 8, false, false, false, false);
5814 CheckDeath(CONST_S("killed by a vile parasite"), 0);
5819 truth
character::CanFollow () const {
5820 return CanMove() && !StateIsActivated(PANIC
) && !IsStuck();
5824 festring
character::GetKillName () const {
5825 if (!GetPolymorphBackup()) return GetName(INDEFINITE
);
5827 GetPolymorphBackup()->AddName(KillName
, INDEFINITE
);
5828 KillName
<< " polymorphed into ";
5829 id::AddName(KillName
, INDEFINITE
);
5834 festring
character::GetPanelName () const {
5836 Name
<< AssignedName
<< " the " << game::GetVerbalPlayerAlignment() << ' ';
5837 id::AddName(Name
, UNARTICLED
);
5842 sLong
character::GetMoveAPRequirement (int Difficulty
) const {
5843 return (!StateIsActivated(PANIC
) ? 10000000 : 8000000) * Difficulty
/ (APBonus(GetAttribute(AGILITY
)) * GetMoveEase());
5847 bodypart
*character::HealHitPoint() {
5848 int NeedHeal
= 0, NeedHealIndex
[MAX_BODYPARTS
];
5849 for (int c
= 0; c
< BodyParts
; ++c
) {
5850 bodypart
*BodyPart
= GetBodyPart(c
);
5851 if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < BodyPart
->GetMaxHP()) NeedHealIndex
[NeedHeal
++] = c
;
5854 bodypart
*BodyPart
= GetBodyPart(NeedHealIndex
[RAND() % NeedHeal
]);
5855 BodyPart
->IncreaseHP();
5863 void character::CreateHomeData () {
5864 HomeData
= new homedata
;
5865 lsquare
*Square
= GetLSquareUnder();
5866 HomeData
->Pos
= Square
->GetPos();
5867 HomeData
->Dungeon
= Square
->GetDungeonIndex();
5868 HomeData
->Level
= Square
->GetLevelIndex();
5869 HomeData
->Room
= Square
->GetRoomIndex();
5873 room
*character::GetHomeRoom() const {
5874 if (HomeDataIsValid() && HomeData
->Room
) return GetLevel()->GetRoom(HomeData
->Room
);
5879 void character::RemoveHomeData () {
5885 void character::AddESPConsumeMessage () const {
5886 if (IsPlayer()) ADD_MESSAGE("You feel a strange mental activity.");
5890 void character::SetBodyPart (int I
, bodypart
*What
) {
5891 BodyPartSlot
[I
].PutInItem(What
);
5893 What
->SignalPossibleUsabilityChange();
5895 AddOriginalBodyPartID(I
, What
->GetID());
5896 if (What
->GetMainMaterial()->IsInfectedByLeprosy()) GainIntrinsic(LEPROSY
);
5897 else if (StateIsActivated(LEPROSY
)) What
->GetMainMaterial()->SetIsInfectedByLeprosy(true);
5902 truth
character::ConsumeItem (item
*Item
, cfestring
&ConsumeVerb
) {
5903 if (IsPlayer() && HasHadBodyPart(Item
) && !game::TruthQuestion(CONST_S("Are you sure? You may be able to put it back..."))) {
5906 if (Item
->IsOnGround() && GetRoom() && !GetRoom()->ConsumeItem(this, Item
, 1)) {
5909 if (IsPlayer()) ADD_MESSAGE("You begin %s %s.", ConsumeVerb
.CStr(), Item
->CHAR_NAME(DEFINITE
));
5910 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins %s %s.", CHAR_NAME(DEFINITE
), ConsumeVerb
.CStr(), Item
->CHAR_NAME(DEFINITE
));
5911 consume
*Consume
= consume::Spawn(this);
5912 Consume
->SetDescription(ConsumeVerb
);
5913 Consume
->SetConsumingID(Item
->GetID());
5920 truth
character::CheckThrow () const {
5922 ADD_MESSAGE("This monster type cannot throw.");
5929 void character::GetHitByExplosion (const explosion
*Explosion
, int Damage
) {
5930 int DamageDirection
= GetPos() == Explosion
->Pos
? RANDOM_DIR
: game::CalculateRoughDirection(GetPos() - Explosion
->Pos
);
5931 if (!IsPet() && Explosion
->Terrorist
&& Explosion
->Terrorist
->IsPet()) Explosion
->Terrorist
->Hostility(this);
5932 GetTorso()->SpillBlood((8 - Explosion
->Size
+ RAND() % (8 - Explosion
->Size
)) >> 1);
5933 if (DamageDirection
== RANDOM_DIR
) DamageDirection
= RAND()&7;
5934 v2 SpillPos
= GetPos() + game::GetMoveVector(DamageDirection
);
5935 if (SquareUnder
[0] && GetArea()->IsValidPos(SpillPos
)) GetTorso()->SpillBlood((8-Explosion
->Size
+RAND()%(8-Explosion
->Size
))>>1, SpillPos
);
5936 if (IsPlayer()) ADD_MESSAGE("You are hit by the explosion!");
5937 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is hit by the explosion.", CHAR_NAME(DEFINITE
));
5938 truth WasUnconscious
= GetAction() && GetAction()->IsUnconsciousness();
5939 ReceiveDamage(Explosion
->Terrorist
, Damage
>> 1, FIRE
, ALL
, DamageDirection
, true, false, false, false);
5941 ReceiveDamage(Explosion
->Terrorist
, Damage
>> 1, PHYSICAL_DAMAGE
, ALL
, DamageDirection
, true, false, false, false);
5942 CheckDeath(Explosion
->DeathMsg
, Explosion
->Terrorist
, !WasUnconscious
? IGNORE_UNCONSCIOUSNESS
: 0);
5947 void character::SortAllItems (const sortdata
&SortData
) {
5948 GetStack()->SortAllItems(SortData
);
5949 doforequipmentswithparam
<const sortdata
&>()(this, &item::SortAllItems
, SortData
);
5953 void character::PrintBeginSearchingMessage () const {
5954 if (IsPlayer()) ADD_MESSAGE("You feel you can now notice even the very smallest details around you.");
5958 void character::PrintEndSearchingMessage () const {
5959 if (IsPlayer()) ADD_MESSAGE("You feel less perceptive.");
5963 void character::SearchingHandler () {
5964 if (!game::IsInWilderness()) Search(15);
5968 void character::Search (int Perception
) {
5969 for (int d
= 0; d
< GetExtendedNeighbourSquares(); ++d
) {
5970 lsquare
*LSquare
= GetNeighbourLSquare(d
);
5971 if (LSquare
) LSquare
->GetStack()->Search(this, Min(Perception
, 200));
5976 // surprisingly returns 0 if fails
5977 character
*character::GetRandomNeighbour (int RelationFlags
) const {
5978 character
*Chars
[MAX_NEIGHBOUR_SQUARES
];
5980 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
5981 lsquare
*LSquare
= GetNeighbourLSquare(d
);
5983 character
*Char
= LSquare
->GetCharacter();
5984 if (Char
&& (GetRelation(Char
) & RelationFlags
)) Chars
[Index
++] = Char
;
5987 return Index
? Chars
[RAND() % Index
] : 0;
5991 void character::ResetStates () {
5992 for (int c
= 0; c
< STATES
; ++c
) {
5993 if (1 << c
!= POLYMORPHED
&& TemporaryStateIsActivated(1 << c
) && TemporaryStateCounter
[c
] != PERMANENT
) {
5994 TemporaryState
&= ~(1 << c
);
5995 if (StateData
[c
].EndHandler
) {
5996 (this->*StateData
[c
].EndHandler
)();
5997 if (!IsEnabled())return;
6004 void characterdatabase::InitDefaults (const characterprototype
*NewProtoType
, int NewConfig
, cfestring
&acfgstrname
) {
6006 ProtoType
= NewProtoType
;
6008 CfgStrName
= acfgstrname
;
6013 void character::PrintBeginGasImmunityMessage () const {
6014 if (IsPlayer()) ADD_MESSAGE("All smells fade away.");
6018 void character::PrintEndGasImmunityMessage () const {
6019 if (IsPlayer()) ADD_MESSAGE("Yuck! The world smells bad again.");
6023 void character::ShowAdventureInfo () const {
6024 static const char *lists
[4][4] = {
6025 { "Show massacre history",
6027 "Show message history",
6030 "Show message history",
6033 { "Show message history",
6037 { "Show massacre history",
6038 "Show message history",
6042 // massacre, inventory, messages
6043 static const int nums
[4][3] = {
6050 if (GetStack()->GetItems()) {
6051 idx
= game::MassacreListsEmpty() ? 1 : 0;
6053 idx
= game::MassacreListsEmpty() ? 2 : 3;
6057 sel
= game::ListSelectorArray(sel
, CONST_S("Do you want to see some funny history?"), lists
[idx
]);
6059 if (sel
== nums
[idx
][0] && !game::MassacreListsEmpty()) {
6060 game::DisplayMassacreLists();
6062 if (sel
== nums
[idx
][1] && GetStack()->GetItems()) {
6063 GetStack()->DrawContents(this, CONST_S("Your inventory"), NO_SELECT
);
6064 for(stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) i
->DrawContents(this);
6065 doforequipmentswithparam
<ccharacter
*>()(this, &item::DrawContents
, this);
6067 if (sel
== nums
[idx
][2]) {
6068 msgsystem::DrawMessageHistory();
6074 truth
character::EditAllAttributes (int Amount
) {
6075 if (!Amount
) return true;
6077 truth MayEditMore
= false;
6078 for (c
= 0; c
< BodyParts
; ++c
) {
6079 bodypart
*BodyPart
= GetBodyPart(c
);
6080 if (BodyPart
&& BodyPart
->EditAllAttributes(Amount
)) MayEditMore
= true;
6082 for (c
= 0; c
< BASE_ATTRIBUTES
; ++c
) {
6083 if (BaseExperience
[c
]) {
6084 BaseExperience
[c
] += Amount
* EXP_MULTIPLIER
;
6085 LimitRef(BaseExperience
[c
], MIN_EXP
, MAX_EXP
);
6086 if ((Amount
< 0 && BaseExperience
[c
] != MIN_EXP
) || (Amount
> 0 && BaseExperience
[c
] != MAX_EXP
)) MayEditMore
= true;
6093 game::SendLOSUpdateRequest();
6096 if (IsPlayerKind()) UpdatePictures();
6102 void character::AddAttributeInfo (festring
&Entry
) const {
6104 Entry
<< GetAttribute(ENDURANCE
);
6106 Entry
<< GetAttribute(PERCEPTION
);
6108 Entry
<< GetAttribute(INTELLIGENCE
);
6110 Entry
<< GetAttribute(WISDOM
);
6112 Entry
<< GetAttribute(CHARISMA
);
6114 Entry
<< GetAttribute(MANA
);
6118 void character::AddDefenceInfo (felist
&List
) const {
6120 for (int c
= 0; c
< BodyParts
; ++c
) {
6121 bodypart
*BodyPart
= GetBodyPart(c
);
6123 Entry
= CONST_S(" ");
6124 BodyPart
->AddName(Entry
, UNARTICLED
);
6126 Entry
<< BodyPart
->GetMaxHP();
6128 Entry
<< BodyPart
->GetTotalResistance(PHYSICAL_DAMAGE
);
6129 List
.AddEntry(Entry
, LIGHT_GRAY
);
6135 void character::DetachBodyPart () {
6136 ADD_MESSAGE("You haven't got any extra bodyparts.");
6141 void character::ReceiveHolyBanana (sLong Amount
) {
6143 EditExperience(ARM_STRENGTH
, Amount
, 1 << 13);
6144 EditExperience(LEG_STRENGTH
, Amount
, 1 << 13);
6145 EditExperience(DEXTERITY
, Amount
, 1 << 13);
6146 EditExperience(AGILITY
, Amount
, 1 << 13);
6147 EditExperience(ENDURANCE
, Amount
, 1 << 13);
6148 EditExperience(PERCEPTION
, Amount
, 1 << 13);
6149 EditExperience(INTELLIGENCE
, Amount
, 1 << 13);
6150 EditExperience(WISDOM
, Amount
, 1 << 13);
6151 EditExperience(CHARISMA
, Amount
, 1 << 13);
6156 void character::AddHolyBananaConsumeEndMessage () const {
6157 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
6158 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE
));
6162 void character::ReceiveHolyMango (sLong Amount
) {
6164 EditExperience(ARM_STRENGTH
, Amount
, 1 << 13);
6165 EditExperience(LEG_STRENGTH
, Amount
, 1 << 13);
6166 EditExperience(DEXTERITY
, Amount
, 1 << 13);
6167 EditExperience(AGILITY
, Amount
, 1 << 13);
6168 EditExperience(ENDURANCE
, Amount
, 1 << 13);
6169 EditExperience(PERCEPTION
, Amount
, 1 << 13);
6170 EditExperience(INTELLIGENCE
, Amount
, 1 << 13);
6171 EditExperience(WISDOM
, Amount
, 1 << 13);
6172 EditExperience(CHARISMA
, Amount
, 1 << 13);
6177 void character::AddHolyMangoConsumeEndMessage () const {
6178 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
6179 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE
));
6183 truth
character::PreProcessForBone () {
6184 if (IsPet() && IsEnabled()) {
6185 Die(0, CONST_S(""), FORBID_REINCARNATION
);
6188 if (GetAction()) GetAction()->Terminate(false);
6189 if (TemporaryStateIsActivated(POLYMORPHED
)) {
6190 character
*PolymorphBackup
= GetPolymorphBackup();
6192 PolymorphBackup
->PreProcessForBone();
6195 if (MustBeRemovedFromBone()) return false;
6196 if (IsUnique() && !CanBeGenerated()) game::SignalQuestMonsterFound();
6200 GetStack()->PreProcessForBone();
6201 doforequipments()(this, &item::PreProcessForBone
);
6202 doforbodyparts()(this, &bodypart::PreProcessForBone
);
6203 game::RemoveCharacterID(ID
);
6205 game::AddCharacterID(this, ID
);
6210 truth
character::PostProcessForBone (double &DangerSum
, int& Enemies
) {
6211 if (PostProcessForBone()) {
6212 if (GetRelation(PLAYER
) == HOSTILE
) {
6213 double Danger
= GetRelativeDanger(PLAYER
, true);
6214 if (Danger
> 99.0) game::SetTooGreatDangerFound(true);
6215 else if (!IsUnique() && !IgnoreDanger()) {
6216 DangerSum
+= Danger
;
6226 truth
character::PostProcessForBone () {
6227 feuLong NewID
= game::CreateNewCharacterID(this);
6228 game::GetBoneCharacterIDMap().insert(std::make_pair(-ID
, NewID
));
6229 game::RemoveCharacterID(ID
);
6231 if (IsUnique() && CanBeGenerated()) {
6232 if (DataBase
->Flags
& HAS_BEEN_GENERATED
) return false;
6235 GetStack()->PostProcessForBone();
6236 doforequipments()(this, &item::PostProcessForBone
);
6237 doforbodyparts()(this, &bodypart::PostProcessForBone
);
6242 void character::FinalProcessForBone () {
6244 GetStack()->FinalProcessForBone();
6245 doforequipments()(this, &item::FinalProcessForBone
);
6247 for (c
= 0; c
< BodyParts
; ++c
) {
6248 for (std::list
<feuLong
>::iterator i
= OriginalBodyPartID
[c
].begin(); i
!= OriginalBodyPartID
[c
].end();) {
6249 boneidmap::iterator BI
= game::GetBoneItemIDMap().find(*i
);
6250 if (BI
== game::GetBoneItemIDMap().end()) {
6251 std::list
<feuLong
>::iterator Dirt
= i
++;
6252 OriginalBodyPartID
[c
].erase(Dirt
);
6262 void character::SetSoulID (feuLong What
) {
6263 if (GetPolymorphBackup()) GetPolymorphBackup()->SetSoulID(What
);
6267 truth
character::SearchForItem (citem
*Item
) const {
6268 if (combineequipmentpredicateswithparam
<feuLong
>()(this, &item::HasID
, Item
->GetID(), 1)) return true;
6269 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (*i
== Item
) return true;
6274 item
*character::SearchForItem (const sweaponskill
*SWeaponSkill
) const {
6275 for (int c
= 0; c
< GetEquipments(); ++c
) {
6276 item
*Equipment
= GetEquipment(c
);
6277 if (Equipment
&& SWeaponSkill
->IsSkillOf(Equipment
)) return Equipment
;
6279 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (SWeaponSkill
->IsSkillOf(*i
)) return *i
;
6284 void character::PutNear (v2 Pos
) {
6285 v2 NewPos
= game::GetCurrentLevel()->GetNearestFreeSquare(this, Pos
, false);
6286 if (NewPos
== ERROR_V2
) {
6287 do { NewPos
= game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos
== Pos
);
6293 void character::PutToOrNear (v2 Pos
) {
6294 if (game::IsInWilderness() || (CanMoveOn(game::GetCurrentLevel()->GetLSquare(Pos
)) && IsFreeForMe(game::GetCurrentLevel()->GetLSquare(Pos
))))
6301 void character::PutTo (v2 Pos
) {
6302 SquareUnder
[0] = game::GetCurrentArea()->GetSquare(Pos
);
6303 SquareUnder
[0]->AddCharacter(this);
6307 void character::Remove () {
6308 SquareUnder
[0]->RemoveCharacter();
6313 void character::SendNewDrawRequest () const {
6314 for (int c
= 0; c
< SquaresUnder
; ++c
) {
6315 square
*Square
= GetSquareUnder(c
);
6316 if (Square
) Square
->SendNewDrawRequest();
6321 truth
character::IsOver (v2 Pos
) const {
6322 for (int c
= 0; c
< SquaresUnder
; ++c
) {
6323 square
*Square
= GetSquareUnder(c
);
6324 if (Square
&& Square
->GetPos() == Pos
) return true;
6330 truth
character::CanTheoreticallyMoveOn (const lsquare
*LSquare
) const { return GetMoveType() & LSquare
->GetTheoreticalWalkability(); }
6331 truth
character::CanMoveOn (const lsquare
*LSquare
) const { return GetMoveType() & LSquare
->GetWalkability(); }
6332 truth
character::CanMoveOn (const square
*Square
) const { return GetMoveType() & Square
->GetSquareWalkability(); }
6333 truth
character::CanMoveOn (const olterrain
*OLTerrain
) const { return GetMoveType() & OLTerrain
->GetWalkability(); }
6334 truth
character::CanMoveOn (const oterrain
*OTerrain
) const { return GetMoveType() & OTerrain
->GetWalkability(); }
6335 truth
character::IsFreeForMe(square
*Square
) const { return !Square
->GetCharacter() || Square
->GetCharacter() == this; }
6336 void character::LoadSquaresUnder () { SquareUnder
[0] = game::GetSquareInLoad(); }
6338 truth
character::AttackAdjacentEnemyAI () {
6339 if (!IsEnabled()) return false;
6340 character
*Char
[MAX_NEIGHBOUR_SQUARES
];
6341 v2 Pos
[MAX_NEIGHBOUR_SQUARES
];
6342 int Dir
[MAX_NEIGHBOUR_SQUARES
];
6344 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
6345 square
*Square
= GetNeighbourSquare(d
);
6347 character
*Enemy
= Square
->GetCharacter();
6348 if (Enemy
&& (GetRelation(Enemy
) == HOSTILE
|| StateIsActivated(CONFUSED
))) {
6350 Pos
[Index
] = Square
->GetPos();
6351 Char
[Index
++] = Enemy
;
6356 int ChosenIndex
= RAND() % Index
;
6357 Hit(Char
[ChosenIndex
], Pos
[ChosenIndex
], Dir
[ChosenIndex
]);
6364 void character::SignalStepFrom (lsquare
**OldSquareUnder
) {
6366 lsquare
*NewSquareUnder
[MAX_SQUARES_UNDER
];
6367 for (c
= 0; c
< GetSquaresUnder(); ++c
) NewSquareUnder
[c
] = GetLSquareUnder(c
);
6368 for (c
= 0; c
< GetSquaresUnder(); ++c
) {
6369 if (IsEnabled() && GetLSquareUnder(c
) == NewSquareUnder
[c
]) NewSquareUnder
[c
]->StepOn(this, OldSquareUnder
);
6374 int character::GetSumOfAttributes () const {
6375 return GetAttribute(ENDURANCE
) + GetAttribute(PERCEPTION
) + GetAttribute(INTELLIGENCE
) + GetAttribute(WISDOM
) + GetAttribute(CHARISMA
) + GetAttribute(ARM_STRENGTH
) + GetAttribute(AGILITY
);
6379 void character::IntelligenceAction (int Difficulty
) {
6380 EditAP(-20000 * Difficulty
/ APBonus(GetAttribute(INTELLIGENCE
)));
6381 EditExperience(INTELLIGENCE
, Difficulty
* 15, 1 << 7);
6385 struct walkabilitycontroller
{
6386 static truth
Handler (int x
, int y
) {
6387 return x
>= 0 && y
>= 0 && x
< LevelXSize
&& y
< LevelYSize
&& Map
[x
][y
]->GetTheoreticalWalkability() & MoveType
;
6389 static lsquare
***Map
;
6390 static int LevelXSize
, LevelYSize
;
6391 static int MoveType
;
6395 lsquare
***walkabilitycontroller::Map
;
6396 int walkabilitycontroller::LevelXSize
, walkabilitycontroller::LevelYSize
;
6397 int walkabilitycontroller::MoveType
;
6400 truth
character::CreateRoute () {
6402 if (GetAttribute(INTELLIGENCE
) >= 10 && !StateIsActivated(CONFUSED
)) {
6404 walkabilitycontroller::Map
= GetLevel()->GetMap();
6405 walkabilitycontroller::LevelXSize
= GetLevel()->GetXSize();
6406 walkabilitycontroller::LevelYSize
= GetLevel()->GetYSize();
6407 walkabilitycontroller::MoveType
= GetMoveType();
6409 for (int c
= 0; c
< game::GetTeams(); ++c
)
6410 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
6411 character
*Char
= *i
;
6412 if (Char
->IsEnabled() && !Char
->Route
.empty() && (Char
->GetMoveType()&GetMoveType()) == Char
->GetMoveType()) {
6413 v2 CharGoingTo
= Char
->Route
[0];
6414 v2 iPos
= Char
->Route
.back();
6415 if ((GoingTo
-CharGoingTo
).GetLengthSquare() <= 100 && (Pos
- iPos
).GetLengthSquare() <= 100 &&
6416 mapmath
<walkabilitycontroller
>::DoLine(CharGoingTo
.X
, CharGoingTo
.Y
, GoingTo
.X
, GoingTo
.Y
, SKIP_FIRST
) &&
6417 mapmath
<walkabilitycontroller
>::DoLine(Pos
.X
, Pos
.Y
, iPos
.X
, iPos
.Y
, SKIP_FIRST
)) {
6418 if (!Illegal
.empty() && Illegal
.find(Char
->Route
.back()) != Illegal
.end()) continue;
6419 Node
= GetLevel()->FindRoute(CharGoingTo
, GoingTo
, Illegal
, GetMoveType());
6420 if (Node
) { while(Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
6421 else { Route
.clear(); continue; }
6422 Route
.insert(Route
.end(), Char
->Route
.begin(), Char
->Route
.end());
6423 Node
= GetLevel()->FindRoute(Pos
, iPos
, Illegal
, GetMoveType());
6424 if (Node
) { while (Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
6425 else { Route
.clear(); continue; }
6426 IntelligenceAction(1);
6431 Node
= GetLevel()->FindRoute(Pos
, GoingTo
, Illegal
, GetMoveType());
6432 if (Node
) { while(Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
6433 else TerminateGoingTo();
6434 IntelligenceAction(5);
6441 void character::SetGoingTo (v2 What
) {
6442 if (GoingTo
!= What
) {
6450 void character::TerminateGoingTo () {
6457 truth
character::CheckForFood (int Radius
) {
6458 if (StateIsActivated(PANIC
) || !UsesNutrition() || !IsEnabled()) return false;
6461 for (int r
= 1; r
<= Radius
; ++r
) {
6464 for (y
= Pos
.Y
-r
; y
<= Pos
.Y
+r
; ++y
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6467 if (x
< GetLevel()->GetXSize()) {
6468 for (y
= Pos
.Y
-r
; y
<= Pos
.Y
+r
; ++y
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6472 for (x
= Pos
.X
-r
; x
<= Pos
.X
+r
; ++x
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6475 if (y
< GetLevel()->GetYSize()) {
6476 for (x
= Pos
.X
-r
; x
<= Pos
.X
+r
; ++x
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6483 truth
character::CheckForFoodInSquare (v2 Pos
) {
6484 level
*Level
= GetLevel();
6485 if (Level
->IsValidPos(Pos
)) {
6486 lsquare
*Square
= Level
->GetLSquare(Pos
);
6487 stack
*Stack
= Square
->GetStack();
6488 if (Stack
->GetItems()) {
6489 for (stackiterator i
= Stack
->GetBottom(); i
.HasItem(); ++i
) {
6490 if (i
->IsPickable(this) && i
->CanBeSeenBy(this) && i
->CanBeEatenByAI(this) && (!Square
->GetRoomIndex() || Square
->GetRoom()->AllowFoodSearch())) {
6492 return MoveTowardsTarget(false);
6501 void character::SetConfig (int NewConfig
, int SpecialFlags
) {
6502 databasecreator
<character
>::InstallDataBase(this, NewConfig
);
6505 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdatePictures();
6509 truth
character::IsOver (citem
*Item
) const {
6510 for (int c1
= 0; c1
< Item
->GetSquaresUnder(); ++c1
)
6511 for (int c2
= 0; c2
< SquaresUnder
; ++c2
)
6512 if (Item
->GetPos(c1
) == GetPos(c2
)) return true;
6517 truth
character::CheckConsume (cfestring
&Verb
) const {
6518 if (!UsesNutrition()) {
6519 if (IsPlayer()) ADD_MESSAGE("In this form you can't and don't need to %s.", Verb
.CStr());
6526 void character::PutTo (lsquare
*To
) {
6527 PutTo(To
->GetPos());
6531 double character::RandomizeBabyExperience (double SumE
) {
6532 if (!SumE
) return 0;
6533 double E
= (SumE
/ 4) - (SumE
/ 32) + (double(RAND()) / MAX_RAND
) * (SumE
/ 16 + 1);
6534 return Limit(E
, MIN_EXP
, MAX_EXP
);
6538 liquid
*character::CreateBlood (sLong Volume
) const {
6539 return liquid::Spawn(GetBloodMaterial(), Volume
);
6543 void character::SpillFluid (character
*Spiller
, liquid
*Liquid
, int SquareIndex
) {
6544 sLong ReserveVolume
= Liquid
->GetVolume() >> 1;
6545 Liquid
->EditVolume(-ReserveVolume
);
6546 GetStack()->SpillFluid(Spiller
, Liquid
, sLong(Liquid
->GetVolume() * sqrt(double(GetStack()->GetVolume()) / GetVolume())));
6547 Liquid
->EditVolume(ReserveVolume
);
6549 sLong Modifier
[MAX_BODYPARTS
], ModifierSum
= 0;
6550 for (c
= 0; c
< BodyParts
; ++c
) {
6551 if (GetBodyPart(c
)) {
6552 Modifier
[c
] = sLong(sqrt(GetBodyPart(c
)->GetVolume()));
6553 if (Modifier
[c
]) Modifier
[c
] *= 1 + (RAND() & 3);
6554 ModifierSum
+= Modifier
[c
];
6559 for (c
= 1; c
< GetBodyParts(); ++c
) {
6560 if (GetBodyPart(c
) && IsEnabled())
6561 GetBodyPart(c
)->SpillFluid(Spiller
, Liquid
->SpawnMoreLiquid(Liquid
->GetVolume() * Modifier
[c
] / ModifierSum
), SquareIndex
);
6564 Liquid
->SetVolume(Liquid
->GetVolume() * Modifier
[TORSO_INDEX
] / ModifierSum
);
6565 GetTorso()->SpillFluid(Spiller
, Liquid
, SquareIndex
);
6570 void character::StayOn (liquid
*Liquid
) {
6571 Liquid
->TouchEffect(this, TORSO_INDEX
);
6575 truth
character::IsAlly (ccharacter
*Char
) const {
6576 return Char
->GetTeam()->GetID() == GetTeam()->GetID();
6580 void character::ResetSpoiling () {
6581 doforbodyparts()(this, &bodypart::ResetSpoiling
);
6585 item
*character::SearchForItem (ccharacter
*Char
, sorter Sorter
) const {
6586 item
*Equipment
= findequipment
<ccharacter
*>()(this, Sorter
, Char
);
6587 if (Equipment
) return Equipment
;
6588 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (((*i
)->*Sorter
)(Char
)) return *i
;
6593 truth
character::DetectMaterial (cmaterial
*Material
) const {
6594 return GetStack()->DetectMaterial(Material
) ||
6595 combinebodypartpredicateswithparam
<cmaterial
*>()(this, &bodypart::DetectMaterial
, Material
, 1) ||
6596 combineequipmentpredicateswithparam
<cmaterial
*>()(this, &item::DetectMaterial
, Material
, 1);
6600 truth
character::DamageTypeDestroysBodyPart (int Type
) {
6601 return (Type
&0xFFF) != PHYSICAL_DAMAGE
;
6605 truth
character::CheckIfTooScaredToHit (ccharacter
*Enemy
) const {
6606 if (IsPlayer() && StateIsActivated(PANIC
)) {
6607 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
6608 square
*Square
= GetNeighbourSquare(d
);
6610 if(CanMoveOn(Square
) && (!Square
->GetCharacter() || Square
->GetCharacter()->IsPet())) {
6611 ADD_MESSAGE("You are too scared to attack %s.", Enemy
->CHAR_DESCRIPTION(DEFINITE
));
6621 void character::PrintBeginLevitationMessage () const {
6623 if (IsPlayer()) ADD_MESSAGE("You rise into the air like a small hot-air balloon.");
6624 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins to float.", CHAR_NAME(DEFINITE
));
6629 void character::PrintEndLevitationMessage () const {
6631 if (IsPlayer()) ADD_MESSAGE("You descend gently onto the ground.");
6632 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s drops onto the ground.", CHAR_NAME(DEFINITE
));
6637 truth
character::IsLimbIndex (int I
) {
6639 case RIGHT_ARM_INDEX
:
6640 case LEFT_ARM_INDEX
:
6641 case RIGHT_LEG_INDEX
:
6642 case LEFT_LEG_INDEX
:
6649 void character::EditExperience (int Identifier
, double Value
, double Speed
) {
6650 if (!AllowExperience() || (Identifier
== ENDURANCE
&& UseMaterialAttributes())) return;
6651 int Change
= RawEditExperience(BaseExperience
[Identifier
], GetNaturalExperience(Identifier
), Value
, Speed
);
6652 if (!Change
) return;
6653 cchar
*PlayerMsg
= 0, *NPCMsg
= 0;
6654 switch (Identifier
) {
6657 PlayerMsg
= "You feel tougher than anything!";
6658 if (IsPet()) NPCMsg
= "Suddenly %s looks tougher.";
6660 PlayerMsg
= "You feel less healthy.";
6661 if (IsPet()) NPCMsg
= "Suddenly %s looks less healthy.";
6663 CalculateBodyPartMaxHPs();
6664 CalculateMaxStamina();
6669 PlayerMsg
= "You now see the world in much better detail than before.";
6671 PlayerMsg
= "You feel very guru.";
6672 game::GetGod(VALPURUS
)->AdjustRelation(100);
6674 game::SendLOSUpdateRequest();
6679 if (Change
> 0) PlayerMsg
= "Suddenly the inner structure of the Multiverse around you looks quite simple.";
6680 else PlayerMsg
= "It surely is hard to think today.";
6683 if (IsPlayerKind()) UpdatePictures();
6687 if (Change
> 0) PlayerMsg
= "You feel your life experience increasing all the time.";
6688 else PlayerMsg
= "You feel like having done something unwise.";
6690 if (IsPlayerKind()) UpdatePictures();
6694 PlayerMsg
= "You feel very confident of your social skills.";
6696 if (GetAttribute(CHARISMA
) <= 15) NPCMsg
= "%s looks less ugly.";
6697 else NPCMsg
= "%s looks more attractive.";
6700 PlayerMsg
= "You feel somehow disliked.";
6702 if (GetAttribute(CHARISMA
) < 15) NPCMsg
= "%s looks more ugly.";
6703 else NPCMsg
= "%s looks less attractive.";
6706 if (IsPlayerKind()) UpdatePictures();
6710 PlayerMsg
= "You feel magical forces coursing through your body!";
6711 NPCMsg
= "You notice an odd glow around %s.";
6713 PlayerMsg
= "You feel your magical abilities withering slowly.";
6714 NPCMsg
= "You notice strange vibrations in the air around %s. But they disappear rapidly.";
6719 if (IsPlayer()) ADD_MESSAGE("%s", PlayerMsg
);
6720 else if (NPCMsg
&& CanBeSeenByPlayer()) ADD_MESSAGE(NPCMsg
, CHAR_NAME(DEFINITE
));
6722 CalculateBattleInfo();
6726 int character::RawEditExperience (double &Exp
, double NaturalExp
, double Value
, double Speed
) const {
6727 double OldExp
= Exp
;
6732 if(!OldExp
|| !Value
|| (Value
> 0 && OldExp
>= NaturalExp
* (100 + Value
) / 100) ||
6733 (Value
< 0 && OldExp
<= NaturalExp
* (100 + Value
) / 100)) return 0;
6734 if (!IsPlayer()) Speed
*= 1.5;
6735 Exp
+= (NaturalExp
* (100 + Value
) - 100 * OldExp
) * Speed
* EXP_DIVISOR
;
6736 LimitRef(Exp
, MIN_EXP
, MAX_EXP
);
6737 int NewA
= int(Exp
* EXP_DIVISOR
);
6738 int OldA
= int(OldExp
* EXP_DIVISOR
);
6739 int Delta
= NewA
- OldA
;
6740 if (Delta
> 0) Exp
= Max(Exp
, (NewA
+ 0.05) * EXP_MULTIPLIER
);
6741 else if (Delta
< 0) Exp
= Min(Exp
, (NewA
+ 0.95) * EXP_MULTIPLIER
);
6742 LimitRef(Exp
, MIN_EXP
, MAX_EXP
);
6747 int character::GetAttribute (int Identifier
, truth AllowBonus
) const {
6748 int A
= int(BaseExperience
[Identifier
] * EXP_DIVISOR
);
6749 if (AllowBonus
&& Identifier
== INTELLIGENCE
&& BrainsHurt()) return Max((A
+ AttributeBonus
[INTELLIGENCE
]) / 3, 1);
6750 return A
&& AllowBonus
? Max(A
+ AttributeBonus
[Identifier
], 1) : A
;
6754 void characterdatabase::PostProcess () {
6755 double AM
= (100 + AttributeBonus
) * EXP_MULTIPLIER
/ 100;
6756 for (int c
= 0; c
< ATTRIBUTES
; ++c
) NaturalExperience
[c
] = this->*ExpPtr
[c
] * AM
;
6760 void character::EditDealExperience (sLong Price
) {
6761 EditExperience(CHARISMA
, sqrt(Price
) / 5, 1 << 9);
6765 void character::PrintBeginLeprosyMessage () const {
6766 if (IsPlayer()) ADD_MESSAGE("You feel you're falling in pieces.");
6770 void character::PrintEndLeprosyMessage () const {
6771 if (IsPlayer()) ADD_MESSAGE("You feel your limbs are stuck in place tightly."); // CHANGE OR DIE
6775 void character::TryToInfectWithLeprosy (ccharacter
*Infector
) {
6776 if (!IsImmuneToLeprosy() &&
6777 ((GetRelation(Infector
) == HOSTILE
&& !RAND_N(50 * GetAttribute(ENDURANCE
))) ||
6778 !RAND_N(500 * GetAttribute(ENDURANCE
)))) GainIntrinsic(LEPROSY
);
6782 void character::SignalGeneration () {
6783 const_cast<database
*>(DataBase
)->Flags
|= HAS_BEEN_GENERATED
;
6787 void character::CheckIfSeen () {
6788 if (IsPlayer() || CanBeSeenByPlayer()) SignalSeen();
6792 void character::SignalSeen () {
6793 if (!(WarnFlags
& WARNED
) && GetRelation(PLAYER
) == HOSTILE
&& !StateIsActivated(FEARLESS
)) {
6794 double Danger
= GetRelativeDanger(PLAYER
);
6796 game::SetDangerFound(Max(game::GetDangerFound(), Danger
));
6797 if (Danger
> 500.0 && !(WarnFlags
& HAS_CAUSED_PANIC
)) {
6798 WarnFlags
|= HAS_CAUSED_PANIC
;
6799 game::SetCausePanicFlag(true);
6801 WarnFlags
|= WARNED
;
6804 const_cast<database
*>(DataBase
)->Flags
|= HAS_BEEN_SEEN
;
6808 int character::GetPolymorphIntelligenceRequirement () const {
6809 if (DataBase
->PolymorphIntelligenceRequirement
== DEPENDS_ON_ATTRIBUTES
) return Max(GetAttributeAverage() - 5, 0);
6810 return DataBase
->PolymorphIntelligenceRequirement
;
6814 void character::RemoveAllItems () {
6815 GetStack()->Clean();
6816 for (int c
= 0; c
< GetEquipments(); ++c
) {
6817 item
*Equipment
= GetEquipment(c
);
6819 Equipment
->RemoveFromSlot();
6820 Equipment
->SendToHell();
6826 int character::CalculateWeaponSkillHits (ccharacter
*Enemy
) const {
6827 if (Enemy
->IsPlayer()) {
6828 configid
ConfigID(GetType(), GetConfig());
6829 const dangerid
& DangerID
= game::GetDangerMap().find(ConfigID
)->second
;
6830 return Min(int(DangerID
.EquippedDanger
* 2000), 1000);
6832 return Min(int(GetRelativeDanger(Enemy
, true) * 2000), 1000);
6836 truth
character::CanUseEquipment (int I
) const {
6837 return CanUseEquipment() && I
< GetEquipments() && GetBodyPartOfEquipment(I
) && EquipmentIsAllowed(I
);
6841 /* Target mustn't have any equipment */
6842 void character::DonateEquipmentTo (character
*Character
) {
6844 feuLong
*EquipmentMemory
= game::GetEquipmentMemory();
6845 for (int c
= 0; c
< MAX_EQUIPMENT_SLOTS
; ++c
) {
6846 item
*Item
= GetEquipment(c
);
6848 if (Character
->CanUseEquipment(c
)) {
6849 Item
->RemoveFromSlot();
6850 Character
->SetEquipment(c
, Item
);
6852 EquipmentMemory
[c
] = Item
->GetID();
6853 Item
->MoveTo(Character
->GetStack());
6855 } else if (CanUseEquipment(c
)) {
6856 EquipmentMemory
[c
] = 0;
6857 } else if (EquipmentMemory
[c
] && Character
->CanUseEquipment(c
)) {
6858 for (stackiterator i
= Character
->GetStack()->GetBottom(); i
.HasItem(); ++i
) {
6859 if (i
->GetID() == EquipmentMemory
[c
]) {
6861 Item
->RemoveFromSlot();
6862 Character
->SetEquipment(c
, Item
);
6866 EquipmentMemory
[c
] = 0;
6870 for (int c
= 0; c
< GetEquipments(); ++c
) {
6871 item
*Item
= GetEquipment(c
);
6873 if (Character
->CanUseEquipment(c
)) {
6874 Item
->RemoveFromSlot();
6875 Character
->SetEquipment(c
, Item
);
6877 Item
->MoveTo(Character
->GetStackUnder());
6885 void character::ReceivePeaSoup (sLong
) {
6886 if (!game::IsInWilderness()) {
6887 lsquare
*Square
= GetLSquareUnder();
6888 if (Square
->IsFlyable()) Square
->AddSmoke(gas::Spawn(FART
, 250));
6893 void character::AddPeaSoupConsumeEndMessage () const {
6895 if (CanHear()) ADD_MESSAGE("Mmmh! The soup is very tasty. You hear a small puff.");
6896 else ADD_MESSAGE("Mmmh! The soup is very tasty.");
6897 } else if (CanBeSeenByPlayer() && PLAYER
->CanHear()) {
6899 ADD_MESSAGE("You hear a small puff.");
6904 void character::CalculateMaxStamina () {
6905 MaxStamina
= TorsoIsAlive() ? GetAttribute(ENDURANCE
) * 10000 : 0;
6909 void character::EditStamina (int Amount
, truth CanCauseUnconsciousness
) {
6910 if (!TorsoIsAlive()) return;
6911 int UnconsciousnessStamina
= MaxStamina
>> 3;
6912 if (!CanCauseUnconsciousness
&& Amount
< 0) {
6913 if (Stamina
> UnconsciousnessStamina
) {
6915 if (Stamina
< UnconsciousnessStamina
) Stamina
= UnconsciousnessStamina
;
6919 int OldStamina
= Stamina
;
6921 if (Stamina
> MaxStamina
) {
6922 Stamina
= MaxStamina
;
6923 } else if (Stamina
< 0) {
6925 LoseConsciousness(250 + RAND_N(250));
6926 } else if (IsPlayer()) {
6927 if (OldStamina
>= MaxStamina
>> 2 && Stamina
< MaxStamina
>> 2) {
6928 ADD_MESSAGE("You are getting a little tired.");
6929 } else if(OldStamina
>= UnconsciousnessStamina
&& Stamina
< UnconsciousnessStamina
) {
6930 ADD_MESSAGE("You are seriously out of breath!");
6931 game::SetPlayerIsRunning(false);
6934 if (IsPlayer() && StateIsActivated(PANIC
) && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6938 void character::RegenerateStamina () {
6939 if (GetTirednessState() != UNTIRED
) {
6940 EditExperience(ENDURANCE
, 50, 1);
6941 if (Sweats() && TorsoIsAlive() && !RAND_N(30) && !game::IsInWilderness()) {
6942 // Sweat amount proportional to endurance also
6943 //sLong Volume = sLong(0.05 * sqrt(GetBodyVolume()));
6944 sLong Volume
= long(0.05*sqrt(GetBodyVolume()*GetAttribute(ENDURANCE
)/10));
6945 if (GetTirednessState() == FAINTING
) Volume
<<= 1;
6946 for (int c
= 0; c
< SquaresUnder
; ++c
) GetLSquareUnder(c
)->SpillFluid(0, CreateSweat(Volume
), false, false);
6951 if (Action
->IsRest()) {
6952 if (SquaresUnder
== 1) {
6953 Bonus
= GetSquareUnder()->GetRestModifier() << 1;
6955 int Lowest
= GetSquareUnder(0)->GetRestModifier();
6956 for (int c
= 1; c
< GetSquaresUnder(); ++c
) {
6957 int Mod
= GetSquareUnder(c
)->GetRestModifier();
6958 if (Mod
< Lowest
) Lowest
= Mod
;
6960 Bonus
= Lowest
<< 1;
6962 } else if (Action
->IsUnconsciousness()) Bonus
= 2;
6965 auto bst
= GetBurdenState();
6966 if (bst
== OVER_LOADED
) Plus1
= 25;
6967 else if (bst
== STRESSED
) Plus1
= 50;
6968 else if (bst
== BURDENED
) Plus1
= 75;
6971 auto hst
= GetHungerState();
6972 if (hst
== STARVING
) Plus2
= 25;
6973 else if (hst
== VERY_HUNGRY
) Plus2
= 50;
6974 else if (hst
== HUNGRY
) Plus2
= 75;
6976 Stamina
+= Plus1
* Plus2
* Bonus
/ 1000;
6977 if (Stamina
> MaxStamina
) Stamina
= MaxStamina
;
6978 if (IsPlayer() && StateIsActivated(PANIC
) && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6982 void character::BeginPanic () {
6983 if (IsPlayer() && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6984 DeActivateVoluntaryAction();
6988 void character::EndPanic () {
6989 if (IsPlayer()) game::SetPlayerIsRunning(false);
6993 int character::GetTirednessState () const {
6994 if (Stamina
>= MaxStamina
>> 2) return UNTIRED
;
6995 if (Stamina
>= MaxStamina
>> 3) return EXHAUSTED
;
7000 void character::ReceiveBlackUnicorn (sLong Amount
) {
7001 if (!(RAND() % 160)) game::DoEvilDeed(Amount
/ 50);
7002 BeginTemporaryState(TELEPORT
, Amount
/ 100);
7003 for (int c
= 0; c
< STATES
; ++c
) {
7004 if (StateData
[c
].Flags
& DUR_TEMPORARY
) {
7005 BeginTemporaryState(1 << c
, Amount
/ 100);
7006 if (!IsEnabled()) return;
7007 } else if (StateData
[c
].Flags
& DUR_PERMANENT
) {
7008 GainIntrinsic(1 << c
);
7009 if (!IsEnabled()) return;
7015 void character::ReceiveGrayUnicorn (sLong Amount
) {
7016 if (!(RAND() % 80)) game::DoEvilDeed(Amount
/ 50);
7017 BeginTemporaryState(TELEPORT
, Amount
/ 100);
7018 for (int c
= 0; c
< STATES
; ++c
) {
7019 if (1 << c
!= TELEPORT
) {
7020 DecreaseStateCounter(1 << c
, -Amount
/ 100);
7021 if (!IsEnabled()) return;
7027 void character::ReceiveWhiteUnicorn (sLong Amount
) {
7028 if (!(RAND() % 40)) game::DoEvilDeed(Amount
/ 50);
7029 BeginTemporaryState(TELEPORT
, Amount
/ 100);
7030 DecreaseStateCounter(LYCANTHROPY
, -Amount
/ 100);
7031 DecreaseStateCounter(POISONED
, -Amount
/ 100);
7032 DecreaseStateCounter(PARASITIZED
, -Amount
/ 100);
7033 DecreaseStateCounter(LEPROSY
, -Amount
/ 100);
7034 DecreaseStateCounter(VAMPIRISM
, -Amount
/ 100);
7038 /* Counter should be negative. Removes intrinsics. */
7039 void character::DecreaseStateCounter (sLong State
, int Counter
) {
7041 for (Index
= 0; Index
< STATES
; ++Index
) if (1 << Index
== State
) break;
7042 if (Index
== STATES
) ABORT("DecreaseTemporaryStateCounter works only when State == 2^n!");
7043 if (TemporaryState
& State
) {
7044 if (TemporaryStateCounter
[Index
] == PERMANENT
|| (TemporaryStateCounter
[Index
] += Counter
) <= 0) {
7045 TemporaryState
&= ~State
;
7046 if (!(EquipmentState
& State
)) {
7047 if (StateData
[Index
].EndHandler
) {
7048 (this->*StateData
[Index
].EndHandler
)();
7049 if (!IsEnabled()) return;
7051 (this->*StateData
[Index
].PrintEndMessage
)();
7058 truth
character::IsImmuneToLeprosy () const {
7059 return (DataBase
->IsImmuneToLeprosy
|| UseMaterialAttributes() || StateIsActivated(DISEASE_IMMUNITY
));
7063 void character::LeprosyHandler () {
7064 EditExperience(ARM_STRENGTH
, -25, 1 << 1);
7065 EditExperience(LEG_STRENGTH
, -25, 1 << 1);
7066 EditExperience(DEXTERITY
, -25, 1 << 1);
7067 EditExperience(AGILITY
, -25, 1 << 1);
7068 EditExperience(ENDURANCE
, -25, 1 << 1);
7069 EditExperience(CHARISMA
, -25, 1 << 1);
7070 CheckDeath(CONST_S("killed by leprosy"));
7074 bodypart
*character::SearchForOriginalBodyPart (int I
) const {
7075 for (stackiterator i1
= GetStackUnder()->GetBottom(); i1
.HasItem(); ++i1
) {
7076 for (std::list
<feuLong
>::iterator i2
= OriginalBodyPartID
[I
].begin(); i2
!= OriginalBodyPartID
[I
].end(); ++i2
)
7077 if (i1
->GetID() == *i2
) return static_cast<bodypart
*>(*i1
);
7083 void character::SetLifeExpectancy (int Base
, int RandPlus
) {
7085 for (c
= 0; c
< BodyParts
; ++c
) {
7086 bodypart
*BodyPart
= GetBodyPart(c
);
7087 if (BodyPart
) BodyPart
->SetLifeExpectancy(Base
, RandPlus
);
7089 for (c
= 0; c
< GetEquipments(); ++c
) {
7090 item
*Equipment
= GetEquipment(c
);
7091 if (Equipment
) Equipment
->SetLifeExpectancy(Base
, RandPlus
);
7096 /* Receiver should be a fresh duplicate of this */
7097 void character::DuplicateEquipment (character
*Receiver
, feuLong Flags
) {
7098 for (int c
= 0; c
< GetEquipments(); ++c
) {
7099 item
*Equipment
= GetEquipment(c
);
7101 item
*Duplicate
= Equipment
->Duplicate(Flags
);
7102 Receiver
->SetEquipment(c
, Duplicate
);
7108 void character::Disappear (corpse
*Corpse
, cchar
*Verb
, truth (item::*ClosePredicate
)() const) {
7109 truth TorsoDisappeared
= false;
7110 truth CanBeSeen
= Corpse
? Corpse
->CanBeSeenByPlayer() : IsPlayer() || CanBeSeenByPlayer();
7112 if ((GetTorso()->*ClosePredicate
)()) {
7114 if (Corpse
) ADD_MESSAGE("%s %ss.", Corpse
->CHAR_NAME(DEFINITE
), Verb
);
7115 else if (IsPlayer()) ADD_MESSAGE("You %s.", Verb
);
7116 else ADD_MESSAGE("%s %ss.", CHAR_NAME(DEFINITE
), Verb
);
7118 TorsoDisappeared
= true;
7119 for (c
= 0; c
< GetEquipments(); ++c
) {
7120 item
*Equipment
= GetEquipment(c
);
7121 if (Equipment
&& (Equipment
->*ClosePredicate
)()) {
7122 Equipment
->RemoveFromSlot();
7123 Equipment
->SendToHell();
7126 itemvector ItemVector
;
7127 GetStack()->FillItemVector(ItemVector
);
7128 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
7129 if (ItemVector
[c
] && (ItemVector
[c
]->*ClosePredicate
)()) {
7130 ItemVector
[c
]->RemoveFromSlot();
7131 ItemVector
[c
]->SendToHell();
7135 for (c
= 1; c
< GetBodyParts(); ++c
) {
7136 bodypart
*BodyPart
= GetBodyPart(c
);
7138 if ((BodyPart
->*ClosePredicate
)()) {
7139 if (!TorsoDisappeared
&& CanBeSeen
) {
7140 if(IsPlayer()) ADD_MESSAGE("Your %s %ss.", GetBodyPartName(c
).CStr(), Verb
);
7141 else ADD_MESSAGE("The %s of %s %ss.", GetBodyPartName(c
).CStr(), CHAR_NAME(DEFINITE
), Verb
);
7143 BodyPart
->DropEquipment();
7144 item
*BodyPart
= SevereBodyPart(c
);
7145 if (BodyPart
) BodyPart
->SendToHell();
7146 } else if (TorsoDisappeared
) {
7147 BodyPart
->DropEquipment();
7148 item
*BodyPart
= SevereBodyPart(c
);
7150 if (Corpse
) Corpse
->GetSlot()->AddFriendItem(BodyPart
);
7151 else if (!game::IsInWilderness()) GetStackUnder()->AddItem(BodyPart
);
7152 else BodyPart
->SendToHell();
7157 if (TorsoDisappeared
) {
7159 Corpse
->RemoveFromSlot();
7160 Corpse
->SendToHell();
7162 CheckDeath(festring(Verb
) + "ed", 0, FORCE_DEATH
|DISALLOW_CORPSE
|DISALLOW_MSG
);
7165 CheckDeath(festring(Verb
) + "ed", 0, DISALLOW_MSG
);
7170 void character::SignalDisappearance () {
7171 if (GetMotherEntity()) GetMotherEntity()->SignalDisappearance();
7172 else Disappear(0, "disappear", &item::IsVeryCloseToDisappearance
);
7176 truth
character::HornOfFearWorks () const {
7177 return CanHear() && GetPanicLevel() > RAND()%33 && !StateIsActivated(FEARLESS
);
7181 void character::BeginLeprosy () {
7182 doforbodypartswithparam
<truth
>()(this, &bodypart::SetIsInfectedByLeprosy
, true);
7186 void character::EndLeprosy () {
7187 doforbodypartswithparam
<truth
>()(this, &bodypart::SetIsInfectedByLeprosy
, false);
7191 truth
character::IsSameAs (ccharacter
*What
) const {
7192 return What
->GetType() == GetType() && What
->GetConfig() == GetConfig();
7196 feuLong
character::GetCommandFlags () const {
7197 return !StateIsActivated(PANIC
) ? CommandFlags
: CommandFlags
|FLEE_FROM_ENEMIES
;
7201 feuLong
character::GetConstantCommandFlags () const {
7202 return !StateIsActivated(PANIC
) ? DataBase
->ConstantCommandFlags
: DataBase
->ConstantCommandFlags
|FLEE_FROM_ENEMIES
;
7206 feuLong
character::GetPossibleCommandFlags () const {
7207 int Int
= GetAttribute(INTELLIGENCE
);
7208 feuLong Flags
= ALL_COMMAND_FLAGS
;
7209 if (!CanMove() || Int
< 4) Flags
&= ~FOLLOW_LEADER
;
7210 if (!CanMove() || Int
< 6) Flags
&= ~FLEE_FROM_ENEMIES
;
7211 if (!CanUseEquipment() || Int
< 8) Flags
&= ~DONT_CHANGE_EQUIPMENT
;
7212 if (!UsesNutrition() || Int
< 8) Flags
&= ~DONT_CONSUME_ANYTHING_VALUABLE
;
7217 truth
character::IsRetreating () const {
7218 return StateIsActivated(PANIC
) || (CommandFlags
& FLEE_FROM_ENEMIES
&& IsPet());
7222 truth
character::ChatMenu () {
7223 if (GetAction() && !GetAction()->CanBeTalkedTo()) {
7224 ADD_MESSAGE("%s is silent.", CHAR_DESCRIPTION(DEFINITE
));
7225 PLAYER
->EditAP(-200);
7228 feuLong ManagementFlags
= GetManagementFlags();
7229 if (ManagementFlags
== CHAT_IDLY
|| !IsPet()) return ChatIdly();
7230 static cchar
*const ChatMenuEntry
[CHAT_MENU_ENTRIES
] = {
7237 static const petmanagementfunction PMF
[CHAT_MENU_ENTRIES
] = {
7238 &character::ChangePetEquipment
,
7239 &character::TakePetItems
,
7240 &character::GivePetItems
,
7241 &character::IssuePetCommands
,
7242 &character::ChatIdly
7244 felist
List(CONST_S("Choose action:"));
7245 game::SetStandardListAttributes(List
);
7246 List
.AddFlags(SELECTABLE
);
7248 for (c
= 0; c
< CHAT_MENU_ENTRIES
; ++c
) if (1 << c
& ManagementFlags
) List
.AddEntry(ChatMenuEntry
[c
], LIGHT_GRAY
);
7249 int Chosen
= List
.Draw();
7250 if (Chosen
& FELIST_ERROR_BIT
) return false;
7251 for (c
= 0, i
= 0; c
< CHAT_MENU_ENTRIES
; ++c
) {
7252 if (1 << c
& ManagementFlags
&& i
++ == Chosen
) return (this->*PMF
[c
])();
7254 return false; // dummy
7258 truth
character::ChangePetEquipment () {
7259 if (EquipmentScreen(PLAYER
->GetStack(), GetStack())) {
7267 truth
character::TakePetItems () {
7268 truth Success
= false;
7269 stack::SetSelected(0);
7272 game::DrawEverythingNoBlit();
7273 GetStack()->DrawContents(
7277 CONST_S("What do you want to take from ") + CHAR_DESCRIPTION(DEFINITE
) + '?',
7280 GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState(),
7281 GetVerbalBurdenStateColor(),
7283 if (ToTake
.empty()) break;
7284 for (uInt c
= 0; c
< ToTake
.size(); ++c
) ToTake
[c
]->MoveTo(PLAYER
->GetStack());
7285 ADD_MESSAGE("You take %s.", ToTake
[0]->GetName(DEFINITE
, ToTake
.size()).CStr());
7290 PLAYER
->DexterityAction(2);
7296 truth
character::GivePetItems () {
7297 truth Success
= false;
7298 stack::SetSelected(0);
7301 game::DrawEverythingNoBlit();
7302 PLAYER
->GetStack()->DrawContents(
7306 CONST_S("What do you want to give to ") + CHAR_DESCRIPTION(DEFINITE
) + '?',
7309 GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState(),
7310 GetVerbalBurdenStateColor(),
7312 if (ToGive
.empty()) break;
7313 for (uInt c
= 0; c
< ToGive
.size(); ++c
) ToGive
[c
]->MoveTo(GetStack());
7314 ADD_MESSAGE("You give %s to %s.", ToGive
[0]->GetName(DEFINITE
, ToGive
.size()).CStr(), CHAR_DESCRIPTION(DEFINITE
));
7319 PLAYER
->DexterityAction(2);
7325 truth
character::IssuePetCommands () {
7326 if (!IsConscious()) {
7327 ADD_MESSAGE("%s is unconscious.", CHAR_DESCRIPTION(DEFINITE
));
7330 feuLong PossibleC
= GetPossibleCommandFlags();
7332 ADD_MESSAGE("%s cannot be commanded.", CHAR_DESCRIPTION(DEFINITE
));
7335 feuLong OldC
= GetCommandFlags();
7336 feuLong NewC
= OldC
, VaryFlags
= 0;
7337 game::CommandScreen(CONST_S("Issue commands to ")+GetDescription(DEFINITE
), PossibleC
, GetConstantCommandFlags(), VaryFlags
, NewC
);
7338 if (NewC
== OldC
) return false;
7339 SetCommandFlags(NewC
);
7340 PLAYER
->EditAP(-500);
7341 PLAYER
->EditExperience(CHARISMA
, 25, 1 << 7);
7346 truth
character::ChatIdly () {
7347 if (!TryToTalkAboutScience()) {
7349 PLAYER
->EditExperience(CHARISMA
, 75, 1 << 7);
7351 PLAYER
->EditAP(-1000);
7356 int character::HasSomethingToEquipAt (int chosen
, truth equippedIsTrue
) {
7357 if (!GetBodyPartOfEquipment(chosen
)) return 0;
7359 item
*oldEquipment
= GetEquipment(chosen
);
7360 if (!IsPlayer() && oldEquipment
&& BoundToUse(oldEquipment
, chosen
)) return 0;
7362 stack
*mainStack
= GetStack();
7363 sorter Sorter
= EquipmentSorter(chosen
);
7364 auto count
= mainStack
->SortedItemsCount(this, Sorter
);
7366 if (equippedIsTrue
&& oldEquipment
) ++count
;
7372 // returns 0, 1 or pickup time
7373 feuLong
character::HasSomethingToEquipAtRecentTime (int chosen
, truth equippedIsTrue
) {
7374 if (!GetBodyPartOfEquipment(chosen
)) return 0;
7376 item
*oldEquipment
= GetEquipment(chosen
);
7377 if (!IsPlayer() && oldEquipment
&& BoundToUse(oldEquipment
, chosen
)) return 0;
7379 stack
*mainStack
= GetStack();
7380 sorter Sorter
= EquipmentSorter(chosen
);
7381 feuLong highestTime
= mainStack
->SortedItemsRecentTime(this, Sorter
);
7383 if (equippedIsTrue
&& oldEquipment
&& oldEquipment
->pickupTime
> highestTime
) highestTime
= oldEquipment
->pickupTime
;
7389 truth
character::EquipmentScreen (stack
*MainStack
, stack
*SecStack
) {
7390 if (!CanUseEquipment()) {
7391 ADD_MESSAGE("%s cannot use equipment.", CHAR_DESCRIPTION(DEFINITE
));
7395 truth EquipmentChanged
= false;
7396 felist
List(CONST_S("Equipment menu [ESC exits]"));
7400 List
.EmptyDescription();
7402 List
.AddDescription(CONST_S(""));
7403 List
.AddDescription(festring(GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState()).CapitalizeCopy(), GetVerbalBurdenStateColor());
7405 int totalWeight
= 0;
7406 for (int c
= 0; c
< GetEquipments(); ++c
) {
7407 item
*Equipment
= GetEquipment(c
);
7408 totalWeight
+= (Equipment
? Equipment
->GetWeight() : 0);
7410 festring
Total("Total weight: ");
7411 Total
<< totalWeight
;
7413 List
.AddDescription(CONST_S(""));
7414 List
.AddDescription(Total
);
7416 int firstEmpty
= -1, firstNonEmpty
= -1, selected
= -1;
7417 feuLong selPickTime
= 1;
7418 feuLong armPickTime
= 0;
7420 //truth selectedIsEmpty = false;
7421 for (int c
= 0; c
< GetEquipments(); ++c
) {
7422 truth isArm
= (c
== 5 || c
== 6);
7423 //int bpidx = (GetBodyPartOfEquipment(c) ? GetBodyPartOfEquipment(c)->GetBodyPartIndex() : -1);
7424 truth equippable
= !!GetBodyPartOfEquipment(c
);
7425 Entry
= GetEquipmentName(c
);
7428 item
*Equipment
= GetEquipment(c
);
7429 feuLong pickTm
= (equippable
? HasSomethingToEquipAtRecentTime(c
, false) : 0);
7430 int availEquipCount
= (equippable
? HasSomethingToEquipAt(c
, false) : 0);
7431 if (pickTm
> 1 && game::GetTick()-pickTm
> game::PickTimeout
) pickTm
= 0;
7432 //fprintf(stderr, "c=%d; equippable=%d; availcount=%d; pickTm=%u; tick=%u\n", c, (int)equippable, availEquipCount, pickTm, game::GetTick());
7434 Equipment
->AddInventoryEntry(this, Entry
, 1, true);
7435 AddSpecialEquipmentInfo(Entry
, c
);
7436 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
7437 if (firstNonEmpty
< 0 && equippable
&& !isArm
&& availEquipCount
> 0) firstNonEmpty
= c
;
7439 if (availEquipCount
> 0 && isArm
&& armPickTime
< pickTm
) { armFirst
= c
; armPickTime
= pickTm
; }
7440 if (selPickTime
< pickTm
&& equippable
&& !isArm
) { selected
= c
; selPickTime
= pickTm
; }
7442 List
.AddEntry(Entry
, (availEquipCount
? ORANGE
: LIGHT_GRAY
), 20, ImageKey
, true);
7444 truth canUse
= !!GetBodyPartOfEquipment(c
);
7445 Entry
<< (canUse
? "-" : "can't use");
7447 if (canUse
&& availEquipCount
> 0) {
7448 if (firstEmpty
< 0 && equippable
&& !isArm
&& availEquipCount
> 0) firstEmpty
= c
;
7449 if (equippable
&& isArm
&& armFirst
< 0) armFirst
= c
;
7450 switch (availEquipCount
) {
7451 case 0: color
= RED
; break;
7452 case 1: color
= LIGHT_GRAY
; break;
7453 default: color
= ORANGE
; break;
7456 if (color
!= RED
&& equippable
) {
7457 if (pickTm
> selPickTime
&& !isArm
) { selected
= c
; selPickTime
= pickTm
; }
7459 List
.AddEntry(Entry
, color
, 20, game::AddToItemDrawVector(itemvector()));
7462 game::DrawEverythingNoBlit();
7463 game::SetStandardListAttributes(List
);
7465 //fprintf(stderr, " selected=%d; firstEmpty=%d; firstNonEmpty=%d; armFirst=%d; armPickTime=%u\n", selected, firstEmpty, firstNonEmpty, armFirst, armPickTime);
7467 if (armPickTime
> 0) selected
= armFirst
;
7468 else if (firstEmpty
>= 0) selected
= firstEmpty
;
7469 else if (firstNonEmpty
>= 0) selected
= firstNonEmpty
;
7471 if (selected
>= 0) List
.SetSelected(selected
);
7473 List
.SetFlags(SELECTABLE
|DRAW_BACKGROUND_AFTERWARDS
);
7474 List
.SetEntryDrawer(game::ItemEntryDrawer
);
7475 Chosen
= List
.Draw();
7476 game::ClearItemDrawVector();
7477 if (Chosen
>= GetEquipments()) break;
7478 EquipmentChanged
= TryToChangeEquipment(MainStack
, SecStack
, Chosen
);
7480 if (EquipmentChanged
) DexterityAction(5);
7481 return EquipmentChanged
;
7485 feuLong
character::GetManagementFlags () const {
7486 feuLong Flags
= ALL_MANAGEMENT_FLAGS
;
7487 if (!CanUseEquipment() || !AllowPlayerToChangeEquipment()) Flags
&= ~CHANGE_EQUIPMENT
;
7488 if (!GetStack()->GetItems()) Flags
&= ~TAKE_ITEMS
;
7489 if (!WillCarryItems()) Flags
&= ~GIVE_ITEMS
;
7490 if (!GetPossibleCommandFlags()) Flags
&= ~ISSUE_COMMANDS
;
7495 cchar
*VerbalBurdenState
[] = { "overloaded", "stressed", "burdened", "unburdened" };
7496 col16 VerbalBurdenStateColor
[] = { RED
, BLUE
, BLUE
, WHITE
};
7498 cchar
*character::GetVerbalBurdenState () const { return VerbalBurdenState
[BurdenState
]; }
7499 col16
character::GetVerbalBurdenStateColor () const { return VerbalBurdenStateColor
[BurdenState
]; }
7500 int character::GetAttributeAverage () const { return GetSumOfAttributes()/7; }
7502 cfestring
&character::GetStandVerb() const {
7503 if (ForceCustomStandVerb()) return DataBase
->StandVerb
;
7504 static festring Hovering
= "hovering";
7505 static festring Swimming
= "swimming";
7506 if (StateIsActivated(LEVITATION
)) return Hovering
;
7507 if (IsSwimming()) return Swimming
;
7508 return DataBase
->StandVerb
;
7512 truth
character::CheckApply () const {
7514 ADD_MESSAGE("This monster type cannot apply.");
7521 void character::EndLevitation () {
7522 if (!IsFlying() && GetSquareUnder()) {
7523 if (!game::IsInWilderness()) SignalStepFrom(0);
7524 if (game::IsInWilderness() || !GetLSquareUnder()->IsFreezed()) TestWalkability();
7529 truth
character::CanMove () const {
7530 return !IsRooted() || StateIsActivated(LEVITATION
);
7534 void character::CalculateEnchantments () {
7535 doforequipments()(this, &item::CalculateEnchantment
);
7536 GetStack()->CalculateEnchantments();
7540 truth
character::GetNewFormForPolymorphWithControl (character
*&NewForm
) {
7541 if (StateIsActivated(POLYMORPH_LOCK
)) { ADD_MESSAGE("You feel uncertain about your body for a moment."); return false; }
7542 festring Topic
, Temp
;
7545 festring Temp
= game::DefaultQuestion(CONST_S("What do you want to become? [press '?' for a list]"), game::GetDefaultPolymorphTo(), &game::PolymorphControlKeyHandler
);
7546 NewForm
= protosystem::CreateMonster(Temp
);
7548 if (NewForm
->IsSameAs(this)) {
7550 ADD_MESSAGE("You choose not to polymorph.");
7554 if (PolymorphBackup
&& NewForm
->IsSameAs(PolymorphBackup
)) {
7556 NewForm
= ForceEndPolymorph();
7559 if (NewForm
->GetPolymorphIntelligenceRequirement() > GetAttribute(INTELLIGENCE
) && !game::WizardModeIsActive()) {
7560 ADD_MESSAGE("You feel your mind isn't yet powerful enough to call forth the form of %s.", NewForm
->CHAR_NAME(INDEFINITE
));
7564 NewForm
->RemoveAllItems();
7572 liquid
*character::CreateSweat(sLong Volume
) const {
7573 //return liquid::Spawn(GetSweatMaterial(), Volume);
7574 return liquid::Spawn(GetCurrentSweatMaterial(), Volume
);
7578 truth
character::TeleportRandomItem (truth TryToHinderVisibility
) {
7579 if (IsImmuneToItemTeleport() || StateIsActivated(TELEPORT_LOCK
)) return false;
7580 itemvector ItemVector
;
7581 std::vector
<sLong
> PossibilityVector
;
7582 int TotalPossibility
= 0;
7583 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
7584 ItemVector
.push_back(*i
);
7585 int Possibility
= i
->GetTeleportPriority();
7586 if (TryToHinderVisibility
) Possibility
+= i
->GetHinderVisibilityBonus(this);
7587 PossibilityVector
.push_back(Possibility
);
7588 TotalPossibility
+= Possibility
;
7590 for (int c
= 0; c
< GetEquipments(); ++c
) {
7591 item
*Equipment
= GetEquipment(c
);
7593 ItemVector
.push_back(Equipment
);
7594 int Possibility
= Equipment
->GetTeleportPriority();
7595 if (TryToHinderVisibility
) Possibility
+= Equipment
->GetHinderVisibilityBonus(this);
7596 PossibilityVector
.push_back(Possibility
<<= 1);
7597 TotalPossibility
+= Possibility
;
7600 if (!TotalPossibility
) return false;
7601 int Chosen
= femath::WeightedRand(PossibilityVector
, TotalPossibility
);
7602 item
*Item
= ItemVector
[Chosen
];
7603 truth Equipped
= PLAYER
->Equips(Item
);
7604 truth Seen
= Item
->CanBeSeenByPlayer();
7605 Item
->RemoveFromSlot();
7606 if (Seen
) ADD_MESSAGE("%s disappears.", Item
->CHAR_NAME(DEFINITE
));
7607 if (Equipped
) game::AskForEscPress(CONST_S("Equipment lost!"));
7609 int Range
= Item
->GetEmitation() && TryToHinderVisibility
? 25 : 5;
7610 rect
Border(Pos
+ v2(-Range
, -Range
), Pos
+ v2(Range
, Range
));
7611 Pos
= GetLevel()->GetRandomSquare(this, 0, &Border
);
7612 if (Pos
== ERROR_V2
) Pos
= GetLevel()->GetRandomSquare();
7613 GetNearLSquare(Pos
)->GetStack()->AddItem(Item
);
7614 if (Item
->CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", Item
->CHAR_NAME(INDEFINITE
));
7619 truth
character::HasClearRouteTo (v2 Pos
) const {
7620 pathcontroller::Map
= GetLevel()->GetMap();
7621 pathcontroller::Character
= this;
7622 v2 ThisPos
= GetPos();
7623 return mapmath
<pathcontroller
>::DoLine(ThisPos
.X
, ThisPos
.Y
, Pos
.X
, Pos
.Y
, SKIP_FIRST
);
7627 truth
character::IsTransparent () const {
7628 return !IsEnormous() || GetTorso()->GetMainMaterial()->IsTransparent() || StateIsActivated(INVISIBLE
);
7632 void character::SignalPossibleTransparencyChange () {
7633 if (!game::IsInWilderness()) {
7634 for (int c
= 0; c
< SquaresUnder
; ++c
) {
7635 lsquare
*Square
= GetLSquareUnder(c
);
7636 if (Square
) Square
->SignalPossibleTransparencyChange();
7642 int character::GetCursorData () const {
7644 int Color
= (game::PlayerIsRunning() ? BLUE_CURSOR
: DARK_CURSOR
);
7645 for (int c
= 0; c
< BodyParts
; ++c
) {
7646 bodypart
*BodyPart
= GetBodyPart(c
);
7647 if (BodyPart
&& BodyPart
->IsUsable()) {
7648 int ConditionColorIndex
= BodyPart
->GetConditionColorIndex();
7649 if ((BodyPartIsVital(c
) && !ConditionColorIndex
) || (ConditionColorIndex
<= 1 && ++Bad
== 2)) return Color
|CURSOR_FLASH
;
7650 } else if (++Bad
== 2) {
7651 return Color
|CURSOR_FLASH
;
7654 Color
= (game::PlayerIsRunning() ? YELLOW_CURSOR
: RED_CURSOR
);
7655 return (Bad
? Color
|CURSOR_FLASH
: Color
);
7659 void character::TryToName () {
7660 if (!IsPet()) ADD_MESSAGE("%s refuses to let YOU decide what %s's called.", CHAR_NAME(DEFINITE
), CHAR_PERSONAL_PRONOUN
);
7661 else if (IsPlayer()) ADD_MESSAGE("You can't rename yourself.");
7662 else if (!IsNameable()) ADD_MESSAGE("%s refuses to be called anything else but %s.", CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
));
7664 festring Topic
= CONST_S("What name will you give to ")+GetName(DEFINITE
)+'?';
7665 festring Name
= game::StringQuestion(Topic
, WHITE
, 0, 80, true);
7666 if (Name
.GetSize()) SetAssignedName(Name
);
7671 double character::GetSituationDanger (ccharacter
*Enemy
, v2 ThisPos
, v2 EnemyPos
, truth SeesEnemy
) const {
7673 if (IgnoreDanger() && !IsPlayer()) {
7674 if (Enemy
->IgnoreDanger() && !Enemy
->IsPlayer()) {
7675 Danger
= double(GetHP())*GetHPRequirementForGeneration()/(Enemy
->GetHP()*Enemy
->GetHPRequirementForGeneration());
7678 Danger
= 0.25*GetHPRequirementForGeneration()/Enemy
->GetHP();
7680 } else if (Enemy
->IgnoreDanger() && !Enemy
->IsPlayer()) {
7681 Danger
= 4.0*GetHP()/Enemy
->GetHPRequirementForGeneration();
7683 Danger
= GetRelativeDanger(Enemy
);
7685 Danger
*= 3.0/((EnemyPos
-ThisPos
).GetManhattanLength()+2);
7686 if (!SeesEnemy
) Danger
*= 0.2;
7687 if (StateIsActivated(PANIC
)) Danger
*= 0.2;
7688 Danger
*= double(GetHP())*Enemy
->GetMaxHP()/(Enemy
->GetHP()*GetMaxHP());
7693 void character::ModifySituationDanger (double &Danger
) const {
7694 switch (GetTirednessState()) {
7695 case FAINTING
: Danger
*= 1.5;
7696 case EXHAUSTED
: Danger
*= 1.25;
7698 for (int c
= 0; c
< STATES
; ++c
) {
7699 if (StateIsActivated(1 << c
) && StateData
[c
].SituationDangerModifier
!= 0) (this->*StateData
[c
].SituationDangerModifier
)(Danger
);
7704 void character::LycanthropySituationDangerModifier (double &Danger
) const {
7705 character
*Wolf
= werewolfwolf::Spawn();
7706 double DangerToWolf
= GetRelativeDanger(Wolf
);
7707 Danger
*= pow(DangerToWolf
, 0.1);
7712 void character::PoisonedSituationDangerModifier (double &Danger
) const {
7713 int C
= GetTemporaryStateCounter(POISONED
);
7714 Danger
*= (1+(C
*C
)/(GetHP()*10000.0*(GetGlobalResistance(POISON
)+1)));
7718 void character::PolymorphingSituationDangerModifier (double &Danger
) const {
7719 if ((!StateIsActivated(POLYMORPH_CONTROL
)) && (!StateIsActivated(POLYMORPH_LOCK
))) Danger
*= 1.5;
7723 void character::PanicSituationDangerModifier (double &Danger
) const {
7728 void character::ConfusedSituationDangerModifier (double &Danger
) const {
7733 void character::ParasitizedSituationDangerModifier (double &Danger
) const {
7738 void character::LeprosySituationDangerModifier (double &Danger
) const {
7743 void character::AddRandomScienceName (festring
&String
) const {
7744 festring Science
= GetScienceTalkName().GetRandomElement().CStr();
7745 if (Science
[0] == '!') {
7746 String
<< Science
.CStr()+1;
7749 festring Attribute
= GetScienceTalkAdjectiveAttribute().GetRandomElement();
7751 truth NoAttrib
= Attribute
.IsEmpty(), NoSecondAdjective
= false;
7752 if (!Attribute
.IsEmpty() && Attribute
[0] == '!') {
7753 NoSecondAdjective
= true;
7754 Attribute
.Erase(0, 1);
7756 if (!Science
.Find("the ")) {
7757 Science
.Erase(0, 4);
7758 if (!Attribute
.Find("the ", 0, 4)) Attribute
<< " the"; else Attribute
.Insert(0, "the ", 4);
7760 if (islower(Science
[0]) && Science
.Find(' ') == festring::NPos
&& Science
.Find('-') == festring::NPos
&&
7761 Science
.Find("phobia") == festring::NPos
) {
7762 Prefix
= GetScienceTalkPrefix().GetRandomElement();
7763 if (!Prefix
.IsEmpty() && Science
.Find(Prefix
) != festring::NPos
) Prefix
.Empty();
7765 int L
= Prefix
.GetSize();
7766 if (L
&& Prefix
[L
-1] == Science
[0]) Science
.Erase(0, 1);
7767 if (!NoAttrib
&& !NoSecondAdjective
== !RAND_GOOD(3)) {
7768 int S1
= NoSecondAdjective
? 0 : GetScienceTalkAdjectiveAttribute().Size
;
7769 int S2
= GetScienceTalkSubstantiveAttribute().Size
;
7770 festring OtherAttribute
;
7771 int Chosen
= RAND_GOOD(S1
+S2
);
7772 if (Chosen
< S1
) OtherAttribute
= GetScienceTalkAdjectiveAttribute()[Chosen
];
7773 else OtherAttribute
= GetScienceTalkSubstantiveAttribute()[Chosen
- S1
];
7774 if (!OtherAttribute
.IsEmpty() && OtherAttribute
.Find("the ", 0, 4) && Attribute
.Find(OtherAttribute
) == festring::NPos
) {
7775 String
<< Attribute
<< ' ' << OtherAttribute
<< ' ' << Prefix
<< Science
;
7779 String
<< Attribute
;
7780 if (!NoAttrib
) String
<< ' ';
7781 String
<< Prefix
<< Science
;
7785 truth
character::TryToTalkAboutScience () {
7786 if (GetRelation(PLAYER
) == HOSTILE
||
7787 GetScienceTalkPossibility() <= RAND_GOOD(100) ||
7788 PLAYER
->GetAttribute(INTELLIGENCE
) < GetScienceTalkIntelligenceRequirement() ||
7789 PLAYER
->GetAttribute(WISDOM
) < GetScienceTalkWisdomRequirement() ||
7790 PLAYER
->GetAttribute(CHARISMA
) < GetScienceTalkCharismaRequirement())
7794 AddRandomScienceName(Science
);
7797 AddRandomScienceName(S1
);
7798 AddRandomScienceName(S2
);
7799 if (S1
.Find(S2
) == festring::NPos
&& S2
.Find(S1
) == festring::NPos
) {
7800 switch (RAND_GOOD(3)) {
7801 case 0: Science
= "the relation of "; break;
7802 case 1: Science
= "the differences of "; break;
7803 case 2: Science
= "the similarities of "; break;
7805 Science
<< S1
<< " and " << S2
;
7808 AddRandomScienceName(Science
);
7811 switch ((RAND() + GET_TICK()) % 10) {
7813 ADD_MESSAGE("You have a rather pleasant chat about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7816 ADD_MESSAGE("%s explains a few of %s opinions regarding %s to you.", CHAR_DESCRIPTION(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, Science
.CStr());
7819 ADD_MESSAGE("%s reveals a number of %s insightful views of %s to you.", CHAR_DESCRIPTION(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, Science
.CStr());
7822 ADD_MESSAGE("You exchange some information pertaining to %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7825 ADD_MESSAGE("You engage in a pretty intriguing conversation about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7828 ADD_MESSAGE("You discuss at length about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7831 ADD_MESSAGE("You have a somewhat boring talk concerning %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7834 ADD_MESSAGE("You are drawn into a heated argument regarding %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7837 ADD_MESSAGE("%s delivers a long monologue concerning eg. %s.", CHAR_DESCRIPTION(DEFINITE
), Science
.CStr());
7840 ADD_MESSAGE("You dive into a brief but thought-provoking debate over %s with %s", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7843 PLAYER
->EditExperience(INTELLIGENCE
, 1000, 50. * GetScienceTalkIntelligenceModifier() / ++ScienceTalks
);
7844 PLAYER
->EditExperience(WISDOM
, 1000, 50. * GetScienceTalkWisdomModifier() / ++ScienceTalks
);
7845 PLAYER
->EditExperience(CHARISMA
, 1000, 50. * GetScienceTalkCharismaModifier() / ++ScienceTalks
);
7850 truth
character::IsUsingWeaponOfCategory (int Category
) const {
7852 ((GetMainWielded() && GetMainWielded()->GetWeaponCategory() == Category
) ||
7853 (GetSecondaryWielded() && GetSecondaryWielded()->GetWeaponCategory() == Category
));
7857 truth
character::TryToUnStickTraps (v2 Dir
) {
7858 if (!TrapData
) return true;
7859 std::vector
<trapdata
> TrapVector
;
7860 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) TrapVector
.push_back(*TrapData
);
7861 for (uInt c
= 0; c
< TrapVector
.size(); ++c
) {
7863 entity
*Trap
= game::SearchTrap(TrapVector
[c
].TrapID
);
7864 /*k8:??? if(!Trap->Exists()) int esko = esko = 2; */
7865 if (!Trap
->Exists()) continue; /*k8: ??? added by me; what this means? */
7866 if (Trap
->GetVictimID() == GetID() && Trap
->TryToUnStick(this, Dir
)) break;
7869 return !TrapData
&& IsEnabled();
7873 struct trapidcomparer
{
7874 trapidcomparer (feuLong ID
) : ID(ID
) {}
7875 truth
operator () (const trapdata
*T
) const { return T
->TrapID
== ID
; }
7880 void character::RemoveTrap (feuLong ID
) {
7881 trapdata
*&T
= ListFind(TrapData
, trapidcomparer(ID
));
7883 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7887 void character::AddTrap (feuLong ID
, feuLong BodyParts
) {
7888 trapdata
*&T
= ListFind(TrapData
, trapidcomparer(ID
));
7889 if (T
) T
->BodyParts
|= BodyParts
;
7890 else T
= new trapdata(ID
, GetID(), BodyParts
);
7891 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7895 truth
character::IsStuckToTrap (feuLong ID
) const {
7896 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) if (T
->TrapID
== ID
) return true;
7901 void character::RemoveTraps () {
7902 for (trapdata
*T
= TrapData
; T
; T
= T
->Next
) {
7903 entity
*Trap
= game::SearchTrap(T
->TrapID
);
7904 if (Trap
) Trap
->UnStick();
7906 deleteList(TrapData
);
7907 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7911 void character::RemoveTraps (int BodyPartIndex
) {
7912 feuLong Flag
= 1 << BodyPartIndex
;
7913 for (trapdata
**T
= &TrapData
; *T
;) {
7914 if ((*T
)->BodyParts
& Flag
) {
7915 entity
*Trap
= game::SearchTrap((*T
)->TrapID
);
7916 if (!((*T
)->BodyParts
&= ~Flag
)) {
7917 if (Trap
) Trap
->UnStick();
7918 trapdata
*ToDel
= *T
;
7922 if (Trap
) Trap
->UnStick(BodyPartIndex
);
7930 if (GetBodyPart(BodyPartIndex
)) GetBodyPart(BodyPartIndex
)->SignalPossibleUsabilityChange();
7934 festring
character::GetTrapDescription () const {
7936 std::pair
<entity
*, int> TrapStack
[3];
7938 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) {
7940 entity
*Trap
= game::SearchTrap(T
->TrapID
);
7943 for (c
= 0; c
< Index
; ++c
) if (TrapStack
[c
].first
->GetTrapType() == Trap
->GetTrapType()) ++TrapStack
[c
].second
;
7944 if (c
== Index
) TrapStack
[Index
++] = std::make_pair(Trap
, 1);
7952 TrapStack
[0].first
->AddTrapName(Desc
, TrapStack
[0].second
);
7955 TrapStack
[1].first
->AddTrapName(Desc
, TrapStack
[1].second
);
7956 } else if (Index
== 3) {
7958 TrapStack
[1].first
->AddTrapName(Desc
, TrapStack
[1].second
);
7960 TrapStack
[2].first
->AddTrapName(Desc
, TrapStack
[2].second
);
7963 Desc
<< "lots of traps";
7969 int character::RandomizeHurtBodyPart (feuLong BodyParts
) const {
7970 int BodyPartIndex
[MAX_BODYPARTS
];
7972 for (int c
= 0; c
< GetBodyParts(); ++c
) {
7973 if (1 << c
& BodyParts
) {
7974 /*k8: ??? if(!GetBodyPart(c)) int esko = esko = 2; */
7975 if (!GetBodyPart(c
)) continue;
7976 BodyPartIndex
[Index
++] = c
;
7978 /*k8: ??? if(!Index) int esko = esko = 2;*/
7981 fprintf(stderr
, "FATAL: RandomizeHurtBodyPart -- Index==0\n");
7984 return BodyPartIndex
[RAND_N(Index
)];
7988 truth
character::BodyPartIsStuck (int I
) const {
7989 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) if (1 << I
& T
->BodyParts
) return true;
7994 void character::PrintAttribute (cchar
*Desc
, int I
, int PanelPosX
, int PanelPosY
) const {
7995 int Attribute
= GetAttribute(I
);
7996 int NoBonusAttribute
= GetAttribute(I
, false);
7997 col16 C
= game::GetAttributeColor(I
);
7998 festring String
= Desc
;
8000 String
<< Attribute
;
8002 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
* 10), C
, "%s", String
.CStr());
8003 if (Attribute
!= NoBonusAttribute
) {
8004 int Where
= PanelPosX
+ ((String
.GetSize() + 1) << 3);
8005 FONT
->Printf(DOUBLE_BUFFER
, v2(Where
, PanelPosY
* 10), LIGHT_GRAY
, "%d", NoBonusAttribute
);
8010 truth
character::AllowUnconsciousness () const {
8011 return DataBase
->AllowUnconsciousness
&& TorsoIsAlive();
8015 truth
character::CanPanic () const {
8016 return !Action
|| !Action
->IsUnconsciousness() || !StateIsActivated(FEARLESS
);
8020 int character::GetRandomBodyPart (feuLong Possible
) const {
8021 int OKBodyPart
[MAX_BODYPARTS
];
8022 int OKBodyParts
= 0;
8023 for (int c
= 0; c
< BodyParts
; ++c
) if (1 << c
& Possible
&& GetBodyPart(c
)) OKBodyPart
[OKBodyParts
++] = c
;
8024 return OKBodyParts
? OKBodyPart
[RAND_N(OKBodyParts
)] : NONE_INDEX
;
8028 void character::EditNP (sLong What
) {
8029 int OldState
= GetHungerState();
8031 int NewState
= GetHungerState();
8032 if (OldState
> VERY_HUNGRY
&& NewState
== VERY_HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
8033 if (OldState
> STARVING
&& NewState
== STARVING
) DeActivateVoluntaryAction(CONST_S("You are getting extremely hungry."));
8037 truth
character::IsSwimming () const {
8038 return !IsFlying() && GetSquareUnder() && GetSquareUnder()->GetSquareWalkability() & SWIM
;
8042 void character::AddBlackUnicornConsumeEndMessage () const {
8043 if (IsPlayer()) ADD_MESSAGE("You feel dirty and loathsome.");
8047 void character::AddGrayUnicornConsumeEndMessage () const {
8048 if (IsPlayer()) ADD_MESSAGE("You feel neutralized.");
8052 void character::AddWhiteUnicornConsumeEndMessage () const {
8053 if (IsPlayer()) ADD_MESSAGE("You feel purified.");
8057 void character::AddOmmelBoneConsumeEndMessage () const {
8058 if (IsPlayer()) ADD_MESSAGE("You feel the power of all your canine ancestors combining in your body.");
8059 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s looks extremely ferocious. You shudder.", CHAR_NAME(DEFINITE
));
8063 void character::AddLiquidHorrorConsumeEndMessage () const {
8064 if (IsPlayer()) ADD_MESSAGE("Untold horrors flash before your eyes. The melancholy of the world is on your shoulders!");
8065 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks as if the melancholy of the world is on %s shoulders!.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
8069 void character::AddAlienFleshConsumeEndMessage() const
8071 if (IsPlayer()) ADD_MESSAGE("You feel somehow sick by eating such acidic corpse...");
8072 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks like he eat something bad.", CHAR_NAME(DEFINITE
));
8076 int character::GetBodyPartSparkleFlags (int) const {
8078 ((GetNaturalSparkleFlags() & SKIN_COLOR
? SPARKLING_A
: 0) |
8079 (GetNaturalSparkleFlags() & TORSO_MAIN_COLOR
? SPARKLING_B
: 0) |
8080 (GetNaturalSparkleFlags() & TORSO_SPECIAL_COLOR
? SPARKLING_D
: 0));
8084 truth
character::IsAnimated () const {
8085 return combinebodypartpredicates()(this, &bodypart::IsAnimated
, 1);
8089 double character::GetNaturalExperience (int Identifier
) const {
8090 return DataBase
->NaturalExperience
[Identifier
];
8094 truth
character::HasBodyPart (sorter Sorter
) const {
8095 if (Sorter
== 0) return true;
8096 return combinebodypartpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1);
8100 truth
character::PossessesItem (sorter Sorter
) const {
8101 if (Sorter
== 0) return true;
8103 (GetStack()->SortedItems(this, Sorter
) ||
8104 combinebodypartpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1) ||
8105 combineequipmentpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1));
8109 truth
character::MoreThanOnePossessesItem (sorter Sorter
) const {
8113 for (int c
= 0; c
< BodyParts
; ++c
) {
8114 bodypart
*BodyPart
= GetBodyPart(c
);
8116 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) {
8117 if (++count
> 1) return true;
8120 for (int c
= 0; c
< GetEquipments(); ++c
) {
8121 item
*Equipment
= GetEquipment(c
);
8123 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) {
8124 if (++count
> 1) return true;
8127 for (int c
= 0; c
< GetStack()->GetItems(); ++c
) {
8128 item
*Stk
= GetStack()->GetItem(c
);
8130 if (Stk
&& (Sorter
== 0 || (Stk
->*Sorter
)(this))) {
8131 if (++count
> 1) return true;
8140 item
*character::FirstPossessesItem (sorter Sorter
) const {
8142 for (int c
= 0; c
< BodyParts
; ++c
) {
8143 bodypart
*BodyPart
= GetBodyPart(c
);
8145 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) return BodyPart
;
8147 for (int c
= 0; c
< GetEquipments(); ++c
) {
8148 item
*Equipment
= GetEquipment(c
);
8150 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) return Equipment
;
8152 for (int c
= 0; c
< GetStack()->GetItems(); ++c
) {
8153 item
*Stk
= GetStack()->GetItem(c
);
8155 if (Stk
&& (Sorter
== 0 || (Stk
->*Sorter
)(this))) return Stk
;
8163 cchar
*character::GetRunDescriptionLine (int I
) const {
8164 if (!GetRunDescriptionLineOne().IsEmpty()) return !I
? GetRunDescriptionLineOne().CStr() : GetRunDescriptionLineTwo().CStr();
8165 if (IsFlying()) return !I
? "Flying" : "very fast";
8166 if (IsSwimming()) return !I
? "Swimming" : "very fast";
8167 return !I
? "Running" : "";
8171 void character::VomitAtRandomDirection (int Amount
) {
8172 if (game::IsInWilderness()) return;
8173 /* Lacks support of multitile monsters */
8176 for (int d
= 0; d
< 9; ++d
) {
8177 lsquare
*Square
= GetLSquareUnder()->GetNeighbourLSquare(d
);
8178 if (Square
&& !Square
->VomitingIsDangerous(this)) Possible
[Index
++] = Square
->GetPos();
8180 if (Index
) Vomit(Possible
[RAND_N(Index
)], Amount
);
8181 else Vomit(GetPos(), Amount
);
8185 void character::RemoveLifeSavers () {
8186 for (int c
= 0; c
< GetEquipments(); ++c
) {
8187 item
*Equipment
= GetEquipment(c
);
8188 if (Equipment
&& Equipment
->IsInCorrectSlot(c
) && Equipment
->GetGearStates() & LIFE_SAVED
) {
8189 Equipment
->SendToHell();
8190 Equipment
->RemoveFromSlot();
8196 ccharacter
*character::FindCarrier () const {
8197 return this; //check
8201 void character::PrintBeginHiccupsMessage () const {
8202 if (IsPlayer()) ADD_MESSAGE("Your diaphragm is spasming vehemently.");
8206 void character::PrintEndHiccupsMessage () const {
8207 if (IsPlayer()) ADD_MESSAGE("You feel your annoying hiccoughs have finally subsided.");
8211 void character::HiccupsHandler () {
8213 if (!(RAND() % 2000)) {
8214 if (IsPlayer()) ADD_MESSAGE("");
8215 else if (CanBeSeenByPlayer()) ADD_MESSAGE("");
8216 else if ((PLAYER->GetPos()-GetPos()).GetLengthSquare() <= 400) ADD_MESSAGE("");
8217 game::CallForAttention(GetPos(), 400);
8223 void character::VampirismHandler () {
8224 //EditExperience(ARM_STRENGTH, -25, 1 << 1);
8225 //EditExperience(LEG_STRENGTH, -25, 1 << 1);
8226 //EditExperience(DEXTERITY, -25, 1 << 1);
8227 //EditExperience(AGILITY, -25, 1 << 1);
8228 //EditExperience(ENDURANCE, -25, 1 << 1);
8229 EditExperience(CHARISMA
, -25, 1 << 1);
8230 EditExperience(WISDOM
, -25, 1 << 1);
8231 EditExperience(INTELLIGENCE
, -25, 1 << 1);
8232 CheckDeath(CONST_S("killed by vampirism"));
8236 void character::HiccupsSituationDangerModifier (double &Danger
) const {
8241 void character::VampirismSituationDangerModifier (double &Danger
) const {
8242 character
*Vampire
= vampire::Spawn();
8243 double DangerToVampire
= GetRelativeDanger(Vampire
);
8244 Danger
*= pow(DangerToVampire
, 0.1);
8249 bool character::IsConscious () const {
8250 return !Action
|| !Action
->IsUnconsciousness();
8254 wsquare
*character::GetNearWSquare (v2 Pos
) const {
8255 return static_cast<wsquare
*>(GetSquareUnder()->GetArea()->GetSquare(Pos
));
8259 wsquare
*character::GetNearWSquare (int x
, int y
) const {
8260 return static_cast<wsquare
*>(GetSquareUnder()->GetArea()->GetSquare(x
, y
));
8264 void character::ForcePutNear (v2 Pos
) {
8265 /* GUM SOLUTION!!! */
8266 v2 NewPos
= game::GetCurrentLevel()->GetNearestFreeSquare(PLAYER
, Pos
, false);
8267 if (NewPos
== ERROR_V2
) do { NewPos
= game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos
== Pos
);
8272 void character::ReceiveMustardGas (int BodyPart
, sLong Volume
) {
8273 if (Volume
) GetBodyPart(BodyPart
)->AddFluid(liquid::Spawn(MUSTARD_GAS_LIQUID
, Volume
), CONST_S("skin"), 0, true);
8277 void character::ReceiveMustardGasLiquid (int BodyPartIndex
, sLong Modifier
) {
8278 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
8279 if (BodyPart
->GetMainMaterial()->GetInteractionFlags() & IS_AFFECTED_BY_MUSTARD_GAS
) {
8280 sLong Tries
= Modifier
;
8281 Modifier
-= Tries
; //opt%?
8283 for (sLong c
= 0; c
< Tries
; ++c
) if (!(RAND() % 100)) ++Damage
;
8284 if (Modifier
&& !(RAND() % 1000 / Modifier
)) ++Damage
;
8286 feuLong Minute
= game::GetTotalMinutes();
8287 if (GetLastAcidMsgMin() != Minute
&& (CanBeSeenByPlayer() || IsPlayer())) {
8288 SetLastAcidMsgMin(Minute
);
8289 if (IsPlayer()) ADD_MESSAGE("Mustard gas dissolves the skin of your %s.", BodyPart
->GetBodyPartName().CStr());
8290 else ADD_MESSAGE("Mustard gas dissolves %s.", CHAR_NAME(DEFINITE
));
8292 ReceiveBodyPartDamage(0, Damage
, MUSTARD_GAS_DAMAGE
, BodyPartIndex
, YOURSELF
, false, false, false);
8293 CheckDeath(CONST_S("killed by a fatal exposure to mustard gas"));
8299 truth
character::IsBadPath (v2 Pos
) const {
8300 if (!IsGoingSomeWhere()) return false;
8301 v2 TPos
= !Route
.empty() ? Route
.back() : GoingTo
;
8302 return ((TPos
- Pos
).GetManhattanLength() > (TPos
- GetPos()).GetManhattanLength());
8306 double &character::GetExpModifierRef (expid E
) {
8307 return ExpModifierMap
.insert(std::make_pair(E
, 1.)).first
->second
;
8311 /* Should probably do more. Now only makes Player forget gods */
8312 truth
character::ForgetRandomThing () {
8314 /* hopefully this code isn't some where else */
8315 std::vector
<god
*> Known
;
8316 for (int c
= 1; c
<= GODS
; ++c
) if (game::GetGod(c
)->IsKnown()) Known
.push_back(game::GetGod(c
));
8317 if (Known
.empty()) return false;
8318 int RandomGod
= RAND_N(Known
.size());
8319 Known
.at(RAND_N(Known
.size()))->SetIsKnown(false);
8320 ADD_MESSAGE("You forget how to pray to %s.", Known
.at(RandomGod
)->GetName());
8327 int character::CheckForBlock (character
*Enemy
, item
*Weapon
, double ToHitValue
, int Damage
, int Success
, int Type
) {
8332 void character::ApplyAllGodsKnownBonus () {
8333 stack
*AddPlace
= GetStackUnder();
8334 if (game::IsInWilderness()) AddPlace
= GetStack(); else AddPlace
= GetStackUnder();
8335 pantheonbook
*NewBook
= pantheonbook::Spawn();
8336 AddPlace
->AddItem(NewBook
);
8337 ADD_MESSAGE("\"MORTAL! BEHOLD THE HOLY SAGA\"");
8338 ADD_MESSAGE("%s materializes near your feet.", NewBook
->CHAR_NAME(INDEFINITE
));
8342 void character::ReceiveSirenSong (character
*Siren
) {
8343 if (Siren
->GetTeam() == GetTeam()) return;
8345 if (IsPlayer()) ADD_MESSAGE("The beautiful melody of %s makes you feel sleepy.", Siren
->CHAR_NAME(DEFINITE
));
8346 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The beautiful melody of %s makes %s look sleepy.", Siren
->CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
)); /*k8*/
8347 Stamina
-= (1 + RAND_N(4)) * 10000;
8350 if (!IsPlayer() && IsCharmable() && !RAND_N(5)) {
8351 ChangeTeam(Siren
->GetTeam());
8352 ADD_MESSAGE("%s seems to be totally brainwashed by %s melodies.", CHAR_NAME(DEFINITE
), Siren
->CHAR_NAME(DEFINITE
));
8356 item
*What
= GiveMostExpensiveItem(Siren
);
8359 ADD_MESSAGE("%s music persuades you to give %s to %s as a present.", Siren
->CHAR_NAME(DEFINITE
), What
->CHAR_NAME(DEFINITE
), Siren
->CHAR_OBJECT_PRONOUN
);
8361 ADD_MESSAGE("%s is persuated to give %s to %s because of %s beautiful singing.", CHAR_NAME(DEFINITE
), What
->CHAR_NAME(INDEFINITE
), Siren
->CHAR_NAME(DEFINITE
), Siren
->CHAR_OBJECT_PRONOUN
);
8364 if (IsPlayer()) ADD_MESSAGE("You would like to give something to %s.", Siren
->CHAR_NAME(DEFINITE
));
8371 // return 0, if no item found
8372 item
*character::FindMostExpensiveItem () const {
8374 item
*MostExpensive
= 0;
8375 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
8376 if ((*i
)->GetPrice() > MaxPrice
) {
8377 MaxPrice
= (*i
)->GetPrice();
8378 MostExpensive
= (*i
);
8381 for (int c
= 0; c
< GetEquipments(); ++c
) {
8382 item
*Equipment
= GetEquipment(c
);
8383 if (Equipment
&& Equipment
->GetPrice() > MaxPrice
) {
8384 MaxPrice
= Equipment
->GetPrice();
8385 MostExpensive
= Equipment
;
8388 return MostExpensive
;
8392 // returns 0 if no items available
8393 item
*character::GiveMostExpensiveItem(character
*ToWhom
) {
8394 item
*ToGive
= FindMostExpensiveItem();
8395 if (!ToGive
) return 0;
8396 truth Equipped
= PLAYER
->Equips(ToGive
);
8397 ToGive
->RemoveFromSlot();
8398 if (Equipped
) game::AskForEscPress(CONST_S("Equipment lost!"));
8399 ToWhom
->ReceiveItemAsPresent(ToGive
);
8405 void character::ReceiveItemAsPresent (item
*Present
) {
8406 if (TestForPickup(Present
)) GetStack()->AddItem(Present
); else GetStackUnder()->AddItem(Present
);
8410 /* returns 0 if no enemies in sight */
8411 character
*character::GetNearestEnemy () const {
8412 character
*NearestEnemy
= 0;
8413 sLong NearestEnemyDistance
= 0x7FFFFFFF;
8415 for (int c
= 0; c
< game::GetTeams(); ++c
) {
8416 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
8417 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
8418 if ((*i
)->IsEnabled()) {
8419 sLong ThisDistance
= Max
<sLong
>(abs((*i
)->GetPos().X
- Pos
.X
), abs((*i
)->GetPos().Y
- Pos
.Y
));
8420 if ((ThisDistance
< NearestEnemyDistance
|| (ThisDistance
== NearestEnemyDistance
&& !(RAND() % 3))) && (*i
)->CanBeSeenBy(this)) {
8422 NearestEnemyDistance
= ThisDistance
;
8428 return NearestEnemy
;
8432 truth
character::MindWormCanPenetrateSkull (mindworm
*) const {
8437 truth
character::CanTameWithDulcis (const character
*Tamer
) const {
8438 int TamingDifficulty
= GetTamingDifficulty();
8439 if (TamingDifficulty
== NO_TAMING
) return false;
8440 if (GetAttachedGod() == DULCIS
) return true;
8441 int Modifier
= Tamer
->GetAttribute(WISDOM
) + Tamer
->GetAttribute(CHARISMA
);
8442 if (Tamer
->IsPlayer()) Modifier
+= game::GetGod(DULCIS
)->GetRelation() / 20;
8443 else if (Tamer
->GetAttachedGod() == DULCIS
) Modifier
+= 50;
8444 if (TamingDifficulty
== 0) {
8445 if (!IgnoreDanger()) TamingDifficulty
= int(10 * GetRelativeDanger(Tamer
));
8446 else TamingDifficulty
= 10 * GetHPRequirementForGeneration()/Max(Tamer
->GetHP(), 1);
8448 return Modifier
>= TamingDifficulty
* 3;
8452 truth
character::CanTameWithLyre (const character
*Tamer
) const {
8453 int TamingDifficulty
= GetTamingDifficulty();
8454 if (TamingDifficulty
== NO_TAMING
) return false;
8455 if (TamingDifficulty
== 0) {
8456 if (!IgnoreDanger()) TamingDifficulty
= int(10 * GetRelativeDanger(Tamer
));
8457 else TamingDifficulty
= 10*GetHPRequirementForGeneration()/Max(Tamer
->GetHP(), 1);
8459 return Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
;
8463 truth
character::CanTameWithScroll (const character
*Tamer
) const {
8464 int TamingDifficulty
= GetTamingDifficulty();
8466 (TamingDifficulty
!= NO_TAMING
&&
8467 (TamingDifficulty
== 0 ||
8468 Tamer
->GetAttribute(INTELLIGENCE
) * 4 + Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
* 5));
8472 truth
character::CanTameWithResurrection (const character
*Tamer
) const {
8473 int TamingDifficulty
= GetTamingDifficulty();
8474 if (TamingDifficulty
== NO_TAMING
) return false;
8475 if (TamingDifficulty
== 0) return true;
8476 return (Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
/2);
8480 truth
character::CheckSadism () {
8481 if (!IsSadist() || !HasSadistAttackMode() || !IsSmall()) return false; // gum
8483 for (int d
= 0; d
< MDIR_STAND
; ++d
) {
8484 square
*Square
= GetNeighbourSquare(d
);
8486 character
*Char
= Square
->GetCharacter();
8487 if (Char
&& Char
->IsMasochist() && GetRelation(Char
) == FRIEND
&&
8488 Char
->GetHP() * 3 >= Char
->GetMaxHP() * 2 && Hit(Char
, Square
->GetPos(), d
, SADIST_HIT
)) {
8499 truth
character::CheckForBeverage () {
8500 if (StateIsActivated(PANIC
) || !IsEnabled() || !UsesNutrition() || CheckIfSatiated()) return false;
8501 itemvector ItemVector
;
8502 GetStack()->FillItemVector(ItemVector
);
8503 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) if (ItemVector
[c
]->IsBeverage(this) && TryToConsume(ItemVector
[c
])) return true;
8508 void character::Haste () {
8509 doforbodyparts()(this, &bodypart::Haste
);
8510 doforequipments()(this, &item::Haste
);
8511 BeginTemporaryState(HASTE
, 500 + RAND() % 1000);
8515 void character::Slow () {
8516 doforbodyparts()(this, &bodypart::Slow
);
8517 doforequipments()(this, &item::Slow
);
8518 //BeginTemporaryState(HASTE, 500 + RAND() % 1000); // this seems to be a bug
8519 BeginTemporaryState(SLOW
, 500 + RAND() % 1000);
8523 void character::SurgicallyDetachBodyPart () {
8524 ADD_MESSAGE("You haven't got any extra bodyparts.");
8528 truth
character::CanHear() const
8530 return DataBase
->CanHear
&& HasHead();
8534 truth
character::IsAllowedInDungeon (int dunIndex
) {
8535 const fearray
<int> &dlist
= GetAllowedDungeons();
8537 for (uInt f
= 0; f
< dlist
.Size
; ++f
) {
8538 if (dlist
[f
] == ALL_DUNGEONS
|| dlist
[f
] == dunIndex
) {
8539 fprintf(stderr
, "OK!\n");
8543 fprintf(stderr
, "NO!\n");
8548 truth
character::IsESPBlockedByEquipment () const {
8549 for (int c
= 0; c
< GetEquipments(); ++c
) {
8550 item
*Item
= GetEquipment(c
);
8551 if (Item
&& Item
->IsHelmet(this) &&
8552 ((Item
->GetMainMaterial() && Item
->GetMainMaterial()->BlockESP()) ||
8553 (Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->BlockESP()))) return true;
8559 truth
character::TemporaryStateIsActivated (sLong What
) const {
8560 if ((What
&ESP
) && (TemporaryState
&ESP
) && IsESPBlockedByEquipment()) {
8561 return ((TemporaryState
&What
)&(~ESP
));
8563 return (TemporaryState
& What
);
8567 truth
character::StateIsActivated (sLong What
) const {
8568 if ((What
&ESP
) && ((TemporaryState
|EquipmentState
)&ESP
) && IsESPBlockedByEquipment()) {
8569 return ((TemporaryState
&What
)&(~ESP
)) || ((EquipmentState
&What
)&(~ESP
));
8571 return (TemporaryState
& What
) || (EquipmentState
& What
);
8575 void character::PrintBeginFearlessMessage () const {
8576 if (!StateIsActivated(FEARLESS
)) {
8577 if (IsPlayer()) ADD_MESSAGE("You feel very comfortable.");
8578 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems very comfortable.", CHAR_NAME(DEFINITE
));
8582 void character::PrintEndFearlessMessage () const {
8583 if (!StateIsActivated(FEARLESS
)) {
8584 if (IsPlayer()) ADD_MESSAGE("Everything looks more dangerous now.");
8585 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems to have lost his confidence.", CHAR_NAME(DEFINITE
));
8589 void character::BeginFearless () {
8590 DeActivateTemporaryState(PANIC
);
8593 void character::EndFearless () {
8598 void character::PrintBeginEtherealityMessage () const {
8599 if (IsPlayer()) ADD_MESSAGE("You feel like many miscible droplets of ether.");
8600 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s melds into the surroundings.", CHAR_NAME(DEFINITE
));
8603 void character::PrintEndEtherealityMessage () const {
8604 if (IsPlayer()) ADD_MESSAGE("You drop out of the firmament, feeling suddenly quite dense.");
8605 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s displaces the air with a puff.", CHAR_NAME(INDEFINITE
));
8608 void character::BeginEthereality () {}
8610 void character::EndEthereality () {}
8613 void character::PrintBeginPolymorphLockMessage () const {
8614 if (IsPlayer()) ADD_MESSAGE("You feel incredibly stubborn about who you are.");
8617 void character::PrintEndPolymorphLockMessage () const {
8618 if (IsPlayer()) ADD_MESSAGE("You feel more open to new ideas.");
8621 void character::PolymorphLockHandler () {
8622 if (TemporaryStateIsActivated(POLYMORPHED
)) {
8623 EditTemporaryStateCounter(POLYMORPHED
, 1);
8624 if (GetTemporaryStateCounter(POLYMORPHED
) < 1000) EditTemporaryStateCounter(POLYMORPHED
, 1);
8628 void character::PrintBeginRegenerationMessage () const {
8629 if (IsPlayer()) ADD_MESSAGE("Your heart races.");
8632 void character::PrintEndRegenerationMessage () const {
8633 if (IsPlayer()) ADD_MESSAGE("Your rapid heartbeat calms down.");
8636 void character::PrintBeginDiseaseImmunityMessage () const {
8637 if (IsPlayer()) ADD_MESSAGE("You feel especially healthy.");
8640 void character::PrintEndDiseaseImmunityMessage () const {
8641 if (IsPlayer()) ADD_MESSAGE("You develop a sudden fear of germs.");
8644 void character::PrintBeginTeleportLockMessage () const {
8645 if (IsPlayer()) ADD_MESSAGE("You feel firmly planted in reality.");
8648 void character::PrintEndTeleportLockMessage () const {
8649 if (IsPlayer()) ADD_MESSAGE("Your mind soars far and wide.");
8652 void character::TeleportLockHandler () {
8653 if (StateIsActivated(TELEPORT_LOCK
)) {
8654 EditTemporaryStateCounter(TELEPORT_LOCK
, 1);
8655 if (GetTemporaryStateCounter(TELEPORT_LOCK
) < 1000) EditTemporaryStateCounter(TELEPORT_LOCK
, 1);
8660 void character::PrintBeginSwimmingMessage () const {
8661 if (IsPlayer()) ADD_MESSAGE("You fondly remember the sound of ocean waves.");
8662 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks wet.", CHAR_NAME(DEFINITE
));
8665 void character::PrintEndSwimmingMessage () const {
8666 if (IsPlayer()) ADD_MESSAGE("You suddenly remember how you nearly drowned as a child.");
8667 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks less wet.", CHAR_NAME(INDEFINITE
));
8670 void character::BeginSwimming () {}
8671 void character::EndSwimming () {}