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
,
295 &character::EndSwimming
,
301 SECRET
|(RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
)),
302 &character::PrintBeginDetectMessage
,
303 &character::PrintEndDetectMessage
,
306 &character::DetectHandler
,
312 &character::PrintBeginEtherealityMessage
,
313 &character::PrintEndEtherealityMessage
,
314 &character::BeginEthereality
,
315 &character::EndEthereality
,
321 RANDOMIZABLE
&~SRC_EVIL
,
322 &character::PrintBeginFearlessMessage
,
323 &character::PrintEndFearlessMessage
,
324 &character::BeginFearless
,
325 &character::EndFearless
,
332 &character::PrintBeginPolymorphLockMessage
,
333 &character::PrintEndPolymorphLockMessage
,
336 &character::PolymorphLockHandler
,
341 SECRET
|(RANDOMIZABLE
&~SRC_EVIL
),
342 &character::PrintBeginRegenerationMessage
,
343 &character::PrintEndRegenerationMessage
,
351 SECRET
|(RANDOMIZABLE
&~SRC_EVIL
),
352 &character::PrintBeginDiseaseImmunityMessage
,
353 &character::PrintEndDiseaseImmunityMessage
,
362 &character::PrintBeginTeleportLockMessage
,
363 &character::PrintEndTeleportLockMessage
,
366 &character::TeleportLockHandler
,
373 characterprototype::characterprototype (const characterprototype
*Base
, characterspawner Spawner
,
374 charactercloner Cloner
, cchar
*ClassID
)
380 Index
= protocontainer
<character
>::Add(this);
384 void character::CreateInitialEquipment (int SpecialFlags
) { AddToInventory(DataBase
->Inventory
, SpecialFlags
); }
385 void character::EditAP (sLong What
) { AP
= Limit
<sLong
>(AP
+What
, -12000, 1200); }
386 int character::GetRandomStepperBodyPart () const { return TORSO_INDEX
; }
387 void character::GainIntrinsic (sLong What
) { BeginTemporaryState(What
, PERMANENT
); }
388 truth
character::IsUsingArms () const { return GetAttackStyle() & USE_ARMS
; }
389 truth
character::IsUsingLegs () const { return GetAttackStyle() & USE_LEGS
; }
390 truth
character::IsUsingHead () const { return GetAttackStyle() & USE_HEAD
; }
391 void character::CalculateAllowedWeaponSkillCategories () { AllowedWeaponSkillCategories
= MARTIAL_SKILL_CATEGORIES
; }
392 festring
character::GetBeVerb () const { return IsPlayer() ? CONST_S("are") : CONST_S("is"); }
393 void character::SetEndurance (int What
) { BaseExperience
[ENDURANCE
] = What
* EXP_MULTIPLIER
; }
394 void character::SetPerception (int What
) { BaseExperience
[PERCEPTION
] = What
* EXP_MULTIPLIER
; }
395 void character::SetIntelligence (int What
) { BaseExperience
[INTELLIGENCE
] = What
* EXP_MULTIPLIER
; }
396 void character::SetWisdom (int What
) { BaseExperience
[WISDOM
] = What
* EXP_MULTIPLIER
; }
397 void character::SetWillPower (int What
) { BaseExperience
[WILL_POWER
] = What
* EXP_MULTIPLIER
; }
398 void character::SetCharisma (int What
) { BaseExperience
[CHARISMA
] = What
* EXP_MULTIPLIER
; }
399 void character::SetMana (int What
) { BaseExperience
[MANA
] = What
* EXP_MULTIPLIER
; }
400 truth
character::IsOnGround () const { return MotherEntity
&& MotherEntity
->IsOnGround(); }
401 truth
character::LeftOversAreUnique () const { return GetArticleMode() || AssignedName
.GetSize(); }
402 truth
character::HomeDataIsValid () const { return (HomeData
&& HomeData
->Level
== GetLSquareUnder()->GetLevelIndex() && HomeData
->Dungeon
== GetLSquareUnder()->GetDungeonIndex()); }
403 void character::SetHomePos (v2 Pos
) { HomeData
->Pos
= Pos
; }
404 cchar
*character::FirstPersonUnarmedHitVerb () const { return "hit"; }
405 cchar
*character::FirstPersonCriticalUnarmedHitVerb () const { return "critically hit"; }
406 cchar
*character::ThirdPersonUnarmedHitVerb () const { return "hits"; }
407 cchar
*character::ThirdPersonCriticalUnarmedHitVerb () const { return "critically hits"; }
408 cchar
*character::FirstPersonKickVerb () const { return "kick"; }
409 cchar
*character::FirstPersonCriticalKickVerb () const { return "critically kick"; }
410 cchar
*character::ThirdPersonKickVerb () const { return "kicks"; }
411 cchar
*character::ThirdPersonCriticalKickVerb () const { return "critically kicks"; }
412 cchar
*character::FirstPersonBiteVerb () const { return "bite"; }
413 cchar
*character::FirstPersonCriticalBiteVerb () const { return "critically bite"; }
414 cchar
*character::ThirdPersonBiteVerb () const { return "bites"; }
415 cchar
*character::ThirdPersonCriticalBiteVerb () const { return "critically bites"; }
416 cchar
*character::UnarmedHitNoun () const { return "attack"; }
417 cchar
*character::KickNoun () const { return "kick"; }
418 cchar
*character::BiteNoun () const { return "attack"; }
419 cchar
*character::GetEquipmentName (int) const { return ""; }
420 const std::list
<feuLong
> &character::GetOriginalBodyPartID (int I
) const { return OriginalBodyPartID
[I
]; }
421 square
*character::GetNeighbourSquare (int I
) const { return GetSquareUnder()->GetNeighbourSquare(I
); }
422 lsquare
*character::GetNeighbourLSquare (int I
) const { return static_cast<lsquare
*>(GetSquareUnder())->GetNeighbourLSquare(I
); }
423 //wsquare *character::GetNeighbourWSquare (int I) const { return static_cast<wsquare *>(GetSquareUnder())->GetNeighbourWSquare(I); }
424 god
*character::GetMasterGod () const { return game::GetGod(GetConfig()); }
425 col16
character::GetBodyPartColorA (int, truth
) const { return GetSkinColor(); }
426 col16
character::GetBodyPartColorB (int, truth
) const { return GetTorsoMainColor(); }
427 col16
character::GetBodyPartColorC (int, truth
) const { return GetBeltColor(); } // sorry...
428 col16
character::GetBodyPartColorD (int, truth
) const { return GetTorsoSpecialColor(); }
429 int character::GetRandomApplyBodyPart () const { return TORSO_INDEX
; }
430 truth
character::IsPet () const { return GetTeam()->GetID() == PLAYER_TEAM
; }
431 character
* character::GetLeader () const { return GetTeam()->GetLeader(); }
432 festring
character::GetZombieDescription () const { return " of "+GetName(INDEFINITE
); }
433 truth
character::BodyPartCanBeSevered (int I
) const { return I
; }
434 truth
character::HasBeenSeen () const { return DataBase
->Flags
& HAS_BEEN_SEEN
; }
435 truth
character::IsTemporary () const { return GetTorso()->GetLifeExpectancy(); }
436 cchar
*character::GetNormalDeathMessage () const { return "killed @k"; }
439 truth
character::IsHomeLevel (level
*lvl
) const {
440 if (!lvl
) return false;
442 const fearray
<festring
> &hlist
= DataBase
->HomeLevel
;
443 if (hlist
.Size
== 0) return false;
445 for (uInt f
= 0; f
< hlist
.Size
; ++f
) { if (hlist
[f
] == "*") return true; }
446 // taken from unique characters code
447 cfestring
*tag
= lvl
->GetLevelScript()->GetTag();
448 if (!tag
) return false; // not a possible home level
449 // check for home level
450 for (uInt f
= 0; f
< hlist
.Size
; ++f
) { if (hlist
[f
] == *tag
) return true; }
455 truth
character::MustBeRemovedFromBone () const {
456 if (IsUnique() && !CanBeGenerated()) return true;
458 const fearray
<festring
> &hlist
= DataBase
->HomeLevel
;
459 if (hlist
.Size
== 0) return false;
461 for (uInt f
= 0; f
< hlist
.Size
; ++f
) { if (hlist
[f
] == "*") return true; }
462 // taken from unique characters code
463 if (!IsEnabled() || GetTeam()->GetID() != DataBase
->NaturalTeam
) return true;
464 return IsHomeLevel(GetLevel());
468 int character::GetMoveType () const {// return (!StateIsActivated(LEVITATION) ? DataBase->MoveType : DataBase->MoveType | FLY); }
470 (!StateIsActivated(LEVITATION
) ? DataBase
->MoveType
: DataBase
->MoveType
|FLY
)|
471 (!StateIsActivated(ETHEREAL_MOVING
) ? DataBase
->MoveType
: DataBase
->MoveType
|ETHEREAL
)|
472 (!StateIsActivated(SWIMMING
) ? DataBase
->MoveType
: DataBase
->MoveType
|WALK
|SWIM
)|
478 int characterdatabase::*ExpPtr
[ATTRIBUTES
] = {
479 &characterdatabase::DefaultEndurance
,
480 &characterdatabase::DefaultPerception
,
481 &characterdatabase::DefaultIntelligence
,
482 &characterdatabase::DefaultWisdom
,
483 &characterdatabase::DefaultWillPower
,
484 &characterdatabase::DefaultCharisma
,
485 &characterdatabase::DefaultMana
,
486 &characterdatabase::DefaultArmStrength
,
487 &characterdatabase::DefaultLegStrength
,
488 &characterdatabase::DefaultDexterity
,
489 &characterdatabase::DefaultAgility
493 contentscript
<item
> characterdatabase::*EquipmentDataPtr
[EQUIPMENT_DATAS
] = {
494 &characterdatabase::Helmet
,
495 &characterdatabase::Amulet
,
496 &characterdatabase::Cloak
,
497 &characterdatabase::BodyArmor
,
498 &characterdatabase::Belt
,
499 &characterdatabase::RightWielded
,
500 &characterdatabase::LeftWielded
,
501 &characterdatabase::RightRing
,
502 &characterdatabase::LeftRing
,
503 &characterdatabase::RightGauntlet
,
504 &characterdatabase::LeftGauntlet
,
505 &characterdatabase::RightBoot
,
506 &characterdatabase::LeftBoot
510 character::character (ccharacter
&Char
) :
511 entity(Char
), id(Char
), NP(Char
.NP
), AP(Char
.AP
),
512 TemporaryState(Char
.TemporaryState
&~POLYMORPHED
),
513 Team(Char
.Team
), GoingTo(ERROR_V2
), Money(0),
514 AssignedName(Char
.AssignedName
), Action(0),
515 DataBase(Char
.DataBase
), MotherEntity(0),
516 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
517 AllowedWeaponSkillCategories(Char
.AllowedWeaponSkillCategories
),
518 BodyParts(Char
.BodyParts
),
519 RegenerationCounter(Char
.RegenerationCounter
),
520 SquaresUnder(Char
.SquaresUnder
), LastAcidMsgMin(0),
521 Stamina(Char
.Stamina
), MaxStamina(Char
.MaxStamina
),
522 BlocksSinceLastTurn(0), GenerationDanger(Char
.GenerationDanger
),
523 CommandFlags(Char
.CommandFlags
), WarnFlags(0),
524 ScienceTalks(Char
.ScienceTalks
), TrapData(0), CounterToMindWormHatch(0)
528 Flags
|= C_INITIALIZING
|C_IN_NO_MSG_MODE
;
529 Stack
= new stack(0, this, HIDDEN
);
530 for (c
= 0; c
< STATES
; ++c
) TemporaryStateCounter
[c
] = Char
.TemporaryStateCounter
[c
];
531 if (Team
) Team
->Add(this);
532 for (c
= 0; c
< BASE_ATTRIBUTES
; ++c
) BaseExperience
[c
] = Char
.BaseExperience
[c
];
533 BodyPartSlot
= new bodypartslot
[BodyParts
];
534 OriginalBodyPartID
= new std::list
<feuLong
>[BodyParts
];
535 CWeaponSkill
= new cweaponskill
[AllowedWeaponSkillCategories
];
536 SquareUnder
= new square
*[SquaresUnder
];
537 if (SquaresUnder
== 1) *SquareUnder
= 0; else memset(SquareUnder
, 0, SquaresUnder
*sizeof(square
*));
538 for (c
= 0; c
< BodyParts
; ++c
) {
539 BodyPartSlot
[c
].SetMaster(this);
540 bodypart
*CharBodyPart
= Char
.GetBodyPart(c
);
541 OriginalBodyPartID
[c
] = Char
.OriginalBodyPartID
[c
];
543 bodypart
*BodyPart
= static_cast<bodypart
*>(CharBodyPart
->Duplicate());
544 SetBodyPart(c
, BodyPart
);
545 BodyPart
->CalculateEmitation();
548 for (c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) CWeaponSkill
[c
] = Char
.CWeaponSkill
[c
];
549 HomeData
= Char
.HomeData
? new homedata(*Char
.HomeData
) : 0;
550 ID
= game::CreateNewCharacterID(this);
554 character::character () :
555 entity(HAS_BE
), NP(50000), AP(0), TemporaryState(0), Team(0),
556 GoingTo(ERROR_V2
), Money(0), Action(0), MotherEntity(0),
557 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
558 RegenerationCounter(0), HomeData(0), LastAcidMsgMin(0),
559 BlocksSinceLastTurn(0), GenerationDanger(DEFAULT_GENERATION_DANGER
),
560 WarnFlags(0), ScienceTalks(0), TrapData(0), CounterToMindWormHatch(0)
562 Stack
= new stack(0, this, HIDDEN
);
566 character::~character () {
567 if (Action
) delete Action
;
568 if (Team
) Team
->Remove(this);
570 for (int c
= 0; c
< BodyParts
; ++c
) delete GetBodyPart(c
);
571 delete [] BodyPartSlot
;
572 delete [] OriginalBodyPartID
;
573 delete PolymorphBackup
;
574 delete [] SquareUnder
;
575 delete [] CWeaponSkill
;
577 deleteList(TrapData
);
578 game::RemoveCharacterID(ID
);
582 void character::Hunger () {
583 auto bst
= GetBurdenState();
584 if (bst
== OVER_LOADED
|| bst
== STRESSED
) {
586 EditExperience(LEG_STRENGTH
, 150, 1 << 2);
587 EditExperience(AGILITY
, -50, 1 << 2);
588 } else if (bst
== BURDENED
) {
590 EditExperience(LEG_STRENGTH
, 75, 1 << 1);
591 EditExperience(AGILITY
, -25, 1 << 1);
592 } else if (bst
== UNBURDENED
) {
596 auto hst
= GetHungerState();
597 if (hst
== STARVING
) {
598 EditExperience(ARM_STRENGTH
, -75, 1 << 3);
599 EditExperience(LEG_STRENGTH
, -75, 1 << 3);
600 } else if (hst
== VERY_HUNGRY
) {
601 EditExperience(ARM_STRENGTH
, -50, 1 << 2);
602 EditExperience(LEG_STRENGTH
, -50, 1 << 2);
603 } else if (hst
== HUNGRY
) {
604 EditExperience(ARM_STRENGTH
, -25, 1 << 1);
605 EditExperience(LEG_STRENGTH
, -25, 1 << 1);
606 } else if (hst
== SATIATED
) {
607 EditExperience(AGILITY
, -25, 1 << 1);
608 } else if (hst
== BLOATED
) {
609 EditExperience(AGILITY
, -50, 1 << 2);
610 } else if (hst
== OVER_FED
) {
611 EditExperience(AGILITY
, -75, 1 << 3);
614 CheckStarvationDeath(CONST_S("starved to death"));
618 int character::TakeHit (character
*Enemy
, item
*Weapon
, bodypart
*EnemyBodyPart
, v2 HitPos
, double Damage
,
619 double ToHitValue
, int Success
, int Type
, int GivenDir
, truth Critical
, truth ForceHit
)
622 if (!game::RunCharEvent("before_take_hit", this, Enemy
, Weapon
)) return DID_NO_DAMAGE
;
624 int Dir
= (Type
== BITE_ATTACK
? YOURSELF
: GivenDir
);
625 double DodgeValue
= GetDodgeValue();
626 if (!Enemy
->IsPlayer() && GetAttackWisdomLimit() != NO_LIMIT
) Enemy
->EditExperience(WISDOM
, 75, 1 << 13);
627 if (!Enemy
->CanBeSeenBy(this)) ToHitValue
*= 2;
628 if (!CanBeSeenBy(Enemy
)) DodgeValue
*= 2;
629 if (Enemy
->StateIsActivated(CONFUSED
)) ToHitValue
*= 0.75;
631 switch (Enemy
->GetTirednessState()) {
637 switch (GetTirednessState()) {
645 if (!IsRetreating()) SetGoingTo(Enemy
->GetPos());
646 else SetGoingTo(GetPos()-((Enemy
->GetPos()-GetPos())<<4));
647 if (!Enemy
->IsRetreating()) Enemy
->SetGoingTo(GetPos());
648 else Enemy
->SetGoingTo(Enemy
->GetPos()-((GetPos()-Enemy
->GetPos())<<4));
651 /* Effectively, the average chance to hit is 100% / (DV/THV + 1). */
652 if (RAND() % int(100+ToHitValue
/DodgeValue
*(100+Success
)) < 100 && !Critical
&& !ForceHit
) {
653 Enemy
->AddMissMessage(this);
654 EditExperience(AGILITY
, 150, 1 << 7);
655 EditExperience(PERCEPTION
, 75, 1 << 7);
656 if (Enemy
->CanBeSeenByPlayer())
657 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
659 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
663 int TrueDamage
= int(Damage
*(100+Success
)/100)+(RAND()%3 ? 1 : 0);
665 TrueDamage
+= TrueDamage
>> 1;
669 int BodyPart
= ChooseBodyPartToReceiveHit(ToHitValue
, DodgeValue
);
671 if (Type
== UNARMED_ATTACK
) Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalUnarmedHitVerb(), Enemy
->ThirdPersonCriticalUnarmedHitVerb(), BodyPart
);
672 else if (Type
== WEAPON_ATTACK
) Enemy
->AddWeaponHitMessage(this, Weapon
, BodyPart
, true);
673 else if (Type
== KICK_ATTACK
) Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalKickVerb(), Enemy
->ThirdPersonCriticalKickVerb(), BodyPart
);
674 else if (Type
== BITE_ATTACK
) Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalBiteVerb(), Enemy
->ThirdPersonCriticalBiteVerb(), BodyPart
);
676 if (Type
== UNARMED_ATTACK
) Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonUnarmedHitVerb(), Enemy
->ThirdPersonUnarmedHitVerb(), BodyPart
);
677 else if (Type
== WEAPON_ATTACK
) Enemy
->AddWeaponHitMessage(this, Weapon
, BodyPart
, false);
678 else if (Type
== KICK_ATTACK
) Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonKickVerb(), Enemy
->ThirdPersonKickVerb(), BodyPart
);
679 else if (Type
== BITE_ATTACK
) Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonBiteVerb(), Enemy
->ThirdPersonBiteVerb(), BodyPart
);
682 if (!Critical
&& TrueDamage
&& Enemy
->AttackIsBlockable(Type
)) {
683 TrueDamage
= CheckForBlock(Enemy
, Weapon
, ToHitValue
, TrueDamage
, Success
, Type
);
684 if (!TrueDamage
|| (Weapon
&& !Weapon
->Exists())) {
685 if (Enemy
->CanBeSeenByPlayer())
686 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
688 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
693 int WeaponSkillHits
= CalculateWeaponSkillHits(Enemy
);
694 int DoneDamage
= ReceiveBodyPartDamage(Enemy
, TrueDamage
, PHYSICAL_DAMAGE
, BodyPart
, Dir
, false, Critical
, true, Type
== BITE_ATTACK
&& Enemy
->BiteCapturesBodyPart());
695 truth Succeeded
= (GetBodyPart(BodyPart
) && HitEffect(Enemy
, Weapon
, HitPos
, Type
, BodyPart
, Dir
, !DoneDamage
, Critical
, DoneDamage
)) || DoneDamage
;
696 if (Succeeded
) Enemy
->WeaponSkillHit(Weapon
, Type
, WeaponSkillHits
);
699 if (Weapon
->Exists() && DoneDamage
< TrueDamage
) Weapon
->ReceiveDamage(Enemy
, TrueDamage
-DoneDamage
, PHYSICAL_DAMAGE
);
700 if (Weapon
->Exists() && DoneDamage
&& SpillsBlood() && GetBodyPart(BodyPart
) &&
701 (GetBodyPart(BodyPart
)->IsAlive() || GetBodyPart(BodyPart
)->GetMainMaterial()->IsLiquid()))
702 Weapon
->SpillFluid(0, CreateBlood(15+RAND()%15));
705 if (Enemy
->AttackIsBlockable(Type
)) SpecialBodyDefenceEffect(Enemy
, EnemyBodyPart
, Type
);
708 if (Enemy
->CanBeSeenByPlayer())
709 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
711 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
713 return DID_NO_DAMAGE
;
716 if (CheckDeath(GetNormalDeathMessage(), Enemy
, Enemy
->IsPlayer() ? FORCE_MSG
: 0)) return HAS_DIED
;
718 if (Enemy
->CanBeSeenByPlayer())
719 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
721 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
727 struct svpriorityelement
{
728 svpriorityelement (int BodyPart
, int StrengthValue
) : BodyPart(BodyPart
), StrengthValue(StrengthValue
) {}
729 bool operator < (const svpriorityelement
&AnotherPair
) const { return StrengthValue
> AnotherPair
.StrengthValue
; }
735 int character::ChooseBodyPartToReceiveHit (double ToHitValue
, double DodgeValue
) {
736 if (BodyParts
== 1) return 0;
737 std::priority_queue
<svpriorityelement
> SVQueue
;
738 for (int c
= 0; c
< BodyParts
; ++c
) {
739 bodypart
*BodyPart
= GetBodyPart(c
);
740 if (BodyPart
&& (BodyPart
->GetHP() != 1 || BodyPart
->CanBeSevered(PHYSICAL_DAMAGE
)))
741 SVQueue
.push(svpriorityelement(c
, ModifyBodyPartHitPreference(c
, BodyPart
->GetStrengthValue()+BodyPart
->GetHP())));
743 while (SVQueue
.size()) {
744 svpriorityelement E
= SVQueue
.top();
745 int ToHitPercentage
= int(GLOBAL_WEAK_BODYPART_HIT_MODIFIER
*ToHitValue
*GetBodyPart(E
.BodyPart
)->GetBodyPartVolume()/(DodgeValue
*GetBodyVolume()));
746 ToHitPercentage
= ModifyBodyPartToHitChance(E
.BodyPart
, ToHitPercentage
);
747 if (ToHitPercentage
< 1) ToHitPercentage
= 1;
748 else if (ToHitPercentage
> 95) ToHitPercentage
= 95;
749 if (ToHitPercentage
> RAND()%100) return E
.BodyPart
;
756 void character::Be () {
757 if (game::ForceJumpToPlayerBe()) {
758 if (!IsPlayer()) return;
759 game::SetForceJumpToPlayerBe(false);
761 truth ForceBe
= HP
!= MaxHP
|| AllowSpoil();
762 for (int c
= 0; c
< BodyParts
; ++c
) {
763 bodypart
*BodyPart
= GetBodyPart(c
);
764 if (BodyPart
&& (ForceBe
|| BodyPart
->NeedsBe())) BodyPart
->Be();
767 if (!IsEnabled()) return;
768 if (GetTeam() == PLAYER
->GetTeam()) {
769 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) {
770 if (CWeaponSkill
[c
].Tick() && IsPlayer()) CWeaponSkill
[c
].AddLevelDownMessage(c
);
775 if (GetHungerState() == STARVING
&& !(RAND()%50)) LoseConsciousness(250+RAND_N(250), true);
776 if (!Action
|| Action
->AllowFoodConsumption()) Hunger();
778 if (Stamina
!= MaxStamina
) RegenerateStamina();
779 if (HP
!= MaxHP
|| StateIsActivated(REGENERATION
)) Regenerate();
780 if (Action
&& AP
>= 1000) ActionAutoTermination();
781 if (Action
&& AP
>= 1000) {
783 if (!IsEnabled()) return;
785 EditAP(GetStateAPGain(100));
789 SpecialTurnHandler();
790 BlocksSinceLastTurn
= 0;
792 static int Timer
= 0;
794 if (ivanconfig::GetAutoSaveInterval() && !GetAction() && ++Timer
>= ivanconfig::GetAutoSaveInterval()) {
795 //fprintf(stderr, "autosaving..."); fflush(stderr);
796 game::Save(game::GetAutoSaveFileName());
797 //fprintf(stderr, "done\n"); fflush(stderr);
800 game::CalculateNextDanger();
801 if (!StateIsActivated(POLYMORPHED
)) game::UpdatePlayerAttributeAverage();
802 if (!game::IsInWilderness()) Search(GetAttribute(PERCEPTION
));
806 if (Action
->ShowEnvironment()) {
807 static int Counter
= 0;
808 if (++Counter
== 10) {
809 game::DrawEverything();
813 msgsystem::ThyMessagesAreNowOld();
814 if (Action
->IsVoluntary() && READ_KEY()) Action
->Terminate(false);
817 if (!Action
&& !game::IsInWilderness()) GetAICommand();
823 void character::Move (v2 MoveTo
, truth TeleportMove
, truth Run
) {
824 if (!IsEnabled()) return;
825 /* Test whether the player is stuck to something */
826 if (!TeleportMove
&& !TryToUnStickTraps(MoveTo
-GetPos())) return;
827 if (Run
&& !IsPlayer() && TorsoIsAlive() &&
828 (Stamina
<= 10000/Max(GetAttribute(LEG_STRENGTH
), 1) || (!StateIsActivated(PANIC
) && Stamina
< MaxStamina
>>2))) {
832 if (GetBurdenState() != OVER_LOADED
|| TeleportMove
) {
833 lsquare
*OldSquareUnder
[MAX_SQUARES_UNDER
];
835 if (!game::IsInWilderness()) {
838 area
*ca
= GetSquareUnder()->GetArea();
840 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
841 v2 np
= GetPos()+game::GetMoveVector(f
);
843 if (np
.X
>= 0 && np
.Y
>= 0 && np
.X
< ca
->GetXSize() && np
.Y
< ca
->GetYSize()) {
844 lsquare
*sq
= static_cast<lsquare
*>(ca
->GetSquare(np
.X
, np
.Y
));
851 for (int c
= 0; c
< GetSquaresUnder(); ++c
) OldSquareUnder
[c
] = GetLSquareUnder(c
);
856 /* Multitiled creatures should behave differently, maybe? */
858 int ED
= GetSquareUnder()->GetEntryDifficulty(), Base
= 10000;
860 EditAP(-GetMoveAPRequirement(ED
)>>1);
862 EditExperience(AGILITY
, 125, ED
<<7);
864 auto hst
= GetHungerState();
865 if (hst
== SATIATED
) Base
= 11000;
866 else if (hst
== BLOATED
) Base
= 12500;
867 else if (hst
== OVER_FED
) Base
= 15000;
869 EditStamina(-Base
/Max(GetAttribute(LEG_STRENGTH
), 1), true);
871 int ED
= GetSquareUnder()->GetEntryDifficulty();
873 EditAP(-GetMoveAPRequirement(ED
));
875 EditExperience(AGILITY
, 75, ED
<<7);
878 if (IsPlayer()) ShowNewPosInfo();
879 if (IsPlayer() && !game::IsInWilderness()) GetStackUnder()->SetSteppedOn(true);
880 if (!game::IsInWilderness()) SignalStepFrom(OldSquareUnder
);
883 cchar
*CrawlVerb
= StateIsActivated(LEVITATION
) ? "float" : "crawl";
885 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb
);
892 void character::GetAICommand () {
893 //if (strcmp(GetTypeID(), "siren") == 0) fprintf(stderr, "siren::GetAICommand; enabled=%d\n", IsEnabled());
894 //fprintf(stderr, "char::GetAICommand; class=<%s>\n", GetTypeID());
895 SeekLeader(GetLeader());
896 if (FollowLeader(GetLeader())) return;
897 if (CheckForEnemies(true, true, true)) return;
898 if (CheckForUsefulItemsOnGround()) return;
899 if (CheckForDoors()) return;
900 if (CheckSadism()) return;
901 if (MoveRandomly()) return;
906 truth
character::MoveTowardsTarget (truth Run
) {
911 if (!Route
.empty()) {
914 } else TPos
= GoingTo
;
916 MoveTo
[0] = v2(0, 0);
917 MoveTo
[1] = v2(0, 0);
918 MoveTo
[2] = v2(0, 0);
920 if (TPos
.X
< Pos
.X
) {
921 if (TPos
.Y
< Pos
.Y
) {
922 MoveTo
[0] = v2(-1, -1);
923 MoveTo
[1] = v2(-1, 0);
924 MoveTo
[2] = v2( 0, -1);
925 } else if (TPos
.Y
== Pos
.Y
) {
926 MoveTo
[0] = v2(-1, 0);
927 MoveTo
[1] = v2(-1, -1);
928 MoveTo
[2] = v2(-1, 1);
929 } else if (TPos
.Y
> Pos
.Y
) {
930 MoveTo
[0] = v2(-1, 1);
931 MoveTo
[1] = v2(-1, 0);
932 MoveTo
[2] = v2( 0, 1);
934 } else if (TPos
.X
== Pos
.X
) {
935 if (TPos
.Y
< Pos
.Y
) {
936 MoveTo
[0] = v2( 0, -1);
937 MoveTo
[1] = v2(-1, -1);
938 MoveTo
[2] = v2( 1, -1);
939 } else if (TPos
.Y
== Pos
.Y
) {
942 } else if (TPos
.Y
> Pos
.Y
) {
943 MoveTo
[0] = v2( 0, 1);
944 MoveTo
[1] = v2(-1, 1);
945 MoveTo
[2] = v2( 1, 1);
947 } else if (TPos
.X
> Pos
.X
) {
948 if (TPos
.Y
< Pos
.Y
) {
949 MoveTo
[0] = v2(1, -1);
950 MoveTo
[1] = v2(1, 0);
951 MoveTo
[2] = v2(0, -1);
952 } else if (TPos
.Y
== Pos
.Y
) {
953 MoveTo
[0] = v2(1, 0);
954 MoveTo
[1] = v2(1, -1);
955 MoveTo
[2] = v2(1, 1);
956 } else if (TPos
.Y
> Pos
.Y
) {
957 MoveTo
[0] = v2(1, 1);
958 MoveTo
[1] = v2(1, 0);
959 MoveTo
[2] = v2(0, 1);
963 v2 ModifiedMoveTo
= ApplyStateModification(MoveTo
[0]);
965 if (TryMove(ModifiedMoveTo
, true, Run
)) return true;
967 int L
= (Pos
-TPos
).GetManhattanLength();
969 if (RAND()&1) Swap(MoveTo
[1], MoveTo
[2]);
971 if (Pos
.IsAdjacent(TPos
)) {
976 if ((Pos
+MoveTo
[1]-TPos
).GetManhattanLength() <= L
&& TryMove(ApplyStateModification(MoveTo
[1]), true, Run
)) return true;
977 if ((Pos
+MoveTo
[2]-TPos
).GetManhattanLength() <= L
&& TryMove(ApplyStateModification(MoveTo
[2]), true, Run
)) return true;
978 Illegal
.insert(Pos
+ModifiedMoveTo
);
979 if (CreateRoute()) return true;
984 int character::CalculateNewSquaresUnder (lsquare
**NewSquare
, v2 Pos
) const {
985 if (GetLevel()->IsValidPos(Pos
)) {
986 *NewSquare
= GetNearLSquare(Pos
);
993 truth
character::TryMove (v2 MoveVector
, truth Important
, truth Run
) {
994 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
995 character
*Pet
[MAX_SQUARES_UNDER
];
996 character
*Neutral
[MAX_SQUARES_UNDER
];
997 character
*Hostile
[MAX_SQUARES_UNDER
];
998 v2 PetPos
[MAX_SQUARES_UNDER
];
999 v2 NeutralPos
[MAX_SQUARES_UNDER
];
1000 v2 HostilePos
[MAX_SQUARES_UNDER
];
1001 v2 MoveTo
= GetPos()+MoveVector
;
1002 int Direction
= game::GetDirectionForVector(MoveVector
);
1003 if (Direction
== DIR_ERROR
) ABORT("Direction fault.");
1004 if (!game::IsInWilderness()) {
1005 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, MoveTo
);
1010 for (int c
= 0; c
< Squares
; ++c
) {
1011 character
* Char
= MoveToSquare
[c
]->GetCharacter();
1012 if (Char
&& Char
!= this) {
1013 v2 Pos
= MoveToSquare
[c
]->GetPos();
1016 PetPos
[Pets
++] = Pos
;
1017 } else if (Char
->GetRelation(this) != HOSTILE
) {
1018 Neutral
[Neutrals
] = Char
;
1019 NeutralPos
[Neutrals
++] = Pos
;
1021 Hostile
[Hostiles
] = Char
;
1022 HostilePos
[Hostiles
++] = Pos
;
1027 if (Hostiles
== 1) return Hit(Hostile
[0], HostilePos
[0], Direction
);
1029 int Index
= RAND() % Hostiles
;
1030 return Hit(Hostile
[Index
], HostilePos
[Index
], Direction
);
1033 if (Neutrals
== 1) {
1034 if (!IsPlayer() && !Pets
&& Important
&& CanMoveOn(MoveToSquare
[0]))
1035 return HandleCharacterBlockingTheWay(Neutral
[0], NeutralPos
[0], Direction
);
1037 return IsPlayer() && Hit(Neutral
[0], NeutralPos
[0], Direction
);
1038 } else if (Neutrals
) {
1040 int Index
= RAND() % Neutrals
;
1041 return Hit(Neutral
[Index
], NeutralPos
[Index
], Direction
);
1047 for (int c
= 0; c
< Squares
; ++c
) if (MoveToSquare
[c
]->IsScary(this)) return false;
1051 if (IsPlayer() && !ivanconfig::GetBeNice() && Pet
[0]->IsMasochist() && HasSadistAttackMode() &&
1052 game::TruthQuestion("Do you want to punish " + Pet
[0]->GetObjectPronoun() + "?"))
1053 return Hit(Pet
[0], PetPos
[0], Direction
, SADIST_HIT
);
1055 return (Important
&& (CanMoveOn(MoveToSquare
[0]) ||
1056 (IsPlayer() && game::GoThroughWallsCheatIsActive())) && Displace(Pet
[0]));
1057 } else if (Pets
) return false;
1059 if ((CanMove() && CanMoveOn(MoveToSquare
[0])) || ((game::GoThroughWallsCheatIsActive() && IsPlayer()))) {
1060 Move(MoveTo
, false, Run
);
1061 if (IsEnabled() && GetPos() == GoingTo
) TerminateGoingTo();
1064 for (int c
= 0; c
< Squares
; ++c
) {
1065 olterrain
*Terrain
= MoveToSquare
[c
]->GetOLTerrain();
1066 if (Terrain
&& Terrain
->CanBeOpened()) {
1068 if (Terrain
->IsLocked()) {
1071 if (ivanconfig::GetKickDownDoors()) {
1072 if (game::TruthQuestion(CONST_S("Locked! Do you want to kick ")+Terrain
->GetName(DEFINITE
)+"?", true, game::GetMoveCommandKeyBetweenPoints(PLAYER
->GetPos(), MoveToSquare
[0]->GetPos()))) {
1073 Kick(MoveToSquare
[c
], Direction
);
1080 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 */
1082 } else if (Important
&& CheckKick()) {
1083 room
*Room
= MoveToSquare
[c
]->GetRoom();
1084 if (!Room
|| Room
->AllowKick(this, MoveToSquare
[c
])) {
1085 int HP
= Terrain
->GetHP();
1086 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s kicks %s.", CHAR_NAME(DEFINITE
), Terrain
->CHAR_NAME(DEFINITE
));
1087 Kick(MoveToSquare
[c
], Direction
);
1088 olterrain
*NewTerrain
= MoveToSquare
[c
]->GetOLTerrain();
1089 if (NewTerrain
== Terrain
&& Terrain
->GetHP() == HP
) { // BUG!
1090 Illegal
.insert(MoveTo
);
1096 } else { /* if (Terrain->IsLocked()) */
1097 /*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);*/
1098 /* Non-players always try to open it */
1099 if (!IsPlayer()) return MoveToSquare
[c
]->Open(this);
1100 if (game::TruthQuestion(CONST_S("Do you want to open ")+Terrain
->GetName(DEFINITE
)+"?", false, game::GetMoveCommandKeyBetweenPoints(PLAYER
->GetPos(), MoveToSquare
[0]->GetPos()))) {
1101 return MoveToSquare
[c
]->Open(this);
1104 } /* if (Terrain->IsLocked()) */
1105 } else { /* if (CanOpen()) */
1107 ADD_MESSAGE("This monster type cannot open doors.");
1111 Illegal
.insert(MoveTo
);
1112 return CreateRoute();
1114 } /* if (CanOpen()) */
1115 } /* if (Terrain && Terrain->CanBeOpened()) */
1120 if (IsPlayer() && !IsStuck() && GetLevel()->IsOnGround() && game::TruthQuestion(CONST_S("Do you want to leave ")+game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex())+"?")) {
1121 if (HasPetrussNut() && !HasGoldenEagleShirt()) {
1122 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!"));
1123 game::GetCurrentArea()->SendNewDrawRequest();
1124 game::DrawEverything();
1125 ShowAdventureInfo();
1126 festring Msg
= CONST_S("killed Petrus and became the Avatar of Chaos");
1127 PLAYER
->AddScoreEntry(Msg
, 3, false);
1131 if (game::TryTravel(WORLD_MAP
, WORLD_MAP
, game::GetCurrentDungeonIndex())) return true;
1136 /** No multitile support */
1137 if (CanMove() && GetArea()->IsValidPos(MoveTo
) && (CanMoveOn(GetNearWSquare(MoveTo
)) || game::GoThroughWallsCheatIsActive())) {
1138 if (!game::GoThroughWallsCheatIsActive()) {
1139 charactervector
&V
= game::GetWorldMap()->GetPlayerGroup();
1140 truth Discard
= false;
1141 for (uInt c
= 0; c
< V
.size(); ++c
) {
1142 if (!V
[c
]->CanMoveOn(GetNearWSquare(MoveTo
))) {
1144 ADD_MESSAGE("One or more of your team members cannot cross this terrain.");
1145 if (!game::TruthQuestion("Discard them?")) return false;
1148 if (Discard
) delete V
[c
];
1149 V
.erase(V
.begin() + c
--);
1153 Move(MoveTo
, false);
1162 void character::CreateCorpse (lsquare
*Square
) {
1163 if (!BodyPartsDisappearWhenSevered() && !game::AllBodyPartsVanish()) {
1164 corpse
*Corpse
= corpse::Spawn(0, NO_MATERIALS
);
1165 Corpse
->SetDeceased(this);
1166 Square
->AddItem(Corpse
);
1174 void character::Die (character
*Killer
, cfestring
&Msg
, feuLong DeathFlags
) {
1175 /* Note: This function musn't delete any objects, since one of these may be
1176 the one currently processed by pool::Be()! */
1177 if (!IsEnabled()) return;
1179 if (!game::RunCharEvent("before_die", this, Killer
)) return;
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 outputfile::makeDir(dir
.CStr());
1191 dir
<< getenv("HOME") << "/.ivan-save";
1192 outputfile::makeDir(dir
.CStr());
1194 dir
<< "/deathshots";
1195 outputfile::makeDir(dir
.CStr());
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 festring ext
= ".png";
1212 festring fname
= dir
+"/deathshot_"+timestr
;
1213 if (inputfile::fileExists(fname
+ext
)) {
1214 for (int f
= 0; f
< 1000; f
++) {
1216 sprintf(buf
, "%03d", f
);
1217 festring fn
= fname
+buf
;
1218 if (!inputfile::fileExists(fn
+ext
)) {
1225 //fprintf(stderr, "deathshot: %s\n", fname.CStr());
1226 DOUBLE_BUFFER
->SavePNG(fname
);
1228 if (game::WizardModeIsActive()) {
1229 game::DrawEverything();
1230 if (!game::TruthQuestion(CONST_S("Do you want to do this, cheater?"), REQUIRES_ANSWER
)) {
1236 SetNP(SATIATED_LEVEL
);
1237 SendNewDrawRequest();
1241 } else if (CanBeSeenByPlayer() && !(DeathFlags
&DISALLOW_MSG
)) {
1242 ProcessAndAddMessage(GetDeathMessage());
1243 } else if (DeathFlags
&FORCE_MSG
) {
1244 ADD_MESSAGE("You sense the death of something.");
1247 if (!(DeathFlags
&FORBID_REINCARNATION
)) {
1248 if (StateIsActivated(LIFE_SAVED
) && CanMoveOn(!game::IsInWilderness() ? GetSquareUnder() : PLAYER
->GetSquareUnder())) {
1252 if (SpecialSaveLife()) return;
1253 } else if (StateIsActivated(LIFE_SAVED
)) {
1257 Flags
|= C_IN_NO_MSG_MODE
;
1258 character
*Ghost
= 0;
1260 game::RemoveSaves();
1261 if (!game::IsInWilderness()) {
1262 Ghost
= game::CreateGhost();
1267 square
*SquareUnder
[MAX_SQUARES_UNDER
];
1268 memset(SquareUnder
, 0, sizeof(SquareUnder
));
1270 if (IsPlayer() || !game::IsInWilderness()) {
1271 for (int c
= 0; c
< SquaresUnder
; ++c
) SquareUnder
[c
] = GetSquareUnder(c
);
1274 charactervector
& V
= game::GetWorldMap()->GetPlayerGroup();
1275 V
.erase(std::find(V
.begin(), V
.end(), this));
1277 //lsquare **LSquareUnder = reinterpret_cast<lsquare **>(SquareUnder); /* warning; wtf? */
1278 lsquare
*LSquareUnder
[MAX_SQUARES_UNDER
];
1279 memmove(LSquareUnder
, SquareUnder
, sizeof(SquareUnder
));
1281 if (!game::IsInWilderness()) {
1282 if (!StateIsActivated(POLYMORPHED
)) {
1283 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(this, Killer
, Msg
);
1284 if (!(DeathFlags
&DISALLOW_CORPSE
)) CreateCorpse(LSquareUnder
[0]); else SendToHell();
1286 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(GetPolymorphBackup(), Killer
, Msg
);
1287 GetPolymorphBackup()->CreateCorpse(LSquareUnder
[0]);
1288 GetPolymorphBackup()->Flags
&= ~C_POLYMORPHED
;
1289 SetPolymorphBackup(0);
1293 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(this, Killer
, Msg
);
1298 if (!game::IsInWilderness()) {
1299 for (int c
= 0; c
< GetSquaresUnder(); ++c
) LSquareUnder
[c
]->SetTemporaryEmitation(GetEmitation());
1301 ShowAdventureInfo();
1302 if (!game::IsInWilderness()) {
1303 for(int c
= 0; c
< GetSquaresUnder(); ++c
) LSquareUnder
[c
]->SetTemporaryEmitation(0);
1307 if (!game::IsInWilderness()) {
1308 if (GetSquaresUnder() == 1) {
1309 stack
*StackUnder
= LSquareUnder
[0]->GetStack();
1310 GetStack()->MoveItemsTo(StackUnder
);
1311 doforbodypartswithparam
<stack
*>()(this, &bodypart::DropEquipment
, StackUnder
);
1313 while (GetStack()->GetItems()) GetStack()->GetBottom()->MoveTo(LSquareUnder
[RAND_N(GetSquaresUnder())]->GetStack());
1314 for (int c
= 0; c
< BodyParts
; ++c
) {
1315 bodypart
*BodyPart
= GetBodyPart(c
);
1316 if (BodyPart
) BodyPart
->DropEquipment(LSquareUnder
[RAND_N(GetSquaresUnder())]->GetStack());
1321 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(0);
1323 Flags
&= ~C_IN_NO_MSG_MODE
;
1327 if (!game::IsInWilderness()) {
1328 Ghost
->PutTo(LSquareUnder
[0]->GetPos());
1332 game::TextScreen(CONST_S("Unfortunately you died."), ZERO_V2
, WHITE
, true, true, &game::ShowDeathSmiley
);
1338 void character::AddMissMessage (ccharacter
*Enemy
) const {
1340 if (Enemy
->IsPlayer()) Msg
= GetDescription(DEFINITE
)+" misses you!";
1341 else if (IsPlayer()) Msg
= CONST_S("You miss ")+Enemy
->GetDescription(DEFINITE
)+'!';
1342 else if (CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer()) Msg
= GetDescription(DEFINITE
)+" misses "+Enemy
->GetDescription(DEFINITE
)+'!';
1344 ADD_MESSAGE("%s", Msg
.CStr());
1348 void character::AddBlockMessage (ccharacter
*Enemy
, citem
*Blocker
, cfestring
&HitNoun
, truth Partial
) const {
1350 festring BlockVerb
= (Partial
? " to partially block the " : " to block the ")+HitNoun
;
1352 Msg
<< "You manage" << BlockVerb
<< " with your " << Blocker
->GetName(UNARTICLED
) << '!';
1353 } else if (Enemy
->IsPlayer() || Enemy
->CanBeSeenByPlayer()) {
1354 if (CanBeSeenByPlayer())
1355 Msg
<< GetName(DEFINITE
) << " manages" << BlockVerb
<< " with " << GetPossessivePronoun() << ' ' << Blocker
->GetName(UNARTICLED
) << '!';
1357 Msg
<< "Something manages" << BlockVerb
<< " with something!";
1361 ADD_MESSAGE("%s", Msg
.CStr());
1365 void character::AddPrimitiveHitMessage (ccharacter
*Enemy
, cfestring
&FirstPersonHitVerb
,
1366 cfestring
&ThirdPersonHitVerb
, int BodyPart
) const
1369 festring BodyPartDescription
;
1370 if (BodyPart
&& (Enemy
->CanBeSeenByPlayer() || Enemy
->IsPlayer()))
1371 BodyPartDescription
<< " in the " << Enemy
->GetBodyPartName(BodyPart
);
1372 if (Enemy
->IsPlayer())
1373 Msg
<< GetDescription(DEFINITE
) << ' ' << ThirdPersonHitVerb
<< " you" << BodyPartDescription
<< '!';
1374 else if (IsPlayer())
1375 Msg
<< "You " << FirstPersonHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
<< '!';
1376 else if (CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer())
1377 Msg
<< GetDescription(DEFINITE
) << ' ' << ThirdPersonHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) + BodyPartDescription
<< '!';
1380 ADD_MESSAGE("%s", Msg
.CStr());
1384 cchar
*const HitVerb
[] = { "strike", "slash", "stab" };
1385 cchar
*const HitVerb3rdPersonEnd
[] = { "s", "es", "s" };
1388 void character::AddWeaponHitMessage (ccharacter
*Enemy
, citem
*Weapon
, int BodyPart
, truth Critical
) const {
1390 festring BodyPartDescription
;
1392 if (BodyPart
&& (Enemy
->CanBeSeenByPlayer() || Enemy
->IsPlayer()))
1393 BodyPartDescription
<< " in the " << Enemy
->GetBodyPartName(BodyPart
);
1395 int FittingTypes
= 0;
1396 int DamageFlags
= Weapon
->GetDamageFlags();
1399 for (int c
= 0; c
< DAMAGE_TYPES
; ++c
) {
1400 if (1 << c
& DamageFlags
) {
1401 if (!FittingTypes
|| !RAND_N(FittingTypes
+1)) DamageType
= c
;
1406 if (!FittingTypes
) ABORT("No damage flags specified for %s!", Weapon
->CHAR_NAME(UNARTICLED
));
1408 festring NewHitVerb
= Critical
? " critically " : " ";
1409 NewHitVerb
<< HitVerb
[DamageType
];
1410 cchar
*const E
= HitVerb3rdPersonEnd
[DamageType
];
1412 if (Enemy
->IsPlayer()) {
1413 Msg
<< GetDescription(DEFINITE
) << NewHitVerb
<< E
<< " you" << BodyPartDescription
;
1414 if (CanBeSeenByPlayer()) Msg
<< " with " << GetPossessivePronoun() << ' ' << Weapon
->GetName(UNARTICLED
);
1416 } else if (IsPlayer()) {
1417 Msg
<< "You" << NewHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
<< '!';
1418 } else if(CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer()) {
1419 Msg
<< GetDescription(DEFINITE
) << NewHitVerb
<< E
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
;
1420 if (CanBeSeenByPlayer()) Msg
<< " with " << GetPossessivePronoun() << ' ' << Weapon
->GetName(UNARTICLED
);
1425 ADD_MESSAGE("%s", Msg
.CStr());
1429 truth
character::GeneralHasItem (ItemCheckerCB chk
, truth checkContainers
) const {
1431 if (GetStack()->GeneralHasItem(this, chk
, checkContainers
)) return true;
1433 for (int c
= 0; c
< GetEquipments(); ++c
) {
1434 item
*it
= GetEquipment(c
);
1436 if (chk(it
)) return true;
1437 if (it
->IsOpenable(this)) {
1438 stack
*cst
= it
->GetContained();
1440 if (cst
->GeneralHasItem(this, chk
, checkContainers
)) return true;
1448 static truth
isEncryptedScroll (item
*i
) { return i
->IsEncryptedScroll(); }
1449 truth
character::HasEncryptedScroll () const {
1450 return GeneralHasItem(::isEncryptedScroll
, false); // not in containers
1451 //return combineequipmentpredicates()(this, &item::IsEncryptedScroll, 1);
1455 static truth
isElpuriHead (item
*i
) { return i
->IsHeadOfElpuri(); }
1456 truth
character::HasHeadOfElpuri () const {
1457 return GeneralHasItem(::isElpuriHead
, true); // Petrus can see into containers
1458 //return combineequipmentpredicates()(this, &item::IsHeadOfElpuri, 1);
1462 static truth
isPetrussNut (item
*i
) { return i
->IsPetrussNut(); }
1463 truth
character::HasPetrussNut () const {
1464 return GeneralHasItem(::isPetrussNut
, true); // gods can see into containers
1465 //return combineequipmentpredicates()(this, &item::IsPetrussNut, 1);
1469 static truth
isGoldenEagleShirt (item
*i
) { return i
->IsGoldenEagleShirt(); }
1470 truth
character::HasGoldenEagleShirt () const {
1471 return GeneralHasItem(::isGoldenEagleShirt
, true); // Petrus can see into containers
1472 //return combineequipmentpredicates()(this, &item::IsGoldenEagleShirt, 1);
1476 static truth
isShadowVeil (item
*i
) { return i
->IsShadowVeil(); }
1477 truth
character::HasShadowVeil () const {
1478 return GeneralHasItem(::isShadowVeil
, false); // not in containers
1479 //return combineequipmentpredicates()(this, &item::IsShadowVeil, 1);
1483 static truth
isLostRubyFlamingSword (item
*i
) { return i
->IsLostRubyFlamingSword(); }
1484 truth
character::HasLostRubyFlamingSword () const {
1485 return GeneralHasItem(::isLostRubyFlamingSword
, true); // it shines even through a container
1486 //return combineequipmentpredicates()(this, &item::IsLostRubyFlamingSword, 1);
1490 truth
character::HasOmmelBlood () const {
1491 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1492 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) return true;
1494 for (int c
= 0; c
< GetEquipments(); ++c
) {
1495 item
*Item
= GetEquipment(c
);
1496 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) return true;
1498 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1502 truth
character::HasCurdledBlood () const {
1503 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1504 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) return true;
1506 for (int c
= 0; c
< GetEquipments(); ++c
) {
1507 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();
1542 for (int c
= 0; c
< GetEquipments(); ++c
) {
1543 item
*Item
= GetEquipment(c
);
1544 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) {
1545 Item
->RemoveFromSlot();
1554 int character::GeneralRemoveItem (ItemCheckerCB chk
, truth allItems
, truth checkContainers
) {
1556 int count
= GetStack()->GeneralRemoveItem(this, chk
, allItems
, checkContainers
);
1558 if (count
== 0 || allItems
) {
1559 for (int c
= 0; c
< GetEquipments(); ++c
) {
1560 item
*it
= GetEquipment(c
);
1563 it
->RemoveFromSlot();
1566 if (!allItems
) break;
1567 } else if (it
->IsOpenable(this)) {
1568 stack
*cst
= it
->GetContained();
1570 int rcc
= cst
->GeneralRemoveItem(this, chk
, allItems
, checkContainers
);
1573 if (!allItems
) break;
1583 //static truth isEncryptedScroll (item *i) { return i->IsEncryptedScroll(); }
1584 truth
character::RemoveEncryptedScroll () { return GeneralRemoveItem(::isEncryptedScroll
, false, true) != 0; }
1587 static truth
isMondedrPass (item
*i
) { return i
->IsMondedrPass(); }
1588 truth
character::RemoveMondedrPass () { return GeneralRemoveItem(::isMondedrPass
, false, false) != 0; }
1591 static truth
isRingOfThieves (item
*i
) { return i
->IsRingOfThieves(); }
1592 truth
character::RemoveRingOfThieves () { return GeneralRemoveItem(::isRingOfThieves
, false, true) != 0; }
1595 truth
character::RemoveShadowVeil () { return (GeneralRemoveItem(::isShadowVeil
, false, false) != 0); }
1597 truth
character::RemoveHeadOfElpuri () { return (GeneralRemoveItem(::isElpuriHead
, false, false) != 0); }
1600 truth
character::ReadItem (item
*ToBeRead
) {
1601 if (!ToBeRead
->CanBeRead(this)) {
1602 if (IsPlayer()) ADD_MESSAGE("You can't read this.");
1605 if (!GetLSquareUnder()->IsDark() || game::GetSeeWholeMapCheatMode()) {
1606 if (StateIsActivated(CONFUSED
) && !(RAND()&7)) {
1607 if (!ToBeRead
->IsDestroyable(this)) {
1608 ADD_MESSAGE("You read some words of %s and understand exactly nothing.", ToBeRead
->CHAR_NAME(DEFINITE
));
1610 ADD_MESSAGE("%s is very confusing. Or perhaps you are just too confused?", ToBeRead
->CHAR_NAME(DEFINITE
));
1611 ActivateRandomState(SRC_CONFUSE_READ
, 1000+RAND()%1500);
1612 ToBeRead
->RemoveFromSlot();
1613 ToBeRead
->SendToHell();
1618 if (ToBeRead
->Read(this)) {
1619 if (!game::WizardModeIsActive()) {
1620 /* This AP is used to take the stuff out of backpack */
1627 if (IsPlayer()) ADD_MESSAGE("It's too dark here to read.");
1632 void character::CalculateBurdenState () {
1633 int OldBurdenState
= BurdenState
;
1634 sLong SumOfMasses
= GetCarriedWeight();
1635 sLong CarryingStrengthUnits
= sLong(GetCarryingStrength())*2500;
1636 if (SumOfMasses
> (CarryingStrengthUnits
<< 1) + CarryingStrengthUnits
) BurdenState
= OVER_LOADED
;
1637 else if (SumOfMasses
> CarryingStrengthUnits
<< 1) BurdenState
= STRESSED
;
1638 else if (SumOfMasses
> CarryingStrengthUnits
) BurdenState
= BURDENED
;
1639 else BurdenState
= UNBURDENED
;
1640 if (!IsInitializing() && BurdenState
!= OldBurdenState
) CalculateBattleInfo();
1644 void character::Save (outputfile
&SaveFile
) const {
1645 #ifdef SAVE_TEMP_GUARDS
1646 SaveFile
<< (uShort
)GetConfig();
1648 SaveFile
<< DataBase
->CfgStrName
;
1650 Stack
->Save(SaveFile
);
1652 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) SaveFile
<< BaseExperience
[c
];
1654 SaveFile
<< ExpModifierMap
;
1655 SaveFile
<< NP
<< AP
<< Stamina
<< GenerationDanger
<< ScienceTalks
<< CounterToMindWormHatch
;
1656 SaveFile
<< TemporaryState
<< EquipmentState
<< Money
<< GoingTo
<< RegenerationCounter
<< Route
<< Illegal
;
1657 SaveFile
<< CurrentSweatMaterial
;
1658 SaveFile
.Put(!!IsEnabled());
1659 SaveFile
<< HomeData
<< BlocksSinceLastTurn
<< CommandFlags
;
1660 SaveFile
<< WarnFlags
<< (uShort
)Flags
;
1662 for (int c
= 0; c
< BodyParts
; ++c
) SaveFile
<< BodyPartSlot
[c
] << OriginalBodyPartID
[c
];
1664 SaveLinkedList(SaveFile
, TrapData
);
1667 for (int c
= 0; c
< STATES
; ++c
) SaveFile
<< TemporaryStateCounter
[c
];
1671 SaveFile
<< (feuLong
)Team
->GetID(); // feuLong
1673 SaveFile
.Put(false);
1676 if (GetTeam() && GetTeam()->GetLeader() == this) SaveFile
.Put(true); else SaveFile
.Put(false);
1678 SaveFile
<< AssignedName
<< PolymorphBackup
;
1680 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) SaveFile
<< CWeaponSkill
[c
];
1685 void character::Load (inputfile
&SaveFile
) {
1688 #ifdef SAVE_TEMP_GUARDS
1689 uShort checkcfgid
= ReadType(uShort
, SaveFile
);
1692 SaveFile
>> acfgname
;
1693 int acfgid
= databasecreator
<character
>::FindConfigByName(FindProtoType(), acfgname
);
1694 if (acfgid
== -1) ABORT("Cannot find '%s' config '%s'!", FindProtoType()->GetClassID(), acfgname
.CStr());
1696 Stack
->Load(SaveFile
);
1698 game::AddCharacterID(this, ID
);
1700 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) SaveFile
>> BaseExperience
[c
];
1702 SaveFile
>> ExpModifierMap
;
1703 SaveFile
>> NP
>> AP
>> Stamina
>> GenerationDanger
>> ScienceTalks
>> CounterToMindWormHatch
;
1704 SaveFile
>> TemporaryState
>> EquipmentState
>> Money
>> GoingTo
>> RegenerationCounter
>> Route
>> Illegal
;
1705 SaveFile
>> CurrentSweatMaterial
;
1707 if (!SaveFile
.Get()) Disable();
1709 SaveFile
>> HomeData
>> BlocksSinceLastTurn
>> CommandFlags
;
1710 SaveFile
>> WarnFlags
;
1711 WarnFlags
&= ~WARNED
;
1712 Flags
|= ReadType(uShort
, SaveFile
) & ~ENTITY_FLAGS
;
1714 for (int c
= 0; c
< BodyParts
; ++c
) {
1715 SaveFile
>> BodyPartSlot
[c
] >> OriginalBodyPartID
[c
];
1716 item
*BodyPart
= *BodyPartSlot
[c
];
1717 if (BodyPart
) BodyPart
->Disable();
1720 LoadLinkedList(SaveFile
, TrapData
);
1723 if (Action
) Action
->SetActor(this);
1725 for (int c
= 0; c
< STATES
; ++c
) SaveFile
>> TemporaryStateCounter
[c
];
1727 if (SaveFile
.Get()) SetTeam(game::GetTeam(ReadType(feuLong
, SaveFile
)));
1729 if (SaveFile
.Get()) GetTeam()->SetLeader(this);
1731 SaveFile
>> AssignedName
>> PolymorphBackup
;
1733 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) SaveFile
>> CWeaponSkill
[c
];
1735 //databasecreator<character>::InstallDataBase(this, ReadType(uShort, SaveFile));
1736 databasecreator
<character
>::InstallDataBase(this, acfgid
);
1738 if (IsEnabled() && !game::IsInWilderness()) {
1739 for (int c
= 1; c
< GetSquaresUnder(); ++c
) GetSquareUnder(c
)->SetCharacter(this);
1742 #ifdef SAVE_TEMP_GUARDS
1743 if (acfgid
!= (int)checkcfgid
) {
1744 fprintf(stderr
, "character::Load(0): typeid=<%s>; config=%d; configname=<%s>; dbcfg=%d (%s) [%u]\n", GetTypeID(), acfgid
, acfgname
.CStr(), DataBase
->Config
, DataBase
->CfgStrName
.CStr(), (unsigned)checkcfgid
);
1748 const fearray<festring> < = GetLevelTags();
1750 fprintf(stderr, "====\n");
1751 for (uInt f = 0; f < lt.Size; ++f) fprintf(stderr, " %u: [%s]\n", f, lt[f].CStr());
1757 truth
character::Engrave (cfestring
&What
) {
1758 GetLSquareUnder()->Engrave(What
);
1762 truth
character::MoveRandomly () {
1763 //if (strcmp(GetTypeID(), "siren") == 0) fprintf(stderr, "siren::MoveRandomly; enabled=%d\n", IsEnabled());
1764 if (!IsEnabled()) return false;
1765 for (int c
= 0; c
< 10; ++c
) {
1766 v2 ToTry
= game::GetMoveVector(RAND()&7);
1767 if (GetLevel()->IsValidPos(GetPos()+ToTry
)) {
1768 lsquare
*Square
= GetNearLSquare(GetPos()+ToTry
);
1770 if (strcmp(GetTypeID(), "siren") == 0) {
1771 fprintf(stderr, "siren::MoveRandomly; c=%d; IsDangerous=%d; IsScary=d\n", Square->IsDangerous(this), Square->IsScary(this));
1774 if (!Square
->IsDangerous(this) && !Square
->IsScary(this) && TryMove(ToTry
, false, false)) return true;
1781 truth
character::TestForPickup (item
*ToBeTested
) const {
1782 if (MakesBurdened(ToBeTested
->GetWeight()+GetCarriedWeight())) return false;
1787 void character::AddScoreEntry (cfestring
&Description
, double Multiplier
, truth AddEndLevel
) const {
1788 if (!game::WizardModeIsReallyActive()) {
1790 if (!HScore
.CheckVersion()) {
1791 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;
1794 festring Desc
= game::GetPlayerName();
1795 Desc
<< ", " << Description
;
1796 if (AddEndLevel
) Desc
<< " in "+(game::IsInWilderness() ? "the world map" : game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex()));
1797 HScore
.Add(sLong(game::GetScore()*Multiplier
), Desc
);
1803 truth
character::CheckDeath (cfestring
&Msg
, character
*Murderer
, feuLong DeathFlags
) {
1804 if (!IsEnabled()) return true;
1805 if (game::IsSumoWrestling() && IsDead()) {
1806 game::EndSumoWrestling(!!IsPlayer());
1809 if ((DeathFlags
&FORCE_DEATH
) || IsDead()) {
1810 if (Murderer
&& Murderer
->IsPlayer() && GetTeam()->GetKillEvilness()) game::DoEvilDeed(GetTeam()->GetKillEvilness());
1811 festring SpecifierMsg
;
1812 int SpecifierParts
= 0;
1813 if (GetPolymorphBackup()) {
1814 SpecifierMsg
<< " polymorphed into ";
1815 id::AddName(SpecifierMsg
, INDEFINITE
);
1818 if (!(DeathFlags
&IGNORE_TRAPS
) && IsStuck()) {
1819 if (SpecifierParts
++) SpecifierMsg
<< " and";
1820 SpecifierMsg
<< " caught in " << GetTrapDescription();
1822 if (GetAction() && !((DeathFlags
&IGNORE_UNCONSCIOUSNESS
) && GetAction()->IsUnconsciousness())) {
1823 festring ActionMsg
= GetAction()->GetDeathExplanation();
1824 if (!ActionMsg
.IsEmpty()) {
1825 if (SpecifierParts
> 1) {
1826 SpecifierMsg
= ActionMsg
<< ',' << SpecifierMsg
;
1828 if (SpecifierParts
) SpecifierMsg
<< " and";
1829 SpecifierMsg
<< ActionMsg
;
1834 festring NewMsg
= Msg
;
1835 if (Murderer
== this) {
1836 SEARCH_N_REPLACE(NewMsg
, "@bkp", CONST_S("by ") + GetPossessivePronoun(false) + " own");
1837 SEARCH_N_REPLACE(NewMsg
, "@bk", CONST_S("by ") + GetObjectPronoun(false) + "self");
1838 SEARCH_N_REPLACE(NewMsg
, "@k", GetObjectPronoun(false) + "self");
1839 } else if (Murderer
) {
1840 SEARCH_N_REPLACE(NewMsg
, "@bkp", CONST_S("by ") + Murderer
->GetName(INDEFINITE
) + "'s");
1841 SEARCH_N_REPLACE(NewMsg
, "@bk", CONST_S("by ") + Murderer
->GetName(INDEFINITE
));
1842 SEARCH_N_REPLACE(NewMsg
, "@k", CONST_S("by ") + Murderer
->GetName(INDEFINITE
));
1844 if (SpecifierParts
) NewMsg
<< " while" << SpecifierMsg
;
1845 if (IsPlayer() && game::WizardModeIsActive()) ADD_MESSAGE("Death message: %s. Score: %d.", NewMsg
.CStr(), game::GetScore());
1846 Die(Murderer
, NewMsg
, DeathFlags
);
1853 truth
character::CheckStarvationDeath (cfestring
&Msg
) {
1854 if (GetNP() < 1 && UsesNutrition()) return CheckDeath(Msg
, 0, FORCE_DEATH
);
1859 void character::ThrowItem (int Direction
, item
*ToBeThrown
) {
1860 if (Direction
> 7) ABORT("Throw in TOO odd direction...");
1861 ToBeThrown
->Fly(this, Direction
, GetAttribute(ARM_STRENGTH
));
1865 void character::HasBeenHitByItem (character
*Thrower
, item
*Thingy
, int Damage
, double ToHitValue
, int Direction
) {
1866 if (IsPlayer()) ADD_MESSAGE("%s hits you.", Thingy
->CHAR_NAME(DEFINITE
));
1867 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s.", Thingy
->CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
));
1868 int BodyPart
= ChooseBodyPartToReceiveHit(ToHitValue
, DodgeValue
);
1869 int WeaponSkillHits
= Thrower
? CalculateWeaponSkillHits(Thrower
) : 0;
1870 int DoneDamage
= ReceiveBodyPartDamage(Thrower
, Damage
, PHYSICAL_DAMAGE
, BodyPart
, Direction
);
1871 truth Succeeded
= (GetBodyPart(BodyPart
) && HitEffect(Thrower
, Thingy
, Thingy
->GetPos(), THROW_ATTACK
, BodyPart
, Direction
, !DoneDamage
, false, DoneDamage
)) || DoneDamage
;
1872 if (Succeeded
&& Thrower
) Thrower
->WeaponSkillHit(Thingy
, THROW_ATTACK
, WeaponSkillHits
);
1873 festring DeathMsg
= CONST_S("killed by a flying ")+Thingy
->GetName(UNARTICLED
);
1874 if (CheckDeath(DeathMsg
, Thrower
)) return;
1876 if (Thrower
->CanBeSeenByPlayer())
1877 DeActivateVoluntaryAction(CONST_S("The attack of ")+Thrower
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
1879 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
1881 DeActivateVoluntaryAction(CONST_S("The hit interrupts you."));
1886 truth
character::DodgesFlyingItem (item
*Item
, double ToHitValue
) {
1887 return !Item
->EffectIsGood() && RAND() % int(100+ToHitValue
/DodgeValue
*100) < 100;
1891 void character::GetPlayerCommand () {
1893 truth HasActed
= false;
1895 game::DrawEverything();
1896 if (game::GetDangerFound() && !StateIsActivated(FEARLESS
)) {
1897 if (game::GetDangerFound() > 500.) {
1898 if (game::GetCausePanicFlag()) {
1899 game::SetCausePanicFlag(false);
1900 BeginTemporaryState(PANIC
, 500+RAND_N(500));
1902 game::AskForEscPress(CONST_S("You are horrified by your situation!"));
1903 } else if (ivanconfig::GetWarnAboutDanger()) {
1904 if (game::GetDangerFound() > 50.) game::AskForEscPress(CONST_S("You sense great danger!"));
1905 else game::AskForEscPress(CONST_S("You sense danger!"));
1907 game::SetDangerFound(0);
1909 game::SetIsInGetCommand(true);
1910 int Key
= GET_KEY();
1911 game::SetIsInGetCommand(false);
1912 if (Key
!= '+' && Key
!= '-' && Key
!= 'M') msgsystem::ThyMessagesAreNowOld(); // gum
1913 truth ValidKeyPressed
= false;
1915 for (int c
= 0; c
< DIRECTION_COMMAND_KEYS
; ++c
) {
1916 if (Key
== game::GetMoveCommandKey(c
)) {
1917 if (c
!= MDIR_STAND
&& globalwindowhandler::lastCtrl
&& !globalwindowhandler::lastAlt
&& !globalwindowhandler::lastShift
&& !game::IsInWilderness()) {
1920 go
*Go
= go::Spawn(this);
1921 Go
->SetDirection(Dir
);
1922 Go
->SetPrevWasTurn(false);
1924 EditAP(GetStateAPGain(100)); // gum solution
1928 HasActed
= TryMove(ApplyStateModification(game::GetMoveVector(c
)), true, game::PlayerIsRunning());
1930 ValidKeyPressed
= true;
1931 if (HasActed
) break;
1935 if (!ValidKeyPressed
) {
1936 for (int c
= 0; (cmd
= commandsystem::GetCommand(c
)); ++c
) {
1938 /* Numpad aliases for most commonly used commands */
1939 if (Key
== KEY_DEL
&& cmd
->GetName() == "Eat") Key
= cmd
->GetKey();
1940 if (Key
== KEY_INS
&& cmd
->GetName() == "PickUp") Key
= cmd
->GetKey();
1941 if (Key
== KEY_PLUS
&& cmd
->GetName() == "EquipmentScreen") Key
= cmd
->GetKey();
1943 if (Key
== cmd
->GetKey()) {
1944 if (game::IsInWilderness() && !commandsystem::GetCommand(c
)->IsUsableInWilderness()) {
1945 ADD_MESSAGE("This function cannot be used while in wilderness.");
1946 } else if (!game::WizardModeIsActive() && commandsystem::GetCommand(c
)->IsWizardModeFunction()) {
1947 ADD_MESSAGE("Activate wizardmode to use this function.");
1949 HasActed
= commandsystem::GetCommand(c
)->GetLinkedFunction()(this);
1951 ValidKeyPressed
= true;
1956 if (!ValidKeyPressed
) ADD_MESSAGE("Unknown key. Press '?' for a list of commands.");
1958 game::IncreaseTurn();
1962 void character::Vomit (v2 Pos
, int Amount
, truth ShowMsg
) {
1963 if (!CanVomit()) return;
1965 if (IsPlayer()) ADD_MESSAGE("You vomit.");
1966 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s vomits.", CHAR_NAME(DEFINITE
));
1968 if (VomittingIsUnhealthy()) {
1969 EditExperience(ARM_STRENGTH
, -75, 1 << 9);
1970 EditExperience(LEG_STRENGTH
, -75, 1 << 9);
1973 EditNP(-2500-RAND()%2501);
1974 CheckStarvationDeath(CONST_S("vomited himself to death"));
1976 if (StateIsActivated(PARASITIZED
) && !(RAND() & 7)) {
1977 if (IsPlayer()) ADD_MESSAGE("You notice a dead broad tapeworm among your former stomach contents.");
1978 DeActivateTemporaryState(PARASITIZED
);
1980 if (!game::IsInWilderness()) {
1981 GetNearLSquare(Pos
)->ReceiveVomit(this, liquid::Spawn(GetVomitMaterial(), sLong(sqrt(GetBodyVolume())*Amount
/1000)));
1986 truth
character::Polymorph (character
*NewForm
, int Counter
) {
1987 if (!IsPolymorphable() || (!IsPlayer() && game::IsInWilderness())) {
1992 if (GetAction()) GetAction()->Terminate(false);
1993 NewForm
->SetAssignedName("");
1995 ADD_MESSAGE("Your body glows in a crimson light. You transform into %s!", NewForm
->CHAR_NAME(INDEFINITE
));
1996 else if (CanBeSeenByPlayer())
1997 ADD_MESSAGE("%s glows in a crimson light and %s transforms into %s!", CHAR_NAME(DEFINITE
), GetPersonalPronoun().CStr(), NewForm
->CHAR_NAME(INDEFINITE
));
1999 Flags
|= C_IN_NO_MSG_MODE
;
2000 NewForm
->Flags
|= C_IN_NO_MSG_MODE
;
2001 NewForm
->ChangeTeam(GetTeam());
2002 NewForm
->GenerationDanger
= GenerationDanger
;
2003 NewForm
->mOnEvents
= this->mOnEvents
;
2005 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(NewForm
);
2009 NewForm
->PutToOrNear(Pos
);
2010 NewForm
->SetAssignedName(GetAssignedName());
2011 NewForm
->ActivateTemporaryState(POLYMORPHED
);
2012 NewForm
->SetTemporaryStateCounter(POLYMORPHED
, Counter
);
2014 if (TemporaryStateIsActivated(POLYMORPHED
)) {
2015 NewForm
->SetPolymorphBackup(GetPolymorphBackup());
2016 SetPolymorphBackup(0);
2019 NewForm
->SetPolymorphBackup(this);
2020 Flags
|= C_POLYMORPHED
;
2024 GetStack()->MoveItemsTo(NewForm
->GetStack());
2025 NewForm
->SetMoney(GetMoney());
2026 DonateEquipmentTo(NewForm
);
2027 Flags
&= ~C_IN_NO_MSG_MODE
;
2028 NewForm
->Flags
&= ~C_IN_NO_MSG_MODE
;
2029 NewForm
->CalculateAll();
2033 game::SetPlayer(NewForm
);
2034 game::SendLOSUpdateRequest();
2038 NewForm
->TestWalkability();
2043 void character::BeKicked (character
*Kicker
, item
*Boot
, bodypart
*Leg
, v2 HitPos
, double KickDamage
,
2044 double ToHitValue
, int Success
, int Direction
, truth Critical
, truth ForceHit
)
2047 if (!game::RunCharEvent("before_kicked_by", this, Kicker
, Boot
)) return;
2049 auto hitres
= (TakeHit(Kicker
, Boot
, Leg
, HitPos
, KickDamage
, ToHitValue
, Success
, KICK_ATTACK
, Direction
, Critical
, ForceHit
));
2050 if (hitres
== HAS_HIT
|| hitres
== HAS_BLOCKED
|| hitres
== DID_NO_DAMAGE
) {
2051 if (IsEnabled() && !CheckBalance(KickDamage
)) {
2052 if (IsPlayer()) ADD_MESSAGE("The kick throws you off balance.");
2053 else if (Kicker
->IsPlayer()) ADD_MESSAGE("The kick throws %s off balance.", CHAR_DESCRIPTION(DEFINITE
));
2054 v2 FallToPos
= GetPos()+game::GetMoveVector(Direction
);
2055 FallTo(Kicker
, FallToPos
);
2061 /* Return true if still in balance */
2062 truth
character::CheckBalance (double KickDamage
) {
2063 return !CanMove() || IsStuck() || !KickDamage
|| (!IsFlying() && KickDamage
*5 < RAND()%GetSize());
2067 void character::FallTo (character
*GuiltyGuy
, v2 Where
) {
2069 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
2070 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, Where
);
2072 truth NoRoom
= false;
2073 for (int c
= 0; c
< Squares
; ++c
) {
2074 olterrain
*Terrain
= MoveToSquare
[c
]->GetOLTerrain();
2075 if (Terrain
&& !CanMoveOn(Terrain
)) { NoRoom
= true; break; }
2079 if (IsPlayer()) ADD_MESSAGE("You hit your head on the wall.");
2080 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s head on the wall.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
2082 ReceiveDamage(GuiltyGuy
, 1+RAND()%5, PHYSICAL_DAMAGE
, HEAD
);
2083 CheckDeath(CONST_S("killed by hitting a wall due to being kicked @bk"), GuiltyGuy
);
2085 if (IsFreeForMe(MoveToSquare
[0])) Move(Where
, true);
2086 // Place code that handles characters bouncing to each other here
2092 truth
character::CheckCannibalism (cmaterial
*What
) const {
2093 return GetTorso()->GetMainMaterial()->IsSameAs(What
);
2097 void character::StandIdleAI () {
2098 SeekLeader(GetLeader());
2099 if (CheckForEnemies(true, true, true)) return;
2100 if (CheckForUsefulItemsOnGround()) return;
2101 if (FollowLeader(GetLeader())) return;
2102 if (CheckForDoors()) return;
2103 if (MoveTowardsHomePos()) return;
2104 if (CheckSadism()) return;
2109 truth
character::LoseConsciousness (int Counter
, truth HungerFaint
) {
2110 if (!AllowUnconsciousness()) return false;
2111 action
*Action
= GetAction();
2113 if (HungerFaint
&& !Action
->AllowUnconsciousness()) return false;
2114 if (Action
->IsUnconsciousness()) {
2115 static_cast<unconsciousness
*>(Action
)->RaiseCounterTo(Counter
);
2118 Action
->Terminate(false);
2120 if (IsPlayer()) ADD_MESSAGE("You lose consciousness.");
2121 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s loses consciousness.", CHAR_NAME(DEFINITE
));
2122 unconsciousness
*Unconsciousness
= unconsciousness::Spawn(this);
2123 Unconsciousness
->SetCounter(Counter
);
2124 SetAction(Unconsciousness
);
2129 void character::DeActivateVoluntaryAction (cfestring
&Reason
) {
2130 if (GetAction() && GetAction()->IsVoluntary()) {
2132 if (Reason
.GetSize()) ADD_MESSAGE("%s", Reason
.CStr());
2133 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"?")) GetAction()->ActivateInDNDMode();
2134 else GetAction()->Terminate(false);
2136 GetAction()->Terminate(false);
2142 void character::ActionAutoTermination () {
2143 if (!GetAction() || !GetAction()->IsVoluntary() || GetAction()->InDNDMode()) return;
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() && ch
->CanBeSeenBy(this, false, true) && (ch
->CanMove() || ch
->GetPos().IsAdjacent(Pos
)) && ch
->CanAttack()) {
2151 ADD_MESSAGE("%s seems to be hostile.", ch
->CHAR_NAME(DEFINITE
));
2152 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"?")) GetAction()->ActivateInDNDMode();
2153 else GetAction()->Terminate(false);
2155 GetAction()->Terminate(false);
2165 truth
character::CheckForEnemies (truth CheckDoors
, truth CheckGround
, truth MayMoveRandomly
, truth RunTowardsTarget
) {
2166 if (!IsEnabled()) return false;
2167 truth HostileCharsNear
= false;
2168 character
*NearestChar
= 0;
2169 sLong NearestDistance
= 0x7FFFFFFF;
2171 for (int c
= 0; c
< game::GetTeams(); ++c
) {
2172 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
2173 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
2175 if (ch
->IsEnabled() && GetAttribute(WISDOM
) < ch
->GetAttackWisdomLimit()) {
2176 sLong ThisDistance
= Max
<sLong
>(abs(ch
->GetPos().X
- Pos
.X
), abs(ch
->GetPos().Y
- Pos
.Y
));
2177 if (ThisDistance
<= GetLOSRangeSquare()) HostileCharsNear
= true;
2178 if ((ThisDistance
< NearestDistance
|| (ThisDistance
== NearestDistance
&& !(RAND() % 3))) &&
2179 ch
->CanBeSeenBy(this, false, IsGoingSomeWhere()) &&
2180 (!IsGoingSomeWhere() || HasClearRouteTo(ch
->GetPos()))) {
2182 NearestDistance
= ThisDistance
;
2190 if (GetAttribute(INTELLIGENCE
) >= 10 || IsSpy()) game::CallForAttention(GetPos(), 100);
2191 if (SpecialEnemySightedReaction(NearestChar
)) return true;
2192 if (IsExtraCoward() && !StateIsActivated(PANIC
) && NearestChar
->GetRelativeDanger(this) >= 0.5 && !StateIsActivated(FEARLESS
)) {
2193 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s sees %s.", CHAR_NAME(DEFINITE
), NearestChar
->CHAR_DESCRIPTION(DEFINITE
));
2194 BeginTemporaryState(PANIC
, 500+RAND()%500);
2196 if (!IsRetreating()) {
2197 if (CheckGround
&& NearestDistance
> 2 && CheckForUsefulItemsOnGround(false)) return true;
2198 SetGoingTo(NearestChar
->GetPos());
2200 SetGoingTo(Pos
-((NearestChar
->GetPos()-Pos
)<<4));
2202 return MoveTowardsTarget(true);
2204 character
*Leader
= GetLeader();
2205 if (Leader
== this) Leader
= 0;
2206 if (!Leader
&& IsGoingSomeWhere()) {
2207 if (!MoveTowardsTarget(RunTowardsTarget
)) {
2211 if (!IsEnabled()) return true;
2212 if (GetPos() == GoingTo
) TerminateGoingTo();
2216 if ((!Leader
|| (Leader
&& !IsGoingSomeWhere())) && HostileCharsNear
) {
2217 if (CheckDoors
&& CheckForDoors()) return true;
2218 if (CheckGround
&& CheckForUsefulItemsOnGround()) return true;
2219 if (MayMoveRandomly
&& MoveRandomly()) return true; // one has heard that an enemy is near but doesn't know where
2227 truth
character::CheckForDoors () {
2228 if (!CanOpen() || !IsEnabled()) return false;
2229 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2230 lsquare
*Square
= GetNeighbourLSquare(d
);
2231 if (Square
&& Square
->GetOLTerrain() && Square
->GetOLTerrain()->Open(this)) return true;
2237 truth
character::CheckForUsefulItemsOnGround (truth CheckFood
) {
2238 if (StateIsActivated(PANIC
) || !IsEnabled()) return false;
2239 itemvector ItemVector
;
2240 GetStackUnder()->FillItemVector(ItemVector
);
2241 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
2242 if (ItemVector
[c
]->CanBeSeenBy(this) && ItemVector
[c
]->IsPickable(this)) {
2243 if (!(CommandFlags
& DONT_CHANGE_EQUIPMENT
) && TryToEquip(ItemVector
[c
])) return true;
2244 if (CheckFood
&& UsesNutrition() && !CheckIfSatiated() && TryToConsume(ItemVector
[c
])) return true;
2245 if (IsRangedAttacker() && (ItemVector
[c
])->GetThrowItemTypes() && TryToAddToInventory(ItemVector
[c
])) return true;
2252 truth
character::TryToAddToInventory (item
*Item
) {
2253 if (!(GetBurdenState() > STRESSED
) || !CanUseEquipment() || Item
->GetSquaresUnder() != 1) return false;
2254 room
*Room
= GetRoom();
2255 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
2256 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up %s from the ground.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(INDEFINITE
));
2257 Item
->MoveTo(GetStack());
2265 truth
character::CheckInventoryForItemToThrow (item
*ToBeChecked
) {
2266 return (ToBeChecked
->GetThrowItemTypes() & GetWhatThrowItemTypesToThrow()) ? true : false; //hehe
2270 truth
character::CheckThrowItemOpportunity () {
2271 if (!IsRangedAttacker() || !CanThrow() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2272 //fprintf(stderr, "character::CheckThrowItemOpportunity...\n");
2274 // (1) - Acquire target as nearest enemy
2275 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2276 // (3) - check inventory for throwing weapon, select this weapon
2277 // (4) - throw item in direction where the enemy is
2279 //Check the visible area for hostiles
2280 int ThrowDirection
= 0;
2281 int TargetFound
= 0;
2284 int RangeMax
= GetLOSRange();
2285 int CandidateDirections
[7] = {0, 0, 0, 0, 0, 0, 0};
2286 int HostileFound
= 0;
2287 item
*ToBeThrown
= 0;
2288 level
*Level
= GetLevel();
2290 for (int r
= 1; r
<= RangeMax
; ++r
) {
2291 for (int dir
= 0; dir
< MDIR_STAND
; ++dir
) {
2294 case 0: TestPos
= v2(Pos
.X
-r
, Pos
.Y
-r
); break;
2295 case 1: TestPos
= v2(Pos
.X
, Pos
.Y
-r
); break;
2296 case 2: TestPos
= v2(Pos
.X
+r
, Pos
.Y
-r
); break;
2297 case 3: TestPos
= v2(Pos
.X
-r
, Pos
.Y
); break;
2298 case 4: TestPos
= v2(Pos
.X
+r
, Pos
.Y
); break;
2299 case 5: TestPos
= v2(Pos
.X
-r
, Pos
.Y
+r
); break;
2300 case 6: TestPos
= v2(Pos
.X
, Pos
.Y
+r
); break;
2301 case 7: TestPos
= v2(Pos
.X
+r
, Pos
.Y
+r
); break;
2303 if (Level
->IsValidPos(TestPos
)) {
2304 square
*TestSquare
= GetNearSquare(TestPos
);
2305 character
*Dude
= TestSquare
->GetCharacter();
2307 if (Dude
&& Dude
->IsEnabled() && Dude
->CanBeSeenBy(this, false, true)) {
2308 if (GetRelation(Dude
) != HOSTILE
) CandidateDirections
[dir
] = BLOCKED
;
2309 else if (GetRelation(Dude
) == HOSTILE
&& CandidateDirections
[dir
] != BLOCKED
) {
2310 //then load this candidate position direction into the vector of possible throw directions
2311 CandidateDirections
[dir
] = SUCCESS
;
2320 for (int dir
= 0; dir
< MDIR_STAND
; ++dir
) {
2321 if (CandidateDirections
[dir
] == SUCCESS
&& !TargetFound
) {
2322 ThrowDirection
= dir
;
2327 if (!TargetFound
) return false;
2331 //fprintf(stderr, "throw: has target.\n");
2332 // check inventory for throwing weapon
2333 itemvector ItemVector
;
2334 GetStack()->FillItemVector(ItemVector
);
2335 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
2336 if (ItemVector
[c
]->IsThrowingWeapon()) {
2337 ToBeThrown
= ItemVector
[c
];
2341 if (!ToBeThrown
) return false;
2342 //fprintf(stderr, "throw: has throwing weapon.\n");
2343 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s throws %s.", CHAR_NAME(DEFINITE
), ToBeThrown
->CHAR_NAME(INDEFINITE
));
2344 ThrowItem(ThrowDirection
, ToBeThrown
);
2345 EditExperience(ARM_STRENGTH
, 75, 1<<8);
2346 EditExperience(DEXTERITY
, 75, 1<<8);
2347 EditExperience(PERCEPTION
, 75, 1<<8);
2355 truth
character::CheckAIZapOpportunity () {
2356 if (/*!IsRangedAttacker() || */ !CanZap() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2358 // (1) - Acquire target as nearest enemy
2359 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2360 // (3) - check inventory for zappable item
2361 // (4) - zap item in direction where the enemy is
2362 //Check the rest of the visible area for hostiles
2365 int SensibleRange
= 5;
2366 int RangeMax
= GetLOSRange();
2367 if (RangeMax
< SensibleRange
) SensibleRange
= RangeMax
;
2368 int CandidateDirections
[7] = {0, 0, 0, 0, 0, 0, 0};
2369 int HostileFound
= 0;
2370 int ZapDirection
= 0;
2371 int TargetFound
= 0;
2372 item
*ToBeZapped
= 0;
2373 level
*Level
= GetLevel();
2375 for (int r
= 2; r
<= SensibleRange
; ++r
) {
2376 for (int dir
= 0; dir
< MDIR_STAND
; ++dir
) {
2378 case 0: TestPos
= v2(Pos
.X
-r
, Pos
.Y
-r
); break;
2379 case 1: TestPos
= v2(Pos
.X
, Pos
.Y
-r
); break;
2380 case 2: TestPos
= v2(Pos
.X
+r
, Pos
.Y
-r
); break;
2381 case 3: TestPos
= v2(Pos
.X
-r
, Pos
.Y
); break;
2382 case 4: TestPos
= v2(Pos
.X
+r
, Pos
.Y
); break;
2383 case 5: TestPos
= v2(Pos
.X
-r
, Pos
.Y
+r
); break;
2384 case 6: TestPos
= v2(Pos
.X
, Pos
.Y
+r
); break;
2385 case 7: TestPos
= v2(Pos
.X
+r
, Pos
.Y
+r
); break;
2387 if (Level
->IsValidPos(TestPos
)) {
2388 square
*TestSquare
= GetNearSquare(TestPos
);
2389 character
*Dude
= TestSquare
->GetCharacter();
2391 if (Dude
&& Dude
->IsEnabled() && Dude
->CanBeSeenBy(this, false, true)) {
2392 if (GetRelation(Dude
) != HOSTILE
) CandidateDirections
[dir
] = BLOCKED
;
2393 else if (GetRelation(Dude
) == HOSTILE
&& CandidateDirections
[dir
] != BLOCKED
) {
2394 //then load this candidate position direction into the vector of possible zap directions
2395 CandidateDirections
[dir
] = SUCCESS
;
2404 for (int dir
= 0; dir
< MDIR_STAND
; ++dir
) {
2405 if (CandidateDirections
[dir
] == SUCCESS
&& !TargetFound
) {
2411 if (!TargetFound
) return false;
2415 // check inventory for zappable item
2416 itemvector ItemVector
;
2417 GetStack()->FillItemVector(ItemVector
);
2418 for (unsigned int c
= 0; c
< ItemVector
.size(); ++c
) {
2419 if (ItemVector
[c
]->GetMinCharges() > 0 && ItemVector
[c
]->GetPrice()) {
2420 // bald-faced gum solution for choosing zappables that have shots left.
2421 // MinCharges needs to be replaced. Empty wands have zero price!
2422 ToBeZapped
= ItemVector
[c
];
2426 if (!ToBeZapped
) return false;
2427 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s zaps %s.", CHAR_NAME(DEFINITE
), ToBeZapped
->CHAR_NAME(INDEFINITE
));
2428 if (ToBeZapped
->Zap(this, GetPos(), ZapDirection
)) {
2429 EditAP(-100000/APBonus(GetAttribute(PERCEPTION
)));
2439 truth
character::FollowLeader (character
*Leader
) {
2440 if (!Leader
|| Leader
== this || !IsEnabled()) return false;
2441 if ((CommandFlags
&FOLLOW_LEADER
) && Leader
->CanBeSeenBy(this) && Leader
->SquareUnderCanBeSeenBy(this, true)) {
2442 v2 Distance
= GetPos()-GoingTo
;
2443 if (abs(Distance
.X
) <= 2 && abs(Distance
.Y
) <= 2) return false;
2444 return MoveTowardsTarget(false);
2446 if (IsGoingSomeWhere()) {
2447 if (!MoveTowardsTarget(true)) {
2457 void character::SeekLeader (ccharacter
*Leader
) {
2458 if (Leader
&& Leader
!= this) {
2459 if (Leader
->CanBeSeenBy(this) && (Leader
->SquareUnderCanBeSeenBy(this, true) || !IsGoingSomeWhere())) {
2460 if (CommandFlags
&FOLLOW_LEADER
) SetGoingTo(Leader
->GetPos());
2461 } else if (!IsGoingSomeWhere()) {
2462 team
*Team
= GetTeam();
2463 for (std::list
<character
*>::const_iterator i
= Team
->GetMember().begin(); i
!= Team
->GetMember().end(); ++i
) {
2465 if (ch
->IsEnabled() && ch
->GetID() != GetID() &&
2466 (CommandFlags
& FOLLOW_LEADER
) == (ch
->CommandFlags
& FOLLOW_LEADER
) && ch
->CanBeSeenBy(this)) {
2467 v2 Pos
= ch
->GetPos();
2468 v2 Distance
= GetPos()-Pos
;
2469 if (abs(Distance
.X
) > 2 && abs(Distance
.Y
) > 2) {
2480 int character::GetMoveEase () const {
2481 if (BurdenState
== OVER_LOADED
|| BurdenState
== STRESSED
) return 50;
2482 if (BurdenState
== BURDENED
) return 75;
2483 if (BurdenState
== UNBURDENED
) return 100;
2488 int character::GetLOSRange () const {
2489 if (!game::IsInWilderness()) return GetAttribute(PERCEPTION
)*GetLevel()->GetLOSModifier()/48;
2494 truth
character::Displace (character
*Who
, truth Forced
) {
2495 if (GetBurdenState() == OVER_LOADED
) {
2497 cchar
*CrawlVerb
= StateIsActivated(LEVITATION
) ? "float" : "crawl";
2498 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb
);
2505 double Danger
= GetRelativeDanger(Who
);
2506 int PriorityDifference
= Limit(GetDisplacePriority()-Who
->GetDisplacePriority(), -31, 31);
2508 if (IsPlayer()) ++PriorityDifference
;
2509 else if (Who
->IsPlayer()) --PriorityDifference
;
2511 if (PriorityDifference
>= 0) Danger
*= 1 << PriorityDifference
;
2512 else Danger
/= 1 << -PriorityDifference
;
2514 if (IsSmall() && Who
->IsSmall() &&
2515 (Forced
|| Danger
> 1.0 || !(Who
->IsPlayer() || Who
->IsBadPath(GetPos()))) &&
2516 !IsStuck() && !Who
->IsStuck() && (!Who
->GetAction() || Who
->GetAction()->TryDisplace()) &&
2517 CanMove() && Who
->CanMove() && Who
->CanMoveOn(GetLSquareUnder())) {
2518 if (IsPlayer()) ADD_MESSAGE("You displace %s!", Who
->CHAR_DESCRIPTION(DEFINITE
));
2519 else if (Who
->IsPlayer()) ADD_MESSAGE("%s displaces you!", CHAR_DESCRIPTION(DEFINITE
));
2520 else if (CanBeSeenByPlayer() || Who
->CanBeSeenByPlayer()) ADD_MESSAGE("%s displaces %s!", CHAR_DESCRIPTION(DEFINITE
), Who
->CHAR_DESCRIPTION(DEFINITE
));
2521 lsquare
*OldSquareUnder1
[MAX_SQUARES_UNDER
];
2522 lsquare
*OldSquareUnder2
[MAX_SQUARES_UNDER
];
2523 for (int c
= 0; c
< GetSquaresUnder(); ++c
) OldSquareUnder1
[c
] = GetLSquareUnder(c
);
2524 for (int c
= 0; c
< Who
->GetSquaresUnder(); ++c
) OldSquareUnder2
[c
] = Who
->GetLSquareUnder(c
);
2526 v2 WhoPos
= Who
->GetPos();
2531 EditAP(-GetMoveAPRequirement(GetSquareUnder()->GetEntryDifficulty()) - 500);
2532 EditNP(-12*GetSquareUnder()->GetEntryDifficulty());
2533 EditExperience(AGILITY
, 75, GetSquareUnder()->GetEntryDifficulty() << 7);
2534 if (IsPlayer()) ShowNewPosInfo();
2535 if (Who
->IsPlayer()) Who
->ShowNewPosInfo();
2536 SignalStepFrom(OldSquareUnder1
);
2537 Who
->SignalStepFrom(OldSquareUnder2
);
2541 ADD_MESSAGE("%s resists!", Who
->CHAR_DESCRIPTION(DEFINITE
));
2550 void character::SetNP (sLong What
) {
2551 int OldState
= GetHungerState();
2554 int NewState
= GetHungerState();
2555 if (NewState
== STARVING
&& OldState
> STARVING
) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
2556 else if (NewState
== VERY_HUNGRY
&& OldState
> VERY_HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting very hungry."));
2557 else if (NewState
== HUNGRY
&& OldState
> HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting hungry."));
2562 void character::ShowNewPosInfo () const {
2563 msgsystem::EnterBigMessageMode();
2566 if (ivanconfig::GetAutoCenterMap()) {
2567 game::UpdateCameraX();
2568 game::UpdateCameraY();
2570 if (Pos
.X
< game::GetCamera().X
+3 || Pos
.X
>= game::GetCamera().X
+game::GetScreenXSize()-3) game::UpdateCameraX();
2571 if (Pos
.Y
< game::GetCamera().Y
+3 || Pos
.Y
>= game::GetCamera().Y
+game::GetScreenYSize()-3) game::UpdateCameraY();
2574 game::SendLOSUpdateRequest();
2575 game::DrawEverythingNoBlit();
2578 if (!game::IsInWilderness()) {
2579 if (GetLSquareUnder()->IsDark() && !game::GetSeeWholeMapCheatMode()) ADD_MESSAGE("It's dark in here!");
2581 GetLSquareUnder()->ShowSmokeMessage();
2582 itemvectorvector PileVector
;
2583 GetStackUnder()->Pile(PileVector
, this, CENTER
);
2585 if (PileVector
.size()) {
2586 truth Feel
= !GetLSquareUnder()->IsTransparent() || GetLSquareUnder()->IsDark();
2588 if (PileVector
.size() == 1) {
2590 ADD_MESSAGE("You feel %s lying here.", PileVector
[0][0]->GetName(INDEFINITE
, PileVector
[0].size()).CStr());
2592 if (ivanconfig::GetShowFullItemDesc() && PileVector
[0][0]->AllowDetailedDescription()) {
2595 PileVector
[0][0]->AddInventoryEntry(PLAYER
, text
, PileVector
[0].size(), true);
2596 //fprintf(stderr, "invdsc : [%s]\n", text.CStr());
2597 ADD_MESSAGE("%s %s lying here.", text
.CStr(), PileVector
[0].size() == 1 ? "is" : "are");
2599 ADD_MESSAGE("%s %s lying here.", PileVector
[0][0]->GetName(INDEFINITE
, PileVector
[0].size()).CStr(), PileVector
[0].size() == 1 ? "is" : "are");
2602 fprintf(stderr, "description: [%s]\n", PileVector[0][0]->GetDescription(INDEFINITE).CStr());
2603 fprintf(stderr, "strength : [%s]\n", PileVector[0][0]->GetStrengthValueDescription());
2604 fprintf(stderr, "basetohit : [%s]\n", PileVector[0][0]->GetBaseToHitValueDescription());
2605 fprintf(stderr, "baseblock : [%s]\n", PileVector[0][0]->GetBaseBlockValueDescription());
2606 fprintf(stderr, "extdsc : [%s]\n", PileVector[0][0]->GetExtendedDescription().CStr());
2611 for (uInt c
= 0; c
< PileVector
.size(); ++c
) {
2612 if ((Items
+= PileVector
[c
].size()) > 3) break;
2615 if (Feel
) ADD_MESSAGE("You feel several items lying here.");
2616 else ADD_MESSAGE("Several items are lying here.");
2618 if (Feel
) ADD_MESSAGE("You feel a few items lying here.");
2619 else ADD_MESSAGE("A few items are lying here.");
2625 GetLSquareUnder()->GetSideItemDescription(SideItems
);
2627 if (!SideItems
.IsEmpty()) ADD_MESSAGE("There is %s.", SideItems
.CStr());
2629 if (GetLSquareUnder()->HasEngravings()) {
2630 if (CanRead()) ADD_MESSAGE("Something has been engraved here: \"%s\"", GetLSquareUnder()->GetEngraved());
2631 else ADD_MESSAGE("Something has been engraved here.");
2635 msgsystem::LeaveBigMessageMode();
2639 void character::Hostility (character
*Enemy
) {
2640 if (Enemy
== this || !Enemy
|| !Team
|| !Enemy
->Team
) return;
2641 if (Enemy
->IsMasochist() && GetRelation(Enemy
) == FRIEND
) return;
2642 if (!IsAlly(Enemy
)) {
2643 GetTeam()->Hostility(Enemy
->GetTeam());
2644 } else if (IsPlayer() && !Enemy
->IsPlayer()) {
2645 // I believe both may be players due to polymorph feature...
2646 if (Enemy
->CanBeSeenByPlayer()) ADD_MESSAGE("%s becomes enraged.", Enemy
->CHAR_NAME(DEFINITE
));
2647 Enemy
->ChangeTeam(game::GetTeam(BETRAYED_TEAM
));
2652 stack
*character::GetGiftStack () const {
2653 if (GetLSquareUnder()->GetRoomIndex() && !GetLSquareUnder()->GetRoom()->AllowDropGifts()) return GetStack();
2654 return GetStackUnder();
2658 truth
character::MoveRandomlyInRoom () {
2659 for (int c
= 0; c
< 10; ++c
) {
2660 v2 ToTry
= game::GetMoveVector(RAND()&7);
2661 if (GetLevel()->IsValidPos(GetPos()+ToTry
)) {
2662 lsquare
*Square
= GetNearLSquare(GetPos()+ToTry
);
2663 if (!Square
->IsDangerous(this) && !Square
->IsScary(this) &&
2664 (!Square
->GetOLTerrain() || !Square
->GetOLTerrain()->IsDoor()) &&
2665 TryMove(ToTry
, false, false)) return true;
2672 //#define dirlogf(...) do { fprintf(stderr, __VA_ARGS__); } while (0)
2673 #define dirlogf(...) ((void)0)
2676 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
};
2677 static const bool orthoDir
[MDIR_STAND
] = { false, true, false, true, true, false, true, false };
2680 // only for ortho moveDir
2681 static inline truth
IsDirExcluded (int moveDir
, int dir
) {
2682 if (moveDir
== dir
) return true;
2684 case MDIR_UP
: return (dir
== MDIR_UP_LEFT
|| dir
== MDIR_UP_RIGHT
);
2685 case MDIR_LEFT
: return (dir
== MDIR_UP_LEFT
|| dir
== MDIR_DOWN_LEFT
);
2686 case MDIR_RIGHT
: return (dir
== MDIR_UP_RIGHT
|| dir
== MDIR_DOWN_RIGHT
);
2687 case MDIR_DOWN
: return (dir
== MDIR_DOWN_LEFT
|| dir
== MDIR_DOWN_RIGHT
);
2693 truth
character::IsPassableSquare (int x
, int y
) const {
2694 if (x
>= 0 && y
>= 0) {
2695 area
*ca
= GetSquareUnder()->GetArea();
2698 if (x
>= ca
->GetXSize() || y
>= ca
->GetYSize()) return false;
2699 sq
= static_cast<lsquare
*>(ca
->GetSquare(x
, y
));
2700 return sq
&& CanMoveOn(sq
);
2706 void character::CountPossibleMoveDirs (cv2 pos
, int *odirs
, int *ndirs
, int exclideDir
) const {
2707 if (odirs
) *odirs
= 0;
2708 if (ndirs
) *ndirs
= 0;
2709 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2710 if (!IsDirExcluded(exclideDir
, f
)) {
2711 if (IsPassableSquare(pos
+game::GetMoveVector(f
))) {
2713 if (odirs
) ++(*odirs
);
2715 if (ndirs
) ++(*ndirs
);
2724 * in corridor (for orto-dirs):
2725 * count dirs excluding ortho-dir we going:
2726 * if there is one or less ortho-dirs and one or less non-ortho-dirs, we are in corridor
2728 // only for ortho-dirs
2729 truth
character::IsInCorridor (int x
, int y
, int moveDir
) const {
2732 dirlogf("IsInCorridor(%d,%d,%d)\n", x
, y
, moveDir
);
2734 moveDir
= (moveDir
>= 0 && moveDir
< MDIR_STAND
? revDir
[moveDir
] : -1);
2735 dirlogf(" reversedDir: %d\n", moveDir
);
2736 CountPossibleMoveDirs(v2(x
, y
), &od
, &nd
, moveDir
);
2737 dirlogf(" possibleDirs: (%d:%d)\n", od
, nd
);
2738 dirlogf(" IsInCorridor: %s\n", ((od
<= 1 && nd
<= 1) ? "yes" : "no"));
2739 return (od
<= 1 && nd
<= 1);
2743 cv2
character::GetDiagonalForDirs (int moveDir
, int newDir
) const {
2747 case MDIR_LEFT
: return game::GetMoveVector(MDIR_UP_LEFT
);
2748 case MDIR_RIGHT
: return game::GetMoveVector(MDIR_UP_RIGHT
);
2753 case MDIR_LEFT
: return game::GetMoveVector(MDIR_DOWN_LEFT
);
2754 case MDIR_RIGHT
: return game::GetMoveVector(MDIR_DOWN_RIGHT
);
2759 case MDIR_UP
: return game::GetMoveVector(MDIR_UP_LEFT
);
2760 case MDIR_DOWN
: return game::GetMoveVector(MDIR_DOWN_LEFT
);
2765 case MDIR_UP
: return game::GetMoveVector(MDIR_UP_RIGHT
);
2766 case MDIR_DOWN
: return game::GetMoveVector(MDIR_DOWN_RIGHT
);
2770 ABORT("wtf in character::GetDiagonalForDirs()");
2774 truth
character::IsInTunnelDeadEnd () const {
2777 CountPossibleMoveDirs(GetPos(), &od
, &nd
, -1);
2778 return (od
<= 1 && nd
== 0);
2783 * try to walk in the given dir
2784 * can do two steps without a turn and still in corridor?
2788 * go in non-ortho dir, set prevdir to last ortho-dir from corridor tracing
2790 // only for ortho-dirs; assume that the char is in corridor
2791 int character::CheckCorridorMove (v2
&moveVector
, cv2 pos
, int moveDir
, truth
*markAsTurn
) const {
2792 moveVector
= game::GetMoveVector(moveDir
);
2793 v2
ps1(pos
+moveVector
);
2795 if (markAsTurn
) *markAsTurn
= true;
2797 if (IsPassableSquare(ps1
)) {
2798 // we can do first step in the given dir
2799 // check if we will be in corridor after it
2800 dirlogf("CheckCorridorMove: can do first step\n");
2801 if (IsInCorridor(ps1
, moveDir
)) {
2802 // check second step
2803 v2
ps2(ps1
+moveVector
);
2804 dirlogf("CheckCorridorMove: still in corridor after the first step\n");
2805 if (IsPassableSquare(ps2
)) {
2806 // can do second step
2807 dirlogf("CheckCorridorMove: can do second step\n");
2810 // can't do second step; but we still in corridor, so we should make a turn
2811 int newDir
= -1; // direction to turn
2812 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2813 if (f
!= moveDir
&& orthoDir
[f
] && f
!= revDir
[moveDir
] && IsPassableSquare(ps1
+game::GetMoveVector(f
))) {
2818 dirlogf("CheckCorridorMove: can't do second step; moveDir=%d; newDir=%d\n", moveDir
, newDir
);
2820 // dead end, will stop
2823 // we should do diagonal move
2824 moveVector
= GetDiagonalForDirs(moveDir
, newDir
);
2825 // if this is 'one-tile-turn', we should not change the direction to newDir
2826 if (IsPassableSquare(ps1
+game::GetMoveVector(newDir
)+game::GetMoveVector(moveDir
))) {
2827 // yes, this is 'one-tile-turn'
2828 dirlogf("CheckCorridorMove: one-tile-turn, don't change dir\n");
2836 * 'g'o right: should stop at '*', but it just goes right
2838 if (markAsTurn
) *markAsTurn
= IsInCorridor(ps1
+game::GetMoveVector(newDir
), newDir
);
2844 dirlogf("CheckCorridorMove: can do one or two steps; move forward\n");
2845 // can do one or two steps: check for T-junction
2846 // we should stop if we have more than two open dirs, or one of open dirs is not moveDir
2848 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2849 if (f
== revDir
[moveDir
]) continue; // skip "reverse dir" check
2850 v2
ps2(pos
+game::GetMoveVector(f
));
2851 if (IsPassableSquare(ps2
)) {
2853 if (dcount
> 2) return -1; // more than two open dirs, stop
2854 if (f
!= moveDir
) return -1; // one of open dirs is not moveDir
2857 // just move forward
2860 dirlogf("CheckCorridorMove: dead end\n");
2861 // can't go, assume invalid direction
2866 truth
character::IsDangerousSquare (v2 pos
) const {
2867 if (!IsPassableSquare(pos
)) return false;
2868 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
2869 auto Squares
= CalculateNewSquaresUnder(MoveToSquare
, pos
);
2870 for (decltype(Squares
) c
= 0; c
< Squares
; ++c
) {
2871 lsquare
*Square
= MoveToSquare
[c
];
2873 if (!Square
->HasBeenSeen()) continue;
2875 if (!Square
->CanBeSeenBy(this)) continue;
2877 // check if someone is standing at the square
2878 if (Square
->GetCharacter() && GetTeam() != Square
->GetCharacter()->GetTeam() && Square
->GetCharacter()->CanBeSeenBy(this)) return true;
2879 if (Square
->IsDangerous(this)) {
2880 if (IsPlayer() && Square
->HasBeenSeen()) return true;
2881 if (Square
->CanBeSeenBy(this)) return true;
2888 void character::MarkAdjacentItemsAsSeen (v2 pos
) {
2889 lsquare
*sqlist
[MAX_SQUARES_UNDER
];
2890 for (int d
= 0; d
< MDIR_STAND
; ++d
) {
2891 auto np
= pos
+game::GetMoveVector(d
);
2892 if (!IsPassableSquare(np
)) continue;
2893 auto sqcount
= CalculateNewSquaresUnder(sqlist
, np
);
2894 for (int n
= 0; n
< sqcount
; ++n
) {
2895 lsquare
*sq
= sqlist
[n
];
2896 if ((IsPlayer() && sq
->HasBeenSeen()) || sq
->CanBeSeenBy(this)) {
2897 sq
->GetStack()->SetSteppedOn(true);
2904 truth
character::HasInterestingItemsAt (v2 pos
) {
2905 if (!IsPassableSquare(pos
)) return false;
2906 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
2907 int sq2
= CalculateNewSquaresUnder(MoveToSquare
, pos
);
2908 for (int c
= 0; c
< sq2
; ++c
) {
2909 lsquare
*sq
= MoveToSquare
[c
];
2911 if (!sq
->HasBeenSeen()) continue;
2913 if (!sq
->CanBeSeenBy(this)) continue;
2915 if (sq
->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
2923 void character::GoOn (go
*Go
, truth FirstStep
) {
2924 dirlogf("=== character::GoOn; dir=%d; pos=(%d,%d) ===\n", Go
->GetDirection(), GetPos().X
, GetPos().Y
);
2926 dirlogf("FirstStep\n");
2927 mPrevMoveDir
= Go
->GetDirection();
2928 Go
->SetIsWalkingInOpen(!IsInCorridor(Go
->GetDirection()));
2931 v2 MoveVector
= ApplyStateModification(game::GetMoveVector(Go
->GetDirection()));
2932 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
2933 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, GetPos()+MoveVector
);
2934 int moveDir
= game::MoveVectorToDirection(MoveVector
);
2936 if (Squares
== 0 || !CanMoveOn(MoveToSquare
[0])) {
2937 dirlogf("just can't move\n");
2938 Go
->Terminate(false);
2943 // first step: mark all adjacent items as seen
2944 MarkAdjacentItemsAsSeen(GetPos());
2948 // check for corridor<->open place
2949 if (!Go
->GetPrevWasTurn() && Go
->IsWalkingInOpen() != !IsInCorridor(GetPos(), moveDir
)) {
2950 dirlogf("moved to/from open place\n");
2951 Go
->Terminate(false);
2955 // check for room change
2956 uInt OldRoomIndex
= GetLSquareUnder()->GetRoomIndex();
2957 uInt CurrentRoomIndex
= MoveToSquare
[0]->GetRoomIndex();
2958 if (OldRoomIndex
&& (CurrentRoomIndex
!= OldRoomIndex
)) {
2959 // room is about to be changed, stop here
2960 dirlogf("room about to be changed\n");
2961 Go
->Terminate(false);
2965 // stop near a dangerous square
2966 if (IsDangerousSquare(GetPos()+MoveVector
)) {
2967 dirlogf("sense the danger\n");
2968 Go
->Terminate(false);
2973 // if the state modified the direction, move and stop
2974 if (moveDir
!= Go
->GetDirection()) {
2975 dirlogf("move affected by state\n");
2976 if (TryMove(MoveVector
, true, game::PlayerIsRunning())) {
2977 game::DrawEverything();
2978 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2980 Go
->Terminate(false);
2984 truth doStop
= false, markAsTurn
= false;
2986 // continuous walking
2987 if (Go
->IsWalkingInOpen() || !orthoDir
[moveDir
]) {
2988 // walking in open space or diagonal walking
2989 v2
newPos(GetPos()+MoveVector
);
2990 int ood
, ond
, nod
, nnd
;
2992 * open: stop if # of possible dirs in next step != # of possible dirs in current step
2993 * (or next step is in corridor)
2995 dirlogf("open walking\n");
2996 if (IsInCorridor(newPos
, moveDir
)) {
2997 // trying to enter the corridor, stop right here
2998 dirlogf("entering the corridor\n");
2999 Go
->Terminate(false);
3002 CountPossibleMoveDirs(GetPos(), &ood
, &ond
);
3003 CountPossibleMoveDirs(newPos
, &nod
, &nnd
);
3004 if (ood
!= nod
|| ond
!= nnd
) {
3005 // # of directions to walk to changed, stop right here
3006 dirlogf("# of directions changed from (%d:%d) to (%d:%d)\n", ood
, ond
, nod
, nnd
);
3007 //Go->Terminate(false);
3011 // ok, we can do this move
3013 // ortho-walking thru the corridor
3014 int newDir
= CheckCorridorMove(MoveVector
, GetPos(), moveDir
, &markAsTurn
);
3016 // ah, something weird; stop right here
3017 Go
->Terminate(false);
3020 // if this is diagonal move: check if we have something interesting in previous dir
3021 if (MoveVector
.X
&& MoveVector
.Y
) {
3022 if (HasInterestingItemsAt(GetPos()+game::GetMoveVector(moveDir
))) {
3023 // move to item and stop
3025 MoveVector
= game::GetMoveVector(moveDir
);
3029 // as `MoveVector` can change here, recalc squares
3030 Squares
= CalculateNewSquaresUnder(MoveToSquare
, GetPos()+MoveVector
);
3031 // perform possible turn
3032 Go
->SetDirection(newDir
);
3035 // stop near the dangerous square
3036 for (int mdv
= 0; mdv
< MDIR_STAND
; ++mdv
) {
3037 if (IsDangerousSquare(GetPos()+MoveVector
+game::GetMoveVector(mdv
))) {
3038 dirlogf(" danger!\n");
3039 Go
->Terminate(false);
3045 // now try to perform the move
3046 dirlogf("trying to make the move\n");
3048 square
*BeginSquare
= GetSquareUnder();
3049 uInt OldRoomIndex
= GetLSquareUnder()->GetRoomIndex();
3050 uInt CurrentRoomIndex
= MoveToSquare
[0]->GetRoomIndex();
3052 // stop on the square with something interesting
3055 area
*ca
= GetSquareUnder()->GetArea();
3056 v2 npos
= GetPos()+MoveVector
;
3057 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
3058 v2 np
= npos
+game::GetMoveVector(f
);
3059 if (np
.X
>= 0 && np
.Y
>= 0 && np
.X
< ca
->GetXSize() && np
.Y
< ca
->GetYSize()) {
3060 lsquare
*sq
= static_cast<lsquare
*>(ca
->GetSquare(np
.X
, np
.Y
));
3062 if (!sq
->HasBeenSeen()) continue;
3064 if (!sq
->CanBeSeenBy(this)) continue;
3066 //if (!sq->CanBeSeenBy(this)) continue;
3067 olterrain
*terra
= sq
->GetOLTerrain();
3069 dirlogf("** OK terra at %d; door: %s; seen: %s\n", f
, (terra
->IsDoor() ? "yes" : "no"), (sq
->IsGoSeen() ? "yes" : "no"));
3070 if (terra
->IsDoor()) {
3071 if (ivanconfig::GetStopOnSeenDoors() || !sq
->IsGoSeen()) {
3072 dirlogf(" *** stop near the door\n");
3083 for (int c
= 0; c
< Squares
; ++c
) {
3084 lsquare
*Square
= MoveToSquare
[c
];
3086 if (!Square
->HasBeenSeen()) continue;
3088 if (!Square
->CanBeSeenBy(this)) continue;
3090 if (Square
->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
3091 dirlogf(" stepped near something interesting\n");
3098 // check items in adjacent squares too, so diagonal move won't miss any
3100 for (int f
= 0; f
< MDIR_STAND
&& !doStop
; ++f
) {
3101 v2 np
= game::GetMoveVector(f
);
3102 if (np
== MoveVector
) continue; // this will be checked on the next move
3103 if (HasInterestingItemsAt(GetPos()+np
)) {
3104 dirlogf(" stepped near something interesting\n");
3106 Go
->Terminate(false);
3113 Go
->SetPrevWasTurn(markAsTurn
&& MoveVector
.X
&& MoveVector
.Y
); // diagonal move?
3115 truth moveOk
= TryMove(MoveVector
, true, game::PlayerIsRunning());
3117 if (!moveOk
|| BeginSquare
== GetSquareUnder() || (CurrentRoomIndex
&& (OldRoomIndex
!= CurrentRoomIndex
))) {
3118 dirlogf(" stopped\n");
3120 game::DrawEverything();
3121 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
3123 Go
->Terminate(false);
3128 mPrevMoveDir
= Go
->GetDirection();
3129 Go
->SetIsWalkingInOpen(!IsInCorridor(moveDir
));
3132 game::DrawEverything();
3133 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
3134 if (doStop
) Go
->Terminate(false);
3138 void character::SetTeam (team
*What
) {
3144 void character::ChangeTeam (team
*What
) {
3145 if (Team
) Team
->Remove(this);
3147 SendNewDrawRequest();
3148 if (Team
) Team
->Add(this);
3152 truth
character::ChangeRandomAttribute (int HowMuch
) {
3153 for (int c
= 0; c
< 50; ++c
) {
3154 int AttribID
= RAND()%ATTRIBUTES
;
3155 if (EditAttribute(AttribID
, HowMuch
)) return true;
3161 int character::RandomizeReply (sLong
&Said
, int Replies
) {
3162 truth NotSaid
= false;
3163 for (int c
= 0; c
< Replies
; ++c
) {
3164 if (!(Said
& (1 << c
))) {
3169 if (!NotSaid
) Said
= 0;
3171 while (Said
& 1 << (ToSay
= RAND() % Replies
));
3177 void character::DisplayInfo (festring
&Msg
) {
3179 Msg
<< " You are " << GetStandVerb() << " here.";
3181 Msg
<< ' ' << GetName(INDEFINITE
).CapitalizeCopy() << " is " << GetStandVerb() << " here. " << GetPersonalPronoun().CapitalizeCopy();
3182 cchar
*Separator1
= GetAction() ? "," : " and";
3183 cchar
*Separator2
= " and";
3184 if (GetTeam() == PLAYER
->GetTeam()) {
3187 int Relation
= GetRelation(PLAYER
);
3188 if (Relation
== HOSTILE
) Msg
<< " is hostile";
3189 else if (Relation
== UNCARING
) {
3190 Msg
<< " does not care about you";
3191 Separator1
= Separator2
= " and is";
3193 Msg
<< " is friendly";
3196 if (StateIsActivated(PANIC
)) {
3197 Msg
<< Separator1
<< " panicked";
3198 Separator2
= " and";
3200 if (GetAction()) Msg
<< Separator2
<< ' ' << GetAction()->GetDescription();
3206 void character::TestWalkability () {
3207 if (!IsEnabled()) return;
3208 square
*SquareUnder
= !game::IsInWilderness() ? GetSquareUnder() : PLAYER
->GetSquareUnder();
3209 if (SquareUnder
->IsFatalToStay() && !CanMoveOn(SquareUnder
)) {
3210 truth Alive
= false;
3211 if (!game::IsInWilderness() || IsPlayer()) {
3212 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
3213 square
*Square
= GetNeighbourSquare(d
);
3214 if (Square
&& CanMoveOn(Square
) && IsFreeForMe(Square
)) {
3215 if (IsPlayer()) ADD_MESSAGE("%s.", SquareUnder
->SurviveMessage(this));
3216 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE
), SquareUnder
->MonsterSurviveMessage(this));
3217 Move(Square
->GetPos(), true); // actually, this shouldn't be a teleport move
3218 SquareUnder
->SurviveEffect(this);
3228 festring DeathMsg
= festring(SquareUnder
->DeathMessage(this));
3229 game::AskForEscPress(DeathMsg
+".");
3230 festring Msg
= SquareUnder
->ScoreEntry(this);
3231 PLAYER
->AddScoreEntry(Msg
);
3234 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE
), SquareUnder
->MonsterDeathVerb(this));
3235 Die(0, SquareUnder
->ScoreEntry(this), DISALLOW_MSG
);
3242 int character::GetSize () const {
3243 if (GetTorso()->GetSize() < 1) {
3244 fprintf(stderr
, "WARNING: character::GetSize() is %d for %s!\n", GetTorso()->GetSize(), GetNameSingular().CStr());
3247 return GetTorso()->GetSize();
3251 void character::SetMainMaterial (material
*NewMaterial
, int SpecialFlags
) {
3252 NewMaterial
->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
3253 GetBodyPart(0)->SetMainMaterial(NewMaterial
, SpecialFlags
);
3254 for (int c
= 1; c
< BodyParts
; ++c
) {
3255 NewMaterial
= NewMaterial
->SpawnMore(GetBodyPart(c
)->GetMainMaterial()->GetVolume());
3256 GetBodyPart(c
)->SetMainMaterial(NewMaterial
, SpecialFlags
);
3261 void character::ChangeMainMaterial (material
*NewMaterial
, int SpecialFlags
) {
3262 NewMaterial
->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
3263 GetBodyPart(0)->ChangeMainMaterial(NewMaterial
, SpecialFlags
);
3264 for (int c
= 1; c
< BodyParts
; ++c
) {
3265 NewMaterial
= NewMaterial
->SpawnMore(GetBodyPart(c
)->GetMainMaterial()->GetVolume());
3266 GetBodyPart(c
)->ChangeMainMaterial(NewMaterial
, SpecialFlags
);
3271 void character::SetSecondaryMaterial (material
*, int) {
3272 ABORT("Illegal character::SetSecondaryMaterial call!");
3276 void character::ChangeSecondaryMaterial (material
*, int) {
3277 ABORT("Illegal character::ChangeSecondaryMaterial call!");
3281 void character::TeleportRandomly (truth Intentional
) {
3282 v2 TelePos
= ERROR_V2
;
3283 if (StateIsActivated(TELEPORT_LOCK
)) { ADD_MESSAGE("You flicker for a second."); return; }
3284 if (StateIsActivated(TELEPORT_CONTROL
)) {
3286 v2 Input
= game::PositionQuestion(CONST_S("Where do you wish to teleport? [direction keys move cursor, space accepts]"), GetPos(), &game::TeleportHandler
, 0, false);
3287 if (Input
== ERROR_V2
) Input
= GetPos(); // esc pressed
3288 lsquare
*Square
= GetNearLSquare(Input
);
3289 if (CanMoveOn(Square
) || game::GoThroughWallsCheatIsActive()) {
3290 if (Square
->GetPos() == GetPos()) {
3291 ADD_MESSAGE("You disappear and reappear.");
3294 if (IsFreeForMe(Square
)) {
3295 if ((Input
-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
3296 EditExperience(INTELLIGENCE
, 100, 1 << 10);
3299 ADD_MESSAGE("You cannot concentrate yourself enough to control a teleport that far.");
3302 character
*C
= Square
->GetCharacter();
3303 if (C
) ADD_MESSAGE("For a moment you feel very much like %s.", C
->CHAR_NAME(INDEFINITE
));
3304 else ADD_MESSAGE("You feel that something weird has happened, but can't really tell what exactly.");
3307 ADD_MESSAGE("You feel like having been hit by something really hard from the inside.");
3309 } else if (!Intentional
) {
3310 if (IsGoingSomeWhere() && GetLevel()->IsValidPos(GoingTo
)) {
3311 v2 Where
= GetLevel()->GetNearestFreeSquare(this, GoingTo
);
3312 if (Where
!= ERROR_V2
&& (Where
-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
3313 EditExperience(INTELLIGENCE
, 100, 1 << 10);
3320 if (TelePos
== ERROR_V2
) {
3321 TelePos
= GetLevel()->GetRandomSquare(this);
3322 if (TelePos
== ERROR_V2
) return;
3326 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.");
3329 room
*PossibleRoom
= game::GetCurrentLevel()->GetLSquare(TelePos
)->GetRoom();
3330 if (!PossibleRoom
) {
3331 //if it's outside of a room
3332 Move(TelePos
, true);
3333 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE
));
3334 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3335 } else if (PossibleRoom
&& PossibleRoom
->IsOKToTeleportInto()) {
3336 // If it's inside of a room, check whether a ward is active that might impede the player
3337 Move(TelePos
, true);
3338 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE
));
3339 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3342 ADD_MESSAGE("A mighty force blasts you back to where you were standing. A ward prevents you from teleporting.");
3344 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);
3349 CONST_S("killed by an explosion triggered when attempting to teleport into room protected by a ward"),
3353 lsquare* Square = GetNearLSquare(GetPos());
3354 Square->DrawParticles(RED);
3355 Square->FireBall(Beam);*/
3360 void character::DoDetecting () {
3361 material
*TempMaterial
;
3364 festring Temp
= game::DefaultQuestion(CONST_S("What material do you want to detect?"), game::GetDefaultDetectMaterial());
3365 TempMaterial
= protosystem::CreateMaterial(Temp
);
3366 if (TempMaterial
) break;
3367 game::DrawEverythingNoBlit();
3370 level
*Level
= GetLevel();
3371 int Squares
= Level
->DetectMaterial(TempMaterial
);
3373 if (Squares
> GetAttribute(INTELLIGENCE
) * (25+RAND()%51)) {
3374 ADD_MESSAGE("An enormous burst of geographical information overwhelms your consciousness. Your mind cannot cope with it and your memories blur.");
3375 Level
->BlurMemory();
3376 BeginTemporaryState(CONFUSED
, 1000 + RAND() % 1000);
3377 EditExperience(INTELLIGENCE
, -100, 1 << 12);
3378 } else if (!Squares
) {
3379 ADD_MESSAGE("You feel a sudden urge to imagine the dark void of a starless night sky.");
3380 EditExperience(INTELLIGENCE
, 200, 1 << 12);
3382 ADD_MESSAGE("You feel attracted to all things made of %s.", TempMaterial
->GetName(false, false).CStr());
3383 game::PositionQuestion(CONST_S("Detecting material [direction keys move cursor, space exits]"), GetPos(), 0, 0, false);
3384 EditExperience(INTELLIGENCE
, 300, 1 << 12);
3387 delete TempMaterial
;
3388 Level
->CalculateLuminances();
3389 game::SendLOSUpdateRequest();
3393 void character::RestoreHP () {
3394 doforbodyparts()(this, &bodypart::FastRestoreHP
);
3399 void character::RestoreLivingHP () {
3401 for (int c
= 0; c
< BodyParts
; ++c
) {
3402 bodypart
*BodyPart
= GetBodyPart(c
);
3403 if (BodyPart
&& BodyPart
->CanRegenerate()) {
3404 BodyPart
->FastRestoreHP();
3405 HP
+= BodyPart
->GetHP();
3411 truth
character::AllowDamageTypeBloodSpill (int Type
) {
3412 if ((Type
&0xFFF) == PHYSICAL_DAMAGE
) return true;
3413 if ((Type
&0xFFF) == SOUND
) return true;
3414 if ((Type
&0xFFF) == ENERGY
) return true;
3416 if ((Type
&0xFFF) == ACID
) return false;
3417 if ((Type
&0xFFF) == FIRE
) return false;
3418 if ((Type
&0xFFF) == DRAIN
) return false;
3419 if ((Type
&0xFFF) == POISON
) return false;
3420 if ((Type
&0xFFF) == ELECTRICITY
) return false;
3421 if ((Type
&0xFFF) == MUSTARD_GAS_DAMAGE
) return false;
3422 if ((Type
&0xFFF) == PSI
) return false;
3424 ABORT("Unknown blood effect destroyed the dungeon!");
3429 /* Returns truly done damage */
3430 int character::ReceiveBodyPartDamage (character
*Damager
, int Damage
, int Type
, int BodyPartIndex
,
3431 int Direction
, truth PenetrateResistance
, truth Critical
, truth ShowNoDamageMsg
, truth CaptureBodyPart
)
3433 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
3434 if (!Damager
|| Damager
->AttackMayDamageArmor()) BodyPart
->DamageArmor(Damager
, Damage
, Type
);
3435 if (!PenetrateResistance
) {
3436 Damage
-= (BodyPart
->GetTotalResistance(Type
)>>1)+RAND()%((BodyPart
->GetTotalResistance(Type
)>>1)+1);
3438 if (int(Damage
) < 1) {
3442 if (ShowNoDamageMsg
) {
3443 if (IsPlayer()) ADD_MESSAGE("You are not hurt.");
3444 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is not hurt.", GetPersonalPronoun().CStr());
3450 if (Critical
&& AllowDamageTypeBloodSpill(Type
) && !game::IsInWilderness()) {
3451 BodyPart
->SpillBlood(2+(RAND()&1));
3452 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
3453 lsquare
*Square
= GetNeighbourLSquare(d
);
3454 if (Square
&& Square
->IsFlyable()) BodyPart
->SpillBlood(1, Square
->GetPos());
3458 if (BodyPart
->ReceiveDamage(Damager
, Damage
, Type
, Direction
) && BodyPartCanBeSevered(BodyPartIndex
)) {
3459 if (DamageTypeDestroysBodyPart(Type
)) {
3460 if (IsPlayer()) ADD_MESSAGE("Your %s is destroyed!", BodyPart
->GetBodyPartName().CStr());
3461 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is destroyed!", GetPossessivePronoun().CStr(), BodyPart
->GetBodyPartName().CStr());
3462 GetBodyPart(BodyPartIndex
)->DropEquipment();
3463 item
*Severed
= SevereBodyPart(BodyPartIndex
);
3464 if (Severed
) Severed
->DestroyBodyPart(!game::IsInWilderness() ? GetStackUnder() : GetStack());
3465 SendNewDrawRequest();
3466 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart destroyed!"));
3468 if (IsPlayer()) ADD_MESSAGE("Your %s is severed off!", BodyPart
->GetBodyPartName().CStr());
3469 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is severed off!", GetPossessivePronoun().CStr(), BodyPart
->GetBodyPartName().CStr());
3470 item
*Severed
= SevereBodyPart(BodyPartIndex
);
3471 SendNewDrawRequest();
3473 if (CaptureBodyPart
) {
3474 Damager
->GetLSquareUnder()->AddItem(Severed
);
3475 } else if (!game::IsInWilderness()) {
3476 /** No multi-tile humanoid support! */
3477 GetStackUnder()->AddItem(Severed
);
3478 if (Direction
!= YOURSELF
) Severed
->Fly(0, Direction
, Damage
);
3480 GetStack()->AddItem(Severed
);
3482 Severed
->DropEquipment();
3483 } else if (IsPlayer() || CanBeSeenByPlayer()) {
3484 ADD_MESSAGE("It vanishes.");
3486 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart severed!"));
3488 if (CanPanicFromSeveredBodyPart() && RAND()%100 < GetPanicLevel() && !StateIsActivated(PANIC
) && !IsDead() && !StateIsActivated(FEARLESS
)) {
3489 BeginTemporaryState(PANIC
, 1000+RAND()%1001);
3491 SpecialBodyPartSeverReaction();
3494 if (!IsDead()) CheckPanic(500);
3500 /* Returns 0 if bodypart disappears */
3501 item
*character::SevereBodyPart (int BodyPartIndex
, truth ForceDisappearance
, stack
*EquipmentDropStack
) {
3502 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
3503 if (StateIsActivated(LEPROSY
)) BodyPart
->GetMainMaterial()->SetIsInfectedByLeprosy(true);
3504 if (ForceDisappearance
|| BodyPartsDisappearWhenSevered() || StateIsActivated(POLYMORPHED
) || game::AllBodyPartsVanish()) {
3505 BodyPart
->DropEquipment(EquipmentDropStack
);
3506 BodyPart
->RemoveFromSlot();
3507 CalculateAttributeBonuses();
3508 CalculateBattleInfo();
3509 BodyPart
->SendToHell();
3510 SignalPossibleTransparencyChange();
3511 RemoveTraps(BodyPartIndex
);
3514 BodyPart
->SetOwnerDescription("of " + GetName(INDEFINITE
));
3515 BodyPart
->SetIsUnique(LeftOversAreUnique());
3516 UpdateBodyPartPicture(BodyPartIndex
, true);
3517 BodyPart
->RemoveFromSlot();
3518 BodyPart
->RandomizePosition();
3519 CalculateAttributeBonuses();
3520 CalculateBattleInfo();
3522 SignalPossibleTransparencyChange();
3523 RemoveTraps(BodyPartIndex
);
3528 /* The second int is actually TargetFlags, which is not used here, but seems to be used in humanoid::ReceiveDamage.
3529 * Returns true if the character really receives damage */
3530 truth
character::ReceiveDamage (character
*Damager
, int Damage
, int Type
, int, int Direction
,
3531 truth
, truth PenetrateArmor
, truth Critical
, truth ShowMsg
)
3533 truth Affected
= ReceiveBodyPartDamage(Damager
, Damage
, Type
, 0, Direction
, PenetrateArmor
, Critical
, ShowMsg
);
3534 if (DamageTypeAffectsInventory(Type
)) {
3535 for (int c
= 0; c
< GetEquipments(); ++c
) {
3536 item
*Equipment
= GetEquipment(c
);
3537 if (Equipment
) Equipment
->ReceiveDamage(Damager
, Damage
, Type
);
3539 GetStack()->ReceiveDamage(Damager
, Damage
, Type
);
3545 festring
character::GetDescription (int Case
) const {
3546 if (IsPlayer()) return CONST_S("you");
3547 if (CanBeSeenByPlayer()) return GetName(Case
);
3548 return CONST_S("something");
3552 festring
character::GetPersonalPronoun (truth PlayersView
) const {
3553 if (IsPlayer() && PlayersView
) return CONST_S("you");
3554 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3555 if (GetSex() == MALE
) return CONST_S("he");
3556 return CONST_S("she");
3560 festring
character::GetPossessivePronoun (truth PlayersView
) const {
3561 if (IsPlayer() && PlayersView
) return CONST_S("your");
3562 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("its");
3563 if (GetSex() == MALE
) return CONST_S("his");
3564 return CONST_S("her");
3568 festring
character::GetObjectPronoun (truth PlayersView
) const {
3569 if (IsPlayer() && PlayersView
) return CONST_S("you");
3570 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3571 if (GetSex() == MALE
) return CONST_S("him");
3572 return CONST_S("her");
3576 void character::AddName (festring
&String
, int Case
) const {
3577 if (AssignedName
.IsEmpty()) {
3578 id::AddName(String
, Case
);
3579 } else if (!(Case
& PLURAL
)) {
3580 if (!ShowClassDescription()) {
3581 String
<< AssignedName
;
3583 String
<< AssignedName
<< ' ';
3584 id::AddName(String
, (Case
|ARTICLE_BIT
)&~INDEFINE_BIT
);
3587 id::AddName(String
, Case
);
3588 String
<< " named " << AssignedName
;
3593 int character::GetHungerState () const {
3594 if (!UsesNutrition()) return NOT_HUNGRY
;
3595 if (GetNP() > OVER_FED_LEVEL
) return OVER_FED
;
3596 if (GetNP() > BLOATED_LEVEL
) return BLOATED
;
3597 if (GetNP() > SATIATED_LEVEL
) return SATIATED
;
3598 if (GetNP() > NOT_HUNGER_LEVEL
) return NOT_HUNGRY
;
3599 if (GetNP() > HUNGER_LEVEL
) return HUNGRY
;
3600 if (GetNP() > VERY_HUNGER_LEVEL
) return VERY_HUNGRY
;
3605 truth
character::CanConsume (material
*Material
) const {
3606 return GetConsumeFlags() & Material
->GetConsumeType();
3610 void character::SetTemporaryStateCounter (sLong State
, int What
) {
3611 for (int c
= 0; c
< STATES
; ++c
) {
3612 if ((1 << c
) & State
) TemporaryStateCounter
[c
] = What
;
3617 void character::EditTemporaryStateCounter (sLong State
, int What
) {
3618 for (int c
= 0; c
< STATES
; ++c
) {
3619 if ((1 << c
) & State
) TemporaryStateCounter
[c
] += What
;
3624 int character::GetTemporaryStateCounter (sLong State
) const {
3625 for (int c
= 0; c
< STATES
; ++c
) {
3626 if ((1 << c
) & State
) return TemporaryStateCounter
[c
];
3628 ABORT("Illegal GetTemporaryStateCounter request!");
3633 truth
character::CheckKick () const {
3635 if (IsPlayer()) ADD_MESSAGE("This race can't kick.");
3642 int character::GetResistance (int Type
) const {
3643 if ((Type
&0xFFF) == PHYSICAL_DAMAGE
) return 0;
3644 if ((Type
&0xFFF) == DRAIN
) return 0;
3645 if ((Type
&0xFFF) == MUSTARD_GAS_DAMAGE
) return 0;
3646 if ((Type
&0xFFF) == PSI
) return 0;
3648 if ((Type
&0xFFF) == ENERGY
) return GetEnergyResistance();
3649 if ((Type
&0xFFF) == FIRE
) return GetFireResistance();
3650 if ((Type
&0xFFF) == POISON
) return GetPoisonResistance();
3651 if ((Type
&0xFFF) == ELECTRICITY
) return GetElectricityResistance();
3652 if ((Type
&0xFFF) == ACID
) return GetAcidResistance();
3653 if ((Type
&0xFFF) == SOUND
) return GetSoundResistance();
3655 ABORT("Resistance lack detected!");
3660 void character::Regenerate () {
3662 if (StateIsActivated(REGENERATION
) && !(RAND()%3000)) {
3663 bodypart
*NewBodyPart
= GenerateRandomBodyPart();
3664 if (!NewBodyPart
) return;
3665 NewBodyPart
->SetHP(1);
3666 if (IsPlayer()) ADD_MESSAGE("You grow a new %s.", NewBodyPart
->GetBodyPartName().CStr());
3667 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), NewBodyPart
->GetBodyPartName().CStr());
3671 sLong RegenerationBonus
= 0;
3672 truth NoHealableBodyParts
= true;
3673 for (int c
= 0; c
< BodyParts
; ++c
) {
3674 bodypart
*BodyPart
= GetBodyPart(c
);
3675 if (BodyPart
&& BodyPart
->CanRegenerate()) {
3676 RegenerationBonus
+= BodyPart
->GetMaxHP();
3677 if (NoHealableBodyParts
&& BodyPart
->GetHP() < BodyPart
->GetMaxHP()) NoHealableBodyParts
= false;
3680 if (!RegenerationBonus
|| NoHealableBodyParts
) return;
3681 RegenerationBonus
*= (50+GetAttribute(ENDURANCE
));
3683 if (Action
&& Action
->IsRest()) {
3684 if (SquaresUnder
== 1) RegenerationBonus
*= GetSquareUnder()->GetRestModifier() << 1;
3686 int Lowest
= GetSquareUnder(0)->GetRestModifier();
3687 for (int c
= 1; c
< GetSquaresUnder(); ++c
) {
3688 int Mod
= GetSquareUnder(c
)->GetRestModifier();
3689 if (Mod
< Lowest
) Lowest
= Mod
;
3691 RegenerationBonus
*= Lowest
<< 1;
3695 RegenerationCounter
+= RegenerationBonus
;
3697 while (RegenerationCounter
> 1250000) {
3698 bodypart
*BodyPart
= HealHitPoint();
3699 if (!BodyPart
) break;
3700 EditNP(-Max(7500/MaxHP
, 1));
3701 RegenerationCounter
-= 1250000;
3702 int HP
= BodyPart
->GetHP();
3703 EditExperience(ENDURANCE
, Min(1000*BodyPart
->GetMaxHP()/(HP
*HP
), 300), 1000);
3708 void character::PrintInfo () const {
3709 felist
Info(CONST_S("Information about ")+GetName(DEFINITE
));
3710 for (int c
= 0; c
< GetEquipments(); ++c
) {
3711 item
*Equipment
= GetEquipment(c
);
3712 if ((EquipmentEasilyRecognized(c
) || game::WizardModeIsActive()) && Equipment
) {
3713 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
3714 Info
.AddEntry(festring(GetEquipmentName(c
))+": "+Equipment
->GetName(INDEFINITE
), LIGHT_GRAY
, 0, ImageKey
, true);
3717 if (Info
.IsEmpty()) {
3718 ADD_MESSAGE("There's nothing special to tell about %s.", CHAR_NAME(DEFINITE
));
3720 game::SetStandardListAttributes(Info
);
3721 Info
.SetEntryDrawer(game::ItemEntryDrawer
);
3724 game::ClearItemDrawVector();
3728 truth
character::TryToRiseFromTheDead () {
3729 for (int c
= 0; c
< BodyParts
; ++c
) {
3730 bodypart
*BodyPart
= GetBodyPart(c
);
3732 BodyPart
->ResetSpoiling();
3733 if (BodyPart
->CanRegenerate() || BodyPart
->GetHP() < 1) BodyPart
->SetHP(1);
3741 truth
character::RaiseTheDead (character
*) {
3742 truth Useful
= false;
3743 for (int c
= 0; c
< BodyParts
; ++c
) {
3744 bodypart
*BodyPart
= GetBodyPart(c
);
3745 if (!BodyPart
&& CanCreateBodyPart(c
)) {
3746 CreateBodyPart(c
)->SetHP(1);
3747 if (IsPlayer()) ADD_MESSAGE("Suddenly you grow a new %s.", GetBodyPartName(c
).CStr());
3748 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), GetBodyPartName(c
).CStr());
3750 } else if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < 1) {
3755 if (IsPlayer()) ADD_MESSAGE("You shudder.");
3756 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s shudders.", CHAR_NAME(DEFINITE
));
3762 void character::SetSize (int Size
) {
3763 for (int c
= 0; c
< BodyParts
; ++c
) {
3764 bodypart
*BodyPart
= GetBodyPart(c
);
3765 if (BodyPart
) BodyPart
->SetSize(GetBodyPartSize(c
, Size
));
3770 sLong
character::GetBodyPartSize (int I
, int TotalSize
) const {
3771 if (I
== TORSO_INDEX
) return TotalSize
;
3772 ABORT("Weird bodypart size request for a character!");
3777 sLong
character::GetBodyPartVolume (int I
) const {
3778 if (I
== TORSO_INDEX
) return GetTotalVolume();
3779 ABORT("Weird bodypart volume request for a character!");
3784 void character::CreateBodyParts (int SpecialFlags
) {
3785 for (int c
= 0; c
< BodyParts
; ++c
) if (CanCreateBodyPart(c
)) CreateBodyPart(c
, SpecialFlags
);
3789 void character::RestoreBodyParts () {
3790 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) CreateBodyPart(c
);
3794 void character::UpdatePictures () {
3795 if (!PictureUpdatesAreForbidden()) for (int c
= 0; c
< BodyParts
; ++c
) UpdateBodyPartPicture(c
, false);
3799 bodypart
*character::MakeBodyPart (int I
) const {
3800 if (I
== TORSO_INDEX
) return normaltorso::Spawn(0, NO_MATERIALS
);
3801 ABORT("Weird bodypart to make for a character!");
3806 bodypart
*character::CreateBodyPart (int I
, int SpecialFlags
) {
3807 bodypart
*BodyPart
= MakeBodyPart(I
);
3808 material
*Material
= CreateBodyPartMaterial(I
, GetBodyPartVolume(I
));
3809 BodyPart
->InitMaterials(Material
, false);
3810 BodyPart
->SetSize(GetBodyPartSize(I
, GetTotalSize()));
3811 BodyPart
->SetBloodMaterial(GetBloodMaterial());
3812 BodyPart
->SetNormalMaterial(Material
->GetConfig());
3814 SetBodyPart(I
, BodyPart
);
3815 BodyPart
->InitSpecialAttributes();
3816 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdateBodyPartPicture(I
, false);
3817 if (!IsInitializing()) {
3818 CalculateBattleInfo();
3819 SendNewDrawRequest();
3820 SignalPossibleTransparencyChange();
3826 v2
character::GetBodyPartBitmapPos (int I
, truth
) const {
3827 if (I
== TORSO_INDEX
) return GetTorsoBitmapPos();
3828 ABORT("Weird bodypart BitmapPos request for a character!");
3833 void character::UpdateBodyPartPicture (int I
, truth Severed
) {
3834 bodypart
*BP
= GetBodyPart(I
);
3836 BP
->SetBitmapPos(GetBodyPartBitmapPos(I
, Severed
));
3837 BP
->GetMainMaterial()->SetSkinColor(GetBodyPartColorA(I
, Severed
));
3838 BP
->GetMainMaterial()->SetSkinColorIsSparkling(GetBodyPartSparkleFlags(I
) & SPARKLING_A
);
3839 BP
->SetMaterialColorB(GetBodyPartColorB(I
, Severed
));
3840 BP
->SetMaterialColorC(GetBodyPartColorC(I
, Severed
));
3841 BP
->SetMaterialColorD(GetBodyPartColorD(I
, Severed
));
3842 BP
->SetSparkleFlags(GetBodyPartSparkleFlags(I
));
3843 BP
->SetSpecialFlags(GetSpecialBodyPartFlags(I
));
3844 BP
->SetWobbleData(GetBodyPartWobbleData(I
));
3845 BP
->UpdatePictures();
3850 void character::LoadDataBaseStats () {
3851 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) {
3852 BaseExperience
[c
] = DataBase
->NaturalExperience
[c
];
3853 if (BaseExperience
[c
]) LimitRef(BaseExperience
[c
], MIN_EXP
, MAX_EXP
);
3855 SetMoney(GetDefaultMoney());
3856 SetInitialSweatMaterial(GetSweatMaterial());
3857 const fearray
<sLong
> &Skills
= GetKnownCWeaponSkills();
3859 const fearray
<sLong
> &Hits
= GetCWeaponSkillHits();
3860 if (Hits
.Size
== 1) {
3861 for (uInt c
= 0; c
< Skills
.Size
; ++c
) {
3862 if (Skills
[c
] < AllowedWeaponSkillCategories
) CWeaponSkill
[Skills
[c
]].AddHit(Hits
[0]*100);
3864 } else if (Hits
.Size
== Skills
.Size
) {
3865 for (uInt c
= 0; c
< Skills
.Size
; ++c
) {
3866 if (Skills
[c
] < AllowedWeaponSkillCategories
) CWeaponSkill
[Skills
[c
]].AddHit(Hits
[c
]*100);
3869 ABORT("Illegal weapon skill hit array size detected!");
3875 character
*characterprototype::SpawnAndLoad (inputfile
&SaveFile
) const {
3876 character
*Char
= Spawner(0, LOAD
);
3877 Char
->Load(SaveFile
);
3878 Char
->CalculateAll();
3883 void character::Initialize (int NewConfig
, int SpecialFlags
) {
3884 Flags
|= C_INITIALIZING
|C_IN_NO_MSG_MODE
;
3885 CalculateBodyParts();
3886 CalculateAllowedWeaponSkillCategories();
3887 CalculateSquaresUnder();
3888 BodyPartSlot
= new bodypartslot
[BodyParts
];
3889 OriginalBodyPartID
= new std::list
<feuLong
>[BodyParts
];
3890 CWeaponSkill
= new cweaponskill
[AllowedWeaponSkillCategories
];
3891 SquareUnder
= new square
*[SquaresUnder
];
3893 if (SquaresUnder
== 1) *SquareUnder
= 0; else memset(SquareUnder
, 0, SquaresUnder
*sizeof(square
*));
3895 for (int c
= 0; c
< BodyParts
; ++c
) BodyPartSlot
[c
].SetMaster(this);
3897 if (!(SpecialFlags
& LOAD
)) {
3898 ID
= game::CreateNewCharacterID(this);
3899 databasecreator
<character
>::InstallDataBase(this, NewConfig
);
3900 LoadDataBaseStats();
3901 TemporaryState
|= GetClassStates();
3902 if (TemporaryState
) {
3903 for (int c
= 0; c
< STATES
; ++c
) if (TemporaryState
& (1 << c
)) TemporaryStateCounter
[c
] = PERMANENT
;
3906 CreateBodyParts(SpecialFlags
| NO_PIC_UPDATE
);
3907 InitSpecialAttributes();
3908 CommandFlags
= GetDefaultCommandFlags();
3910 if (GetAttribute(INTELLIGENCE
, false) < 8) CommandFlags
&= ~DONT_CONSUME_ANYTHING_VALUABLE
; // gum
3911 if (!GetDefaultName().IsEmpty()) SetAssignedName(GetDefaultName());
3914 if (!(SpecialFlags
& LOAD
)) PostConstruct();
3916 if (!(SpecialFlags
& LOAD
)) {
3917 if (!(SpecialFlags
& NO_EQUIPMENT
)) CreateInitialEquipment((SpecialFlags
& NO_EQUIPMENT_PIC_UPDATE
) >> 1);
3918 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdatePictures();
3924 Flags
&= ~(C_INITIALIZING
|C_IN_NO_MSG_MODE
);
3928 truth
character::TeleportNear (character
*Caller
) {
3929 v2 Where
= GetLevel()->GetNearestFreeSquare(this, Caller
->GetPos());
3930 if (Where
== ERROR_V2
) return false;
3936 void character::ReceiveHeal (sLong Amount
) {
3938 for (c
= 0; c
< Amount
/ 10; ++c
) if (!HealHitPoint()) break;
3940 if (RAND()%10 < Amount
) HealHitPoint();
3941 if (Amount
>= 250 || RAND()%250 < Amount
) {
3942 bodypart
*NewBodyPart
= GenerateRandomBodyPart();
3943 if (!NewBodyPart
) return;
3944 NewBodyPart
->SetHP(1);
3945 if (IsPlayer()) ADD_MESSAGE("You grow a new %s.", NewBodyPart
->GetBodyPartName().CStr());
3946 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), NewBodyPart
->GetBodyPartName().CStr());
3951 void character::AddHealingLiquidConsumeEndMessage () const {
3952 if (IsPlayer()) ADD_MESSAGE("You feel better.");
3953 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks healthier.", CHAR_NAME(DEFINITE
));
3957 void character::ReceiveSchoolFood (sLong SizeOfEffect
) {
3958 SizeOfEffect
+= RAND()%SizeOfEffect
;
3959 if (SizeOfEffect
>= 250) VomitAtRandomDirection(SizeOfEffect
);
3960 if (!(RAND() % 3) && SizeOfEffect
>= 500 && EditAttribute(ENDURANCE
, SizeOfEffect
/500)) {
3961 if (IsPlayer()) ADD_MESSAGE("You gain a little bit of toughness for surviving this stuff.");
3962 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks tougher.", CHAR_NAME(DEFINITE
));
3964 BeginTemporaryState(POISONED
, (SizeOfEffect
>>1));
3968 void character::AddSchoolFoodConsumeEndMessage () const {
3969 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff tasted like vomit and old mousepads.");
3973 void character::AddSchoolFoodHitMessage () const {
3974 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff feels like vomit and old mousepads.");
3978 void character::ReceiveNutrition (sLong SizeOfEffect
) {
3979 EditNP(SizeOfEffect
);
3983 void character::ReceiveOmmelUrine (sLong Amount
) {
3984 EditExperience(ARM_STRENGTH
, 500, Amount
<<4);
3985 EditExperience(LEG_STRENGTH
, 500, Amount
<<4);
3986 if (IsPlayer()) game::DoEvilDeed(Amount
/25);
3990 void character::ReceiveOmmelCerumen (sLong Amount
) {
3991 EditExperience(INTELLIGENCE
, 500, Amount
<< 5);
3992 EditExperience(WISDOM
, 500, Amount
<< 5);
3993 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3997 void character::ReceiveOmmelSweat (sLong Amount
) {
3998 EditExperience(AGILITY
, 500, Amount
<< 4);
3999 EditExperience(DEXTERITY
, 500, Amount
<< 4);
4001 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
4005 void character::ReceiveOmmelTears (sLong Amount
) {
4006 EditExperience(PERCEPTION
, 500, Amount
<< 4);
4007 EditExperience(CHARISMA
, 500, Amount
<< 4);
4008 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
4012 void character::ReceiveOmmelSnot (sLong Amount
) {
4013 EditExperience(ENDURANCE
, 500, Amount
<< 5);
4015 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
4019 void character::ReceiveOmmelBone (sLong Amount
) {
4020 EditExperience(ARM_STRENGTH
, 500, Amount
<< 6);
4021 EditExperience(LEG_STRENGTH
, 500, Amount
<< 6);
4022 EditExperience(DEXTERITY
, 500, Amount
<< 6);
4023 EditExperience(AGILITY
, 500, Amount
<< 6);
4024 EditExperience(ENDURANCE
, 500, Amount
<< 6);
4025 EditExperience(PERCEPTION
, 500, Amount
<< 6);
4026 EditExperience(INTELLIGENCE
, 500, Amount
<< 6);
4027 EditExperience(WISDOM
, 500, Amount
<< 6);
4028 EditExperience(CHARISMA
, 500, Amount
<< 6);
4031 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
4035 void character::AddOmmelConsumeEndMessage () const {
4036 if (IsPlayer()) ADD_MESSAGE("You feel a primitive force coursing through your veins.");
4037 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks more powerful.", CHAR_NAME(DEFINITE
));
4041 void character::ReceivePepsi (sLong Amount
) {
4042 ReceiveDamage(0, Amount
/ 100, POISON
, TORSO
);
4043 EditExperience(PERCEPTION
, Amount
, 1 << 14);
4044 if (CheckDeath(CONST_S("was poisoned by pepsi"), 0)) return;
4045 if (IsPlayer()) game::DoEvilDeed(Amount
/ 10);
4049 void character::AddPepsiConsumeEndMessage () const {
4050 if (IsPlayer()) ADD_MESSAGE("Urgh. You feel your guruism fading away.");
4051 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very lame.", CHAR_NAME(DEFINITE
));
4055 void character::ReceiveDarkness (sLong Amount
) {
4056 EditExperience(INTELLIGENCE
, -Amount
/ 5, 1 << 13);
4057 EditExperience(WISDOM
, -Amount
/ 5, 1 << 13);
4058 EditExperience(CHARISMA
, -Amount
/ 5, 1 << 13);
4059 if (IsPlayer()) game::DoEvilDeed(int(Amount
/ 50));
4063 void character::AddFrogFleshConsumeEndMessage () const {
4064 if (IsPlayer()) ADD_MESSAGE("Arg. You feel the fate of a navastater placed upon you...");
4065 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks like a navastater.", CHAR_NAME(DEFINITE
));
4069 void character::ReceiveKoboldFlesh (sLong
) {
4070 /* As it is commonly known, the possibility of fainting per 500 cubic
4071 centimeters of kobold flesh is exactly 5%. */
4072 if (!(RAND() % 20)) {
4073 if (IsPlayer()) ADD_MESSAGE("You lose control of your legs and fall down.");
4074 LoseConsciousness(250 + RAND_N(250));
4079 void character::AddKoboldFleshConsumeEndMessage () const {
4080 if (IsPlayer()) ADD_MESSAGE("This stuff tasted really funny.");
4084 void character::AddKoboldFleshHitMessage () const {
4085 if (IsPlayer()) ADD_MESSAGE("You feel very funny.");
4089 void character::AddBoneConsumeEndMessage () const {
4090 if (IsPlayer()) ADD_MESSAGE("You feel like a hippie.");
4091 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s barks happily.", CHAR_NAME(DEFINITE
)); // this suspects that nobody except dogs can eat bones
4094 truth
character::RawEditAttribute (double &Experience
, int Amount
) const {
4095 /* Check if the attribute is disabled for creature */
4096 if (!Experience
) return false;
4097 if ((Amount
< 0 && Experience
< 2 * EXP_MULTIPLIER
) || (Amount
> 0 && Experience
> 999 * EXP_MULTIPLIER
)) return false;
4098 Experience
+= Amount
* EXP_MULTIPLIER
;
4099 LimitRef
<double>(Experience
, MIN_EXP
, MAX_EXP
);
4104 void character::DrawPanel (truth AnimationDraw
) const {
4105 if (AnimationDraw
) { DrawStats(true); return; }
4106 igraph::BlitBackGround(v2(19 + (game::GetScreenXSize() << 4), 0), v2(RES
.X
- 19 - (game::GetScreenXSize() << 4), RES
.Y
));
4107 igraph::BlitBackGround(v2(16, 45 + (game::GetScreenYSize() << 4)), v2(game::GetScreenXSize() << 4, 9));
4108 FONT
->Printf(DOUBLE_BUFFER
, v2(16, 45 + (game::GetScreenYSize() << 4)), WHITE
, "%s", GetPanelName().CStr());
4109 game::UpdateAttributeMemory();
4110 int PanelPosX
= RES
.X
- 96;
4111 int PanelPosY
= DrawStats(false);
4112 PrintAttribute("End", ENDURANCE
, PanelPosX
, PanelPosY
++);
4113 PrintAttribute("Per", PERCEPTION
, PanelPosX
, PanelPosY
++);
4114 PrintAttribute("Int", INTELLIGENCE
, PanelPosX
, PanelPosY
++);
4115 PrintAttribute("Wis", WISDOM
, PanelPosX
, PanelPosY
++);
4116 PrintAttribute("Wil", WILL_POWER
, PanelPosX
, PanelPosY
++);
4117 PrintAttribute("Cha", CHARISMA
, PanelPosX
, PanelPosY
++);
4118 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Siz %d", GetSize());
4119 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), IsInBadCondition() ? RED
: WHITE
, "HP %d/%d", GetHP(), GetMaxHP());
4121 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Gold: %d", GetMoney());
4124 if (game::IsInWilderness())
4125 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Worldmap");
4127 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", game::GetCurrentDungeon()->GetShortLevelDescription(game::GetCurrentLevelIndex()).CapitalizeCopy().CStr());
4130 game::GetTime(Time
);
4131 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Day %d", Time
.Day
);
4132 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Time %d:%s%d", Time
.Hour
, Time
.Min
< 10 ? "0" : "", Time
.Min
);
4133 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Turn %d", game::GetTurn());
4138 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", festring(GetAction()->GetDescription()).CapitalizeCopy().CStr());
4141 //printf("========= STATES =========\n");
4142 for (int c
= 0; c
< STATES
; ++c
) {
4143 //printf(" %d: %s (%s)\n", c, StateData[c].Description, (StateIsActivated(1<<c) ? "TAN" : "ona"));
4144 if (!(StateData
[c
].Flags
& SECRET
) && StateIsActivated(1 << c
) && (1 << c
!= HASTE
|| !StateIsActivated(SLOW
)) && (1 << c
!= SLOW
|| !StateIsActivated(HASTE
))) {
4145 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), (1 << c
) & EquipmentState
|| TemporaryStateCounter
[c
] >= PERMANENT
? BLUE
: WHITE
, "%s", StateData
[c
].Description
);
4149 auto hst
= GetHungerState();
4150 if (hst
== STARVING
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Starving");
4151 else if (hst
== VERY_HUNGRY
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Very hungry");
4152 else if (hst
== HUNGRY
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), ORANGE
, "Hungry");
4153 else if (hst
== SATIATED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Satiated");
4154 else if (hst
== BLOATED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Bloated");
4155 else if (hst
== OVER_FED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Overfed!");
4157 auto bst
= GetBurdenState();
4158 if (bst
== OVER_LOADED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Overload!");
4159 else if (bst
== STRESSED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), ORANGE
, "Stressed");
4160 else if (bst
== BURDENED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Burdened");
4162 auto trst
= GetTirednessState();
4163 if (trst
== FAINTING
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Fainting");
4164 else if (trst
== EXHAUSTED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), ORANGE
, "Exhausted");
4166 if (game::PlayerIsRunning()) {
4167 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", GetRunDescriptionLine(0));
4168 cchar
*SecondLine
= GetRunDescriptionLine(1);
4169 if (strlen(SecondLine
)) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", SecondLine
);
4174 void character::CalculateDodgeValue () {
4175 DodgeValue
= 0.05 * GetMoveEase() * GetAttribute(AGILITY
) / sqrt(GetSize());
4176 if (IsFlying()) DodgeValue
*= 2;
4177 if (DodgeValue
< 1) DodgeValue
= 1;
4181 truth
character::DamageTypeAffectsInventory (int Type
) {
4182 if ((Type
&0xFFF) == SOUND
) return true;
4183 if ((Type
&0xFFF) == ENERGY
) return true;
4184 if ((Type
&0xFFF) == ACID
) return true;
4185 if ((Type
&0xFFF) == FIRE
) return true;
4186 if ((Type
&0xFFF) == ELECTRICITY
) return true;
4188 if ((Type
&0xFFF) == PHYSICAL_DAMAGE
) return false;
4189 if ((Type
&0xFFF) == POISON
) return false;
4190 if ((Type
&0xFFF) == DRAIN
) return false;
4191 if ((Type
&0xFFF) == MUSTARD_GAS_DAMAGE
) return false;
4192 if ((Type
&0xFFF) == PSI
) return false;
4194 ABORT("Unknown reaping effect destroyed dungeon!");
4199 int character::CheckForBlockWithArm (character
*Enemy
, item
*Weapon
, arm
*Arm
,
4200 double WeaponToHitValue
, int Damage
, int Success
, int Type
)
4202 int BlockStrength
= Arm
->GetBlockCapability();
4203 double BlockValue
= Arm
->GetBlockValue();
4204 if (BlockStrength
&& BlockValue
) {
4205 item
*Blocker
= Arm
->GetWielded();
4206 if (RAND() % int(100+WeaponToHitValue
/BlockValue
/(1<<BlocksSinceLastTurn
)*(100+Success
)) < 100) {
4207 int NewDamage
= BlockStrength
< Damage
? Damage
-BlockStrength
: 0;
4208 if (Type
== UNARMED_ATTACK
) AddBlockMessage(Enemy
, Blocker
, Enemy
->UnarmedHitNoun(), NewDamage
);
4209 else if (Type
== WEAPON_ATTACK
) AddBlockMessage(Enemy
, Blocker
, "attack", NewDamage
);
4210 else if (Type
== KICK_ATTACK
) AddBlockMessage(Enemy
, Blocker
, Enemy
->KickNoun(), NewDamage
);
4211 else if (Type
== BITE_ATTACK
) AddBlockMessage(Enemy
, Blocker
, Enemy
->BiteNoun(), NewDamage
);
4212 sLong Weight
= Blocker
->GetWeight();
4213 sLong StrExp
= Limit(15 * Weight
/ 200, 75, 300);
4214 sLong DexExp
= Weight
? Limit(75000 / Weight
, 75, 300) : 300;
4215 Arm
->EditExperience(ARM_STRENGTH
, StrExp
, 1 << 8);
4216 Arm
->EditExperience(DEXTERITY
, DexExp
, 1 << 8);
4217 EditStamina(-10000 / GetAttribute(ARM_STRENGTH
), false);
4218 if (Arm
->TwoHandWieldIsActive()) {
4219 arm
*PairArm
= Arm
->GetPairArm();
4220 PairArm
->EditExperience(ARM_STRENGTH
, StrExp
, 1 << 8);
4221 PairArm
->EditExperience(DEXTERITY
, DexExp
, 1 << 8);
4223 Blocker
->WeaponSkillHit(Enemy
->CalculateWeaponSkillHits(this));
4224 Blocker
->ReceiveDamage(this, Damage
, PHYSICAL_DAMAGE
);
4225 Blocker
->BlockEffect(this, Enemy
, Weapon
, Type
);
4226 if (Weapon
) Weapon
->ReceiveDamage(Enemy
, Damage
- NewDamage
, PHYSICAL_DAMAGE
);
4227 if (BlocksSinceLastTurn
< 16) ++BlocksSinceLastTurn
;
4235 sLong
character::GetStateAPGain (sLong BaseAPGain
) const {
4236 if (!StateIsActivated(HASTE
) == !StateIsActivated(SLOW
)) return BaseAPGain
;
4237 if (StateIsActivated(HASTE
)) return (BaseAPGain
* 5) >> 2;
4238 return (BaseAPGain
<< 2) / 5;
4242 void character::SignalEquipmentAdd (int EquipmentIndex
) {
4243 item
*Equipment
= GetEquipment(EquipmentIndex
);
4244 if (Equipment
->IsInCorrectSlot(EquipmentIndex
)) {
4245 sLong AddedStates
= Equipment
->GetGearStates();
4247 for (int c
= 0; c
< STATES
; ++c
) {
4248 if (AddedStates
& (1 << c
)) {
4249 if (!StateIsActivated(1 << c
)) {
4250 if (!IsInNoMsgMode()) (this->*StateData
[c
].PrintBeginMessage
)();
4251 EquipmentState
|= 1 << c
;
4252 if (StateData
[c
].BeginHandler
) (this->*StateData
[c
].BeginHandler
)();
4254 EquipmentState
|= 1 << c
;
4260 if (!IsInitializing() && Equipment
->IsInCorrectSlot(EquipmentIndex
)) ApplyEquipmentAttributeBonuses(Equipment
);
4264 void character::SignalEquipmentRemoval (int, citem
*Item
) {
4265 CalculateEquipmentState();
4266 if (CalculateAttributeBonuses()) CheckDeath(festring("lost ")+GetPossessivePronoun(false)+" vital "+Item
->GetName(INDEFINITE
));
4270 void character::CalculateEquipmentState () {
4271 sLong Back
= EquipmentState
;
4273 for (int c
= 0; c
< GetEquipments(); ++c
) {
4274 item
*Equipment
= GetEquipment(c
);
4275 if (Equipment
&& Equipment
->IsInCorrectSlot(c
)) EquipmentState
|= Equipment
->GetGearStates();
4277 for (int c
= 0; c
< STATES
; ++c
) {
4278 if (Back
& (1 << c
) && !StateIsActivated(1 << c
)) {
4279 if (StateData
[c
].EndHandler
) {
4280 (this->*StateData
[c
].EndHandler
)();
4281 if (!IsEnabled()) return;
4283 if (!IsInNoMsgMode()) (this->*StateData
[c
].PrintEndMessage
)();
4289 /* Counter = duration in ticks */
4290 void character::BeginTemporaryState (sLong State
, int Counter
) {
4291 if (!Counter
) return;
4292 if (State
&POLYMORPHED
) ABORT("No Polymorphing with BeginTemporaryState!");
4293 while (State
!= 0) {
4295 for (sidx
= 0; sidx
< STATES
; ++sidx
) if (State
&(1<<sidx
)) { st
= (1<<sidx
); break; }
4296 if (!st
) { break; /*ABORT("BeginTemporaryState works only when State == 2^n!");*/ }
4298 if (TemporaryStateIsActivated(st
)) {
4299 int OldCounter
= GetTemporaryStateCounter(st
);
4300 if (OldCounter
!= PERMANENT
) EditTemporaryStateCounter(st
, Max(Counter
, 50-OldCounter
));
4301 } else if (StateData
[sidx
].IsAllowed
== 0 || (this->*StateData
[sidx
].IsAllowed
)()) {
4302 SetTemporaryStateCounter(st
, Max(Counter
, 50));
4303 if (!EquipmentStateIsActivated(st
)) {
4304 if (!IsInNoMsgMode()) (this->*StateData
[sidx
].PrintBeginMessage
)();
4305 ActivateTemporaryState(st
);
4306 if (StateData
[sidx
].BeginHandler
) (this->*StateData
[sidx
].BeginHandler
)();
4308 ActivateTemporaryState(st
);
4315 void character::HandleStates () {
4316 if (!TemporaryState
&& !EquipmentState
) return;
4317 for (int c
= 0; c
< STATES
; ++c
) {
4318 if (TemporaryState
& (1 << c
) && TemporaryStateCounter
[c
] != PERMANENT
) {
4319 if (!--TemporaryStateCounter
[c
]) {
4320 TemporaryState
&= ~(1 << c
);
4321 if (!(EquipmentState
& (1 << c
))) {
4322 if (StateData
[c
].EndHandler
) {
4323 (this->*StateData
[c
].EndHandler
)();
4324 if (!IsEnabled()) return;
4326 if (!TemporaryStateCounter
[c
]) (this->*StateData
[c
].PrintEndMessage
)();
4330 if (StateIsActivated(1 << c
)) {
4331 if (StateData
[c
].Handler
) (this->*StateData
[c
].Handler
)();
4333 if (!IsEnabled()) return;
4338 void character::PrintBeginPolymorphControlMessage () const {
4339 if (IsPlayer()) ADD_MESSAGE("You feel your mind has total control over your body.");
4343 void character::PrintEndPolymorphControlMessage () const {
4344 if (IsPlayer()) ADD_MESSAGE("You are somehow uncertain of your willpower.");
4348 void character::PrintBeginLifeSaveMessage () const {
4349 if (IsPlayer()) ADD_MESSAGE("You hear Hell's gates being locked just now.");
4353 void character::PrintEndLifeSaveMessage () const {
4354 if (IsPlayer()) ADD_MESSAGE("You feel the Afterlife is welcoming you once again.");
4358 void character::PrintBeginLycanthropyMessage () const {
4359 if (IsPlayer()) ADD_MESSAGE("You suddenly notice you've always loved full moons.");
4363 void character::PrintEndLycanthropyMessage () const {
4364 if (IsPlayer()) ADD_MESSAGE("You feel the wolf inside you has had enough of your bad habits.");
4368 void character::PrintBeginVampirismMessage () const {
4369 if (IsPlayer()) ADD_MESSAGE("You suddenly decide you have always hated garlic.");
4373 void character::PrintEndVampirismMessage () const {
4374 if (IsPlayer()) ADD_MESSAGE("You recall your delight of the morning sunshine back in New Attnam. You are a vampire no longer.");
4378 void character::PrintBeginInvisibilityMessage () const {
4379 if ((PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm()) || (PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5)) {
4380 if (IsPlayer()) ADD_MESSAGE("You seem somehow transparent.");
4381 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems somehow transparent.", CHAR_NAME(DEFINITE
));
4383 if (IsPlayer()) ADD_MESSAGE("You fade away.");
4384 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears!", CHAR_NAME(DEFINITE
));
4389 void character::PrintEndInvisibilityMessage () const {
4390 if ((PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm()) || (PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5)) {
4391 if (IsPlayer()) ADD_MESSAGE("Your notice your transparency has ended.");
4392 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The appearance of %s seems far more solid now.", CHAR_NAME(INDEFINITE
));
4394 if (IsPlayer()) ADD_MESSAGE("You reappear.");
4395 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s appears from nowhere!", CHAR_NAME(INDEFINITE
));
4400 void character::PrintBeginInfraVisionMessage () const {
4402 if (StateIsActivated(INVISIBLE
) && IsWarm() && !(StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5))
4403 ADD_MESSAGE("You reappear.");
4405 ADD_MESSAGE("You feel your perception being magically altered.");
4410 void character::PrintEndInfraVisionMessage () const {
4412 if (StateIsActivated(INVISIBLE
) && IsWarm() && !(StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5))
4413 ADD_MESSAGE("You disappear.");
4415 ADD_MESSAGE("You feel your perception returning to normal.");
4420 void character::PrintBeginESPMessage () const {
4421 if (IsPlayer()) ADD_MESSAGE("You suddenly feel like being only a tiny part of a great network of intelligent minds.");
4425 void character::PrintEndESPMessage () const {
4426 if (IsPlayer()) ADD_MESSAGE("You are filled with desire to be just yourself from now on.");
4430 void character::PrintBeginHasteMessage () const {
4431 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4432 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE
));
4436 void character::PrintEndHasteMessage () const {
4437 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4438 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE
));
4442 void character::PrintBeginSlowMessage () const {
4443 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4444 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE
));
4448 void character::PrintEndSlowMessage () const {
4449 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4450 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE
));
4454 void character::EndPolymorph () {
4455 ForceEndPolymorph();
4459 character
*character::ForceEndPolymorph () {
4461 ADD_MESSAGE("You return to your true form.");
4462 } else if (game::IsInWilderness()) {
4463 ActivateTemporaryState(POLYMORPHED
);
4464 SetTemporaryStateCounter(POLYMORPHED
, 10);
4465 return this; // fast gum solution, state ends when the player enters a dungeon
4467 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s returns to %s true form.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
4469 if (GetAction()) GetAction()->Terminate(false);
4473 character
*Char
= GetPolymorphBackup();
4474 Flags
|= C_IN_NO_MSG_MODE
;
4475 Char
->Flags
|= C_IN_NO_MSG_MODE
;
4476 Char
->PutToOrNear(Pos
);
4477 Char
->ChangeTeam(GetTeam());
4478 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(Char
);
4479 SetPolymorphBackup(0);
4481 Char
->Flags
&= ~C_POLYMORPHED
;
4482 GetStack()->MoveItemsTo(Char
->GetStack());
4483 DonateEquipmentTo(Char
);
4484 Char
->SetMoney(GetMoney());
4485 Flags
&= ~C_IN_NO_MSG_MODE
;
4486 Char
->Flags
&= ~C_IN_NO_MSG_MODE
;
4487 Char
->CalculateAll();
4488 Char
->SetAssignedName(GetAssignedName());
4491 game::SetPlayer(Char
);
4492 game::SendLOSUpdateRequest();
4495 Char
->TestWalkability();
4500 void character::LycanthropyHandler () {
4501 if (StateIsActivated(POLYMORPH_LOCK
)) return;
4502 if (IsOfType("werewolfwolf")) return;
4503 if (!(RAND() % 2000)) {
4504 if (StateIsActivated(POLYMORPH_CONTROL
) && (IsPlayer() ? !game::TruthQuestion(CONST_S("Do you wish to change into a werewolf?")) : false)) return;
4505 Polymorph(werewolfwolf::Spawn(), 1000 + RAND() % 2000);
4510 void character::SaveLife () {
4511 if (TemporaryStateIsActivated(LIFE_SAVED
)) {
4513 ADD_MESSAGE("But wait! You glow briefly red and seem to be in a better shape!");
4514 else if (CanBeSeenByPlayer())
4515 ADD_MESSAGE("But wait, suddenly %s glows briefly red and seems to be in a better shape!", GetPersonalPronoun().CStr());
4516 DeActivateTemporaryState(LIFE_SAVED
);
4518 item
*LifeSaver
= 0;
4519 for (int c
= 0; c
< GetEquipments(); ++c
) {
4520 item
*Equipment
= GetEquipment(c
);
4521 if (Equipment
&& Equipment
->IsInCorrectSlot(c
) && Equipment
->GetGearStates() & LIFE_SAVED
) LifeSaver
= Equipment
;
4523 if (!LifeSaver
) ABORT("The Universe can only kill you once!");
4525 ADD_MESSAGE("But wait! Your %s glows briefly red and disappears and you seem to be in a better shape!", LifeSaver
->CHAR_NAME(UNARTICLED
));
4526 else if (CanBeSeenByPlayer())
4527 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());
4528 LifeSaver
->RemoveFromSlot();
4529 LifeSaver
->SendToHell();
4532 if (IsPlayer()) game::AskForEscPress(CONST_S("Life saved!"));
4540 if (GetNP() < SATIATED_LEVEL
) SetNP(SATIATED_LEVEL
);
4542 SendNewDrawRequest();
4544 if (GetAction()) GetAction()->Terminate(false);
4548 character
*character::PolymorphRandomly (int MinDanger
, int MaxDanger
, int Time
) {
4549 character
*NewForm
= 0;
4550 if (StateIsActivated(POLYMORPH_LOCK
)) { ADD_MESSAGE("You feel uncertain about your body for a moment."); return NewForm
; }
4551 if (StateIsActivated(POLYMORPH_CONTROL
)) {
4553 if (!GetNewFormForPolymorphWithControl(NewForm
)) return NewForm
;
4555 NewForm
= protosystem::CreateMonster(MinDanger
*10, MaxDanger
*10, NO_EQUIPMENT
);
4558 NewForm
= protosystem::CreateMonster(MinDanger
, MaxDanger
, NO_EQUIPMENT
);
4560 Polymorph(NewForm
, Time
);
4565 /* In reality, the reading takes Time / (Intelligence * 10) turns */
4566 void character::StartReading (item
*Item
, sLong Time
) {
4567 study
*Read
= study::Spawn(this);
4568 Read
->SetLiteratureID(Item
->GetID());
4569 if (game::WizardModeIsActive()) Time
= 1;
4570 Read
->SetCounter(Time
);
4572 if (IsPlayer()) ADD_MESSAGE("You start reading %s.", Item
->CHAR_NAME(DEFINITE
));
4573 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s starts reading %s.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(DEFINITE
));
4577 /* Call when one makes something with his/her/its hands.
4578 * Difficulty of 5 takes about one turn, so it's the most common to use. */
4579 void character::DexterityAction (int Difficulty
) {
4580 EditAP(-20000 * Difficulty
/ APBonus(GetAttribute(DEXTERITY
)));
4581 EditExperience(DEXTERITY
, Difficulty
* 15, 1 << 7);
4585 /* If Theoretically != false, range is not a factor. */
4586 truth
character::CanBeSeenByPlayer (truth Theoretically
, truth IgnoreESP
) const {
4587 if (IsEnabled() && !game::IsGenerating() && (Theoretically
|| GetSquareUnder())) {
4588 truth MayBeESPSeen
= PLAYER
->IsEnabled() && !IgnoreESP
&& PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5;
4589 truth MayBeInfraSeen
= PLAYER
->IsEnabled() && PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm();
4590 truth Visible
= !StateIsActivated(INVISIBLE
) || MayBeESPSeen
|| MayBeInfraSeen
;
4591 if (game::IsInWilderness()) return Visible
;
4592 if (MayBeESPSeen
&& (Theoretically
|| GetDistanceSquareFrom(PLAYER
) <= PLAYER
->GetESPRangeSquare())) return true;
4593 if (!Visible
) return false;
4594 return (Theoretically
|| SquareUnderCanBeSeenByPlayer(MayBeInfraSeen
));
4600 truth
character::CanBeSeenBy (ccharacter
*Who
, truth Theoretically
, truth IgnoreESP
) const {
4601 if (Who
->IsPlayer()) return CanBeSeenByPlayer(Theoretically
, IgnoreESP
);
4602 if (IsEnabled() && !game::IsGenerating() && (Theoretically
|| GetSquareUnder())) {
4603 truth MayBeESPSeen
= Who
->IsEnabled() && !IgnoreESP
&& Who
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5;
4604 truth MayBeInfraSeen
= Who
->IsEnabled() && Who
->StateIsActivated(INFRA_VISION
) && IsWarm();
4605 truth Visible
= !StateIsActivated(INVISIBLE
) || MayBeESPSeen
|| MayBeInfraSeen
;
4606 if (game::IsInWilderness()) return Visible
;
4607 if (MayBeESPSeen
&& (Theoretically
|| GetDistanceSquareFrom(Who
) <= Who
->GetESPRangeSquare())) return true;
4608 if (!Visible
) return false;
4609 return (Theoretically
|| SquareUnderCanBeSeenBy(Who
, MayBeInfraSeen
));
4615 truth
character::SquareUnderCanBeSeenByPlayer (truth IgnoreDarkness
) const {
4616 if (!GetSquareUnder()) return false;
4617 int S1
= SquaresUnder
, S2
= PLAYER
->SquaresUnder
;
4618 if (S1
== 1 && S2
== 1) {
4619 if (GetSquareUnder()->CanBeSeenByPlayer(IgnoreDarkness
)) return true;
4620 if (IgnoreDarkness
) {
4621 int LOSRangeSquare
= PLAYER
->GetLOSRangeSquare();
4622 if ((GetPos() - PLAYER
->GetPos()).GetLengthSquare() <= LOSRangeSquare
) {
4623 eyecontroller::Map
= GetLevel()->GetMap();
4624 return mapmath
<eyecontroller
>::DoLine(PLAYER
->GetPos().X
, PLAYER
->GetPos().Y
, GetPos().X
, GetPos().Y
, SKIP_FIRST
|LINE_BOTH_DIRS
);
4629 for (int c1
= 0; c1
< S1
; ++c1
) {
4630 lsquare
*Square
= GetLSquareUnder(c1
);
4631 if (Square
->CanBeSeenByPlayer(IgnoreDarkness
)) return true;
4632 else if (IgnoreDarkness
) {
4633 v2 Pos
= Square
->GetPos();
4634 int LOSRangeSquare
= PLAYER
->GetLOSRangeSquare();
4635 for (int c2
= 0; c2
< S2
; ++c2
) {
4636 v2 PlayerPos
= PLAYER
->GetPos(c2
);
4637 if ((Pos
-PlayerPos
).GetLengthSquare() <= LOSRangeSquare
) {
4638 eyecontroller::Map
= GetLevel()->GetMap();
4639 if (mapmath
<eyecontroller
>::DoLine(PlayerPos
.X
, PlayerPos
.Y
, Pos
.X
, Pos
.Y
, SKIP_FIRST
|LINE_BOTH_DIRS
)) return true;
4649 truth
character::SquareUnderCanBeSeenBy (ccharacter
*Who
, truth IgnoreDarkness
) const {
4650 int S1
= SquaresUnder
, S2
= Who
->SquaresUnder
;
4651 int LOSRangeSquare
= Who
->GetLOSRangeSquare();
4652 if (S1
== 1 && S2
== 1) return GetSquareUnder()->CanBeSeenFrom(Who
->GetPos(), LOSRangeSquare
, IgnoreDarkness
);
4653 for (int c1
= 0; c1
< S1
; ++c1
) {
4654 lsquare
*Square
= GetLSquareUnder(c1
);
4655 for (int c2
= 0; c2
< S2
; ++c2
) if (Square
->CanBeSeenFrom(Who
->GetPos(c2
), LOSRangeSquare
, IgnoreDarkness
)) return true;
4661 int character::GetDistanceSquareFrom (ccharacter
*Who
) const {
4662 int S1
= SquaresUnder
, S2
= Who
->SquaresUnder
;
4663 if (S1
== 1 && S2
== 1) return (GetPos() - Who
->GetPos()).GetLengthSquare();
4664 v2
MinDist(0x7FFF, 0x7FFF);
4665 int MinLength
= 0xFFFF;
4666 for (int c1
= 0; c1
< S1
; ++c1
) {
4667 for (int c2
= 0; c2
< S2
; ++c2
) {
4668 v2 Dist
= GetPos(c1
)-Who
->GetPos(c2
);
4669 if (Dist
.X
< 0) Dist
.X
= -Dist
.X
;
4670 if (Dist
.Y
< 0) Dist
.Y
= -Dist
.Y
;
4671 if (Dist
.X
<= MinDist
.X
&& Dist
.Y
<= MinDist
.Y
) {
4673 MinLength
= Dist
.GetLengthSquare();
4674 } else if (Dist
.X
< MinDist
.X
|| Dist
.Y
< MinDist
.Y
) {
4675 int Length
= Dist
.GetLengthSquare();
4676 if (Length
< MinLength
) {
4687 void character::AttachBodyPart (bodypart
*BodyPart
) {
4688 SetBodyPart(BodyPart
->GetBodyPartIndex(), BodyPart
);
4689 if (!AllowSpoil()) BodyPart
->ResetSpoiling();
4690 BodyPart
->ResetPosition();
4691 BodyPart
->UpdatePictures();
4692 CalculateAttributeBonuses();
4693 CalculateBattleInfo();
4694 SendNewDrawRequest();
4695 SignalPossibleTransparencyChange();
4699 /* Returns true if the character has all bodyparts, false if not. */
4700 truth
character::HasAllBodyParts () const {
4701 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) return false;
4706 bodypart
*character::GenerateRandomBodyPart () {
4707 int NeededBodyPart
[MAX_BODYPARTS
];
4709 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) NeededBodyPart
[Index
++] = c
;
4710 return Index
? CreateBodyPart(NeededBodyPart
[RAND() % Index
]) : 0;
4714 /* Searches the character's Stack and if it find some bodyparts there that are the character's
4715 * old bodyparts returns a stackiterator to one of them (choosen in random).
4716 * If no fitting bodyparts are found the function returns 0 */
4717 bodypart
*character::FindRandomOwnBodyPart (truth AllowNonLiving
) const {
4718 itemvector LostAndFound
;
4719 for (int c
= 0; c
< BodyParts
; ++c
) {
4720 if (!GetBodyPart(c
)) {
4721 for (std::list
<feuLong
>::iterator i
= OriginalBodyPartID
[c
].begin(); i
!= OriginalBodyPartID
[c
].end(); ++i
) {
4722 bodypart
*Found
= static_cast<bodypart
*>(SearchForItem(*i
));
4723 if (Found
&& (AllowNonLiving
|| Found
->CanRegenerate())) LostAndFound
.push_back(Found
);
4727 if (LostAndFound
.empty()) return 0;
4728 return static_cast<bodypart
*>(LostAndFound
[RAND() % LostAndFound
.size()]);
4732 void character::PrintBeginPoisonedMessage () const {
4733 if (IsPlayer()) ADD_MESSAGE("You seem to be very ill.");
4734 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very ill.", CHAR_NAME(DEFINITE
));
4738 void character::PrintEndPoisonedMessage () const {
4739 if (IsPlayer()) ADD_MESSAGE("You feel better again.");
4740 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks better.", CHAR_NAME(DEFINITE
));
4744 void character::PoisonedHandler () {
4745 if (!(RAND() % 100)) VomitAtRandomDirection(500 + RAND_N(250));
4747 for (int Used
= 0; Used
< GetTemporaryStateCounter(POISONED
); Used
+= 100) if (!(RAND() % 100)) ++Damage
;
4749 ReceiveDamage(0, Damage
, POISON
, ALL
, 8, false, false, false, false);
4750 CheckDeath(CONST_S("died of acute poisoning"), 0);
4755 truth
character::IsWarm () const {
4756 return combinebodypartpredicates()(this, &bodypart::IsWarm
, 1);
4760 truth
character::IsWarmBlooded() const
4762 return combinebodypartpredicates()(this, &bodypart::IsWarmBlooded
, 1);
4766 void character::BeginInvisibility () {
4768 SendNewDrawRequest();
4769 SignalPossibleTransparencyChange();
4773 void character::BeginInfraVision () {
4774 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4778 void character::BeginESP () {
4779 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4783 void character::EndInvisibility () {
4785 SendNewDrawRequest();
4786 SignalPossibleTransparencyChange();
4790 void character::EndInfraVision () {
4791 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4795 void character::EndESP () {
4796 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4800 void character::Draw (blitdata
&BlitData
) const {
4801 col24 L
= BlitData
.Luminance
;
4802 if (PLAYER
->IsEnabled() &&
4803 ((PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5 &&
4804 (PLAYER
->GetPos() - GetPos()).GetLengthSquare() <= PLAYER
->GetESPRangeSquare()) ||
4805 (PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm())))
4806 BlitData
.Luminance
= ivanconfig::GetContrastLuminance();
4808 DrawBodyParts(BlitData
);
4809 BlitData
.Luminance
= ivanconfig::GetContrastLuminance();
4810 BlitData
.Src
.Y
= 16;
4811 cint SquareIndex
= BlitData
.CustomData
& SQUARE_INDEX_MASK
;
4813 if (GetTeam() == PLAYER
->GetTeam() && !IsPlayer() && SquareIndex
== GetTameSymbolSquareIndex()) {
4814 BlitData
.Src
.X
= 32;
4815 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4818 if (IsFlying() && SquareIndex
== GetFlySymbolSquareIndex()) {
4819 BlitData
.Src
.X
= 128;
4820 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4823 if (IsSwimming() && SquareIndex
== GetSwimmingSymbolSquareIndex()) {
4824 BlitData
.Src
.X
= 240;
4825 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4828 if (GetAction() && GetAction()->IsUnconsciousness() && SquareIndex
== GetUnconsciousSymbolSquareIndex()) {
4829 BlitData
.Src
.X
= 224;
4830 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4833 BlitData
.Src
.X
= BlitData
.Src
.Y
= 0;
4834 BlitData
.Luminance
= L
;
4838 void character::DrawBodyParts (blitdata
&BlitData
) const {
4839 GetTorso()->Draw(BlitData
);
4843 void character::PrintBeginTeleportMessage () const {
4844 if (IsPlayer()) ADD_MESSAGE("You feel jumpy.");
4848 void character::PrintEndTeleportMessage () const {
4849 if (IsPlayer()) ADD_MESSAGE("You suddenly realize you've always preferred walking to jumping.");
4853 void character::PrintBeginDetectMessage () const {
4854 if (IsPlayer()) ADD_MESSAGE("You feel curious about your surroundings.");
4858 void character::PrintEndDetectMessage () const {
4859 if (IsPlayer()) ADD_MESSAGE("You decide to rely on your intuition from now on.");
4863 void character::TeleportHandler () {
4864 if (!(RAND() % 1500) && !game::IsInWilderness()) {
4865 if (IsPlayer()) ADD_MESSAGE("You feel an urgent spatial relocation is now appropriate.");
4866 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears.", CHAR_NAME(DEFINITE
));
4872 void character::DetectHandler () {
4874 //the AI can't be asked position questions! So only the player can hav this state really :/ a bit daft of me
4875 if (!(RAND()%3000) && !game::IsInWilderness()) {
4876 ADD_MESSAGE("Your mind wanders in search of something.");
4877 DoDetecting(); //in fact, who knows what would happen if a dark frog had the detecting state?
4883 void character::PrintBeginPolymorphMessage () const {
4884 if (IsPlayer()) ADD_MESSAGE("An uncomfortable uncertainty of who you really are overwhelms you.");
4888 void character::PrintEndPolymorphMessage () const {
4889 if (IsPlayer()) ADD_MESSAGE("You feel you are you and no one else.");
4893 void character::PolymorphHandler () {
4894 if (!(RAND() % 1500)) PolymorphRandomly(1, 999999, 200 + RAND() % 800);
4897 void character::PrintBeginTeleportControlMessage () const {
4898 if (IsPlayer()) ADD_MESSAGE("You feel very controlled.");
4902 void character::PrintEndTeleportControlMessage () const {
4903 if (IsPlayer()) ADD_MESSAGE("You feel your control slipping.");
4907 void character::DisplayStethoscopeInfo (character
*) const {
4908 felist
Info(CONST_S("Information about ") + GetDescription(DEFINITE
));
4909 AddSpecialStethoscopeInfo(Info
);
4910 Info
.AddEntry(CONST_S("Endurance: ") + GetAttribute(ENDURANCE
), LIGHT_GRAY
);
4911 Info
.AddEntry(CONST_S("Perception: ") + GetAttribute(PERCEPTION
), LIGHT_GRAY
);
4912 Info
.AddEntry(CONST_S("Intelligence: ") + GetAttribute(INTELLIGENCE
), LIGHT_GRAY
);
4913 Info
.AddEntry(CONST_S("Wisdom: ") + GetAttribute(WISDOM
), LIGHT_GRAY
);
4914 //Info.AddEntry(CONST_S("Willpower: ") + GetAttribute(WILL_POWER), LIGHT_GRAY);
4915 Info
.AddEntry(CONST_S("Charisma: ") + GetAttribute(CHARISMA
), LIGHT_GRAY
);
4916 Info
.AddEntry(CONST_S("HP: ") + GetHP() + "/" + GetMaxHP(), IsInBadCondition() ? RED
: LIGHT_GRAY
);
4917 if (GetAction()) Info
.AddEntry(festring(GetAction()->GetDescription()).CapitalizeCopy(), LIGHT_GRAY
);
4918 for (int c
= 0; c
< STATES
; ++c
) {
4919 if (StateIsActivated(1 << c
) && (1 << c
!= HASTE
|| !StateIsActivated(SLOW
)) && (1 << c
!= SLOW
|| !StateIsActivated(HASTE
)))
4920 Info
.AddEntry(StateData
[c
].Description
, LIGHT_GRAY
);
4922 switch (GetTirednessState()) {
4923 case FAINTING
: Info
.AddEntry("Fainting", RED
); break;
4924 case EXHAUSTED
: Info
.AddEntry("Exhausted", LIGHT_GRAY
); break;
4926 game::SetStandardListAttributes(Info
);
4931 truth
character::CanUseStethoscope (truth PrintReason
) const {
4932 if (PrintReason
) ADD_MESSAGE("This type of monster can't use a stethoscope.");
4937 /* Effect used by at least Sophos.
4938 * NOTICE: Doesn't check for death! */
4939 void character::TeleportSomePartsAway (int NumberToTeleport
) {
4940 if (StateIsActivated(TELEPORT_LOCK
)) {
4941 if (IsPlayer()) ADD_MESSAGE("You feel very itchy for a moment.");
4944 for (int c
= 0; c
< NumberToTeleport
; ++c
) {
4945 int RandomBodyPart
= GetRandomNonVitalBodyPart();
4946 if (RandomBodyPart
== NONE_INDEX
) {
4947 for (; c
< NumberToTeleport
; ++c
) {
4948 GetTorso()->SetHP((GetTorso()->GetHP() << 2) / 5);
4949 sLong TorsosVolume
= GetTorso()->GetMainMaterial()->GetVolume() / 10;
4950 if (!TorsosVolume
) break;
4951 sLong Amount
= (RAND() % TorsosVolume
)+1;
4952 item
*Lump
= GetTorso()->GetMainMaterial()->CreateNaturalForm(Amount
);
4953 GetTorso()->GetMainMaterial()->EditVolume(-Amount
);
4954 auto pos
= GetLevel()->GetRandomSquare();
4955 if (pos
== ERROR_V2
) {
4956 fprintf(stderr
, "oops. `character::TeleportSomePartsAway()` failed.\n");
4959 Lump
->MoveTo(GetNearLSquare(pos
)->GetStack());
4961 if (IsPlayer()) ADD_MESSAGE("Parts of you teleport away.");
4962 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Parts of %s teleport away.", CHAR_NAME(DEFINITE
));
4965 item
*SeveredBodyPart
= SevereBodyPart(RandomBodyPart
);
4966 if (SeveredBodyPart
) {
4967 auto pos
= GetLevel()->GetRandomSquare();
4968 if (pos
== ERROR_V2
) {
4969 fprintf(stderr
, "oops. `character::TeleportSomePartsAway()` failed.\n");
4970 //SeveredBodyPart->SendToHell(); //???
4971 delete SeveredBodyPart
;
4972 if (IsPlayer()) ADD_MESSAGE("Your %s disappears.", GetBodyPartName(RandomBodyPart
).CStr());
4973 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s disappears.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4976 GetNearLSquare(pos
)->AddItem(SeveredBodyPart
);
4977 SeveredBodyPart
->DropEquipment();
4978 if (IsPlayer()) ADD_MESSAGE("Your %s teleports away.", GetBodyPartName(RandomBodyPart
).CStr());
4979 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s teleports away.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4981 if (IsPlayer()) ADD_MESSAGE("Your %s disappears.", GetBodyPartName(RandomBodyPart
).CStr());
4982 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s disappears.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4989 /* Returns an index of a random bodypart that is not vital. If no non-vital bodypart is found returns NONE_INDEX */
4990 int character::GetRandomNonVitalBodyPart () const {
4991 int OKBodyPart
[MAX_BODYPARTS
];
4992 int OKBodyParts
= 0;
4993 for (int c
= 0; c
< BodyParts
; ++c
) if (GetBodyPart(c
) && !BodyPartIsVital(c
)) OKBodyPart
[OKBodyParts
++] = c
;
4994 return OKBodyParts
? OKBodyPart
[RAND() % OKBodyParts
] : NONE_INDEX
;
4998 void character::CalculateVolumeAndWeight () {
4999 Volume
= Stack
->GetVolume();
5000 Weight
= Stack
->GetWeight();
5002 CarriedWeight
= Weight
;
5003 for (int c
= 0; c
< BodyParts
; ++c
) {
5004 bodypart
*BodyPart
= GetBodyPart(c
);
5006 BodyVolume
+= BodyPart
->GetBodyPartVolume();
5007 Volume
+= BodyPart
->GetVolume();
5008 CarriedWeight
+= BodyPart
->GetCarriedWeight();
5009 Weight
+= BodyPart
->GetWeight();
5015 void character::SignalVolumeAndWeightChange () {
5016 if (!IsInitializing()) {
5017 CalculateVolumeAndWeight();
5018 if (IsEnabled()) CalculateBurdenState();
5019 if (MotherEntity
) MotherEntity
->SignalVolumeAndWeightChange();
5024 void character::SignalEmitationIncrease (col24 EmitationUpdate
) {
5025 if (game::CompareLights(EmitationUpdate
, Emitation
) > 0) {
5026 game::CombineLights(Emitation
, EmitationUpdate
);
5027 if (MotherEntity
) MotherEntity
->SignalEmitationIncrease(EmitationUpdate
);
5028 else if (SquareUnder
[0] && !game::IsInWilderness()) {
5029 for(int c
= 0; c
< GetSquaresUnder(); ++c
) GetLSquareUnder()->SignalEmitationIncrease(EmitationUpdate
);
5035 void character::SignalEmitationDecrease (col24 EmitationUpdate
) {
5036 if (game::CompareLights(EmitationUpdate
, Emitation
) >= 0 && Emitation
) {
5037 col24 Backup
= Emitation
;
5038 CalculateEmitation();
5039 if (Backup
!= Emitation
) {
5040 if (MotherEntity
) MotherEntity
->SignalEmitationDecrease(EmitationUpdate
);
5041 else if (SquareUnder
[0] && !game::IsInWilderness()) {
5042 for (int c
= 0; c
< GetSquaresUnder(); ++c
) GetLSquareUnder(c
)->SignalEmitationDecrease(EmitationUpdate
);
5049 void character::CalculateEmitation () {
5050 Emitation
= GetBaseEmitation();
5051 for (int c
= 0; c
< BodyParts
; ++c
) {
5052 bodypart
*BodyPart
= GetBodyPart(c
);
5053 if (BodyPart
) game::CombineLights(Emitation
, BodyPart
->GetEmitation());
5055 game::CombineLights(Emitation
, Stack
->GetEmitation());
5059 void character::CalculateAll () {
5060 Flags
|= C_INITIALIZING
;
5061 CalculateAttributeBonuses();
5062 CalculateVolumeAndWeight();
5063 CalculateEmitation();
5064 CalculateBodyPartMaxHPs(0);
5065 CalculateMaxStamina();
5066 CalculateBurdenState();
5067 CalculateBattleInfo();
5068 Flags
&= ~C_INITIALIZING
;
5072 void character::CalculateHP () {
5073 HP
= sumbodypartproperties()(this, &bodypart::GetHP
);
5077 void character::CalculateMaxHP () {
5078 MaxHP
= sumbodypartproperties()(this, &bodypart::GetMaxHP
);
5082 void character::CalculateBodyPartMaxHPs (feuLong Flags
) {
5083 doforbodypartswithparam
<feuLong
>()(this, &bodypart::CalculateMaxHP
, Flags
);
5089 truth
character::EditAttribute (int Identifier
, int Value
) {
5090 if (Identifier
== ENDURANCE
&& UseMaterialAttributes()) return false;
5091 if (RawEditAttribute(BaseExperience
[Identifier
], Value
)) {
5092 if (!IsInitializing()) {
5093 if (Identifier
== LEG_STRENGTH
) CalculateBurdenState();
5094 else if (Identifier
== ENDURANCE
) CalculateBodyPartMaxHPs();
5095 else if (IsPlayer() && Identifier
== PERCEPTION
) game::SendLOSUpdateRequest();
5096 else if (IsPlayerKind() && (Identifier
== INTELLIGENCE
|| Identifier
== WISDOM
|| Identifier
== CHARISMA
)) UpdatePictures();
5097 CalculateBattleInfo();
5105 truth
character::ActivateRandomState (int Flags
, int Time
, sLong Seed
) {
5107 if (Seed
) femath::SetSeed(Seed
);
5108 sLong ToBeActivated
= GetRandomState(Flags
|DUR_TEMPORARY
);
5110 if (!ToBeActivated
) return false;
5111 BeginTemporaryState(ToBeActivated
, Time
);
5116 truth
character::GainRandomIntrinsic (int Flags
) {
5117 sLong ToBeActivated
= GetRandomState(Flags
|DUR_PERMANENT
);
5118 if (!ToBeActivated
) return false;
5119 GainIntrinsic(ToBeActivated
);
5124 /* Returns 0 if state not found */
5125 sLong
character::GetRandomState (int Flags
) const {
5126 sLong OKStates
[STATES
];
5127 int NumberOfOKStates
= 0;
5128 for (int c
= 0; c
< STATES
; ++c
) {
5129 if (StateData
[c
].Flags
& Flags
& DUR_FLAGS
&& StateData
[c
].Flags
& Flags
& SRC_FLAGS
) OKStates
[NumberOfOKStates
++] = 1 << c
;
5131 return NumberOfOKStates
? OKStates
[RAND() % NumberOfOKStates
] : 0;
5135 int characterprototype::CreateSpecialConfigurations (characterdatabase
**TempConfig
, int Configs
, int Level
) {
5136 if (Level
== 0 && TempConfig
[0]->CreateDivineConfigurations
) {
5137 Configs
= databasecreator
<character
>::CreateDivineConfigurations(this, TempConfig
, Configs
);
5139 if (Level
== 1 && TempConfig
[0]->CreateUndeadConfigurations
) {
5140 for (int c
= 1; c
< protocontainer
<character
>::GetSize(); ++c
) {
5141 const character::prototype
*Proto
= protocontainer
<character
>::GetProto(c
);
5142 if (!Proto
) continue; // missing character
5143 const character::database
*const *CharacterConfigData
= Proto
->GetConfigData();
5144 if (!CharacterConfigData
) ABORT("No database entry for character <%s>!", Proto
->GetClassID());
5145 const character::database
*const* End
= CharacterConfigData
+Proto
->GetConfigSize();
5146 for (++CharacterConfigData
; CharacterConfigData
!= End
; ++CharacterConfigData
) {
5147 const character::database
*CharacterDataBase
= *CharacterConfigData
;
5148 if (CharacterDataBase
->UndeadVersions
) {
5149 character::database
* ConfigDataBase
= new character::database(**TempConfig
);
5150 festring ucfgname
= "undead(";
5151 //ucfgname << (*TempConfig)->CfgStrName;
5152 ucfgname
<< CharacterDataBase
->CfgStrName
;
5154 ucfgname
<< Proto
->GetClassID();
5155 //fprintf(stderr, "UNDEAD: %s\n", ucfgname.CStr());
5156 ConfigDataBase
->InitDefaults(this, (c
<< 8) | CharacterDataBase
->Config
, ucfgname
);
5157 ConfigDataBase
->PostFix
<< "of ";
5158 if (CharacterDataBase
->Adjective
.GetSize()) {
5159 if (CharacterDataBase
->UsesLongAdjectiveArticle
) ConfigDataBase
->PostFix
<< "an ";
5160 else ConfigDataBase
->PostFix
<< "a ";
5161 ConfigDataBase
->PostFix
<< CharacterDataBase
->Adjective
<< ' ';
5163 if (CharacterDataBase
->UsesLongArticle
) ConfigDataBase
->PostFix
<< "an ";
5164 else ConfigDataBase
->PostFix
<< "a ";
5166 ConfigDataBase
->PostFix
<< CharacterDataBase
->NameSingular
;
5167 if (CharacterDataBase
->PostFix
.GetSize()) ConfigDataBase
->PostFix
<< ' ' << CharacterDataBase
->PostFix
;
5168 int P1
= TempConfig
[0]->UndeadAttributeModifier
;
5169 int P2
= TempConfig
[0]->UndeadVolumeModifier
;
5171 for (c2
= 0; c2
< ATTRIBUTES
; ++c2
) ConfigDataBase
->*ExpPtr
[c2
] = CharacterDataBase
->*ExpPtr
[c2
] * P1
/ 100;
5172 for (c2
= 0; c2
< EQUIPMENT_DATAS
; ++c2
) ConfigDataBase
->*EquipmentDataPtr
[c2
] = contentscript
<item
>();
5173 ConfigDataBase
->DefaultIntelligence
= 5;
5174 ConfigDataBase
->DefaultWisdom
= 5;
5175 ConfigDataBase
->DefaultCharisma
= 5;
5176 ConfigDataBase
->TotalSize
= CharacterDataBase
->TotalSize
;
5177 ConfigDataBase
->Sex
= CharacterDataBase
->Sex
;
5178 ConfigDataBase
->AttributeBonus
= CharacterDataBase
->AttributeBonus
;
5179 ConfigDataBase
->TotalVolume
= CharacterDataBase
->TotalVolume
* P2
/ 100;
5180 if (TempConfig
[0]->UndeadCopyMaterials
) {
5181 ConfigDataBase
->HeadBitmapPos
= CharacterDataBase
->HeadBitmapPos
;
5182 ConfigDataBase
->HairColor
= CharacterDataBase
->HairColor
;
5183 ConfigDataBase
->EyeColor
= CharacterDataBase
->EyeColor
;
5184 ConfigDataBase
->CapColor
= CharacterDataBase
->CapColor
;
5185 ConfigDataBase
->FleshMaterial
= CharacterDataBase
->FleshMaterial
;
5186 ConfigDataBase
->BloodMaterial
= CharacterDataBase
->BloodMaterial
;
5187 ConfigDataBase
->VomitMaterial
= CharacterDataBase
->VomitMaterial
;
5188 ConfigDataBase
->SweatMaterial
= CharacterDataBase
->SweatMaterial
;
5190 ConfigDataBase
->KnownCWeaponSkills
= CharacterDataBase
->KnownCWeaponSkills
;
5191 ConfigDataBase
->CWeaponSkillHits
= CharacterDataBase
->CWeaponSkillHits
;
5192 ConfigDataBase
->PostProcess();
5193 TempConfig
[Configs
++] = ConfigDataBase
;
5198 if (Level
== 0 && TempConfig
[0]->CreateGolemMaterialConfigurations
) {
5199 for (int c
= 1; c
< protocontainer
<material
>::GetSize(); ++c
) {
5200 const material::prototype
* Proto
= protocontainer
<material
>::GetProto(c
);
5201 if (!Proto
) continue; // missing matherial
5202 const material::database
*const* MaterialConfigData
= Proto
->GetConfigData();
5203 const material::database
*const* End
= MaterialConfigData
+ Proto
->GetConfigSize();
5204 for (++MaterialConfigData
; MaterialConfigData
!= End
; ++MaterialConfigData
) {
5205 const material::database
* MaterialDataBase
= *MaterialConfigData
;
5206 if (MaterialDataBase
->CategoryFlags
&IS_GOLEM_MATERIAL
) {
5207 character::database
* ConfigDataBase
= new character::database(**TempConfig
);
5208 festring gcfgname
= "golem ";
5209 gcfgname
<< MaterialDataBase
->CfgStrName
;
5210 //fprintf(stderr, "GOLEM %d [%s]\n", MaterialDataBase->Config, gcfgname.CStr());
5211 ConfigDataBase
->InitDefaults(this, MaterialDataBase
->Config
, gcfgname
);
5212 ConfigDataBase
->Adjective
= MaterialDataBase
->NameStem
;
5213 ConfigDataBase
->UsesLongAdjectiveArticle
= MaterialDataBase
->NameFlags
& USE_AN
;
5214 ConfigDataBase
->AttachedGod
= MaterialDataBase
->AttachedGod
;
5215 TempConfig
[Configs
++] = ConfigDataBase
;
5224 double character::GetTimeToDie (ccharacter
*Enemy
, int Damage
, double ToHitValue
, truth AttackIsBlockable
, truth UseMaxHP
) const {
5225 double DodgeValue
= GetDodgeValue();
5226 if (!Enemy
->CanBeSeenBy(this, true)) ToHitValue
*= 2;
5227 if (!CanBeSeenBy(Enemy
, true)) DodgeValue
*= 2;
5228 double MinHits
= 1000;
5230 for (int c
= 0; c
< BodyParts
; ++c
) {
5231 if (BodyPartIsVital(c
) && GetBodyPart(c
)) {
5232 double Hits
= GetBodyPart(c
)->GetTimeToDie(Damage
, ToHitValue
, DodgeValue
, AttackIsBlockable
, UseMaxHP
);
5233 if (First
) { MinHits
= Hits
; First
= false; } else MinHits
= 1/(1/MinHits
+1/Hits
);
5240 double character::GetRelativeDanger (ccharacter
*Enemy
, truth UseMaxHP
) const {
5241 double Danger
= Enemy
->GetTimeToKill(this, UseMaxHP
)/GetTimeToKill(Enemy
, UseMaxHP
);
5242 int EnemyAP
= Enemy
->GetMoveAPRequirement(1);
5243 int ThisAP
= GetMoveAPRequirement(1);
5244 if (EnemyAP
> ThisAP
) Danger
*= 1.25; else if (ThisAP
> EnemyAP
) Danger
*= 0.80;
5245 if (!Enemy
->CanBeSeenBy(this, true)) Danger
*= (Enemy
->IsPlayer() ? 0.2 : 0.5);
5246 if (!CanBeSeenBy(Enemy
, true)) Danger
*= (IsPlayer() ? 5.0 : 2.0);
5247 if (GetAttribute(INTELLIGENCE
) < 10 && !IsPlayer()) Danger
*= 0.80;
5248 if (Enemy
->GetAttribute(INTELLIGENCE
) < 10 && !Enemy
->IsPlayer()) Danger
*= 1.25;
5249 return Limit(Danger
, 0.001, 1000.0);
5253 festring
character::GetBodyPartName (int I
, truth Articled
) const {
5254 if (I
== TORSO_INDEX
) return Articled
? CONST_S("a torso") : CONST_S("torso");
5255 ABORT("Illegal character bodypart name request!");
5260 item
*character::SearchForItem (feuLong ID
) const {
5261 item
*Equipment
= findequipment
<feuLong
>()(this, &item::HasID
, ID
);
5262 if (Equipment
) return Equipment
;
5263 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (i
->GetID() == ID
) return *i
;
5268 truth
character::ContentsCanBeSeenBy (ccharacter
*Viewer
) const {
5269 return (Viewer
== this);
5273 truth
character::HitEffect (character
*Enemy
, item
* Weapon
, v2 HitPos
, int Type
, int BodyPartIndex
,
5274 int Direction
, truth BlockedByArmour
, truth Critical
, int DoneDamage
)
5276 if (Weapon
) return Weapon
->HitEffect(this, Enemy
, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5277 if (Type
== UNARMED_ATTACK
) return Enemy
->SpecialUnarmedEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5278 if (Type
== KICK_ATTACK
) return Enemy
->SpecialKickEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5279 if (Type
== BITE_ATTACK
) return Enemy
->SpecialBiteEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
, Critical
, DoneDamage
);
5284 void character::WeaponSkillHit (item
*Weapon
, int Type
, int Hits
) {
5286 if (Type
== UNARMED_ATTACK
) Category
= UNARMED
;
5287 else if (Type
== WEAPON_ATTACK
) { Weapon
->WeaponSkillHit(Hits
); return; }
5288 else if (Type
== KICK_ATTACK
) Category
= KICK
;
5289 else if (Type
== BITE_ATTACK
) Category
= BITE
;
5290 else if (Type
== THROW_ATTACK
) { if (!IsHumanoid()) return; Category
= Weapon
->GetWeaponCategory(); }
5291 else { ABORT("Illegal Type %d passed to character::WeaponSkillHit()!", Type
); return; }
5292 if (GetCWeaponSkill(Category
)->AddHit(Hits
)) {
5293 CalculateBattleInfo();
5294 if (IsPlayer()) GetCWeaponSkill(Category
)->AddLevelUpMessage(Category
);
5299 /* Returns 0 if character cannot be duplicated */
5300 character
*character::Duplicate (feuLong Flags
) {
5301 if (!(Flags
& IGNORE_PROHIBITIONS
) && !CanBeCloned()) return 0;
5302 character
*Char
= GetProtoType()->Clone(this);
5303 if (Flags
& MIRROR_IMAGE
) {
5304 DuplicateEquipment(Char
, Flags
& ~IGNORE_PROHIBITIONS
);
5305 Char
->SetLifeExpectancy(Flags
>> LE_BASE_SHIFT
& LE_BASE_RANGE
, Flags
>> LE_RAND_SHIFT
& LE_RAND_RANGE
);
5307 Char
->CalculateAll();
5308 Char
->CalculateEmitation();
5309 Char
->UpdatePictures();
5310 Char
->Flags
&= ~(C_INITIALIZING
|C_IN_NO_MSG_MODE
);
5315 truth
character::TryToEquip (item
*Item
) {
5316 if (!Item
->AllowEquip() || !CanUseEquipment() || GetAttribute(WISDOM
) >= Item
->GetWearWisdomLimit() || Item
->GetSquaresUnder() != 1) {
5320 for (int e
= 0; e
< GetEquipments(); ++e
) {
5321 if (GetBodyPartOfEquipment(e
) && EquipmentIsAllowed(e
)) {
5322 sorter Sorter
= EquipmentSorter(e
);
5323 if ((Sorter
== 0 || (Item
->*Sorter
)(this)) &&
5324 ((e
!= RIGHT_WIELDED_INDEX
&& e
!= LEFT_WIELDED_INDEX
) ||
5325 Item
->IsWeapon(this) || Item
->IsShield(this)) && AllowEquipment(Item
, e
))
5327 item
*OldEquipment
= GetEquipment(e
);
5328 if (BoundToUse(OldEquipment
, e
)) continue;
5329 lsquare
*LSquareUnder
= GetLSquareUnder();
5330 stack
*StackUnder
= LSquareUnder
->GetStack();
5331 msgsystem::DisableMessages();
5332 Flags
|= C_PICTURE_UPDATES_FORBIDDEN
;
5333 LSquareUnder
->Freeze();
5334 StackUnder
->Freeze();
5335 double Danger
= GetRelativeDanger(PLAYER
);
5336 if (OldEquipment
) OldEquipment
->RemoveFromSlot();
5337 Item
->RemoveFromSlot();
5338 SetEquipment(e
, Item
);
5339 double NewDanger
= GetRelativeDanger(PLAYER
);
5340 Item
->RemoveFromSlot();
5341 StackUnder
->AddItem(Item
);
5342 if (OldEquipment
) SetEquipment(e
, OldEquipment
);
5343 msgsystem::EnableMessages();
5344 Flags
&= ~C_PICTURE_UPDATES_FORBIDDEN
;
5345 LSquareUnder
->UnFreeze();
5346 StackUnder
->UnFreeze();
5348 if (NewDanger
> Danger
|| BoundToUse(Item
, e
)) {
5349 room
*Room
= GetRoom();
5350 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
5351 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
));
5352 if (Room
) Room
->DropItem(this, OldEquipment
, 1);
5353 OldEquipment
->MoveTo(StackUnder
);
5354 Item
->RemoveFromSlot();
5355 SetEquipment(e
, Item
);
5361 if (NewDanger
> Danger
|| (NewDanger
== Danger
&& e
!= RIGHT_WIELDED_INDEX
&& e
!= LEFT_WIELDED_INDEX
) || BoundToUse(Item
, e
)) {
5362 room
*Room
= GetRoom();
5363 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
5364 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up and equips %s.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(INDEFINITE
));
5365 Item
->RemoveFromSlot();
5366 SetEquipment(e
, Item
);
5379 truth
character::TryToConsume (item
*Item
) {
5380 return Item
->CanBeEatenByAI(this) && ConsumeItem(Item
, Item
->GetConsumeMaterial(this)->GetConsumeVerb());
5384 void character::UpdateESPLOS () const {
5385 if (StateIsActivated(ESP
) && !game::IsInWilderness()) {
5386 for (int c
= 0; c
< game::GetTeams(); ++c
) {
5387 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
5388 const character
*ch
= *i
;
5389 if (ch
->IsEnabled()) ch
->SendNewDrawRequest();
5396 int character::GetCWeaponSkillLevel (citem
*Item
) const {
5397 if (Item
->GetWeaponCategory() < GetAllowedWeaponSkillCategories()) return GetCWeaponSkill(Item
->GetWeaponCategory())->GetLevel();
5402 void character::PrintBeginPanicMessage () const {
5403 if (IsPlayer()) ADD_MESSAGE("You panic!");
5404 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s panics.", CHAR_NAME(DEFINITE
));
5408 void character::PrintEndPanicMessage () const {
5409 if (IsPlayer()) ADD_MESSAGE("You finally calm down.");
5410 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s calms down.", CHAR_NAME(DEFINITE
));
5414 void character::CheckPanic (int Ticks
) {
5415 if (GetPanicLevel() > 1 && !StateIsActivated(PANIC
) && GetHP()*100 < RAND()%(GetPanicLevel()*GetMaxHP()<<1) && !StateIsActivated(FEARLESS
)) {
5416 BeginTemporaryState(PANIC
, ((Ticks
* 3) >> 2) + RAND() % ((Ticks
>> 1) + 1)); // 25% randomness to ticks...
5421 /* returns 0 if fails else the newly created character */
5422 character
*character::DuplicateToNearestSquare (character
*Cloner
, feuLong Flags
) {
5423 character
*NewlyCreated
= Duplicate(Flags
);
5424 if (!NewlyCreated
) return 0;
5425 if (Flags
& CHANGE_TEAM
&& Cloner
) NewlyCreated
->ChangeTeam(Cloner
->GetTeam());
5426 if (!NewlyCreated
->PutNear(GetPos(), true)) {
5427 //NewlyCreated->Disable();
5428 //NewlyCreated->SendToHell();
5429 delete NewlyCreated
;
5432 return NewlyCreated
;
5436 void character::SignalSpoil (material
*m
) {
5437 if (GetMotherEntity()) GetMotherEntity()->SignalSpoil(m
);
5438 else Disappear(0, "spoil", &item::IsVeryCloseToSpoiling
);
5442 truth
character::CanHeal () const {
5443 for (int c
= 0; c
< BodyParts
; ++c
) {
5444 bodypart
*BodyPart
= GetBodyPart(c
);
5445 if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < BodyPart
->GetMaxHP()) return true;
5451 int character::GetRelation (ccharacter
*Who
) const {
5452 return GetTeam()->GetRelation(Who
->GetTeam());
5456 truth (item::*AffectTest
[BASE_ATTRIBUTES
])() const = {
5457 &item::AffectsEndurance
,
5458 &item::AffectsPerception
,
5459 &item::AffectsIntelligence
,
5460 &item::AffectsWisdom
,
5461 &item::AffectsWillPower
,
5462 &item::AffectsCharisma
,
5467 /* Returns nonzero if endurance has decreased and death may occur */
5468 truth
character::CalculateAttributeBonuses () {
5469 doforbodyparts()(this, &bodypart::CalculateAttributeBonuses
);
5470 int BackupBonus
[BASE_ATTRIBUTES
];
5471 int BackupCarryingBonus
= CarryingBonus
;
5474 for (c1
= 0; c1
< BASE_ATTRIBUTES
; ++c1
) {
5475 BackupBonus
[c1
] = AttributeBonus
[c1
];
5476 AttributeBonus
[c1
] = 0;
5478 for (c1
= 0; c1
< GetEquipments(); ++c1
) {
5479 item
*Equipment
= GetEquipment(c1
);
5480 if (!Equipment
|| !Equipment
->IsInCorrectSlot(c1
)) continue;
5481 for (int c2
= 0; c2
< BASE_ATTRIBUTES
; ++c2
) {
5482 if ((Equipment
->*AffectTest
[c2
])()) AttributeBonus
[c2
] += Equipment
->GetEnchantment();
5484 if (Equipment
->AffectsCarryingCapacity()) CarryingBonus
+= Equipment
->GetCarryingBonus();
5487 ApplySpecialAttributeBonuses();
5489 if (IsPlayer() && !IsInitializing() && AttributeBonus
[PERCEPTION
] != BackupBonus
[PERCEPTION
]) game::SendLOSUpdateRequest();
5490 if (IsPlayer() && !IsInitializing() && AttributeBonus
[INTELLIGENCE
] != BackupBonus
[INTELLIGENCE
]) UpdateESPLOS();
5492 if (!IsInitializing() && CarryingBonus
!= BackupCarryingBonus
) CalculateBurdenState();
5494 if (!IsInitializing() && AttributeBonus
[ENDURANCE
] != BackupBonus
[ENDURANCE
]) {
5495 CalculateBodyPartMaxHPs();
5496 CalculateMaxStamina();
5497 return AttributeBonus
[ENDURANCE
] < BackupBonus
[ENDURANCE
];
5504 void character::ApplyEquipmentAttributeBonuses (item
*Equipment
) {
5505 if (Equipment
->AffectsEndurance()) {
5506 AttributeBonus
[ENDURANCE
] += Equipment
->GetEnchantment();
5507 CalculateBodyPartMaxHPs();
5508 CalculateMaxStamina();
5510 if (Equipment
->AffectsPerception()) {
5511 AttributeBonus
[PERCEPTION
] += Equipment
->GetEnchantment();
5512 if (IsPlayer()) game::SendLOSUpdateRequest();
5514 if (Equipment
->AffectsIntelligence()) {
5515 AttributeBonus
[INTELLIGENCE
] += Equipment
->GetEnchantment();
5516 if (IsPlayer()) UpdateESPLOS();
5518 if (Equipment
->AffectsWisdom()) AttributeBonus
[WISDOM
] += Equipment
->GetEnchantment();
5519 if (Equipment
->AffectsWillPower()) AttributeBonus
[WILL_POWER
] += Equipment
->GetEnchantment();
5520 if (Equipment
->AffectsCharisma()) AttributeBonus
[CHARISMA
] += Equipment
->GetEnchantment();
5521 if (Equipment
->AffectsMana()) AttributeBonus
[MANA
] += Equipment
->GetEnchantment();
5522 if (Equipment
->AffectsCarryingCapacity()) {
5523 CarryingBonus
+= Equipment
->GetCarryingBonus();
5524 CalculateBurdenState();
5529 void character::ReceiveAntidote (sLong Amount
) {
5530 if (StateIsActivated(POISONED
)) {
5531 if (GetTemporaryStateCounter(POISONED
) > Amount
) {
5532 EditTemporaryStateCounter(POISONED
, -Amount
);
5535 if (IsPlayer()) ADD_MESSAGE("Aaaah... You feel much better.");
5536 Amount
-= GetTemporaryStateCounter(POISONED
);
5537 DeActivateTemporaryState(POISONED
);
5540 if ((Amount
>= 100 || RAND_N(100) < Amount
) && StateIsActivated(PARASITIZED
)) {
5541 if (IsPlayer()) ADD_MESSAGE("Something in your belly didn't seem to like this stuff.");
5542 DeActivateTemporaryState(PARASITIZED
);
5543 Amount
-= Min(100, Amount
);
5545 if ((Amount
>= 100 || RAND_N(100) < Amount
) && StateIsActivated(LEPROSY
)) {
5546 if (IsPlayer()) ADD_MESSAGE("You are not falling to pieces anymore.");
5547 DeActivateTemporaryState(LEPROSY
);
5548 Amount
-= Min(100, Amount
);
5553 void character::AddAntidoteConsumeEndMessage () const {
5554 if (StateIsActivated(POISONED
)) {
5555 // true only if the antidote didn't cure the poison completely
5556 if (IsPlayer()) ADD_MESSAGE("Your body processes the poison in your veins with rapid speed.");
5561 truth
character::IsDead () const {
5562 for (int c
= 0; c
< BodyParts
; ++c
) {
5563 bodypart
*BodyPart
= GetBodyPart(c
);
5564 if (BodyPartIsVital(c
) && (!BodyPart
|| BodyPart
->GetHP() < 1)) return true;
5570 void character::SignalSpoilLevelChange (material
*m
) {
5571 if (GetMotherEntity()) GetMotherEntity()->SignalSpoilLevelChange(m
); else UpdatePictures();
5575 void character::AddOriginalBodyPartID (int I
, feuLong What
) {
5576 if (std::find(OriginalBodyPartID
[I
].begin(), OriginalBodyPartID
[I
].end(), What
) == OriginalBodyPartID
[I
].end()) {
5577 OriginalBodyPartID
[I
].push_back(What
);
5578 if (OriginalBodyPartID
[I
].size() > 100) OriginalBodyPartID
[I
].erase(OriginalBodyPartID
[I
].begin());
5583 void character::AddToInventory (const fearray
<contentscript
<item
> > &ItemArray
, int SpecialFlags
) {
5584 for (uInt c1
= 0; c1
< ItemArray
.Size
; ++c1
) {
5585 if (ItemArray
[c1
].IsValid()) {
5586 const interval
*TimesPtr
= ItemArray
[c1
].GetTimes();
5587 int Times
= TimesPtr
? TimesPtr
->Randomize() : 1;
5588 for (int c2
= 0; c2
< Times
; ++c2
) {
5589 item
*Item
= ItemArray
[c1
].Instantiate(SpecialFlags
);
5591 Stack
->AddItem(Item
);
5592 Item
->SpecialGenerationHandler();
5600 truth
character::HasHadBodyPart (citem
*Item
) const {
5601 for (int c
= 0; c
< BodyParts
; ++c
) {
5602 if (std::find(OriginalBodyPartID
[c
].begin(), OriginalBodyPartID
[c
].end(), Item
->GetID()) != OriginalBodyPartID
[c
].end()) {
5606 return GetPolymorphBackup() && GetPolymorphBackup()->HasHadBodyPart(Item
);
5610 festring
&character::ProcessMessage (festring
&Msg
) const {
5611 SEARCH_N_REPLACE(Msg
, "@nu", GetName(UNARTICLED
));
5612 SEARCH_N_REPLACE(Msg
, "@ni", GetName(INDEFINITE
));
5613 SEARCH_N_REPLACE(Msg
, "@nd", GetName(DEFINITE
));
5614 SEARCH_N_REPLACE(Msg
, "@du", GetDescription(UNARTICLED
));
5615 SEARCH_N_REPLACE(Msg
, "@di", GetDescription(INDEFINITE
));
5616 SEARCH_N_REPLACE(Msg
, "@dd", GetDescription(DEFINITE
));
5617 SEARCH_N_REPLACE(Msg
, "@pp", GetPersonalPronoun());
5618 SEARCH_N_REPLACE(Msg
, "@sp", GetPossessivePronoun());
5619 SEARCH_N_REPLACE(Msg
, "@op", GetObjectPronoun());
5620 SEARCH_N_REPLACE(Msg
, "@Nu", GetName(UNARTICLED
).CapitalizeCopy());
5621 SEARCH_N_REPLACE(Msg
, "@Ni", GetName(INDEFINITE
).CapitalizeCopy());
5622 SEARCH_N_REPLACE(Msg
, "@Nd", GetName(DEFINITE
).CapitalizeCopy());
5623 SEARCH_N_REPLACE(Msg
, "@Du", GetDescription(UNARTICLED
).CapitalizeCopy());
5624 SEARCH_N_REPLACE(Msg
, "@Di", GetDescription(INDEFINITE
).CapitalizeCopy());
5625 SEARCH_N_REPLACE(Msg
, "@Dd", GetDescription(DEFINITE
).CapitalizeCopy());
5626 SEARCH_N_REPLACE(Msg
, "@Pp", GetPersonalPronoun().CapitalizeCopy());
5627 SEARCH_N_REPLACE(Msg
, "@Sp", GetPossessivePronoun().CapitalizeCopy());
5628 SEARCH_N_REPLACE(Msg
, "@Op", GetObjectPronoun().CapitalizeCopy());
5629 SEARCH_N_REPLACE(Msg
, "@Gd", GetMasterGod()->GetName());
5634 void character::ProcessAndAddMessage (festring Msg
) const {
5635 ADD_MESSAGE("%s", ProcessMessage(Msg
).CStr());
5639 void character::BeTalkedTo () {
5641 if (GetRelation(PLAYER
) == HOSTILE
)
5642 ProcessAndAddMessage(GetHostileReplies()[RandomizeReply(Said
, GetHostileReplies().Size
)]);
5644 ProcessAndAddMessage(GetFriendlyReplies()[RandomizeReply(Said
, GetFriendlyReplies().Size
)]);
5648 truth
character::CheckZap () {
5650 ADD_MESSAGE("This monster type can't zap.");
5657 void character::DamageAllItems (character
*Damager
, int Damage
, int Type
) {
5658 GetStack()->ReceiveDamage(Damager
, Damage
, Type
);
5659 for (int c
= 0; c
< GetEquipments(); ++c
) {
5660 item
*Equipment
= GetEquipment(c
);
5661 if (Equipment
) Equipment
->ReceiveDamage(Damager
, Damage
, Type
);
5666 truth
character::Equips (citem
*Item
) const {
5667 return combineequipmentpredicateswithparam
<feuLong
>()(this, &item::HasID
, Item
->GetID(), 1);
5671 void character::PrintBeginConfuseMessage () const {
5672 if (IsPlayer()) ADD_MESSAGE("You feel quite happy.");
5676 void character::PrintEndConfuseMessage () const {
5677 if (IsPlayer()) ADD_MESSAGE("The world is boring again.");
5681 v2
character::ApplyStateModification (v2 TryDirection
) const {
5682 if (!StateIsActivated(CONFUSED
) || RAND() & 15 || game::IsInWilderness()) return TryDirection
;
5683 v2 To
= GetLevel()->GetFreeAdjacentSquare(this, GetPos(), true);
5684 if (To
== ERROR_V2
) return TryDirection
;
5686 if (To
!= TryDirection
&& IsPlayer()) ADD_MESSAGE("Whoa! You somehow don't manage to walk straight.");
5691 void character::AddConfuseHitMessage () const {
5692 if (IsPlayer()) ADD_MESSAGE("This stuff is confusing.");
5696 item
*character::SelectFromPossessions (cfestring
&Topic
, sorter Sorter
) {
5697 itemvector ReturnVector
;
5698 SelectFromPossessions(ReturnVector
, Topic
, NO_MULTI_SELECT
, Sorter
);
5699 return !ReturnVector
.empty() ? ReturnVector
[0] : 0;
5703 truth
character::SelectFromPossessions (itemvector
&ReturnVector
, cfestring
&Topic
, int Flags
, sorter Sorter
) {
5705 truth InventoryPossible
= GetStack()->SortedItems(this, Sorter
);
5706 if (InventoryPossible
) List
.AddEntry(CONST_S("choose from inventory"), LIGHT_GRAY
, 20, game::AddToItemDrawVector(itemvector()));
5711 for (c
= 0; c
< BodyParts
; ++c
) {
5712 bodypart
*BodyPart
= GetBodyPart(c
);
5713 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) {
5714 Item
.push_back(BodyPart
);
5716 BodyPart
->AddName(Entry
, UNARTICLED
);
5717 int ImageKey
= game::AddToItemDrawVector(itemvector(1, BodyPart
));
5718 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
5722 for (c
= 0; c
< GetEquipments(); ++c
) {
5723 item
*Equipment
= GetEquipment(c
);
5724 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) {
5725 Item
.push_back(Equipment
);
5726 Entry
= GetEquipmentName(c
);
5729 Equipment
->AddInventoryEntry(this, Entry
, 1, true);
5730 AddSpecialEquipmentInfo(Entry
, c
);
5731 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
5732 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
5737 game::SetStandardListAttributes(List
);
5738 List
.SetFlags(SELECTABLE
|DRAW_BACKGROUND_AFTERWARDS
);
5739 List
.SetEntryDrawer(game::ItemEntryDrawer
);
5740 game::DrawEverythingNoBlit();
5741 int Chosen
= List
.Draw();
5742 game::ClearItemDrawVector();
5743 if (Chosen
!= ESCAPED
) {
5744 if ((InventoryPossible
&& !Chosen
) || Chosen
& FELIST_ERROR_BIT
) {
5745 GetStack()->DrawContents(ReturnVector
, this, Topic
, Flags
, Sorter
);
5747 ReturnVector
.push_back(Item
[InventoryPossible
? Chosen
- 1 : Chosen
]);
5748 if (Flags
& SELECT_PAIR
&& ReturnVector
[0]->HandleInPairs()) {
5749 item
*PairEquipment
= GetPairEquipment(ReturnVector
[0]->GetEquipmentIndex());
5750 if (PairEquipment
&& PairEquipment
->CanBePiledWith(ReturnVector
[0], this)) ReturnVector
.push_back(PairEquipment
);
5755 if (!GetStack()->SortedItems(this, Sorter
)) return false;
5756 game::ClearItemDrawVector();
5757 GetStack()->DrawContents(ReturnVector
, this, Topic
, Flags
, Sorter
);
5763 truth
character::EquipsSomething (sorter Sorter
) {
5764 for (int c
= 0; c
< GetEquipments(); ++c
) {
5765 item
*Equipment
= GetEquipment(c
);
5766 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) return true;
5772 material
*character::CreateBodyPartMaterial (int, sLong Volume
) const {
5773 return MAKE_MATERIAL(GetFleshMaterial(), Volume
);
5777 truth
character::CheckTalk () {
5779 ADD_MESSAGE("This monster does not know the art of talking.");
5786 truth
character::MoveTowardsHomePos () {
5787 if (HomeDataIsValid() && IsEnabled()) {
5788 SetGoingTo(HomeData
->Pos
);
5789 return MoveTowardsTarget(false) || (!GetPos().IsAdjacent(HomeData
->Pos
) && MoveRandomly());
5795 int character::TryToChangeEquipment (stack
*MainStack
, stack
*SecStack
, int Chosen
) {
5796 if (!GetBodyPartOfEquipment(Chosen
)) {
5797 ADD_MESSAGE("Bodypart missing!");
5801 item
*OldEquipment
= GetEquipment(Chosen
);
5802 if (!IsPlayer() && BoundToUse(OldEquipment
, Chosen
)) {
5803 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE
), OldEquipment
->CHAR_NAME(DEFINITE
));
5806 if (OldEquipment
) OldEquipment
->MoveTo(MainStack
);
5808 sorter Sorter
= EquipmentSorter(Chosen
);
5809 if (!MainStack
->SortedItems(this, Sorter
) && (!SecStack
|| !SecStack
->SortedItems(this, Sorter
))) {
5810 ADD_MESSAGE("You haven't got any item that could be used for this purpose.");
5814 game::DrawEverythingNoBlit();
5815 itemvector ItemVector
;
5816 int Return
= MainStack
->DrawContents(ItemVector
, SecStack
, this,
5817 CONST_S("Choose ")+GetEquipmentName(Chosen
)+':',
5818 (SecStack
? CONST_S("Items in your inventory") : CONST_S("")),
5819 (SecStack
? festring(CONST_S("Items in ")+GetPossessivePronoun()+" inventory") : CONST_S("")),
5820 (SecStack
? festring(GetDescription(DEFINITE
)+" is "+GetVerbalBurdenState()) : CONST_S("")),
5821 GetVerbalBurdenStateColor(),
5822 NONE_AS_CHOICE
|NO_MULTI_SELECT
|SELECT_PAIR
|SKIP_FIRST_IF_NO_OLD
|SELECT_MOST_RECENT
,
5823 Sorter
, OldEquipment
);
5824 if (Return
== ESCAPED
) {
5826 OldEquipment
->RemoveFromSlot();
5827 SetEquipment(Chosen
, OldEquipment
);
5832 item
*Item
= (ItemVector
.empty() ? 0 : ItemVector
[0]);
5833 int otherChosen
= -1;
5836 if (!IsPlayer() && !AllowEquipment(Item
, Chosen
)) {
5837 ADD_MESSAGE("%s refuses to equip %s.", CHAR_DESCRIPTION(DEFINITE
), Item
->CHAR_NAME(DEFINITE
));
5840 if (ItemVector
[0]->HandleInPairs() && ItemVector
.size() > 1) {
5842 case RIGHT_GAUNTLET_INDEX
: otherChosen
= LEFT_GAUNTLET_INDEX
; break;
5843 case LEFT_GAUNTLET_INDEX
: otherChosen
= RIGHT_GAUNTLET_INDEX
; break;
5844 case RIGHT_BOOT_INDEX
: otherChosen
= LEFT_BOOT_INDEX
; break;
5845 case LEFT_BOOT_INDEX
: otherChosen
= RIGHT_BOOT_INDEX
; break;
5848 if (otherChosen
!= -1) {
5849 if (GetBodyPartOfEquipment(otherChosen
)) {
5850 if (!game::TruthQuestion("Do you want to wear both items?", NO
)) otherChosen
= -1;
5856 // wear/wield first item
5857 Item
->RemoveFromSlot();
5858 SetEquipment(Chosen
, Item
);
5859 if (CheckIfEquipmentIsNotUsable(Chosen
)) { Item
->MoveTo(MainStack
); Item
= 0; otherChosen
= -1; } // small bug?
5860 // wear/wield possible second item
5861 if (Item
&& otherChosen
!= -1 && ItemVector
[0]->HandleInPairs() && ItemVector
.size() > 1 && GetBodyPartOfEquipment(otherChosen
)) {
5862 item
*otherOld
= GetEquipment(otherChosen
);
5863 if (otherOld
&& !IsPlayer() && BoundToUse(otherOld
, otherChosen
)) {
5864 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE
), otherOld
->CHAR_NAME(DEFINITE
));
5866 } else if (otherOld
) {
5867 otherOld
->MoveTo(MainStack
);
5869 if (otherChosen
!= -1) {
5870 ItemVector
[1]->RemoveFromSlot();
5871 SetEquipment(otherChosen
, ItemVector
[1]);
5872 if (CheckIfEquipmentIsNotUsable(otherChosen
)) { ItemVector
[1]->MoveTo(MainStack
); otherChosen
= -1; } // small bug?
5877 return (Item
!= OldEquipment
? (otherChosen
!= -1 ? 2 : 1) : 0);
5881 void character::PrintBeginParasitizedMessage () const {
5882 if (IsPlayer()) ADD_MESSAGE("You feel you are no longer alone.");
5886 void character::PrintEndParasitizedMessage () const {
5887 if (IsPlayer()) ADD_MESSAGE("A feeling of long welcome emptiness overwhelms you.");
5891 void character::ParasitizedHandler () {
5893 if (!(RAND() % 250)) {
5894 if (IsPlayer()) ADD_MESSAGE("Ugh. You feel something violently carving its way through your intestines.");
5895 ReceiveDamage(0, 1, POISON
, TORSO
, 8, false, false, false, false);
5896 CheckDeath(CONST_S("killed by a vile parasite"), 0);
5901 truth
character::CanFollow () const {
5902 return CanMove() && !StateIsActivated(PANIC
) && !IsStuck();
5906 festring
character::GetKillName () const {
5907 if (!GetPolymorphBackup()) return GetName(INDEFINITE
);
5909 GetPolymorphBackup()->AddName(KillName
, INDEFINITE
);
5910 KillName
<< " polymorphed into ";
5911 id::AddName(KillName
, INDEFINITE
);
5916 festring
character::GetPanelName () const {
5918 Name
<< AssignedName
<< " the " << game::GetVerbalPlayerAlignment() << ' ';
5919 id::AddName(Name
, UNARTICLED
);
5924 sLong
character::GetMoveAPRequirement (int Difficulty
) const {
5925 return (!StateIsActivated(PANIC
) ? 10000000 : 8000000) * Difficulty
/ (APBonus(GetAttribute(AGILITY
)) * GetMoveEase());
5929 bodypart
*character::HealHitPoint() {
5930 int NeedHeal
= 0, NeedHealIndex
[MAX_BODYPARTS
];
5931 for (int c
= 0; c
< BodyParts
; ++c
) {
5932 bodypart
*BodyPart
= GetBodyPart(c
);
5933 if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < BodyPart
->GetMaxHP()) NeedHealIndex
[NeedHeal
++] = c
;
5936 bodypart
*BodyPart
= GetBodyPart(NeedHealIndex
[RAND() % NeedHeal
]);
5937 BodyPart
->IncreaseHP();
5945 void character::CreateHomeData () {
5946 HomeData
= new homedata
;
5947 lsquare
*Square
= GetLSquareUnder();
5948 HomeData
->Pos
= Square
->GetPos();
5949 HomeData
->Dungeon
= Square
->GetDungeonIndex();
5950 HomeData
->Level
= Square
->GetLevelIndex();
5951 HomeData
->Room
= Square
->GetRoomIndex();
5955 room
*character::GetHomeRoom() const {
5956 if (HomeDataIsValid() && HomeData
->Room
) return GetLevel()->GetRoom(HomeData
->Room
);
5961 void character::RemoveHomeData () {
5967 void character::AddESPConsumeMessage () const {
5968 if (IsPlayer()) ADD_MESSAGE("You feel a strange mental activity.");
5972 void character::SetBodyPart (int I
, bodypart
*What
) {
5973 BodyPartSlot
[I
].PutInItem(What
);
5975 What
->SignalPossibleUsabilityChange();
5977 AddOriginalBodyPartID(I
, What
->GetID());
5978 if (What
->GetMainMaterial()->IsInfectedByLeprosy()) GainIntrinsic(LEPROSY
);
5979 else if (StateIsActivated(LEPROSY
)) What
->GetMainMaterial()->SetIsInfectedByLeprosy(true);
5984 truth
character::ConsumeItem (item
*Item
, cfestring
&ConsumeVerb
) {
5985 if (IsPlayer() && HasHadBodyPart(Item
) && !game::TruthQuestion(CONST_S("Are you sure? You may be able to put it back..."))) {
5988 if (Item
->IsOnGround() && GetRoom() && !GetRoom()->ConsumeItem(this, Item
, 1)) {
5991 if (IsPlayer()) ADD_MESSAGE("You begin %s %s.", ConsumeVerb
.CStr(), Item
->CHAR_NAME(DEFINITE
));
5992 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins %s %s.", CHAR_NAME(DEFINITE
), ConsumeVerb
.CStr(), Item
->CHAR_NAME(DEFINITE
));
5993 consume
*Consume
= consume::Spawn(this);
5994 Consume
->SetDescription(ConsumeVerb
);
5995 Consume
->SetConsumingID(Item
->GetID());
6002 truth
character::CheckThrow () const {
6004 ADD_MESSAGE("This monster type cannot throw.");
6011 void character::GetHitByExplosion (const explosion
*Explosion
, int Damage
) {
6012 int DamageDirection
= GetPos() == Explosion
->Pos
? RANDOM_DIR
: game::CalculateRoughDirection(GetPos() - Explosion
->Pos
);
6013 if (!IsPet() && Explosion
->Terrorist
&& Explosion
->Terrorist
->IsPet()) Explosion
->Terrorist
->Hostility(this);
6014 GetTorso()->SpillBlood((8 - Explosion
->Size
+ RAND() % (8 - Explosion
->Size
)) >> 1);
6015 if (DamageDirection
== RANDOM_DIR
) DamageDirection
= RAND()&7;
6016 v2 SpillPos
= GetPos() + game::GetMoveVector(DamageDirection
);
6017 if (SquareUnder
[0] && GetArea()->IsValidPos(SpillPos
)) GetTorso()->SpillBlood((8-Explosion
->Size
+RAND()%(8-Explosion
->Size
))>>1, SpillPos
);
6018 if (IsPlayer()) ADD_MESSAGE("You are hit by the explosion!");
6019 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is hit by the explosion.", CHAR_NAME(DEFINITE
));
6020 truth WasUnconscious
= GetAction() && GetAction()->IsUnconsciousness();
6021 ReceiveDamage(Explosion
->Terrorist
, Damage
>> 1, FIRE
, ALL
, DamageDirection
, true, false, false, false);
6023 ReceiveDamage(Explosion
->Terrorist
, Damage
>> 1, PHYSICAL_DAMAGE
, ALL
, DamageDirection
, true, false, false, false);
6024 CheckDeath(Explosion
->DeathMsg
, Explosion
->Terrorist
, !WasUnconscious
? IGNORE_UNCONSCIOUSNESS
: 0);
6029 void character::SortAllItems (const sortdata
&SortData
) {
6030 GetStack()->SortAllItems(SortData
);
6031 doforequipmentswithparam
<const sortdata
&>()(this, &item::SortAllItems
, SortData
);
6035 void character::PrintBeginSearchingMessage () const {
6036 if (IsPlayer()) ADD_MESSAGE("You feel you can now notice even the very smallest details around you.");
6040 void character::PrintEndSearchingMessage () const {
6041 if (IsPlayer()) ADD_MESSAGE("You feel less perceptive.");
6045 void character::SearchingHandler () {
6046 if (!game::IsInWilderness()) Search(15);
6050 void character::Search (int Perception
) {
6051 for (int d
= 0; d
< GetExtendedNeighbourSquares(); ++d
) {
6052 lsquare
*LSquare
= GetNeighbourLSquare(d
);
6053 if (LSquare
) LSquare
->GetStack()->Search(this, Min(Perception
, 200));
6058 // surprisingly returns 0 if fails
6059 character
*character::GetRandomNeighbour (int RelationFlags
) const {
6060 character
*Chars
[MAX_NEIGHBOUR_SQUARES
];
6062 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
6063 lsquare
*LSquare
= GetNeighbourLSquare(d
);
6065 character
*Char
= LSquare
->GetCharacter();
6066 if (Char
&& (GetRelation(Char
) & RelationFlags
)) Chars
[Index
++] = Char
;
6069 return Index
? Chars
[RAND() % Index
] : 0;
6073 void character::ResetStates () {
6074 for (int c
= 0; c
< STATES
; ++c
) {
6075 if (1 << c
!= POLYMORPHED
&& TemporaryStateIsActivated(1 << c
) && TemporaryStateCounter
[c
] != PERMANENT
) {
6076 TemporaryState
&= ~(1 << c
);
6077 if (StateData
[c
].EndHandler
) {
6078 (this->*StateData
[c
].EndHandler
)();
6079 if (!IsEnabled())return;
6086 void characterdatabase::InitDefaults (const characterprototype
*NewProtoType
, int NewConfig
, cfestring
&acfgstrname
) {
6088 ProtoType
= NewProtoType
;
6090 CfgStrName
= acfgstrname
;
6095 void character::PrintBeginGasImmunityMessage () const {
6096 if (IsPlayer()) ADD_MESSAGE("All smells fade away.");
6100 void character::PrintEndGasImmunityMessage () const {
6101 if (IsPlayer()) ADD_MESSAGE("Yuck! The world smells bad again.");
6105 void character::ShowAdventureInfo () const {
6106 static const char *lists
[4][4] = {
6107 { "Show massacre history",
6109 "Show message history",
6112 "Show message history",
6115 { "Show message history",
6119 { "Show massacre history",
6120 "Show message history",
6124 // massacre, inventory, messages
6125 static const int nums
[4][3] = {
6132 if (GetStack()->GetItems()) {
6133 idx
= game::MassacreListsEmpty() ? 1 : 0;
6135 idx
= game::MassacreListsEmpty() ? 2 : 3;
6139 sel
= game::ListSelectorArray(sel
, CONST_S("Do you want to see some funny history?"), lists
[idx
]);
6141 if (sel
== nums
[idx
][0] && !game::MassacreListsEmpty()) {
6142 game::DisplayMassacreLists();
6144 if (sel
== nums
[idx
][1] && GetStack()->GetItems()) {
6145 GetStack()->DrawContents(this, CONST_S("Your inventory"), NO_SELECT
);
6146 for(stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) i
->DrawContents(this);
6147 doforequipmentswithparam
<ccharacter
*>()(this, &item::DrawContents
, this);
6149 if (sel
== nums
[idx
][2]) {
6150 msgsystem::DrawMessageHistory();
6156 truth
character::EditAllAttributes (int Amount
) {
6157 if (!Amount
) return true;
6159 truth MayEditMore
= false;
6160 for (c
= 0; c
< BodyParts
; ++c
) {
6161 bodypart
*BodyPart
= GetBodyPart(c
);
6162 if (BodyPart
&& BodyPart
->EditAllAttributes(Amount
)) MayEditMore
= true;
6164 for (c
= 0; c
< BASE_ATTRIBUTES
; ++c
) {
6165 if (BaseExperience
[c
]) {
6166 BaseExperience
[c
] += Amount
* EXP_MULTIPLIER
;
6167 LimitRef(BaseExperience
[c
], MIN_EXP
, MAX_EXP
);
6168 if ((Amount
< 0 && BaseExperience
[c
] != MIN_EXP
) || (Amount
> 0 && BaseExperience
[c
] != MAX_EXP
)) MayEditMore
= true;
6175 game::SendLOSUpdateRequest();
6178 if (IsPlayerKind()) UpdatePictures();
6184 void character::AddAttributeInfo (festring
&Entry
) const {
6186 Entry
<< GetAttribute(ENDURANCE
);
6188 Entry
<< GetAttribute(PERCEPTION
);
6190 Entry
<< GetAttribute(INTELLIGENCE
);
6192 Entry
<< GetAttribute(WISDOM
);
6194 Entry
<< GetAttribute(CHARISMA
);
6196 Entry
<< GetAttribute(MANA
);
6200 void character::AddDefenceInfo (felist
&List
) const {
6202 for (int c
= 0; c
< BodyParts
; ++c
) {
6203 bodypart
*BodyPart
= GetBodyPart(c
);
6205 Entry
= CONST_S(" ");
6206 BodyPart
->AddName(Entry
, UNARTICLED
);
6208 Entry
<< BodyPart
->GetMaxHP();
6210 Entry
<< BodyPart
->GetTotalResistance(PHYSICAL_DAMAGE
);
6211 List
.AddEntry(Entry
, LIGHT_GRAY
);
6217 void character::DetachBodyPart () {
6218 ADD_MESSAGE("You haven't got any extra bodyparts.");
6223 void character::ReceiveHolyBanana (sLong Amount
) {
6225 EditExperience(ARM_STRENGTH
, Amount
, 1 << 13);
6226 EditExperience(LEG_STRENGTH
, Amount
, 1 << 13);
6227 EditExperience(DEXTERITY
, Amount
, 1 << 13);
6228 EditExperience(AGILITY
, Amount
, 1 << 13);
6229 EditExperience(ENDURANCE
, Amount
, 1 << 13);
6230 EditExperience(PERCEPTION
, Amount
, 1 << 13);
6231 EditExperience(INTELLIGENCE
, Amount
, 1 << 13);
6232 EditExperience(WISDOM
, Amount
, 1 << 13);
6233 EditExperience(CHARISMA
, Amount
, 1 << 13);
6238 void character::AddHolyBananaConsumeEndMessage () const {
6239 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
6240 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE
));
6244 void character::ReceiveHolyMango (sLong Amount
) {
6246 EditExperience(ARM_STRENGTH
, Amount
, 1 << 13);
6247 EditExperience(LEG_STRENGTH
, Amount
, 1 << 13);
6248 EditExperience(DEXTERITY
, Amount
, 1 << 13);
6249 EditExperience(AGILITY
, Amount
, 1 << 13);
6250 EditExperience(ENDURANCE
, Amount
, 1 << 13);
6251 EditExperience(PERCEPTION
, Amount
, 1 << 13);
6252 EditExperience(INTELLIGENCE
, Amount
, 1 << 13);
6253 EditExperience(WISDOM
, Amount
, 1 << 13);
6254 EditExperience(CHARISMA
, Amount
, 1 << 13);
6259 void character::AddHolyMangoConsumeEndMessage () const {
6260 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
6261 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE
));
6265 truth
character::PreProcessForBone () {
6266 if (IsPet() && IsEnabled()) {
6267 Die(0, CONST_S(""), FORBID_REINCARNATION
);
6270 if (GetAction()) GetAction()->Terminate(false);
6271 if (TemporaryStateIsActivated(POLYMORPHED
)) {
6272 character
*PolymorphBackup
= GetPolymorphBackup();
6274 PolymorphBackup
->PreProcessForBone();
6277 if (MustBeRemovedFromBone()) return false;
6278 if (IsUnique() && !CanBeGenerated()) game::SignalQuestMonsterFound();
6282 GetStack()->PreProcessForBone();
6283 doforequipments()(this, &item::PreProcessForBone
);
6284 doforbodyparts()(this, &bodypart::PreProcessForBone
);
6285 game::RemoveCharacterID(ID
);
6287 game::AddCharacterID(this, ID
);
6292 truth
character::PostProcessForBone (double &DangerSum
, int& Enemies
) {
6293 if (PostProcessForBone()) {
6294 if (GetRelation(PLAYER
) == HOSTILE
) {
6295 double Danger
= GetRelativeDanger(PLAYER
, true);
6296 if (Danger
> 99.0) game::SetTooGreatDangerFound(true);
6297 else if (!IsUnique() && !IgnoreDanger()) {
6298 DangerSum
+= Danger
;
6308 truth
character::PostProcessForBone () {
6309 feuLong NewID
= game::CreateNewCharacterID(this);
6310 game::GetBoneCharacterIDMap().insert(std::make_pair(-ID
, NewID
));
6311 game::RemoveCharacterID(ID
);
6313 if (IsUnique() && CanBeGenerated()) {
6314 if (DataBase
->Flags
& HAS_BEEN_GENERATED
) return false;
6317 GetStack()->PostProcessForBone();
6318 doforequipments()(this, &item::PostProcessForBone
);
6319 doforbodyparts()(this, &bodypart::PostProcessForBone
);
6324 void character::FinalProcessForBone () {
6326 GetStack()->FinalProcessForBone();
6327 doforequipments()(this, &item::FinalProcessForBone
);
6329 for (c
= 0; c
< BodyParts
; ++c
) {
6330 for (std::list
<feuLong
>::iterator i
= OriginalBodyPartID
[c
].begin(); i
!= OriginalBodyPartID
[c
].end();) {
6331 boneidmap::iterator BI
= game::GetBoneItemIDMap().find(*i
);
6332 if (BI
== game::GetBoneItemIDMap().end()) {
6333 std::list
<feuLong
>::iterator Dirt
= i
++;
6334 OriginalBodyPartID
[c
].erase(Dirt
);
6344 void character::SetSoulID (feuLong What
) {
6345 if (GetPolymorphBackup()) GetPolymorphBackup()->SetSoulID(What
);
6349 truth
character::SearchForItem (citem
*Item
) const {
6350 if (combineequipmentpredicateswithparam
<feuLong
>()(this, &item::HasID
, Item
->GetID(), 1)) return true;
6351 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (*i
== Item
) return true;
6356 item
*character::SearchForItem (const sweaponskill
*SWeaponSkill
) const {
6357 for (int c
= 0; c
< GetEquipments(); ++c
) {
6358 item
*Equipment
= GetEquipment(c
);
6359 if (Equipment
&& SWeaponSkill
->IsSkillOf(Equipment
)) return Equipment
;
6361 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (SWeaponSkill
->IsSkillOf(*i
)) return *i
;
6366 truth
character::PutNear (v2 Pos
, truth allowFail
) {
6367 v2 NewPos
= game::GetCurrentLevel()->GetNearestFreeSquare(this, Pos
, false);
6368 if (NewPos
== ERROR_V2
) {
6370 NewPos
= game::GetCurrentLevel()->GetRandomSquare(this, 0, nullptr, NewPos
);
6371 if (NewPos
== ERROR_V2
) {
6372 if (allowFail
) return false;
6373 ABORT("Oops... Out of free squares in `character::PutNear()`!");
6375 } while (NewPos
== Pos
);
6382 truth
character::PutToOrNearNoTeleport (v2 pos
) {
6383 if (!game::IsInWilderness()) {
6384 // get resulting position; starting position is allowed
6385 pos
= game::GetCurrentLevel()->GetNearestFreeSquare(this, pos
, true);
6387 if (pos
== ERROR_V2
) return false;
6393 truth
character::PutNearNoTeleport (v2 pos
) {
6394 if (!game::IsInWilderness()) {
6395 // get resulting position; starting position is allowed
6396 pos
= game::GetCurrentLevel()->GetNearestFreeSquare(this, pos
, false);
6398 if (pos
== ERROR_V2
) return false;
6404 truth
character::PutToOrNear (v2 Pos
, truth allowFail
) {
6405 if (game::IsInWilderness() || (CanMoveOn(game::GetCurrentLevel()->GetLSquare(Pos
)) && IsFreeForMe(game::GetCurrentLevel()->GetLSquare(Pos
)))) {
6409 return PutNear(Pos
, allowFail
);
6414 void character::PutTo (v2 Pos
) {
6415 SquareUnder
[0] = game::GetCurrentArea()->GetSquare(Pos
);
6416 SquareUnder
[0]->AddCharacter(this);
6420 void character::Remove () {
6421 SquareUnder
[0]->RemoveCharacter();
6426 void character::SendNewDrawRequest () const {
6427 for (int c
= 0; c
< SquaresUnder
; ++c
) {
6428 square
*Square
= GetSquareUnder(c
);
6429 if (Square
) Square
->SendNewDrawRequest();
6434 truth
character::IsOver (v2 Pos
) const {
6435 for (int c
= 0; c
< SquaresUnder
; ++c
) {
6436 square
*Square
= GetSquareUnder(c
);
6437 if (Square
&& Square
->GetPos() == Pos
) return true;
6443 truth
character::CanTheoreticallyMoveOn (const lsquare
*LSquare
) const { return GetMoveType() & LSquare
->GetTheoreticalWalkability(); }
6444 truth
character::CanMoveOn (const lsquare
*LSquare
) const { return GetMoveType() & LSquare
->GetWalkability(); }
6445 truth
character::CanMoveOn (const square
*Square
) const { return GetMoveType() & Square
->GetSquareWalkability(); }
6446 truth
character::CanMoveOn (const olterrain
*OLTerrain
) const { return GetMoveType() & OLTerrain
->GetWalkability(); }
6447 truth
character::CanMoveOn (const oterrain
*OTerrain
) const { return GetMoveType() & OTerrain
->GetWalkability(); }
6448 truth
character::IsFreeForMe(square
*Square
) const { return !Square
->GetCharacter() || Square
->GetCharacter() == this; }
6449 void character::LoadSquaresUnder () { SquareUnder
[0] = game::GetSquareInLoad(); }
6451 truth
character::AttackAdjacentEnemyAI () {
6452 if (!IsEnabled()) return false;
6453 character
*Char
[MAX_NEIGHBOUR_SQUARES
];
6454 v2 Pos
[MAX_NEIGHBOUR_SQUARES
];
6455 int Dir
[MAX_NEIGHBOUR_SQUARES
];
6457 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
6458 square
*Square
= GetNeighbourSquare(d
);
6460 character
*Enemy
= Square
->GetCharacter();
6461 if (Enemy
&& (GetRelation(Enemy
) == HOSTILE
|| StateIsActivated(CONFUSED
))) {
6463 Pos
[Index
] = Square
->GetPos();
6464 Char
[Index
++] = Enemy
;
6469 int ChosenIndex
= RAND() % Index
;
6470 Hit(Char
[ChosenIndex
], Pos
[ChosenIndex
], Dir
[ChosenIndex
]);
6477 void character::SignalStepFrom (lsquare
**OldSquareUnder
) {
6479 lsquare
*NewSquareUnder
[MAX_SQUARES_UNDER
];
6480 for (c
= 0; c
< GetSquaresUnder(); ++c
) NewSquareUnder
[c
] = GetLSquareUnder(c
);
6481 for (c
= 0; c
< GetSquaresUnder(); ++c
) {
6482 if (IsEnabled() && GetLSquareUnder(c
) == NewSquareUnder
[c
]) NewSquareUnder
[c
]->StepOn(this, OldSquareUnder
);
6487 int character::GetSumOfAttributes () const {
6488 return GetAttribute(ENDURANCE
) + GetAttribute(PERCEPTION
) + GetAttribute(INTELLIGENCE
) + GetAttribute(WISDOM
) + GetAttribute(CHARISMA
) + GetAttribute(ARM_STRENGTH
) + GetAttribute(AGILITY
);
6492 void character::IntelligenceAction (int Difficulty
) {
6493 EditAP(-20000 * Difficulty
/ APBonus(GetAttribute(INTELLIGENCE
)));
6494 EditExperience(INTELLIGENCE
, Difficulty
* 15, 1 << 7);
6498 struct walkabilitycontroller
{
6499 static truth
Handler (int x
, int y
) {
6500 return x
>= 0 && y
>= 0 && x
< LevelXSize
&& y
< LevelYSize
&& Map
[x
][y
]->GetTheoreticalWalkability() & MoveType
;
6502 static lsquare
***Map
;
6503 static int LevelXSize
, LevelYSize
;
6504 static int MoveType
;
6508 lsquare
***walkabilitycontroller::Map
;
6509 int walkabilitycontroller::LevelXSize
, walkabilitycontroller::LevelYSize
;
6510 int walkabilitycontroller::MoveType
;
6513 truth
character::CreateRoute () {
6515 if (GetAttribute(INTELLIGENCE
) >= 10 && !StateIsActivated(CONFUSED
)) {
6517 walkabilitycontroller::Map
= GetLevel()->GetMap();
6518 walkabilitycontroller::LevelXSize
= GetLevel()->GetXSize();
6519 walkabilitycontroller::LevelYSize
= GetLevel()->GetYSize();
6520 walkabilitycontroller::MoveType
= GetMoveType();
6522 for (int c
= 0; c
< game::GetTeams(); ++c
)
6523 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
6524 character
*Char
= *i
;
6525 if (Char
->IsEnabled() && !Char
->Route
.empty() && (Char
->GetMoveType()&GetMoveType()) == Char
->GetMoveType()) {
6526 v2 CharGoingTo
= Char
->Route
[0];
6527 v2 iPos
= Char
->Route
.back();
6528 if ((GoingTo
-CharGoingTo
).GetLengthSquare() <= 100 && (Pos
- iPos
).GetLengthSquare() <= 100 &&
6529 mapmath
<walkabilitycontroller
>::DoLine(CharGoingTo
.X
, CharGoingTo
.Y
, GoingTo
.X
, GoingTo
.Y
, SKIP_FIRST
|LINE_BOTH_DIRS
) &&
6530 mapmath
<walkabilitycontroller
>::DoLine(Pos
.X
, Pos
.Y
, iPos
.X
, iPos
.Y
, SKIP_FIRST
|LINE_BOTH_DIRS
)) {
6531 if (!Illegal
.empty() && Illegal
.find(Char
->Route
.back()) != Illegal
.end()) continue;
6532 Node
= GetLevel()->FindRoute(CharGoingTo
, GoingTo
, Illegal
, GetMoveType());
6533 if (Node
) { while(Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
6534 else { Route
.clear(); continue; }
6535 Route
.insert(Route
.end(), Char
->Route
.begin(), Char
->Route
.end());
6536 Node
= GetLevel()->FindRoute(Pos
, iPos
, Illegal
, GetMoveType());
6537 if (Node
) { while (Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
6538 else { Route
.clear(); continue; }
6539 IntelligenceAction(1);
6544 Node
= GetLevel()->FindRoute(Pos
, GoingTo
, Illegal
, GetMoveType());
6545 if (Node
) { while(Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
6546 else TerminateGoingTo();
6547 IntelligenceAction(5);
6554 void character::SetGoingTo (v2 What
) {
6555 if (GoingTo
!= What
) {
6563 void character::TerminateGoingTo () {
6570 truth
character::CheckForFood (int Radius
) {
6571 if (StateIsActivated(PANIC
) || !UsesNutrition() || !IsEnabled()) return false;
6574 for (int r
= 1; r
<= Radius
; ++r
) {
6577 for (y
= Pos
.Y
-r
; y
<= Pos
.Y
+r
; ++y
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6580 if (x
< GetLevel()->GetXSize()) {
6581 for (y
= Pos
.Y
-r
; y
<= Pos
.Y
+r
; ++y
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6585 for (x
= Pos
.X
-r
; x
<= Pos
.X
+r
; ++x
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6588 if (y
< GetLevel()->GetYSize()) {
6589 for (x
= Pos
.X
-r
; x
<= Pos
.X
+r
; ++x
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6596 truth
character::CheckForFoodInSquare (v2 Pos
) {
6597 level
*Level
= GetLevel();
6598 if (Level
->IsValidPos(Pos
)) {
6599 lsquare
*Square
= Level
->GetLSquare(Pos
);
6600 stack
*Stack
= Square
->GetStack();
6601 if (Stack
->GetItems()) {
6602 for (stackiterator i
= Stack
->GetBottom(); i
.HasItem(); ++i
) {
6603 if (i
->IsPickable(this) && i
->CanBeSeenBy(this) && i
->CanBeEatenByAI(this) && (!Square
->GetRoomIndex() || Square
->GetRoom()->AllowFoodSearch())) {
6605 return MoveTowardsTarget(false);
6614 void character::SetConfig (int NewConfig
, int SpecialFlags
) {
6615 databasecreator
<character
>::InstallDataBase(this, NewConfig
);
6618 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdatePictures();
6622 truth
character::IsOver (citem
*Item
) const {
6623 for (int c1
= 0; c1
< Item
->GetSquaresUnder(); ++c1
)
6624 for (int c2
= 0; c2
< SquaresUnder
; ++c2
)
6625 if (Item
->GetPos(c1
) == GetPos(c2
)) return true;
6630 truth
character::CheckConsume (cfestring
&Verb
) const {
6631 if (!UsesNutrition()) {
6632 if (IsPlayer()) ADD_MESSAGE("In this form you can't and don't need to %s.", Verb
.CStr());
6639 void character::PutTo (lsquare
*To
) {
6640 PutTo(To
->GetPos());
6644 double character::RandomizeBabyExperience (double SumE
) {
6645 if (!SumE
) return 0;
6646 double E
= (SumE
/ 4) - (SumE
/ 32) + (double(RAND()) / MAX_RAND
) * (SumE
/ 16 + 1);
6647 return Limit(E
, MIN_EXP
, MAX_EXP
);
6651 liquid
*character::CreateBlood (sLong Volume
) const {
6652 return liquid::Spawn(GetBloodMaterial(), Volume
);
6656 void character::SpillFluid (character
*Spiller
, liquid
*Liquid
, int SquareIndex
) {
6657 //fprintf(stderr, "character(%s:%d)::SpillFluid: Liquid->GetName(0):<%s>\n", GetTypeID(), GetConfig(), Liquid->GetName(false, false).CStr());
6658 sLong ReserveVolume
= Liquid
->GetVolume() >> 1;
6659 Liquid
->EditVolume(-ReserveVolume
);
6660 //fprintf(stderr, "character::SpillFluid: Liquid->GetName(1):<%s>\n", Liquid->GetName(false, false).CStr());
6661 GetStack()->SpillFluid(Spiller
, Liquid
, sLong(Liquid
->GetVolume() * sqrt(double(GetStack()->GetVolume()) / GetVolume())));
6662 //fprintf(stderr, "character::SpillFluid: Liquid->GetName(2):<%s>\n", Liquid->GetName(false, false).CStr());
6663 Liquid
->EditVolume(ReserveVolume
);
6664 //fprintf(stderr, "character::SpillFluid: Liquid->GetName(3):<%s>\n", Liquid->GetName(false, false).CStr());
6665 sLong Modifier
[MAX_BODYPARTS
], ModifierSum
= 0;
6666 for (int c
= 0; c
< BodyParts
; ++c
) {
6667 if (GetBodyPart(c
)) {
6668 Modifier
[c
] = sLong(sqrt(GetBodyPart(c
)->GetVolume()));
6669 if (Modifier
[c
]) Modifier
[c
] *= 1 + (RAND() & 3);
6670 ModifierSum
+= Modifier
[c
];
6675 for (int c
= 1; c
< GetBodyParts(); ++c
) {
6676 if (GetBodyPart(c
) && IsEnabled()) {
6677 //fprintf(stderr, "character::SpillFluid: Liquid->GetName(4:%d):<%s>\n", c, Liquid->GetName(false, false).CStr());
6678 GetBodyPart(c
)->SpillFluid(Spiller
, Liquid
->SpawnMoreLiquid(Liquid
->GetVolume() * Modifier
[c
] / ModifierSum
), SquareIndex
);
6679 //fprintf(stderr, "character::SpillFluid: Liquid->GetName(5:%d):<%s>\n", c, Liquid->GetName(false, false).CStr());
6683 Liquid
->SetVolume(Liquid
->GetVolume() * Modifier
[TORSO_INDEX
] / ModifierSum
);
6684 //fprintf(stderr, "character::SpillFluid: Liquid->GetName(6):<%s>\n", Liquid->GetName(false, false).CStr());
6685 GetTorso()->SpillFluid(Spiller
, Liquid
, SquareIndex
);
6686 //fprintf(stderr, "character::SpillFluid: Liquid->GetName(7):<%s>\n", Liquid->GetName(false, false).CStr());
6688 //fprintf(stderr, "character::SpillFluid: Liquid->GetName(8):<%s>\n", Liquid->GetName(false, false).CStr());
6692 void character::StayOn (liquid
*Liquid
) {
6693 Liquid
->TouchEffect(this, TORSO_INDEX
);
6697 truth
character::IsAlly (ccharacter
*Char
) const {
6698 return Char
->GetTeam()->GetID() == GetTeam()->GetID();
6702 void character::ResetSpoiling () {
6703 doforbodyparts()(this, &bodypart::ResetSpoiling
);
6707 item
*character::SearchForItem (ccharacter
*Char
, sorter Sorter
) const {
6708 item
*Equipment
= findequipment
<ccharacter
*>()(this, Sorter
, Char
);
6709 if (Equipment
) return Equipment
;
6710 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (((*i
)->*Sorter
)(Char
)) return *i
;
6715 truth
character::DetectMaterial (cmaterial
*Material
) const {
6716 return GetStack()->DetectMaterial(Material
) ||
6717 combinebodypartpredicateswithparam
<cmaterial
*>()(this, &bodypart::DetectMaterial
, Material
, 1) ||
6718 combineequipmentpredicateswithparam
<cmaterial
*>()(this, &item::DetectMaterial
, Material
, 1);
6722 truth
character::DamageTypeDestroysBodyPart (int Type
) {
6723 return (Type
&0xFFF) != PHYSICAL_DAMAGE
;
6727 truth
character::CheckIfTooScaredToHit (ccharacter
*Enemy
) const {
6728 if (IsPlayer() && StateIsActivated(PANIC
)) {
6729 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
6730 square
*Square
= GetNeighbourSquare(d
);
6732 if(CanMoveOn(Square
) && (!Square
->GetCharacter() || Square
->GetCharacter()->IsPet())) {
6733 ADD_MESSAGE("You are too scared to attack %s.", Enemy
->CHAR_DESCRIPTION(DEFINITE
));
6743 void character::PrintBeginLevitationMessage () const {
6745 if (IsPlayer()) ADD_MESSAGE("You rise into the air like a small hot-air balloon.");
6746 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins to float.", CHAR_NAME(DEFINITE
));
6751 void character::PrintEndLevitationMessage () const {
6753 if (IsPlayer()) ADD_MESSAGE("You descend gently onto the ground.");
6754 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s drops onto the ground.", CHAR_NAME(DEFINITE
));
6759 truth
character::IsLimbIndex (int I
) {
6761 case RIGHT_ARM_INDEX
:
6762 case LEFT_ARM_INDEX
:
6763 case RIGHT_LEG_INDEX
:
6764 case LEFT_LEG_INDEX
:
6771 void character::EditExperience (int Identifier
, double Value
, double Speed
) {
6772 if (!AllowExperience() || (Identifier
== ENDURANCE
&& UseMaterialAttributes())) return;
6773 int Change
= RawEditExperience(BaseExperience
[Identifier
], GetNaturalExperience(Identifier
), Value
, Speed
);
6774 if (!Change
) return;
6775 cchar
*PlayerMsg
= 0, *NPCMsg
= 0;
6776 switch (Identifier
) {
6779 PlayerMsg
= "You feel tougher than anything!";
6780 if (IsPet()) NPCMsg
= "Suddenly %s looks tougher.";
6782 PlayerMsg
= "You feel less healthy.";
6783 if (IsPet()) NPCMsg
= "Suddenly %s looks less healthy.";
6785 CalculateBodyPartMaxHPs();
6786 CalculateMaxStamina();
6791 PlayerMsg
= "You now see the world in much better detail than before.";
6793 PlayerMsg
= "You feel very guru.";
6794 game::GetGod(VALPURUS
)->AdjustRelation(100);
6796 game::SendLOSUpdateRequest();
6801 if (Change
> 0) PlayerMsg
= "Suddenly the inner structure of the Multiverse around you looks quite simple.";
6802 else PlayerMsg
= "It surely is hard to think today.";
6805 if (IsPlayerKind()) UpdatePictures();
6809 if (Change
> 0) PlayerMsg
= "You feel your life experience increasing all the time.";
6810 else PlayerMsg
= "You feel like having done something unwise.";
6812 if (IsPlayerKind()) UpdatePictures();
6816 PlayerMsg
= "You feel very confident of your social skills.";
6818 if (GetAttribute(CHARISMA
) <= 15) NPCMsg
= "%s looks less ugly.";
6819 else NPCMsg
= "%s looks more attractive.";
6822 PlayerMsg
= "You feel somehow disliked.";
6824 if (GetAttribute(CHARISMA
) < 15) NPCMsg
= "%s looks more ugly.";
6825 else NPCMsg
= "%s looks less attractive.";
6828 if (IsPlayerKind()) UpdatePictures();
6832 PlayerMsg
= "You feel magical forces coursing through your body!";
6833 NPCMsg
= "You notice an odd glow around %s.";
6835 PlayerMsg
= "You feel your magical abilities withering slowly.";
6836 NPCMsg
= "You notice strange vibrations in the air around %s. But they disappear rapidly.";
6841 if (IsPlayer()) ADD_MESSAGE("%s", PlayerMsg
);
6842 else if (NPCMsg
&& CanBeSeenByPlayer()) ADD_MESSAGE(NPCMsg
, CHAR_NAME(DEFINITE
));
6844 CalculateBattleInfo();
6848 int character::RawEditExperience (double &Exp
, double NaturalExp
, double Value
, double Speed
) const {
6849 double OldExp
= Exp
;
6854 if(!OldExp
|| !Value
|| (Value
> 0 && OldExp
>= NaturalExp
* (100 + Value
) / 100) ||
6855 (Value
< 0 && OldExp
<= NaturalExp
* (100 + Value
) / 100)) return 0;
6856 if (!IsPlayer()) Speed
*= 1.5;
6857 Exp
+= (NaturalExp
* (100 + Value
) - 100 * OldExp
) * Speed
* EXP_DIVISOR
;
6858 LimitRef(Exp
, MIN_EXP
, MAX_EXP
);
6859 int NewA
= int(Exp
* EXP_DIVISOR
);
6860 int OldA
= int(OldExp
* EXP_DIVISOR
);
6861 int Delta
= NewA
- OldA
;
6862 if (Delta
> 0) Exp
= Max(Exp
, (NewA
+ 0.05) * EXP_MULTIPLIER
);
6863 else if (Delta
< 0) Exp
= Min(Exp
, (NewA
+ 0.95) * EXP_MULTIPLIER
);
6864 LimitRef(Exp
, MIN_EXP
, MAX_EXP
);
6869 int character::GetAttribute (int Identifier
, truth AllowBonus
) const {
6870 int A
= int(BaseExperience
[Identifier
] * EXP_DIVISOR
);
6871 if (AllowBonus
&& Identifier
== INTELLIGENCE
&& BrainsHurt()) return Max((A
+ AttributeBonus
[INTELLIGENCE
]) / 3, 1);
6872 return A
&& AllowBonus
? Max(A
+ AttributeBonus
[Identifier
], 1) : A
;
6876 void characterdatabase::PostProcess () {
6877 double AM
= (100 + AttributeBonus
) * EXP_MULTIPLIER
/ 100;
6878 for (int c
= 0; c
< ATTRIBUTES
; ++c
) NaturalExperience
[c
] = this->*ExpPtr
[c
] * AM
;
6882 void character::EditDealExperience (sLong Price
) {
6883 EditExperience(CHARISMA
, sqrt(Price
) / 5, 1 << 9);
6887 void character::PrintBeginLeprosyMessage () const {
6888 if (IsPlayer()) ADD_MESSAGE("You feel you're falling in pieces.");
6892 void character::PrintEndLeprosyMessage () const {
6893 if (IsPlayer()) ADD_MESSAGE("You feel your limbs are stuck in place tightly."); // CHANGE OR DIE
6897 void character::TryToInfectWithLeprosy (ccharacter
*Infector
) {
6898 if (!IsImmuneToLeprosy() &&
6899 ((GetRelation(Infector
) == HOSTILE
&& !RAND_N(50 * GetAttribute(ENDURANCE
))) ||
6900 !RAND_N(500 * GetAttribute(ENDURANCE
)))) GainIntrinsic(LEPROSY
);
6904 void character::SignalGeneration () {
6905 const_cast<database
*>(DataBase
)->Flags
|= HAS_BEEN_GENERATED
;
6909 void character::CheckIfSeen () {
6910 if (IsPlayer() || CanBeSeenByPlayer()) SignalSeen();
6914 void character::SignalSeen () {
6915 if (!(WarnFlags
& WARNED
) && GetRelation(PLAYER
) == HOSTILE
&& !StateIsActivated(FEARLESS
)) {
6916 double Danger
= GetRelativeDanger(PLAYER
);
6918 game::SetDangerFound(Max(game::GetDangerFound(), Danger
));
6919 if (Danger
> 500.0 && !(WarnFlags
& HAS_CAUSED_PANIC
)) {
6920 WarnFlags
|= HAS_CAUSED_PANIC
;
6921 game::SetCausePanicFlag(true);
6923 WarnFlags
|= WARNED
;
6926 const_cast<database
*>(DataBase
)->Flags
|= HAS_BEEN_SEEN
;
6930 int character::GetPolymorphIntelligenceRequirement () const {
6931 if (DataBase
->PolymorphIntelligenceRequirement
== DEPENDS_ON_ATTRIBUTES
) return Max(GetAttributeAverage() - 5, 0);
6932 return DataBase
->PolymorphIntelligenceRequirement
;
6936 void character::RemoveAllItems () {
6937 GetStack()->Clean();
6938 for (int c
= 0; c
< GetEquipments(); ++c
) {
6939 item
*Equipment
= GetEquipment(c
);
6941 Equipment
->RemoveFromSlot();
6942 Equipment
->SendToHell();
6948 int character::CalculateWeaponSkillHits (ccharacter
*Enemy
) const {
6949 if (Enemy
->IsPlayer()) {
6950 configid
ConfigID(GetType(), GetConfig());
6951 const dangerid
& DangerID
= game::GetDangerMap().find(ConfigID
)->second
;
6952 return Min(int(DangerID
.EquippedDanger
* 2000), 1000);
6954 return Min(int(GetRelativeDanger(Enemy
, true) * 2000), 1000);
6958 truth
character::CanUseEquipment (int I
) const {
6959 return CanUseEquipment() && I
< GetEquipments() && GetBodyPartOfEquipment(I
) && EquipmentIsAllowed(I
);
6963 /* Target mustn't have any equipment */
6964 void character::DonateEquipmentTo (character
*Character
) {
6966 feuLong
*EquipmentMemory
= game::GetEquipmentMemory();
6967 for (int c
= 0; c
< MAX_EQUIPMENT_SLOTS
; ++c
) {
6968 item
*Item
= GetEquipment(c
);
6970 if (Character
->CanUseEquipment(c
)) {
6971 Item
->RemoveFromSlot();
6972 Character
->SetEquipment(c
, Item
);
6974 EquipmentMemory
[c
] = Item
->GetID();
6975 Item
->MoveTo(Character
->GetStack());
6977 } else if (CanUseEquipment(c
)) {
6978 EquipmentMemory
[c
] = 0;
6979 } else if (EquipmentMemory
[c
] && Character
->CanUseEquipment(c
)) {
6980 for (stackiterator i
= Character
->GetStack()->GetBottom(); i
.HasItem(); ++i
) {
6981 if (i
->GetID() == EquipmentMemory
[c
]) {
6983 Item
->RemoveFromSlot();
6984 Character
->SetEquipment(c
, Item
);
6988 EquipmentMemory
[c
] = 0;
6992 for (int c
= 0; c
< GetEquipments(); ++c
) {
6993 item
*Item
= GetEquipment(c
);
6995 if (Character
->CanUseEquipment(c
)) {
6996 Item
->RemoveFromSlot();
6997 Character
->SetEquipment(c
, Item
);
6999 Item
->MoveTo(Character
->GetStackUnder());
7007 void character::ReceivePeaSoup (sLong
) {
7008 if (!game::IsInWilderness()) {
7009 lsquare
*Square
= GetLSquareUnder();
7010 if (Square
->IsFlyable()) Square
->AddSmoke(gas::Spawn(FART
, 250));
7015 void character::AddPeaSoupConsumeEndMessage () const {
7017 if (CanHear()) ADD_MESSAGE("Mmmh! The soup is very tasty. You hear a small puff.");
7018 else ADD_MESSAGE("Mmmh! The soup is very tasty.");
7019 } else if (CanBeSeenByPlayer() && PLAYER
->CanHear()) {
7021 ADD_MESSAGE("You hear a small puff.");
7026 void character::CalculateMaxStamina () {
7027 MaxStamina
= TorsoIsAlive() ? GetAttribute(ENDURANCE
) * 10000 : 0;
7031 void character::EditStamina (int Amount
, truth CanCauseUnconsciousness
) {
7032 if (!TorsoIsAlive()) return;
7033 int UnconsciousnessStamina
= MaxStamina
>> 3;
7034 if (!CanCauseUnconsciousness
&& Amount
< 0) {
7035 if (Stamina
> UnconsciousnessStamina
) {
7037 if (Stamina
< UnconsciousnessStamina
) Stamina
= UnconsciousnessStamina
;
7041 int OldStamina
= Stamina
;
7043 if (Stamina
> MaxStamina
) {
7044 Stamina
= MaxStamina
;
7045 } else if (Stamina
< 0) {
7047 LoseConsciousness(250 + RAND_N(250));
7048 } else if (IsPlayer()) {
7049 if (OldStamina
>= MaxStamina
>> 2 && Stamina
< MaxStamina
>> 2) {
7050 ADD_MESSAGE("You are getting a little tired.");
7051 } else if(OldStamina
>= UnconsciousnessStamina
&& Stamina
< UnconsciousnessStamina
) {
7052 ADD_MESSAGE("You are seriously out of breath!");
7053 game::SetPlayerIsRunning(false);
7056 if (IsPlayer() && StateIsActivated(PANIC
) && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
7060 void character::RegenerateStamina () {
7061 if (GetTirednessState() != UNTIRED
) {
7062 EditExperience(ENDURANCE
, 50, 1);
7063 if (Sweats() && TorsoIsAlive() && !RAND_N(30) && !game::IsInWilderness()) {
7064 // Sweat amount proportional to endurance also
7065 //sLong Volume = sLong(0.05 * sqrt(GetBodyVolume()));
7066 sLong Volume
= long(0.05*sqrt(GetBodyVolume()*GetAttribute(ENDURANCE
)/10));
7067 if (GetTirednessState() == FAINTING
) Volume
<<= 1;
7068 for (int c
= 0; c
< SquaresUnder
; ++c
) GetLSquareUnder(c
)->SpillFluid(0, CreateSweat(Volume
), false, false);
7073 if (Action
->IsRest()) {
7074 if (SquaresUnder
== 1) {
7075 Bonus
= GetSquareUnder()->GetRestModifier() << 1;
7077 int Lowest
= GetSquareUnder(0)->GetRestModifier();
7078 for (int c
= 1; c
< GetSquaresUnder(); ++c
) {
7079 int Mod
= GetSquareUnder(c
)->GetRestModifier();
7080 if (Mod
< Lowest
) Lowest
= Mod
;
7082 Bonus
= Lowest
<< 1;
7084 } else if (Action
->IsUnconsciousness()) Bonus
= 2;
7087 auto bst
= GetBurdenState();
7088 if (bst
== OVER_LOADED
) Plus1
= 25;
7089 else if (bst
== STRESSED
) Plus1
= 50;
7090 else if (bst
== BURDENED
) Plus1
= 75;
7093 auto hst
= GetHungerState();
7094 if (hst
== STARVING
) Plus2
= 25;
7095 else if (hst
== VERY_HUNGRY
) Plus2
= 50;
7096 else if (hst
== HUNGRY
) Plus2
= 75;
7098 Stamina
+= Plus1
* Plus2
* Bonus
/ 1000;
7099 if (Stamina
> MaxStamina
) Stamina
= MaxStamina
;
7100 if (IsPlayer() && StateIsActivated(PANIC
) && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
7104 void character::BeginPanic () {
7105 if (IsPlayer() && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
7106 DeActivateVoluntaryAction();
7110 void character::EndPanic () {
7111 if (IsPlayer()) game::SetPlayerIsRunning(false);
7115 int character::GetTirednessState () const {
7116 if (Stamina
>= MaxStamina
>> 2) return UNTIRED
;
7117 if (Stamina
>= MaxStamina
>> 3) return EXHAUSTED
;
7122 void character::ReceiveBlackUnicorn (sLong Amount
) {
7123 if (!(RAND() % 160)) game::DoEvilDeed(Amount
/ 50);
7124 BeginTemporaryState(TELEPORT
, Amount
/ 100);
7125 for (int c
= 0; c
< STATES
; ++c
) {
7126 if (StateData
[c
].Flags
& DUR_TEMPORARY
) {
7127 BeginTemporaryState(1 << c
, Amount
/ 100);
7128 if (!IsEnabled()) return;
7129 } else if (StateData
[c
].Flags
& DUR_PERMANENT
) {
7130 GainIntrinsic(1 << c
);
7131 if (!IsEnabled()) return;
7137 void character::ReceiveGrayUnicorn (sLong Amount
) {
7138 if (!(RAND() % 80)) game::DoEvilDeed(Amount
/ 50);
7139 BeginTemporaryState(TELEPORT
, Amount
/ 100);
7140 for (int c
= 0; c
< STATES
; ++c
) {
7141 if (1 << c
!= TELEPORT
) {
7142 DecreaseStateCounter(1 << c
, -Amount
/ 100);
7143 if (!IsEnabled()) return;
7149 void character::ReceiveWhiteUnicorn (sLong Amount
) {
7150 if (!(RAND() % 40)) game::DoEvilDeed(Amount
/ 50);
7151 BeginTemporaryState(TELEPORT
, Amount
/ 100);
7152 DecreaseStateCounter(LYCANTHROPY
, -Amount
/ 100);
7153 DecreaseStateCounter(POISONED
, -Amount
/ 100);
7154 DecreaseStateCounter(PARASITIZED
, -Amount
/ 100);
7155 DecreaseStateCounter(LEPROSY
, -Amount
/ 100);
7156 DecreaseStateCounter(VAMPIRISM
, -Amount
/ 100);
7160 /* Counter should be negative. Removes intrinsics. */
7161 void character::DecreaseStateCounter (sLong State
, int Counter
) {
7162 while (State
!= 0) {
7164 for (sidx
= 0; sidx
< STATES
; ++sidx
) if (State
&(1<<sidx
)) { st
= (1<<sidx
); break; }
7165 if (!st
) { break; /*ABORT("BeginTemporaryState works only when State == 2^n!");*/ }
7167 if (TemporaryState
&st
) {
7168 if (TemporaryStateCounter
[sidx
] == PERMANENT
|| (TemporaryStateCounter
[sidx
] += Counter
) <= 0) {
7169 TemporaryState
&= ~st
;
7170 if (!(EquipmentState
& st
)) {
7171 if (StateData
[sidx
].EndHandler
) {
7172 (this->*StateData
[sidx
].EndHandler
)();
7173 if (!IsEnabled()) return;
7175 (this->*StateData
[sidx
].PrintEndMessage
)();
7183 truth
character::IsImmuneToLeprosy () const {
7184 return (DataBase
->IsImmuneToLeprosy
|| UseMaterialAttributes() || StateIsActivated(DISEASE_IMMUNITY
));
7188 void character::LeprosyHandler () {
7189 EditExperience(ARM_STRENGTH
, -25, 1 << 1);
7190 EditExperience(LEG_STRENGTH
, -25, 1 << 1);
7191 EditExperience(DEXTERITY
, -25, 1 << 1);
7192 EditExperience(AGILITY
, -25, 1 << 1);
7193 EditExperience(ENDURANCE
, -25, 1 << 1);
7194 EditExperience(CHARISMA
, -25, 1 << 1);
7195 CheckDeath(CONST_S("killed by leprosy"));
7199 bodypart
*character::SearchForOriginalBodyPart (int I
) const {
7200 for (stackiterator i1
= GetStackUnder()->GetBottom(); i1
.HasItem(); ++i1
) {
7201 for (std::list
<feuLong
>::iterator i2
= OriginalBodyPartID
[I
].begin(); i2
!= OriginalBodyPartID
[I
].end(); ++i2
)
7202 if (i1
->GetID() == *i2
) return static_cast<bodypart
*>(*i1
);
7208 void character::SetLifeExpectancy (int Base
, int RandPlus
) {
7210 for (c
= 0; c
< BodyParts
; ++c
) {
7211 bodypart
*BodyPart
= GetBodyPart(c
);
7212 if (BodyPart
) BodyPart
->SetLifeExpectancy(Base
, RandPlus
);
7214 for (c
= 0; c
< GetEquipments(); ++c
) {
7215 item
*Equipment
= GetEquipment(c
);
7216 if (Equipment
) Equipment
->SetLifeExpectancy(Base
, RandPlus
);
7221 /* Receiver should be a fresh duplicate of this */
7222 void character::DuplicateEquipment (character
*Receiver
, feuLong Flags
) {
7223 for (int c
= 0; c
< GetEquipments(); ++c
) {
7224 item
*Equipment
= GetEquipment(c
);
7226 item
*Duplicate
= Equipment
->Duplicate(Flags
);
7227 Receiver
->SetEquipment(c
, Duplicate
);
7233 void character::Disappear (corpse
*Corpse
, cchar
*Verb
, truth (item::*ClosePredicate
)() const) {
7234 truth TorsoDisappeared
= false;
7235 truth CanBeSeen
= Corpse
? Corpse
->CanBeSeenByPlayer() : IsPlayer() || CanBeSeenByPlayer();
7237 if ((GetTorso()->*ClosePredicate
)()) {
7239 if (Corpse
) ADD_MESSAGE("%s %ss.", Corpse
->CHAR_NAME(DEFINITE
), Verb
);
7240 else if (IsPlayer()) ADD_MESSAGE("You %s.", Verb
);
7241 else ADD_MESSAGE("%s %ss.", CHAR_NAME(DEFINITE
), Verb
);
7243 TorsoDisappeared
= true;
7244 for (c
= 0; c
< GetEquipments(); ++c
) {
7245 item
*Equipment
= GetEquipment(c
);
7246 if (Equipment
&& (Equipment
->*ClosePredicate
)()) {
7247 Equipment
->RemoveFromSlot();
7248 Equipment
->SendToHell();
7251 itemvector ItemVector
;
7252 GetStack()->FillItemVector(ItemVector
);
7253 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
7254 if (ItemVector
[c
] && (ItemVector
[c
]->*ClosePredicate
)()) {
7255 ItemVector
[c
]->RemoveFromSlot();
7256 ItemVector
[c
]->SendToHell();
7260 for (c
= 1; c
< GetBodyParts(); ++c
) {
7261 bodypart
*BodyPart
= GetBodyPart(c
);
7263 if ((BodyPart
->*ClosePredicate
)()) {
7264 if (!TorsoDisappeared
&& CanBeSeen
) {
7265 if(IsPlayer()) ADD_MESSAGE("Your %s %ss.", GetBodyPartName(c
).CStr(), Verb
);
7266 else ADD_MESSAGE("The %s of %s %ss.", GetBodyPartName(c
).CStr(), CHAR_NAME(DEFINITE
), Verb
);
7268 BodyPart
->DropEquipment();
7269 item
*BodyPart
= SevereBodyPart(c
);
7270 if (BodyPart
) BodyPart
->SendToHell();
7271 } else if (TorsoDisappeared
) {
7272 BodyPart
->DropEquipment();
7273 item
*BodyPart
= SevereBodyPart(c
);
7275 if (Corpse
) Corpse
->GetSlot()->AddFriendItem(BodyPart
);
7276 else if (!game::IsInWilderness()) GetStackUnder()->AddItem(BodyPart
);
7277 else BodyPart
->SendToHell();
7282 if (TorsoDisappeared
) {
7284 Corpse
->RemoveFromSlot();
7285 Corpse
->SendToHell();
7287 CheckDeath(festring(Verb
) + "ed", 0, FORCE_DEATH
|DISALLOW_CORPSE
|DISALLOW_MSG
);
7290 CheckDeath(festring(Verb
) + "ed", 0, DISALLOW_MSG
);
7295 void character::SignalDisappearance () {
7296 if (GetMotherEntity()) GetMotherEntity()->SignalDisappearance();
7297 else Disappear(0, "disappear", &item::IsVeryCloseToDisappearance
);
7301 truth
character::HornOfFearWorks () const {
7302 return CanHear() && GetPanicLevel() > RAND()%33 && !StateIsActivated(FEARLESS
);
7306 void character::BeginLeprosy () {
7307 doforbodypartswithparam
<truth
>()(this, &bodypart::SetIsInfectedByLeprosy
, true);
7311 void character::EndLeprosy () {
7312 doforbodypartswithparam
<truth
>()(this, &bodypart::SetIsInfectedByLeprosy
, false);
7316 truth
character::IsSameAs (ccharacter
*What
) const {
7317 return What
->GetType() == GetType() && What
->GetConfig() == GetConfig();
7321 feuLong
character::GetCommandFlags () const {
7322 return !StateIsActivated(PANIC
) ? CommandFlags
: CommandFlags
|FLEE_FROM_ENEMIES
;
7326 feuLong
character::GetConstantCommandFlags () const {
7327 return !StateIsActivated(PANIC
) ? DataBase
->ConstantCommandFlags
: DataBase
->ConstantCommandFlags
|FLEE_FROM_ENEMIES
;
7331 feuLong
character::GetPossibleCommandFlags () const {
7332 int Int
= GetAttribute(INTELLIGENCE
);
7333 feuLong Flags
= ALL_COMMAND_FLAGS
;
7334 if (!CanMove() || Int
< 4) Flags
&= ~FOLLOW_LEADER
;
7335 if (!CanMove() || Int
< 6) Flags
&= ~FLEE_FROM_ENEMIES
;
7336 if (!CanUseEquipment() || Int
< 8) Flags
&= ~DONT_CHANGE_EQUIPMENT
;
7337 if (!UsesNutrition() || Int
< 8) Flags
&= ~DONT_CONSUME_ANYTHING_VALUABLE
;
7342 truth
character::IsRetreating () const {
7343 return StateIsActivated(PANIC
) || (CommandFlags
& FLEE_FROM_ENEMIES
&& IsPet());
7347 truth
character::ChatMenu () {
7348 if (GetAction() && !GetAction()->CanBeTalkedTo()) {
7349 ADD_MESSAGE("%s is silent.", CHAR_DESCRIPTION(DEFINITE
));
7350 PLAYER
->EditAP(-200);
7353 feuLong ManagementFlags
= GetManagementFlags();
7354 if (ManagementFlags
== CHAT_IDLY
|| !IsPet()) return ChatIdly();
7355 static cchar
*const ChatMenuEntry
[CHAT_MENU_ENTRIES
] = {
7362 static const petmanagementfunction PMF
[CHAT_MENU_ENTRIES
] = {
7363 &character::ChangePetEquipment
,
7364 &character::TakePetItems
,
7365 &character::GivePetItems
,
7366 &character::IssuePetCommands
,
7367 &character::ChatIdly
7369 felist
List(CONST_S("Choose action:"));
7370 game::SetStandardListAttributes(List
);
7371 List
.AddFlags(SELECTABLE
);
7373 for (c
= 0; c
< CHAT_MENU_ENTRIES
; ++c
) if (1 << c
& ManagementFlags
) List
.AddEntry(ChatMenuEntry
[c
], LIGHT_GRAY
);
7374 int Chosen
= List
.Draw();
7375 if (Chosen
& FELIST_ERROR_BIT
) return false;
7376 for (c
= 0, i
= 0; c
< CHAT_MENU_ENTRIES
; ++c
) {
7377 if (1 << c
& ManagementFlags
&& i
++ == Chosen
) return (this->*PMF
[c
])();
7379 return false; // dummy
7383 truth
character::ChangePetEquipment () {
7384 if (EquipmentScreen(PLAYER
->GetStack(), GetStack())) {
7392 truth
character::TakePetItems () {
7393 truth Success
= false;
7394 stack::SetSelected(0);
7397 game::DrawEverythingNoBlit();
7398 GetStack()->DrawContents(
7402 CONST_S("What do you want to take from ") + CHAR_DESCRIPTION(DEFINITE
) + '?',
7405 GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState(),
7406 GetVerbalBurdenStateColor(),
7408 if (ToTake
.empty()) break;
7409 for (uInt c
= 0; c
< ToTake
.size(); ++c
) ToTake
[c
]->MoveTo(PLAYER
->GetStack());
7410 ADD_MESSAGE("You take %s.", ToTake
[0]->GetName(DEFINITE
, ToTake
.size()).CStr());
7415 PLAYER
->DexterityAction(2);
7421 truth
character::GivePetItems () {
7422 truth Success
= false;
7423 stack::SetSelected(0);
7426 game::DrawEverythingNoBlit();
7427 PLAYER
->GetStack()->DrawContents(
7431 CONST_S("What do you want to give to ") + CHAR_DESCRIPTION(DEFINITE
) + '?',
7434 GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState(),
7435 GetVerbalBurdenStateColor(),
7437 if (ToGive
.empty()) break;
7438 for (uInt c
= 0; c
< ToGive
.size(); ++c
) ToGive
[c
]->MoveTo(GetStack());
7439 ADD_MESSAGE("You give %s to %s.", ToGive
[0]->GetName(DEFINITE
, ToGive
.size()).CStr(), CHAR_DESCRIPTION(DEFINITE
));
7444 PLAYER
->DexterityAction(2);
7450 truth
character::IssuePetCommands () {
7451 if (!IsConscious()) {
7452 ADD_MESSAGE("%s is unconscious.", CHAR_DESCRIPTION(DEFINITE
));
7455 feuLong PossibleC
= GetPossibleCommandFlags();
7457 ADD_MESSAGE("%s cannot be commanded.", CHAR_DESCRIPTION(DEFINITE
));
7460 feuLong OldC
= GetCommandFlags();
7461 feuLong NewC
= OldC
, VaryFlags
= 0;
7462 game::CommandScreen(CONST_S("Issue commands to ")+GetDescription(DEFINITE
), PossibleC
, GetConstantCommandFlags(), VaryFlags
, NewC
);
7463 if (NewC
== OldC
) return false;
7464 SetCommandFlags(NewC
);
7465 PLAYER
->EditAP(-500);
7466 PLAYER
->EditExperience(CHARISMA
, 25, 1 << 7);
7471 truth
character::ChatIdly () {
7472 if (!TryToTalkAboutScience()) {
7474 PLAYER
->EditExperience(CHARISMA
, 75, 1 << 7);
7476 PLAYER
->EditAP(-1000);
7481 int character::HasSomethingToEquipAt (int chosen
, truth equippedIsTrue
) {
7482 if (!GetBodyPartOfEquipment(chosen
)) return 0;
7484 item
*oldEquipment
= GetEquipment(chosen
);
7485 if (!IsPlayer() && oldEquipment
&& BoundToUse(oldEquipment
, chosen
)) return 0;
7487 stack
*mainStack
= GetStack();
7488 sorter Sorter
= EquipmentSorter(chosen
);
7489 auto count
= mainStack
->SortedItemsCount(this, Sorter
);
7491 if (equippedIsTrue
&& oldEquipment
) ++count
;
7497 // returns 0, 1 or pickup time
7498 feuLong
character::HasSomethingToEquipAtRecentTime (int chosen
, truth equippedIsTrue
) {
7499 if (!GetBodyPartOfEquipment(chosen
)) return 0;
7501 item
*oldEquipment
= GetEquipment(chosen
);
7502 if (!IsPlayer() && oldEquipment
&& BoundToUse(oldEquipment
, chosen
)) return 0;
7504 stack
*mainStack
= GetStack();
7505 sorter Sorter
= EquipmentSorter(chosen
);
7506 feuLong highestTime
= mainStack
->SortedItemsRecentTime(this, Sorter
);
7508 if (equippedIsTrue
&& oldEquipment
&& oldEquipment
->pickupTime
> highestTime
) highestTime
= oldEquipment
->pickupTime
;
7514 truth
character::EquipmentScreen (stack
*MainStack
, stack
*SecStack
) {
7515 if (!CanUseEquipment()) {
7516 ADD_MESSAGE("%s cannot use equipment.", CHAR_DESCRIPTION(DEFINITE
));
7520 truth EquipmentChanged
= false;
7521 felist
List(CONST_S("Equipment menu [ESC exits]"));
7525 List
.EmptyDescription();
7527 List
.AddDescription(CONST_S(""));
7528 List
.AddDescription(festring(GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState()).CapitalizeCopy(), GetVerbalBurdenStateColor());
7530 int totalWeight
= 0;
7531 for (int c
= 0; c
< GetEquipments(); ++c
) {
7532 item
*Equipment
= GetEquipment(c
);
7533 totalWeight
+= (Equipment
? Equipment
->GetWeight() : 0);
7535 festring
Total("Total weight: ");
7536 Total
<< totalWeight
;
7538 List
.AddDescription(CONST_S(""));
7539 List
.AddDescription(Total
);
7541 int firstEmpty
= -1, firstNonEmpty
= -1, selected
= -1;
7542 feuLong selPickTime
= 1;
7543 feuLong armPickTime
= 0;
7545 //truth selectedIsEmpty = false;
7546 for (int c
= 0; c
< GetEquipments(); ++c
) {
7547 truth isArm
= (c
== 5 || c
== 6);
7548 //int bpidx = (GetBodyPartOfEquipment(c) ? GetBodyPartOfEquipment(c)->GetBodyPartIndex() : -1);
7549 truth equippable
= !!GetBodyPartOfEquipment(c
);
7550 Entry
= GetEquipmentName(c
);
7553 item
*Equipment
= GetEquipment(c
);
7554 feuLong pickTm
= (equippable
? HasSomethingToEquipAtRecentTime(c
, false) : 0);
7555 int availEquipCount
= (equippable
? HasSomethingToEquipAt(c
, false) : 0);
7556 if (pickTm
> 1 && game::GetTick()-pickTm
> game::PickTimeout
) pickTm
= 0;
7557 //fprintf(stderr, "c=%d; equippable=%d; availcount=%d; pickTm=%u; tick=%u\n", c, (int)equippable, availEquipCount, pickTm, game::GetTick());
7559 Equipment
->AddInventoryEntry(this, Entry
, 1, true);
7560 AddSpecialEquipmentInfo(Entry
, c
);
7561 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
7562 if (firstNonEmpty
< 0 && equippable
&& !isArm
&& availEquipCount
> 0) firstNonEmpty
= c
;
7564 if (availEquipCount
> 0 && isArm
&& armPickTime
< pickTm
) { armFirst
= c
; armPickTime
= pickTm
; }
7565 if (selPickTime
< pickTm
&& equippable
&& !isArm
) { selected
= c
; selPickTime
= pickTm
; }
7567 List
.AddEntry(Entry
, (availEquipCount
? ORANGE
: LIGHT_GRAY
), 20, ImageKey
, true);
7569 truth canUse
= !!GetBodyPartOfEquipment(c
);
7570 Entry
<< (canUse
? "-" : "can't use");
7572 if (canUse
&& availEquipCount
> 0) {
7573 if (firstEmpty
< 0 && equippable
&& !isArm
&& availEquipCount
> 0) firstEmpty
= c
;
7574 if (equippable
&& isArm
&& armFirst
< 0) armFirst
= c
;
7575 switch (availEquipCount
) {
7576 case 0: color
= RED
; break;
7577 case 1: color
= LIGHT_GRAY
; break;
7578 default: color
= ORANGE
; break;
7581 if (color
!= RED
&& equippable
) {
7582 if (pickTm
> selPickTime
&& !isArm
) { selected
= c
; selPickTime
= pickTm
; }
7584 List
.AddEntry(Entry
, color
, 20, game::AddToItemDrawVector(itemvector()));
7587 game::DrawEverythingNoBlit();
7588 game::SetStandardListAttributes(List
);
7590 //fprintf(stderr, " selected=%d; firstEmpty=%d; firstNonEmpty=%d; armFirst=%d; armPickTime=%u\n", selected, firstEmpty, firstNonEmpty, armFirst, armPickTime);
7592 if (armPickTime
> 0) selected
= armFirst
;
7593 else if (firstEmpty
>= 0) selected
= firstEmpty
;
7594 else if (firstNonEmpty
>= 0) selected
= firstNonEmpty
;
7596 // 7: right ring; switch to left ring if that slot has nothing in it
7597 if (selected
== 7 && GetBodyPartOfEquipment(8) && !GetEquipment(8)) selected
= 8;
7598 if (selected
>= 0) List
.SetSelected(selected
);
7600 List
.SetFlags(SELECTABLE
|DRAW_BACKGROUND_AFTERWARDS
);
7601 List
.SetEntryDrawer(game::ItemEntryDrawer
);
7602 Chosen
= List
.Draw();
7603 game::ClearItemDrawVector();
7604 if (Chosen
>= GetEquipments()) break;
7605 EquipmentChanged
= TryToChangeEquipment(MainStack
, SecStack
, Chosen
);
7607 if (EquipmentChanged
) DexterityAction(5);
7608 return EquipmentChanged
;
7612 feuLong
character::GetManagementFlags () const {
7613 feuLong Flags
= ALL_MANAGEMENT_FLAGS
;
7614 if (!CanUseEquipment() || !AllowPlayerToChangeEquipment()) Flags
&= ~CHANGE_EQUIPMENT
;
7615 if (!GetStack()->GetItems()) Flags
&= ~TAKE_ITEMS
;
7616 if (!WillCarryItems()) Flags
&= ~GIVE_ITEMS
;
7617 if (!GetPossibleCommandFlags()) Flags
&= ~ISSUE_COMMANDS
;
7622 cchar
*VerbalBurdenState
[] = { "overloaded", "stressed", "burdened", "unburdened" };
7623 col16 VerbalBurdenStateColor
[] = { RED
, BLUE
, BLUE
, WHITE
};
7625 cchar
*character::GetVerbalBurdenState () const { return VerbalBurdenState
[BurdenState
]; }
7626 col16
character::GetVerbalBurdenStateColor () const { return VerbalBurdenStateColor
[BurdenState
]; }
7627 int character::GetAttributeAverage () const { return GetSumOfAttributes()/7; }
7629 cfestring
&character::GetStandVerb() const {
7630 if (ForceCustomStandVerb()) return DataBase
->StandVerb
;
7631 static festring Hovering
= "hovering";
7632 static festring Swimming
= "swimming";
7633 if (StateIsActivated(LEVITATION
)) return Hovering
;
7634 if (IsSwimming()) return Swimming
;
7635 return DataBase
->StandVerb
;
7639 truth
character::CheckApply () const {
7641 ADD_MESSAGE("This monster type cannot apply.");
7648 void character::EndLevitation () {
7649 if (!IsFlying() && GetSquareUnder()) {
7650 if (!game::IsInWilderness()) SignalStepFrom(0);
7651 if (game::IsInWilderness() || !GetLSquareUnder()->IsFreezed()) TestWalkability();
7656 truth
character::CanMove () const {
7657 return !IsRooted() || StateIsActivated(LEVITATION
);
7661 void character::CalculateEnchantments () {
7662 doforequipments()(this, &item::CalculateEnchantment
);
7663 GetStack()->CalculateEnchantments();
7667 truth
character::GetNewFormForPolymorphWithControl (character
*&NewForm
) {
7668 if (StateIsActivated(POLYMORPH_LOCK
)) { ADD_MESSAGE("You feel uncertain about your body for a moment."); return false; }
7669 festring Topic
, Temp
;
7672 festring Temp
= game::DefaultQuestion(CONST_S("What do you want to become? [press '?' for a list]"), game::GetDefaultPolymorphTo(), &game::PolymorphControlKeyHandler
);
7673 NewForm
= protosystem::CreateMonster(Temp
);
7675 if (NewForm
->IsSameAs(this)) {
7677 ADD_MESSAGE("You choose not to polymorph.");
7681 if (PolymorphBackup
&& NewForm
->IsSameAs(PolymorphBackup
)) {
7683 NewForm
= ForceEndPolymorph();
7686 if (NewForm
->GetPolymorphIntelligenceRequirement() > GetAttribute(INTELLIGENCE
) && !game::WizardModeIsActive()) {
7687 ADD_MESSAGE("You feel your mind isn't yet powerful enough to call forth the form of %s.", NewForm
->CHAR_NAME(INDEFINITE
));
7691 NewForm
->RemoveAllItems();
7699 liquid
*character::CreateSweat(sLong Volume
) const {
7700 //return liquid::Spawn(GetSweatMaterial(), Volume);
7701 return liquid::Spawn(GetCurrentSweatMaterial(), Volume
);
7705 truth
character::TeleportRandomItem (truth TryToHinderVisibility
) {
7706 if (IsImmuneToItemTeleport() || StateIsActivated(TELEPORT_LOCK
)) return false;
7707 itemvector ItemVector
;
7708 std::vector
<sLong
> PossibilityVector
;
7709 int TotalPossibility
= 0;
7710 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
7711 ItemVector
.push_back(*i
);
7712 int Possibility
= i
->GetTeleportPriority();
7713 if (TryToHinderVisibility
) Possibility
+= i
->GetHinderVisibilityBonus(this);
7714 PossibilityVector
.push_back(Possibility
);
7715 TotalPossibility
+= Possibility
;
7717 for (int c
= 0; c
< GetEquipments(); ++c
) {
7718 item
*Equipment
= GetEquipment(c
);
7720 ItemVector
.push_back(Equipment
);
7721 int Possibility
= Equipment
->GetTeleportPriority();
7722 if (TryToHinderVisibility
) Possibility
+= Equipment
->GetHinderVisibilityBonus(this);
7723 PossibilityVector
.push_back(Possibility
<<= 1);
7724 TotalPossibility
+= Possibility
;
7727 if (!TotalPossibility
) return false;
7728 int Chosen
= femath::WeightedRand(PossibilityVector
, TotalPossibility
);
7729 item
*Item
= ItemVector
[Chosen
];
7730 truth Equipped
= PLAYER
->Equips(Item
);
7731 truth Seen
= Item
->CanBeSeenByPlayer();
7732 Item
->RemoveFromSlot();
7733 if (Seen
) ADD_MESSAGE("%s disappears.", Item
->CHAR_NAME(DEFINITE
));
7734 if (Equipped
) game::AskForEscPress(CONST_S("Equipment lost!"));
7736 int Range
= (Item
->GetEmitation() && TryToHinderVisibility
? 25 : 5);
7737 rect
Border(Pos
+ v2(-Range
, -Range
), Pos
+ v2(Range
, Range
));
7738 Pos
= GetLevel()->GetRandomSquare(this, 0, &Border
);
7739 if (Pos
== ERROR_V2
) {
7740 Pos
= GetLevel()->GetRandomSquare();
7741 if (Pos
== ERROR_V2
) {
7742 fprintf(stderr
, "oops. `character::TeleportRandomItem()` failed.\n");
7743 Item
->SendToHell(); //???
7747 GetNearLSquare(Pos
)->GetStack()->AddItem(Item
);
7748 if (Item
->CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", Item
->CHAR_NAME(INDEFINITE
));
7753 truth
character::HasClearRouteTo (v2 Pos
) const {
7754 pathcontroller::Map
= GetLevel()->GetMap();
7755 pathcontroller::Character
= this;
7756 v2 ThisPos
= GetPos();
7757 return mapmath
<pathcontroller
>::DoLine(ThisPos
.X
, ThisPos
.Y
, Pos
.X
, Pos
.Y
, SKIP_FIRST
|LINE_BOTH_DIRS
);
7761 truth
character::IsTransparent () const {
7762 return !IsEnormous() || GetTorso()->GetMainMaterial()->IsTransparent() || StateIsActivated(INVISIBLE
);
7766 void character::SignalPossibleTransparencyChange () {
7767 if (!game::IsInWilderness()) {
7768 for (int c
= 0; c
< SquaresUnder
; ++c
) {
7769 lsquare
*Square
= GetLSquareUnder(c
);
7770 if (Square
) Square
->SignalPossibleTransparencyChange();
7776 int character::GetCursorData () const {
7778 int Color
= (game::PlayerIsRunning() ? BLUE_CURSOR
: DARK_CURSOR
);
7779 for (int c
= 0; c
< BodyParts
; ++c
) {
7780 bodypart
*BodyPart
= GetBodyPart(c
);
7781 if (BodyPart
&& BodyPart
->IsUsable()) {
7782 int ConditionColorIndex
= BodyPart
->GetConditionColorIndex();
7783 if ((BodyPartIsVital(c
) && !ConditionColorIndex
) || (ConditionColorIndex
<= 1 && ++Bad
== 2)) return Color
|CURSOR_FLASH
;
7784 } else if (++Bad
== 2) {
7785 return Color
|CURSOR_FLASH
;
7788 Color
= (game::PlayerIsRunning() ? YELLOW_CURSOR
: RED_CURSOR
);
7789 return (Bad
? Color
|CURSOR_FLASH
: Color
);
7793 void character::TryToName () {
7794 if (!IsPet()) ADD_MESSAGE("%s refuses to let YOU decide what %s's called.", CHAR_NAME(DEFINITE
), CHAR_PERSONAL_PRONOUN
);
7795 else if (IsPlayer()) ADD_MESSAGE("You can't rename yourself.");
7796 else if (!IsNameable()) ADD_MESSAGE("%s refuses to be called anything else but %s.", CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
));
7798 festring Topic
= CONST_S("What name will you give to ")+GetName(DEFINITE
)+'?';
7799 festring Name
= game::StringQuestion(Topic
, WHITE
, 0, 80, true);
7800 if (Name
.GetSize()) SetAssignedName(Name
);
7805 double character::GetSituationDanger (ccharacter
*Enemy
, v2 ThisPos
, v2 EnemyPos
, truth SeesEnemy
) const {
7807 if (IgnoreDanger() && !IsPlayer()) {
7808 if (Enemy
->IgnoreDanger() && !Enemy
->IsPlayer()) {
7809 Danger
= double(GetHP())*GetHPRequirementForGeneration()/(Enemy
->GetHP()*Enemy
->GetHPRequirementForGeneration());
7812 Danger
= 0.25*GetHPRequirementForGeneration()/Enemy
->GetHP();
7814 } else if (Enemy
->IgnoreDanger() && !Enemy
->IsPlayer()) {
7815 Danger
= 4.0*GetHP()/Enemy
->GetHPRequirementForGeneration();
7817 Danger
= GetRelativeDanger(Enemy
);
7819 Danger
*= 3.0/((EnemyPos
-ThisPos
).GetManhattanLength()+2);
7820 if (!SeesEnemy
) Danger
*= 0.2;
7821 if (StateIsActivated(PANIC
)) Danger
*= 0.2;
7822 Danger
*= double(GetHP())*Enemy
->GetMaxHP()/(Enemy
->GetHP()*GetMaxHP());
7827 void character::ModifySituationDanger (double &Danger
) const {
7828 switch (GetTirednessState()) {
7829 case FAINTING
: Danger
*= 1.5;
7830 case EXHAUSTED
: Danger
*= 1.25;
7832 for (int c
= 0; c
< STATES
; ++c
) {
7833 if (StateIsActivated(1 << c
) && StateData
[c
].SituationDangerModifier
!= 0) (this->*StateData
[c
].SituationDangerModifier
)(Danger
);
7838 void character::LycanthropySituationDangerModifier (double &Danger
) const {
7839 character
*Wolf
= werewolfwolf::Spawn();
7840 double DangerToWolf
= GetRelativeDanger(Wolf
);
7841 Danger
*= pow(DangerToWolf
, 0.1);
7846 void character::PoisonedSituationDangerModifier (double &Danger
) const {
7847 int C
= GetTemporaryStateCounter(POISONED
);
7848 Danger
*= (1+(C
*C
)/(GetHP()*10000.0*(GetGlobalResistance(POISON
)+1)));
7852 void character::PolymorphingSituationDangerModifier (double &Danger
) const {
7853 if ((!StateIsActivated(POLYMORPH_CONTROL
)) && (!StateIsActivated(POLYMORPH_LOCK
))) Danger
*= 1.5;
7857 void character::PanicSituationDangerModifier (double &Danger
) const {
7862 void character::ConfusedSituationDangerModifier (double &Danger
) const {
7867 void character::ParasitizedSituationDangerModifier (double &Danger
) const {
7872 void character::LeprosySituationDangerModifier (double &Danger
) const {
7877 void character::AddRandomScienceName (festring
&String
) const {
7878 festring Science
= GetScienceTalkName().GetRandomElement().CStr();
7879 if (Science
[0] == '!') {
7880 String
<< Science
.CStr()+1;
7883 festring Attribute
= GetScienceTalkAdjectiveAttribute().GetRandomElement();
7885 truth NoAttrib
= Attribute
.IsEmpty(), NoSecondAdjective
= false;
7886 if (!Attribute
.IsEmpty() && Attribute
[0] == '!') {
7887 NoSecondAdjective
= true;
7888 Attribute
.Erase(0, 1);
7890 if (!Science
.Find("the ")) {
7891 Science
.Erase(0, 4);
7892 if (!Attribute
.Find("the ", 0, 4)) Attribute
<< " the"; else Attribute
.Insert(0, "the ", 4);
7894 if (islower(Science
[0]) && Science
.Find(' ') == festring::NPos
&& Science
.Find('-') == festring::NPos
&&
7895 Science
.Find("phobia") == festring::NPos
) {
7896 Prefix
= GetScienceTalkPrefix().GetRandomElement();
7897 if (!Prefix
.IsEmpty() && Science
.Find(Prefix
) != festring::NPos
) Prefix
.Empty();
7899 int L
= Prefix
.GetSize();
7900 if (L
&& Prefix
[L
-1] == Science
[0]) Science
.Erase(0, 1);
7901 if (!NoAttrib
&& !NoSecondAdjective
== !RAND_GOOD(3)) {
7902 int S1
= NoSecondAdjective
? 0 : GetScienceTalkAdjectiveAttribute().Size
;
7903 int S2
= GetScienceTalkSubstantiveAttribute().Size
;
7904 festring OtherAttribute
;
7905 int Chosen
= RAND_GOOD(S1
+S2
);
7906 if (Chosen
< S1
) OtherAttribute
= GetScienceTalkAdjectiveAttribute()[Chosen
];
7907 else OtherAttribute
= GetScienceTalkSubstantiveAttribute()[Chosen
- S1
];
7908 if (!OtherAttribute
.IsEmpty() && OtherAttribute
.Find("the ", 0, 4) && Attribute
.Find(OtherAttribute
) == festring::NPos
) {
7909 String
<< Attribute
<< ' ' << OtherAttribute
<< ' ' << Prefix
<< Science
;
7913 String
<< Attribute
;
7914 if (!NoAttrib
) String
<< ' ';
7915 String
<< Prefix
<< Science
;
7919 truth
character::TryToTalkAboutScience () {
7920 if (GetRelation(PLAYER
) == HOSTILE
||
7921 GetScienceTalkPossibility() <= RAND_GOOD(100) ||
7922 PLAYER
->GetAttribute(INTELLIGENCE
) < GetScienceTalkIntelligenceRequirement() ||
7923 PLAYER
->GetAttribute(WISDOM
) < GetScienceTalkWisdomRequirement() ||
7924 PLAYER
->GetAttribute(CHARISMA
) < GetScienceTalkCharismaRequirement())
7928 AddRandomScienceName(Science
);
7931 AddRandomScienceName(S1
);
7932 AddRandomScienceName(S2
);
7933 if (S1
.Find(S2
) == festring::NPos
&& S2
.Find(S1
) == festring::NPos
) {
7934 switch (RAND_GOOD(3)) {
7935 case 0: Science
= "the relation of "; break;
7936 case 1: Science
= "the differences of "; break;
7937 case 2: Science
= "the similarities of "; break;
7939 Science
<< S1
<< " and " << S2
;
7942 AddRandomScienceName(Science
);
7945 switch ((RAND() + GET_TICK()) % 10) {
7947 ADD_MESSAGE("You have a rather pleasant chat about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7950 ADD_MESSAGE("%s explains a few of %s opinions regarding %s to you.", CHAR_DESCRIPTION(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, Science
.CStr());
7953 ADD_MESSAGE("%s reveals a number of %s insightful views of %s to you.", CHAR_DESCRIPTION(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, Science
.CStr());
7956 ADD_MESSAGE("You exchange some information pertaining to %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7959 ADD_MESSAGE("You engage in a pretty intriguing conversation about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7962 ADD_MESSAGE("You discuss at length about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7965 ADD_MESSAGE("You have a somewhat boring talk concerning %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7968 ADD_MESSAGE("You are drawn into a heated argument regarding %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7971 ADD_MESSAGE("%s delivers a long monologue concerning eg. %s.", CHAR_DESCRIPTION(DEFINITE
), Science
.CStr());
7974 ADD_MESSAGE("You dive into a brief but thought-provoking debate over %s with %s", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7977 PLAYER
->EditExperience(INTELLIGENCE
, 1000, 50. * GetScienceTalkIntelligenceModifier() / ++ScienceTalks
);
7978 PLAYER
->EditExperience(WISDOM
, 1000, 50. * GetScienceTalkWisdomModifier() / ++ScienceTalks
);
7979 PLAYER
->EditExperience(CHARISMA
, 1000, 50. * GetScienceTalkCharismaModifier() / ++ScienceTalks
);
7984 truth
character::IsUsingWeaponOfCategory (int Category
) const {
7986 ((GetMainWielded() && GetMainWielded()->GetWeaponCategory() == Category
) ||
7987 (GetSecondaryWielded() && GetSecondaryWielded()->GetWeaponCategory() == Category
));
7991 truth
character::TryToUnStickTraps (v2 Dir
) {
7992 if (!TrapData
) return true;
7993 std::vector
<trapdata
> TrapVector
;
7994 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) TrapVector
.push_back(*TrapData
);
7995 for (uInt c
= 0; c
< TrapVector
.size(); ++c
) {
7997 entity
*Trap
= game::SearchTrap(TrapVector
[c
].TrapID
);
7998 /*k8:??? if(!Trap->Exists()) int esko = esko = 2; */
7999 if (!Trap
->Exists()) continue; /*k8: ??? added by me; what this means? */
8000 if (Trap
->GetVictimID() == GetID() && Trap
->TryToUnStick(this, Dir
)) break;
8003 return !TrapData
&& IsEnabled();
8007 struct trapidcomparer
{
8008 trapidcomparer (feuLong ID
) : ID(ID
) {}
8009 truth
operator () (const trapdata
*T
) const { return T
->TrapID
== ID
; }
8014 void character::RemoveTrap (feuLong ID
) {
8015 trapdata
*&T
= ListFind(TrapData
, trapidcomparer(ID
));
8017 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
8021 void character::AddTrap (feuLong ID
, feuLong BodyParts
) {
8022 trapdata
*&T
= ListFind(TrapData
, trapidcomparer(ID
));
8023 if (T
) T
->BodyParts
|= BodyParts
;
8024 else T
= new trapdata(ID
, GetID(), BodyParts
);
8025 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
8029 truth
character::IsStuckToTrap (feuLong ID
) const {
8030 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) if (T
->TrapID
== ID
) return true;
8035 void character::RemoveTraps () {
8036 for (trapdata
*T
= TrapData
; T
; T
= T
->Next
) {
8037 entity
*Trap
= game::SearchTrap(T
->TrapID
);
8038 if (Trap
) Trap
->UnStick();
8040 deleteList(TrapData
);
8041 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
8045 void character::RemoveTraps (int BodyPartIndex
) {
8046 feuLong Flag
= 1 << BodyPartIndex
;
8047 for (trapdata
**T
= &TrapData
; *T
;) {
8048 if ((*T
)->BodyParts
& Flag
) {
8049 entity
*Trap
= game::SearchTrap((*T
)->TrapID
);
8050 if (!((*T
)->BodyParts
&= ~Flag
)) {
8051 if (Trap
) Trap
->UnStick();
8052 trapdata
*ToDel
= *T
;
8056 if (Trap
) Trap
->UnStick(BodyPartIndex
);
8064 if (GetBodyPart(BodyPartIndex
)) GetBodyPart(BodyPartIndex
)->SignalPossibleUsabilityChange();
8068 festring
character::GetTrapDescription () const {
8070 std::pair
<entity
*, int> TrapStack
[3];
8072 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) {
8074 entity
*Trap
= game::SearchTrap(T
->TrapID
);
8077 for (c
= 0; c
< Index
; ++c
) if (TrapStack
[c
].first
->GetTrapType() == Trap
->GetTrapType()) ++TrapStack
[c
].second
;
8078 if (c
== Index
) TrapStack
[Index
++] = std::make_pair(Trap
, 1);
8086 TrapStack
[0].first
->AddTrapName(Desc
, TrapStack
[0].second
);
8089 TrapStack
[1].first
->AddTrapName(Desc
, TrapStack
[1].second
);
8090 } else if (Index
== 3) {
8092 TrapStack
[1].first
->AddTrapName(Desc
, TrapStack
[1].second
);
8094 TrapStack
[2].first
->AddTrapName(Desc
, TrapStack
[2].second
);
8097 Desc
<< "lots of traps";
8103 int character::RandomizeHurtBodyPart (feuLong BodyParts
) const {
8104 int BodyPartIndex
[MAX_BODYPARTS
];
8106 for (int c
= 0; c
< GetBodyParts(); ++c
) {
8107 if (1 << c
& BodyParts
) {
8108 /*k8: ??? if(!GetBodyPart(c)) int esko = esko = 2; */
8109 if (!GetBodyPart(c
)) continue;
8110 BodyPartIndex
[Index
++] = c
;
8112 /*k8: ??? if(!Index) int esko = esko = 2;*/
8115 fprintf(stderr
, "FATAL: RandomizeHurtBodyPart -- Index==0\n");
8118 return BodyPartIndex
[RAND_N(Index
)];
8122 truth
character::BodyPartIsStuck (int I
) const {
8123 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) if (1 << I
& T
->BodyParts
) return true;
8128 void character::PrintAttribute (cchar
*Desc
, int I
, int PanelPosX
, int PanelPosY
) const {
8129 int Attribute
= GetAttribute(I
);
8130 int NoBonusAttribute
= GetAttribute(I
, false);
8131 col16 C
= game::GetAttributeColor(I
);
8132 festring String
= Desc
;
8134 String
<< Attribute
;
8136 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
* 10), C
, "%s", String
.CStr());
8137 if (Attribute
!= NoBonusAttribute
) {
8138 int Where
= PanelPosX
+ ((String
.GetSize() + 1) << 3);
8139 FONT
->Printf(DOUBLE_BUFFER
, v2(Where
, PanelPosY
* 10), LIGHT_GRAY
, "%d", NoBonusAttribute
);
8144 truth
character::AllowUnconsciousness () const {
8145 return (DataBase
->AllowUnconsciousness
&& TorsoIsAlive());
8149 truth
character::CanPanic () const {
8150 return (!Action
|| !Action
->IsUnconsciousness() || !StateIsActivated(FEARLESS
));
8154 int character::GetRandomBodyPart (feuLong Possible
) const {
8155 int OKBodyPart
[MAX_BODYPARTS
];
8156 int OKBodyParts
= 0;
8157 for (int c
= 0; c
< BodyParts
; ++c
) if (1 << c
& Possible
&& GetBodyPart(c
)) OKBodyPart
[OKBodyParts
++] = c
;
8158 return OKBodyParts
? OKBodyPart
[RAND_N(OKBodyParts
)] : NONE_INDEX
;
8162 void character::EditNP (sLong What
) {
8163 int OldState
= GetHungerState();
8165 int NewState
= GetHungerState();
8166 if (OldState
> VERY_HUNGRY
&& NewState
== VERY_HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
8167 if (OldState
> STARVING
&& NewState
== STARVING
) DeActivateVoluntaryAction(CONST_S("You are getting extremely hungry."));
8171 truth
character::IsSwimming () const {
8172 return !IsFlying() && GetSquareUnder() && GetSquareUnder()->GetSquareWalkability() & SWIM
;
8176 void character::AddBlackUnicornConsumeEndMessage () const {
8177 if (IsPlayer()) ADD_MESSAGE("You feel dirty and loathsome.");
8181 void character::AddGrayUnicornConsumeEndMessage () const {
8182 if (IsPlayer()) ADD_MESSAGE("You feel neutralized.");
8186 void character::AddWhiteUnicornConsumeEndMessage () const {
8187 if (IsPlayer()) ADD_MESSAGE("You feel purified.");
8191 void character::AddOmmelBoneConsumeEndMessage () const {
8192 if (IsPlayer()) ADD_MESSAGE("You feel the power of all your canine ancestors combining in your body.");
8193 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s looks extremely ferocious. You shudder.", CHAR_NAME(DEFINITE
));
8197 void character::AddLiquidHorrorConsumeEndMessage () const {
8198 if (IsPlayer()) ADD_MESSAGE("Untold horrors flash before your eyes. The melancholy of the world is on your shoulders!");
8199 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks as if the melancholy of the world is on %s shoulders!.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
8203 void character::AddAlienFleshConsumeEndMessage() const
8205 if (IsPlayer()) ADD_MESSAGE("You feel somehow sick by eating such acidic corpse...");
8206 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks like he eat something bad.", CHAR_NAME(DEFINITE
));
8210 int character::GetBodyPartSparkleFlags (int) const {
8212 ((GetNaturalSparkleFlags() & SKIN_COLOR
? SPARKLING_A
: 0) |
8213 (GetNaturalSparkleFlags() & TORSO_MAIN_COLOR
? SPARKLING_B
: 0) |
8214 (GetNaturalSparkleFlags() & TORSO_SPECIAL_COLOR
? SPARKLING_D
: 0));
8218 truth
character::IsAnimated () const {
8219 return combinebodypartpredicates()(this, &bodypart::IsAnimated
, 1);
8223 double character::GetNaturalExperience (int Identifier
) const {
8224 return DataBase
->NaturalExperience
[Identifier
];
8228 truth
character::HasBodyPart (sorter Sorter
) const {
8229 if (Sorter
== 0) return true;
8230 return combinebodypartpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1);
8234 truth
character::PossessesItem (sorter Sorter
) const {
8235 if (Sorter
== 0) return true;
8237 (GetStack()->SortedItems(this, Sorter
) ||
8238 combinebodypartpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1) ||
8239 combineequipmentpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1));
8243 truth
character::MoreThanOnePossessesItem (sorter Sorter
) const {
8247 for (int c
= 0; c
< BodyParts
; ++c
) {
8248 bodypart
*BodyPart
= GetBodyPart(c
);
8250 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) {
8251 if (++count
> 1) return true;
8254 for (int c
= 0; c
< GetEquipments(); ++c
) {
8255 item
*Equipment
= GetEquipment(c
);
8257 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) {
8258 if (++count
> 1) return true;
8261 for (int c
= 0; c
< GetStack()->GetItems(); ++c
) {
8262 item
*Stk
= GetStack()->GetItem(c
);
8264 if (Stk
&& (Sorter
== 0 || (Stk
->*Sorter
)(this))) {
8265 if (++count
> 1) return true;
8274 item
*character::FirstPossessesItem (sorter Sorter
) const {
8276 for (int c
= 0; c
< BodyParts
; ++c
) {
8277 bodypart
*BodyPart
= GetBodyPart(c
);
8279 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) return BodyPart
;
8281 for (int c
= 0; c
< GetEquipments(); ++c
) {
8282 item
*Equipment
= GetEquipment(c
);
8284 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) return Equipment
;
8286 for (int c
= 0; c
< GetStack()->GetItems(); ++c
) {
8287 item
*Stk
= GetStack()->GetItem(c
);
8289 if (Stk
&& (Sorter
== 0 || (Stk
->*Sorter
)(this))) return Stk
;
8297 cchar
*character::GetRunDescriptionLine (int I
) const {
8298 if (!GetRunDescriptionLineOne().IsEmpty()) return !I
? GetRunDescriptionLineOne().CStr() : GetRunDescriptionLineTwo().CStr();
8299 if (IsFlying()) return !I
? "Flying" : "very fast";
8300 if (IsSwimming()) return !I
? "Swimming" : "very fast";
8301 return !I
? "Running" : "";
8305 void character::VomitAtRandomDirection (int Amount
) {
8306 if (game::IsInWilderness()) return;
8307 /* Lacks support of multitile monsters */
8310 for (int d
= 0; d
< 9; ++d
) {
8311 lsquare
*Square
= GetLSquareUnder()->GetNeighbourLSquare(d
);
8312 if (Square
&& !Square
->VomitingIsDangerous(this)) Possible
[Index
++] = Square
->GetPos();
8314 if (Index
) Vomit(Possible
[RAND_N(Index
)], Amount
);
8315 else Vomit(GetPos(), Amount
);
8319 void character::RemoveLifeSavers () {
8320 for (int c
= 0; c
< GetEquipments(); ++c
) {
8321 item
*Equipment
= GetEquipment(c
);
8322 if (Equipment
&& Equipment
->IsInCorrectSlot(c
) && Equipment
->GetGearStates() & LIFE_SAVED
) {
8323 Equipment
->SendToHell();
8324 Equipment
->RemoveFromSlot();
8330 ccharacter
*character::FindCarrier () const {
8331 return this; //check
8335 void character::PrintBeginHiccupsMessage () const {
8336 if (IsPlayer()) ADD_MESSAGE("Your diaphragm is spasming vehemently.");
8340 void character::PrintEndHiccupsMessage () const {
8341 if (IsPlayer()) ADD_MESSAGE("You feel your annoying hiccoughs have finally subsided.");
8345 void character::HiccupsHandler () {
8347 if (!(RAND() % 2000)) {
8348 if (IsPlayer()) ADD_MESSAGE("");
8349 else if (CanBeSeenByPlayer()) ADD_MESSAGE("");
8350 else if ((PLAYER->GetPos()-GetPos()).GetLengthSquare() <= 400) ADD_MESSAGE("");
8351 game::CallForAttention(GetPos(), 400);
8357 void character::VampirismHandler () {
8358 //EditExperience(ARM_STRENGTH, -25, 1 << 1);
8359 //EditExperience(LEG_STRENGTH, -25, 1 << 1);
8360 //EditExperience(DEXTERITY, -25, 1 << 1);
8361 //EditExperience(AGILITY, -25, 1 << 1);
8362 //EditExperience(ENDURANCE, -25, 1 << 1);
8363 EditExperience(CHARISMA
, -25, 1 << 1);
8364 EditExperience(WISDOM
, -25, 1 << 1);
8365 EditExperience(INTELLIGENCE
, -25, 1 << 1);
8366 CheckDeath(CONST_S("killed by vampirism"));
8370 void character::HiccupsSituationDangerModifier (double &Danger
) const {
8375 void character::VampirismSituationDangerModifier (double &Danger
) const {
8376 character
*Vampire
= vampire::Spawn();
8377 double DangerToVampire
= GetRelativeDanger(Vampire
);
8378 Danger
*= pow(DangerToVampire
, 0.1);
8383 bool character::IsConscious () const {
8384 return !Action
|| !Action
->IsUnconsciousness();
8388 wsquare
*character::GetNearWSquare (v2 Pos
) const {
8389 return static_cast<wsquare
*>(GetSquareUnder()->GetArea()->GetSquare(Pos
));
8393 wsquare
*character::GetNearWSquare (int x
, int y
) const {
8394 return static_cast<wsquare
*>(GetSquareUnder()->GetArea()->GetSquare(x
, y
));
8398 void character::ForcePutNear (v2 Pos
) {
8399 /* GUM SOLUTION!!! */
8400 v2 NewPos
= game::GetCurrentLevel()->GetNearestFreeSquare(PLAYER
, Pos
, false);
8401 if (NewPos
== ERROR_V2
) {
8403 NewPos
= game::GetCurrentLevel()->GetRandomSquare(this, 0, nullptr, NewPos
);
8404 if (NewPos
== ERROR_V2
) ABORT("No country for old fart...");
8405 } while (NewPos
== Pos
);
8411 void character::ReceiveMustardGas (int BodyPart
, sLong Volume
) {
8412 if (Volume
) GetBodyPart(BodyPart
)->AddFluid(liquid::Spawn(MUSTARD_GAS_LIQUID
, Volume
), CONST_S("skin"), 0, true);
8416 void character::ReceiveMustardGasLiquid (int BodyPartIndex
, sLong Modifier
) {
8417 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
8418 if (BodyPart
->GetMainMaterial()->GetInteractionFlags() & IS_AFFECTED_BY_MUSTARD_GAS
) {
8419 sLong Tries
= Modifier
;
8420 Modifier
-= Tries
; //opt%?
8422 for (sLong c
= 0; c
< Tries
; ++c
) if (!(RAND() % 100)) ++Damage
;
8423 if (Modifier
&& !(RAND() % 1000 / Modifier
)) ++Damage
;
8425 feuLong Minute
= game::GetTotalMinutes();
8426 if (GetLastAcidMsgMin() != Minute
&& (CanBeSeenByPlayer() || IsPlayer())) {
8427 SetLastAcidMsgMin(Minute
);
8428 if (IsPlayer()) ADD_MESSAGE("Mustard gas dissolves the skin of your %s.", BodyPart
->GetBodyPartName().CStr());
8429 else ADD_MESSAGE("Mustard gas dissolves %s.", CHAR_NAME(DEFINITE
));
8431 ReceiveBodyPartDamage(0, Damage
, MUSTARD_GAS_DAMAGE
, BodyPartIndex
, YOURSELF
, false, false, false);
8432 CheckDeath(CONST_S("killed by a fatal exposure to mustard gas"));
8438 truth
character::IsBadPath (v2 Pos
) const {
8439 if (!IsGoingSomeWhere()) return false;
8440 v2 TPos
= !Route
.empty() ? Route
.back() : GoingTo
;
8441 return ((TPos
- Pos
).GetManhattanLength() > (TPos
- GetPos()).GetManhattanLength());
8445 double &character::GetExpModifierRef (expid E
) {
8446 return ExpModifierMap
.insert(std::make_pair(E
, 1.)).first
->second
;
8450 /* Should probably do more. Now only makes Player forget gods */
8451 truth
character::ForgetRandomThing () {
8453 /* hopefully this code isn't some where else */
8454 std::vector
<god
*> Known
;
8455 for (int c
= 1; c
<= GODS
; ++c
) if (game::GetGod(c
)->IsKnown()) Known
.push_back(game::GetGod(c
));
8456 if (Known
.empty()) return false;
8457 int RandomGod
= RAND_N(Known
.size());
8458 Known
.at(RAND_N(Known
.size()))->SetIsKnown(false);
8459 ADD_MESSAGE("You forget how to pray to %s.", Known
.at(RandomGod
)->GetName());
8466 int character::CheckForBlock (character
*Enemy
, item
*Weapon
, double ToHitValue
, int Damage
, int Success
, int Type
) {
8471 void character::ApplyAllGodsKnownBonus () {
8472 stack
*AddPlace
= GetStackUnder();
8473 if (game::IsInWilderness()) AddPlace
= GetStack(); else AddPlace
= GetStackUnder();
8474 pantheonbook
*NewBook
= pantheonbook::Spawn();
8475 AddPlace
->AddItem(NewBook
);
8476 ADD_MESSAGE("\"MORTAL! BEHOLD THE HOLY SAGA\"");
8477 ADD_MESSAGE("%s materializes near your feet.", NewBook
->CHAR_NAME(INDEFINITE
));
8481 truth
character::ReceiveSirenSong (character
*Siren
) {
8482 if (Siren
->GetTeam() == GetTeam()) return false;
8484 if (IsPlayer()) ADD_MESSAGE("The beautiful melody of %s makes you feel sleepy.", Siren
->CHAR_NAME(DEFINITE
));
8485 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The beautiful melody of %s makes %s look sleepy.", Siren
->CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
)); /*k8*/
8486 Stamina
-= (1+RAND_N(4))*10000;
8489 if (!IsPlayer() && IsCharmable() && !RAND_N(5)) {
8490 ChangeTeam(Siren
->GetTeam());
8491 ADD_MESSAGE("%s seems to be totally brainwashed by %s melodies.", CHAR_NAME(DEFINITE
), Siren
->CHAR_NAME(DEFINITE
));
8495 item
*What
= GiveMostExpensiveItem(Siren
);
8498 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
);
8500 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
);
8503 if (IsPlayer()) ADD_MESSAGE("You would like to give something to %s.", Siren
->CHAR_NAME(DEFINITE
));
8511 item
*character::findFirstEquippedItem (cfestring
&aclassname
, int aconfig
) const {
8512 for (int f
= 0; f
< GetEquipments(); ++f
) {
8513 item
*it
= GetEquipment(f
);
8515 if (aclassname
.CompareIgnoreCase(it
->GetClassID()) == 0) {
8516 if (aconfig
== -1 || it
->GetConfig() == aconfig
) return it
;
8519 for (uInt c = 0; c < it->GetDataBase()->Alias.Size; ++c) {
8520 if (s.CompareIgnoreCase(it->GetDataBase()->Alias[c]) == 0) return it;
8528 item
*character::findFirstInventoryItem (cfestring
&aclassname
, int aconfig
) const {
8530 GetStack()->FillItemVector(items
);
8531 for (int f
= 0; f
< (int)items
.size(); ++f
) {
8532 item
*it
= items
[f
];
8534 if (aclassname
.CompareIgnoreCase(it
->GetClassID()) == 0) {
8535 if (aconfig
== -1 || it
->GetConfig() == aconfig
) return it
;
8542 item
*character::findFirstItem (cfestring
&aclassname
, int aconfig
) const {
8543 item
*it
= findFirstInventoryItem(aclassname
, aconfig
);
8544 if (!it
) it
= findFirstEquippedItem(aclassname
, aconfig
);
8549 // return 0, if no item found
8550 item
*character::FindMostExpensiveItem () const {
8552 item
*MostExpensive
= 0;
8553 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
8554 if ((*i
)->GetPrice() > MaxPrice
) {
8555 MaxPrice
= (*i
)->GetPrice();
8556 MostExpensive
= (*i
);
8559 for (int c
= 0; c
< GetEquipments(); ++c
) {
8560 item
*Equipment
= GetEquipment(c
);
8561 if (Equipment
&& Equipment
->GetPrice() > MaxPrice
) {
8562 MaxPrice
= Equipment
->GetPrice();
8563 MostExpensive
= Equipment
;
8566 return MostExpensive
;
8570 // returns 0 if no items available
8571 item
*character::GiveMostExpensiveItem(character
*ToWhom
) {
8572 item
*ToGive
= FindMostExpensiveItem();
8573 if (!ToGive
) return 0;
8574 truth Equipped
= PLAYER
->Equips(ToGive
);
8575 ToGive
->RemoveFromSlot();
8576 if (Equipped
) game::AskForEscPress(CONST_S("Equipment lost!"));
8577 ToWhom
->ReceiveItemAsPresent(ToGive
);
8583 void character::ReceiveItemAsPresent (item
*Present
) {
8584 if (TestForPickup(Present
)) GetStack()->AddItem(Present
); else GetStackUnder()->AddItem(Present
);
8588 /* returns 0 if no enemies in sight */
8589 character
*character::GetNearestEnemy () const {
8590 character
*NearestEnemy
= 0;
8591 sLong NearestEnemyDistance
= 0x7FFFFFFF;
8593 for (int c
= 0; c
< game::GetTeams(); ++c
) {
8594 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
8595 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
8596 if ((*i
)->IsEnabled()) {
8597 sLong ThisDistance
= Max
<sLong
>(abs((*i
)->GetPos().X
- Pos
.X
), abs((*i
)->GetPos().Y
- Pos
.Y
));
8598 if ((ThisDistance
< NearestEnemyDistance
|| (ThisDistance
== NearestEnemyDistance
&& !(RAND() % 3))) && (*i
)->CanBeSeenBy(this)) {
8600 NearestEnemyDistance
= ThisDistance
;
8606 return NearestEnemy
;
8610 truth
character::MindWormCanPenetrateSkull (mindworm
*) const {
8615 truth
character::CanTameWithDulcis (const character
*Tamer
) const {
8616 int TamingDifficulty
= GetTamingDifficulty();
8617 if (TamingDifficulty
== NO_TAMING
) return false;
8618 if (GetAttachedGod() == DULCIS
) return true;
8619 int Modifier
= Tamer
->GetAttribute(WISDOM
) + Tamer
->GetAttribute(CHARISMA
);
8620 if (Tamer
->IsPlayer()) Modifier
+= game::GetGod(DULCIS
)->GetRelation() / 20;
8621 else if (Tamer
->GetAttachedGod() == DULCIS
) Modifier
+= 50;
8622 if (TamingDifficulty
== 0) {
8623 if (!IgnoreDanger()) TamingDifficulty
= int(10 * GetRelativeDanger(Tamer
));
8624 else TamingDifficulty
= 10 * GetHPRequirementForGeneration()/Max(Tamer
->GetHP(), 1);
8626 return Modifier
>= TamingDifficulty
* 3;
8630 truth
character::CanTameWithLyre (const character
*Tamer
) const {
8631 int TamingDifficulty
= GetTamingDifficulty();
8632 if (TamingDifficulty
== NO_TAMING
) return false;
8633 if (TamingDifficulty
== 0) {
8634 if (!IgnoreDanger()) TamingDifficulty
= int(10 * GetRelativeDanger(Tamer
));
8635 else TamingDifficulty
= 10*GetHPRequirementForGeneration()/Max(Tamer
->GetHP(), 1);
8637 return Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
;
8641 truth
character::CanTameWithScroll (const character
*Tamer
) const {
8642 int TamingDifficulty
= GetTamingDifficulty();
8644 (TamingDifficulty
!= NO_TAMING
&&
8645 (TamingDifficulty
== 0 ||
8646 Tamer
->GetAttribute(INTELLIGENCE
) * 4 + Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
* 5));
8650 truth
character::CanTameWithResurrection (const character
*Tamer
) const {
8651 int TamingDifficulty
= GetTamingDifficulty();
8652 if (TamingDifficulty
== NO_TAMING
) return false;
8653 if (TamingDifficulty
== 0) return true;
8654 return (Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
/2);
8658 truth
character::CheckSadism () {
8659 if (!IsSadist() || !HasSadistAttackMode() || !IsSmall()) return false; // gum
8661 for (int d
= 0; d
< MDIR_STAND
; ++d
) {
8662 square
*Square
= GetNeighbourSquare(d
);
8664 character
*Char
= Square
->GetCharacter();
8665 if (Char
&& Char
->IsMasochist() && GetRelation(Char
) == FRIEND
&&
8666 Char
->GetHP() * 3 >= Char
->GetMaxHP() * 2 && Hit(Char
, Square
->GetPos(), d
, SADIST_HIT
)) {
8677 truth
character::CheckForBeverage () {
8678 if (StateIsActivated(PANIC
) || !IsEnabled() || !UsesNutrition() || CheckIfSatiated()) return false;
8679 itemvector ItemVector
;
8680 GetStack()->FillItemVector(ItemVector
);
8681 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) if (ItemVector
[c
]->IsBeverage(this) && TryToConsume(ItemVector
[c
])) return true;
8686 void character::Haste () {
8687 doforbodyparts()(this, &bodypart::Haste
);
8688 doforequipments()(this, &item::Haste
);
8689 BeginTemporaryState(HASTE
, 500 + RAND() % 1000);
8693 void character::Slow () {
8694 doforbodyparts()(this, &bodypart::Slow
);
8695 doforequipments()(this, &item::Slow
);
8696 //BeginTemporaryState(HASTE, 500 + RAND() % 1000); // this seems to be a bug
8697 BeginTemporaryState(SLOW
, 500 + RAND() % 1000);
8701 void character::SurgicallyDetachBodyPart () {
8702 ADD_MESSAGE("You haven't got any extra bodyparts.");
8706 truth
character::CanHear() const
8708 return DataBase
->CanHear
&& HasHead();
8712 truth
character::IsAllowedInDungeon (int dunIndex
) {
8713 const fearray
<int> &dlist
= GetAllowedDungeons();
8715 for (uInt f
= 0; f
< dlist
.Size
; ++f
) {
8716 if (dlist
[f
] == ALL_DUNGEONS
|| dlist
[f
] == dunIndex
) {
8717 //fprintf(stderr, "OK!\n");
8721 //fprintf(stderr, "NO!\n");
8726 truth
character::IsESPBlockedByEquipment () const {
8727 for (int c
= 0; c
< GetEquipments(); ++c
) {
8728 item
*Item
= GetEquipment(c
);
8729 if (Item
&& Item
->IsHelmet(this) &&
8730 ((Item
->GetMainMaterial() && Item
->GetMainMaterial()->BlockESP()) ||
8731 (Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->BlockESP()))) return true;
8737 truth
character::TemporaryStateIsActivated (sLong What
) const {
8738 if ((What
&PANIC
) && StateIsActivated(FEARLESS
)) What
&= ~PANIC
;
8739 if ((What
&ESP
) && (TemporaryState
&ESP
) && IsESPBlockedByEquipment()) What
&= ~ESP
;
8740 return ((TemporaryState
&What
) != 0);
8744 truth
character::StateIsActivated (sLong What
) const {
8745 if ((What
&PANIC
) && ((TemporaryState
|EquipmentState
)&FEARLESS
)) What
&= ~PANIC
;
8746 if ((What
&ESP
) && ((TemporaryState
|EquipmentState
)&ESP
) && IsESPBlockedByEquipment()) What
&= ~ESP
;
8747 return (TemporaryState
&What
) || (EquipmentState
&What
);
8751 void character::PrintBeginFearlessMessage () const {
8752 if (!StateIsActivated(FEARLESS
)) {
8753 if (IsPlayer()) ADD_MESSAGE("You feel very comfortable.");
8754 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems very comfortable.", CHAR_NAME(DEFINITE
));
8758 void character::PrintEndFearlessMessage () const {
8759 if (!StateIsActivated(FEARLESS
)) {
8760 if (IsPlayer()) ADD_MESSAGE("Everything looks more dangerous now.");
8761 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems to have lost his confidence.", CHAR_NAME(DEFINITE
));
8765 void character::BeginFearless () {
8766 DeActivateTemporaryState(PANIC
);
8769 void character::EndFearless () {
8774 void character::PrintBeginEtherealityMessage () const {
8775 if (IsPlayer()) ADD_MESSAGE("You feel like many miscible droplets of ether.");
8776 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s melds into the surroundings.", CHAR_NAME(DEFINITE
));
8779 void character::PrintEndEtherealityMessage () const {
8780 if (IsPlayer()) ADD_MESSAGE("You drop out of the firmament, feeling suddenly quite dense.");
8781 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s displaces the air with a puff.", CHAR_NAME(INDEFINITE
));
8784 void character::BeginEthereality () {}
8786 void character::EndEthereality () {}
8789 void character::PrintBeginPolymorphLockMessage () const {
8790 if (IsPlayer()) ADD_MESSAGE("You feel incredibly stubborn about who you are.");
8793 void character::PrintEndPolymorphLockMessage () const {
8794 if (IsPlayer()) ADD_MESSAGE("You feel more open to new ideas.");
8797 void character::PolymorphLockHandler () {
8798 if (TemporaryStateIsActivated(POLYMORPHED
)) {
8799 EditTemporaryStateCounter(POLYMORPHED
, 1);
8800 if (GetTemporaryStateCounter(POLYMORPHED
) < 1000) EditTemporaryStateCounter(POLYMORPHED
, 1);
8804 void character::PrintBeginRegenerationMessage () const {
8805 if (IsPlayer()) ADD_MESSAGE("Your heart races.");
8808 void character::PrintEndRegenerationMessage () const {
8809 if (IsPlayer()) ADD_MESSAGE("Your rapid heartbeat calms down.");
8812 void character::PrintBeginDiseaseImmunityMessage () const {
8813 if (IsPlayer()) ADD_MESSAGE("You feel especially healthy.");
8816 void character::PrintEndDiseaseImmunityMessage () const {
8817 if (IsPlayer()) ADD_MESSAGE("You develop a sudden fear of germs.");
8820 void character::PrintBeginTeleportLockMessage () const {
8821 if (IsPlayer()) ADD_MESSAGE("You feel firmly planted in reality.");
8824 void character::PrintEndTeleportLockMessage () const {
8825 if (IsPlayer()) ADD_MESSAGE("Your mind soars far and wide.");
8828 void character::TeleportLockHandler () {
8829 if (StateIsActivated(TELEPORT_LOCK
)) {
8830 EditTemporaryStateCounter(TELEPORT_LOCK
, 1);
8831 if (GetTemporaryStateCounter(TELEPORT_LOCK
) < 1000) EditTemporaryStateCounter(TELEPORT_LOCK
, 1);
8836 void character::PrintBeginSwimmingMessage () const {
8837 if (IsPlayer()) ADD_MESSAGE("You fondly remember the sound of ocean waves.");
8838 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks wet.", CHAR_NAME(DEFINITE
));
8841 void character::PrintEndSwimmingMessage () const {
8842 if (IsPlayer()) ADD_MESSAGE("You suddenly remember how you nearly drowned as a child.");
8843 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks less wet.", CHAR_NAME(INDEFINITE
));
8846 void character::BeginSwimming () {}
8847 void character::EndSwimming () {}