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 const statedata StateData
[STATES
] =
74 &character::EndPolymorph
,
80 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
81 &character::PrintBeginHasteMessage
,
82 &character::PrintEndHasteMessage
,
90 RANDOMIZABLE
&~SRC_GOOD
,
91 &character::PrintBeginSlowMessage
,
92 &character::PrintEndSlowMessage
,
100 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
|SRC_GOOD
),
101 &character::PrintBeginPolymorphControlMessage
,
102 &character::PrintEndPolymorphControlMessage
,
111 &character::PrintBeginLifeSaveMessage
,
112 &character::PrintEndLifeSaveMessage
,
120 SECRET
|SRC_FOUNTAIN
|SRC_CONFUSE_READ
|DUR_FLAGS
,
121 &character::PrintBeginLycanthropyMessage
,
122 &character::PrintEndLycanthropyMessage
,
125 &character::LycanthropyHandler
,
127 &character::LycanthropySituationDangerModifier
130 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
131 &character::PrintBeginInvisibilityMessage
,
132 &character::PrintEndInvisibilityMessage
,
133 &character::BeginInvisibility
,
134 &character::EndInvisibility
,
140 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
141 &character::PrintBeginInfraVisionMessage
,
142 &character::PrintEndInfraVisionMessage
,
143 &character::BeginInfraVision
,
144 &character::EndInfraVision
,
150 RANDOMIZABLE
&~SRC_EVIL
,
151 &character::PrintBeginESPMessage
,
152 &character::PrintEndESPMessage
,
153 &character::BeginESP
,
161 &character::PrintBeginPoisonedMessage
,
162 &character::PrintEndPoisonedMessage
,
165 &character::PoisonedHandler
,
166 &character::CanBePoisoned
,
167 &character::PoisonedSituationDangerModifier
170 SECRET
|(RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_GOOD
)),
171 &character::PrintBeginTeleportMessage
,
172 &character::PrintEndTeleportMessage
,
175 &character::TeleportHandler
,
180 SECRET
|(RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_GOOD
)),
181 &character::PrintBeginPolymorphMessage
,
182 &character::PrintEndPolymorphMessage
,
185 &character::PolymorphHandler
,
187 &character::PolymorphingSituationDangerModifier
190 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
191 &character::PrintBeginTeleportControlMessage
,
192 &character::PrintEndTeleportControlMessage
,
201 &character::PrintBeginPanicMessage
,
202 &character::PrintEndPanicMessage
,
203 &character::BeginPanic
,
204 &character::EndPanic
,
206 &character::CanPanic
,
207 &character::PanicSituationDangerModifier
210 SECRET
|(RANDOMIZABLE
&~(DUR_PERMANENT
|SRC_GOOD
)),
211 &character::PrintBeginConfuseMessage
,
212 &character::PrintEndConfuseMessage
,
216 &character::CanBeConfused
,
217 &character::ConfusedSituationDangerModifier
220 SECRET
|(RANDOMIZABLE
&~DUR_TEMPORARY
),
221 &character::PrintBeginParasitizedMessage
,
222 &character::PrintEndParasitizedMessage
,
225 &character::ParasitizedHandler
,
226 &character::CanBeParasitized
,
227 &character::ParasitizedSituationDangerModifier
231 &character::PrintBeginSearchingMessage
,
232 &character::PrintEndSearchingMessage
,
235 &character::SearchingHandler
,
240 SECRET
|(RANDOMIZABLE
&~(SRC_GOOD
|SRC_EVIL
)),
241 &character::PrintBeginGasImmunityMessage
,
242 &character::PrintEndGasImmunityMessage
,
250 RANDOMIZABLE
&~SRC_EVIL
,
251 &character::PrintBeginLevitationMessage
,
252 &character::PrintEndLevitationMessage
,
254 &character::EndLevitation
,
260 SECRET
|(RANDOMIZABLE
&~DUR_TEMPORARY
),
261 &character::PrintBeginLeprosyMessage
,
262 &character::PrintEndLeprosyMessage
,
263 &character::BeginLeprosy
,
264 &character::EndLeprosy
,
265 &character::LeprosyHandler
,
267 &character::LeprosySituationDangerModifier
270 SRC_FOUNTAIN
|SRC_CONFUSE_READ
|DUR_FLAGS
,
271 &character::PrintBeginHiccupsMessage
,
272 &character::PrintEndHiccupsMessage
,
275 &character::HiccupsHandler
,
277 &character::HiccupsSituationDangerModifier
280 DUR_FLAGS
, //perhaps no fountain, no secret and no confuse read either: SECRET|SRC_FOUNTAIN|SRC_CONFUSE_READ|
281 &character::PrintBeginVampirismMessage
,
282 &character::PrintEndVampirismMessage
,
285 &character::VampirismHandler
,
287 &character::VampirismSituationDangerModifier
290 SECRET
|(RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
)),
291 &character::PrintBeginDetectMessage
,
292 &character::PrintEndDetectMessage
,
295 &character::DetectHandler
,
301 &character::PrintBeginEtherealityMessage
,
302 &character::PrintEndEtherealityMessage
,
303 &character::BeginEthereality
, &character::EndEthereality
,
309 RANDOMIZABLE
&~SRC_EVIL
,
310 &character::PrintBeginFearlessMessage
,
311 &character::PrintEndFearlessMessage
,
312 &character::BeginFearless
,
313 &character::EndFearless
,
321 characterprototype::characterprototype (const characterprototype
*Base
, characterspawner Spawner
,
322 charactercloner Cloner
, cchar
*ClassID
)
328 Index
= protocontainer
<character
>::Add(this);
332 void character::CreateInitialEquipment (int SpecialFlags
) { AddToInventory(DataBase
->Inventory
, SpecialFlags
); }
333 void character::EditAP (sLong What
) { AP
= Limit
<sLong
>(AP
+What
, -12000, 1200); }
334 int character::GetRandomStepperBodyPart () const { return TORSO_INDEX
; }
335 void character::GainIntrinsic (sLong What
) { BeginTemporaryState(What
, PERMANENT
); }
336 truth
character::IsUsingArms () const { return GetAttackStyle() & USE_ARMS
; }
337 truth
character::IsUsingLegs () const { return GetAttackStyle() & USE_LEGS
; }
338 truth
character::IsUsingHead () const { return GetAttackStyle() & USE_HEAD
; }
339 void character::CalculateAllowedWeaponSkillCategories () { AllowedWeaponSkillCategories
= MARTIAL_SKILL_CATEGORIES
; }
340 festring
character::GetBeVerb () const { return IsPlayer() ? CONST_S("are") : CONST_S("is"); }
341 void character::SetEndurance (int What
) { BaseExperience
[ENDURANCE
] = What
* EXP_MULTIPLIER
; }
342 void character::SetPerception (int What
) { BaseExperience
[PERCEPTION
] = What
* EXP_MULTIPLIER
; }
343 void character::SetIntelligence (int What
) { BaseExperience
[INTELLIGENCE
] = What
* EXP_MULTIPLIER
; }
344 void character::SetWisdom (int What
) { BaseExperience
[WISDOM
] = What
* EXP_MULTIPLIER
; }
345 void character::SetWillPower (int What
) { BaseExperience
[WILL_POWER
] = What
* EXP_MULTIPLIER
; }
346 void character::SetCharisma (int What
) { BaseExperience
[CHARISMA
] = What
* EXP_MULTIPLIER
; }
347 void character::SetMana (int What
) { BaseExperience
[MANA
] = What
* EXP_MULTIPLIER
; }
348 truth
character::IsOnGround () const { return MotherEntity
&& MotherEntity
->IsOnGround(); }
349 truth
character::LeftOversAreUnique () const { return GetArticleMode() || AssignedName
.GetSize(); }
350 truth
character::HomeDataIsValid () const { return (HomeData
&& HomeData
->Level
== GetLSquareUnder()->GetLevelIndex() && HomeData
->Dungeon
== GetLSquareUnder()->GetDungeonIndex()); }
351 void character::SetHomePos (v2 Pos
) { HomeData
->Pos
= Pos
; }
352 cchar
*character::FirstPersonUnarmedHitVerb () const { return "hit"; }
353 cchar
*character::FirstPersonCriticalUnarmedHitVerb () const { return "critically hit"; }
354 cchar
*character::ThirdPersonUnarmedHitVerb () const { return "hits"; }
355 cchar
*character::ThirdPersonCriticalUnarmedHitVerb () const { return "critically hits"; }
356 cchar
*character::FirstPersonKickVerb () const { return "kick"; }
357 cchar
*character::FirstPersonCriticalKickVerb () const { return "critically kick"; }
358 cchar
*character::ThirdPersonKickVerb () const { return "kicks"; }
359 cchar
*character::ThirdPersonCriticalKickVerb () const { return "critically kicks"; }
360 cchar
*character::FirstPersonBiteVerb () const { return "bite"; }
361 cchar
*character::FirstPersonCriticalBiteVerb () const { return "critically bite"; }
362 cchar
*character::ThirdPersonBiteVerb () const { return "bites"; }
363 cchar
*character::ThirdPersonCriticalBiteVerb () const { return "critically bites"; }
364 cchar
*character::UnarmedHitNoun () const { return "attack"; }
365 cchar
*character::KickNoun () const { return "kick"; }
366 cchar
*character::BiteNoun () const { return "attack"; }
367 cchar
*character::GetEquipmentName (int) const { return ""; }
368 const std::list
<feuLong
> &character::GetOriginalBodyPartID (int I
) const { return OriginalBodyPartID
[I
]; }
369 square
*character::GetNeighbourSquare (int I
) const { return GetSquareUnder()->GetNeighbourSquare(I
); }
370 lsquare
*character::GetNeighbourLSquare (int I
) const { return static_cast<lsquare
*>(GetSquareUnder())->GetNeighbourLSquare(I
); }
371 wsquare
*character::GetNeighbourWSquare (int I
) const { return static_cast<wsquare
*>(GetSquareUnder())->GetNeighbourWSquare(I
); }
372 god
*character::GetMasterGod () const { return game::GetGod(GetConfig()); }
373 col16
character::GetBodyPartColorA (int, truth
) const { return GetSkinColor(); }
374 col16
character::GetBodyPartColorB (int, truth
) const { return GetTorsoMainColor(); }
375 col16
character::GetBodyPartColorC (int, truth
) const { return GetBeltColor(); } // sorry...
376 col16
character::GetBodyPartColorD (int, truth
) const { return GetTorsoSpecialColor(); }
377 int character::GetRandomApplyBodyPart () const { return TORSO_INDEX
; }
378 truth
character::IsPet () const { return GetTeam()->GetID() == PLAYER_TEAM
; }
379 character
* character::GetLeader () const { return GetTeam()->GetLeader(); }
380 festring
character::GetZombieDescription () const { return " of "+GetName(INDEFINITE
); }
381 truth
character::BodyPartCanBeSevered (int I
) const { return I
; }
382 truth
character::HasBeenSeen () const { return DataBase
->Flags
& HAS_BEEN_SEEN
; }
383 truth
character::IsTemporary () const { return GetTorso()->GetLifeExpectancy(); }
384 cchar
*character::GetNormalDeathMessage () const { return "killed @k"; }
387 truth
character::IsHomeLevel (level
*lvl
) const {
388 if (!lvl
) return false;
390 const fearray
<festring
> &hlist
= DataBase
->HomeLevel
;
391 if (hlist
.Size
== 0) return false;
393 for (uInt f
= 0; f
< hlist
.Size
; ++f
) { if (hlist
[f
] == "*") return true; }
394 // taken from unique characters code
395 cfestring
*tag
= lvl
->GetLevelScript()->GetTag();
396 if (!tag
) return false; // not a possible home level
397 // check for home level
398 for (uInt f
= 0; f
< hlist
.Size
; ++f
) { if (hlist
[f
] == *tag
) return true; }
403 truth
character::MustBeRemovedFromBone () const {
404 if (IsUnique() && !CanBeGenerated()) return true;
406 const fearray
<festring
> &hlist
= DataBase
->HomeLevel
;
407 if (hlist
.Size
== 0) return false;
409 for (uInt f
= 0; f
< hlist
.Size
; ++f
) { if (hlist
[f
] == "*") return true; }
410 // taken from unique characters code
411 if (!IsEnabled() || GetTeam()->GetID() != DataBase
->NaturalTeam
) return true;
412 return IsHomeLevel(GetLevel());
416 int character::GetMoveType () const {// return (!StateIsActivated(LEVITATION) ? DataBase->MoveType : DataBase->MoveType | FLY); }
418 ((!StateIsActivated(LEVITATION
) ? DataBase
->MoveType
: DataBase
->MoveType
|FLY
)|
419 (!StateIsActivated(ETHEREAL_MOVING
) ? DataBase
->MoveType
: DataBase
->MoveType
|ETHEREAL
));
423 int characterdatabase::*ExpPtr
[ATTRIBUTES
] = {
424 &characterdatabase::DefaultEndurance
,
425 &characterdatabase::DefaultPerception
,
426 &characterdatabase::DefaultIntelligence
,
427 &characterdatabase::DefaultWisdom
,
428 &characterdatabase::DefaultWillPower
,
429 &characterdatabase::DefaultCharisma
,
430 &characterdatabase::DefaultMana
,
431 &characterdatabase::DefaultArmStrength
,
432 &characterdatabase::DefaultLegStrength
,
433 &characterdatabase::DefaultDexterity
,
434 &characterdatabase::DefaultAgility
438 contentscript
<item
> characterdatabase::*EquipmentDataPtr
[EQUIPMENT_DATAS
] = {
439 &characterdatabase::Helmet
,
440 &characterdatabase::Amulet
,
441 &characterdatabase::Cloak
,
442 &characterdatabase::BodyArmor
,
443 &characterdatabase::Belt
,
444 &characterdatabase::RightWielded
,
445 &characterdatabase::LeftWielded
,
446 &characterdatabase::RightRing
,
447 &characterdatabase::LeftRing
,
448 &characterdatabase::RightGauntlet
,
449 &characterdatabase::LeftGauntlet
,
450 &characterdatabase::RightBoot
,
451 &characterdatabase::LeftBoot
455 character::character (ccharacter
&Char
) :
456 entity(Char
), id(Char
), NP(Char
.NP
), AP(Char
.AP
),
457 TemporaryState(Char
.TemporaryState
&~POLYMORPHED
),
458 Team(Char
.Team
), GoingTo(ERROR_V2
), Money(0),
459 AssignedName(Char
.AssignedName
), Action(0),
460 DataBase(Char
.DataBase
), MotherEntity(0),
461 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
462 AllowedWeaponSkillCategories(Char
.AllowedWeaponSkillCategories
),
463 BodyParts(Char
.BodyParts
),
464 RegenerationCounter(Char
.RegenerationCounter
),
465 SquaresUnder(Char
.SquaresUnder
), LastAcidMsgMin(0),
466 Stamina(Char
.Stamina
), MaxStamina(Char
.MaxStamina
),
467 BlocksSinceLastTurn(0), GenerationDanger(Char
.GenerationDanger
),
468 CommandFlags(Char
.CommandFlags
), WarnFlags(0),
469 ScienceTalks(Char
.ScienceTalks
), TrapData(0), CounterToMindWormHatch(0)
473 Flags
|= C_INITIALIZING
|C_IN_NO_MSG_MODE
;
474 Stack
= new stack(0, this, HIDDEN
);
475 for (c
= 0; c
< STATES
; ++c
) TemporaryStateCounter
[c
] = Char
.TemporaryStateCounter
[c
];
476 if (Team
) Team
->Add(this);
477 for (c
= 0; c
< BASE_ATTRIBUTES
; ++c
) BaseExperience
[c
] = Char
.BaseExperience
[c
];
478 BodyPartSlot
= new bodypartslot
[BodyParts
];
479 OriginalBodyPartID
= new std::list
<feuLong
>[BodyParts
];
480 CWeaponSkill
= new cweaponskill
[AllowedWeaponSkillCategories
];
481 SquareUnder
= new square
*[SquaresUnder
];
482 if (SquaresUnder
== 1) *SquareUnder
= 0; else memset(SquareUnder
, 0, SquaresUnder
*sizeof(square
*));
483 for (c
= 0; c
< BodyParts
; ++c
) {
484 BodyPartSlot
[c
].SetMaster(this);
485 bodypart
*CharBodyPart
= Char
.GetBodyPart(c
);
486 OriginalBodyPartID
[c
] = Char
.OriginalBodyPartID
[c
];
488 bodypart
*BodyPart
= static_cast<bodypart
*>(CharBodyPart
->Duplicate());
489 SetBodyPart(c
, BodyPart
);
490 BodyPart
->CalculateEmitation();
493 for (c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) CWeaponSkill
[c
] = Char
.CWeaponSkill
[c
];
494 HomeData
= Char
.HomeData
? new homedata(*Char
.HomeData
) : 0;
495 ID
= game::CreateNewCharacterID(this);
499 character::character () :
500 entity(HAS_BE
), NP(50000), AP(0), TemporaryState(0), Team(0),
501 GoingTo(ERROR_V2
), Money(0), Action(0), MotherEntity(0),
502 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
503 RegenerationCounter(0), HomeData(0), LastAcidMsgMin(0),
504 BlocksSinceLastTurn(0), GenerationDanger(DEFAULT_GENERATION_DANGER
),
505 WarnFlags(0), ScienceTalks(0), TrapData(0), CounterToMindWormHatch(0)
507 Stack
= new stack(0, this, HIDDEN
);
511 character::~character () {
512 if (Action
) delete Action
;
513 if (Team
) Team
->Remove(this);
515 for (int c
= 0; c
< BodyParts
; ++c
) delete GetBodyPart(c
);
516 delete [] BodyPartSlot
;
517 delete [] OriginalBodyPartID
;
518 delete PolymorphBackup
;
519 delete [] SquareUnder
;
520 delete [] CWeaponSkill
;
522 deleteList(TrapData
);
523 game::RemoveCharacterID(ID
);
527 void character::Hunger () {
528 switch (GetBurdenState()) {
532 EditExperience(LEG_STRENGTH
, 150, 1 << 2);
533 EditExperience(AGILITY
, -50, 1 << 2);
537 EditExperience(LEG_STRENGTH
, 75, 1 << 1);
538 EditExperience(AGILITY
, -25, 1 << 1);
545 switch (GetHungerState()) {
547 EditExperience(ARM_STRENGTH
, -75, 1 << 3);
548 EditExperience(LEG_STRENGTH
, -75, 1 << 3);
551 EditExperience(ARM_STRENGTH
, -50, 1 << 2);
552 EditExperience(LEG_STRENGTH
, -50, 1 << 2);
555 EditExperience(ARM_STRENGTH
, -25, 1 << 1);
556 EditExperience(LEG_STRENGTH
, -25, 1 << 1);
559 EditExperience(AGILITY
, -25, 1 << 1);
562 EditExperience(AGILITY
, -50, 1 << 2);
565 EditExperience(AGILITY
, -75, 1 << 3);
568 CheckStarvationDeath(CONST_S("starved to death"));
572 int character::TakeHit (character
*Enemy
, item
*Weapon
, bodypart
*EnemyBodyPart
, v2 HitPos
, double Damage
,
573 double ToHitValue
, int Success
, int Type
, int GivenDir
, truth Critical
, truth ForceHit
)
576 game::ClearEventData();
577 game::mActor
= Enemy
;
578 game::mResult
= DID_NO_DAMAGE
;
579 if (game::RunOnCharEvent(this, CONST_S("take_hit"))) { game::ClearEventData(); return game::mResult
; }
580 game::ClearEventData();
581 int Dir
= Type
== BITE_ATTACK
? YOURSELF
: GivenDir
;
582 double DodgeValue
= GetDodgeValue();
583 if (!Enemy
->IsPlayer() && GetAttackWisdomLimit() != NO_LIMIT
) Enemy
->EditExperience(WISDOM
, 75, 1 << 13);
584 if (!Enemy
->CanBeSeenBy(this)) ToHitValue
*= 2;
585 if (!CanBeSeenBy(Enemy
)) DodgeValue
*= 2;
586 if (Enemy
->StateIsActivated(CONFUSED
)) ToHitValue
*= 0.75;
588 switch (Enemy
->GetTirednessState()) {
594 switch (GetTirednessState()) {
602 if (!IsRetreating()) SetGoingTo(Enemy
->GetPos());
603 else SetGoingTo(GetPos()-((Enemy
->GetPos()-GetPos())<<4));
604 if (!Enemy
->IsRetreating()) Enemy
->SetGoingTo(GetPos());
605 else Enemy
->SetGoingTo(Enemy
->GetPos()-((GetPos()-Enemy
->GetPos())<<4));
608 /* Effectively, the average chance to hit is 100% / (DV/THV + 1). */
609 if (RAND() % int(100+ToHitValue
/DodgeValue
*(100+Success
)) < 100 && !Critical
&& !ForceHit
) {
610 Enemy
->AddMissMessage(this);
611 EditExperience(AGILITY
, 150, 1 << 7);
612 EditExperience(PERCEPTION
, 75, 1 << 7);
613 if (Enemy
->CanBeSeenByPlayer())
614 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
616 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
620 int TrueDamage
= int(Damage
*(100+Success
)/100)+(RAND()%3 ? 1 : 0);
622 TrueDamage
+= TrueDamage
>> 1;
626 int BodyPart
= ChooseBodyPartToReceiveHit(ToHitValue
, DodgeValue
);
630 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalUnarmedHitVerb(), Enemy
->ThirdPersonCriticalUnarmedHitVerb(), BodyPart
);
633 Enemy
->AddWeaponHitMessage(this, Weapon
, BodyPart
, true);
636 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalKickVerb(), Enemy
->ThirdPersonCriticalKickVerb(), BodyPart
);
639 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalBiteVerb(), Enemy
->ThirdPersonCriticalBiteVerb(), BodyPart
);
645 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonUnarmedHitVerb(), Enemy
->ThirdPersonUnarmedHitVerb(), BodyPart
);
648 Enemy
->AddWeaponHitMessage(this, Weapon
, BodyPart
, false);
651 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonKickVerb(), Enemy
->ThirdPersonKickVerb(), BodyPart
);
654 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonBiteVerb(), Enemy
->ThirdPersonBiteVerb(), BodyPart
);
659 if (!Critical
&& TrueDamage
&& Enemy
->AttackIsBlockable(Type
)) {
660 TrueDamage
= CheckForBlock(Enemy
, Weapon
, ToHitValue
, TrueDamage
, Success
, Type
);
661 if (!TrueDamage
|| (Weapon
&& !Weapon
->Exists())) {
662 if (Enemy
->CanBeSeenByPlayer())
663 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
665 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
670 int WeaponSkillHits
= CalculateWeaponSkillHits(Enemy
);
671 int DoneDamage
= ReceiveBodyPartDamage(Enemy
, TrueDamage
, PHYSICAL_DAMAGE
, BodyPart
, Dir
, false, Critical
, true, Type
== BITE_ATTACK
&& Enemy
->BiteCapturesBodyPart());
672 truth Succeeded
= (GetBodyPart(BodyPart
) && HitEffect(Enemy
, Weapon
, HitPos
, Type
, BodyPart
, Dir
, !DoneDamage
, Critical
, DoneDamage
)) || DoneDamage
;
673 if (Succeeded
) Enemy
->WeaponSkillHit(Weapon
, Type
, WeaponSkillHits
);
676 if (Weapon
->Exists() && DoneDamage
< TrueDamage
) Weapon
->ReceiveDamage(Enemy
, TrueDamage
-DoneDamage
, PHYSICAL_DAMAGE
);
677 if (Weapon
->Exists() && DoneDamage
&& SpillsBlood() && GetBodyPart(BodyPart
) &&
678 (GetBodyPart(BodyPart
)->IsAlive() || GetBodyPart(BodyPart
)->GetMainMaterial()->IsLiquid()))
679 Weapon
->SpillFluid(0, CreateBlood(15+RAND()%15));
682 if (Enemy
->AttackIsBlockable(Type
)) SpecialBodyDefenceEffect(Enemy
, EnemyBodyPart
, Type
);
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."));
690 return DID_NO_DAMAGE
;
693 if (CheckDeath(GetNormalDeathMessage(), Enemy
, Enemy
->IsPlayer() ? FORCE_MSG
: 0)) return HAS_DIED
;
695 if (Enemy
->CanBeSeenByPlayer())
696 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
698 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
704 struct svpriorityelement
{
705 svpriorityelement (int BodyPart
, int StrengthValue
) : BodyPart(BodyPart
), StrengthValue(StrengthValue
) {}
706 bool operator < (const svpriorityelement
&AnotherPair
) const { return StrengthValue
> AnotherPair
.StrengthValue
; }
712 int character::ChooseBodyPartToReceiveHit (double ToHitValue
, double DodgeValue
) {
713 if (BodyParts
== 1) return 0;
714 std::priority_queue
<svpriorityelement
> SVQueue
;
715 for (int c
= 0; c
< BodyParts
; ++c
) {
716 bodypart
*BodyPart
= GetBodyPart(c
);
717 if (BodyPart
&& (BodyPart
->GetHP() != 1 || BodyPart
->CanBeSevered(PHYSICAL_DAMAGE
)))
718 SVQueue
.push(svpriorityelement(c
, ModifyBodyPartHitPreference(c
, BodyPart
->GetStrengthValue()+BodyPart
->GetHP())));
720 while (SVQueue
.size()) {
721 svpriorityelement E
= SVQueue
.top();
722 int ToHitPercentage
= int(GLOBAL_WEAK_BODYPART_HIT_MODIFIER
*ToHitValue
*GetBodyPart(E
.BodyPart
)->GetBodyPartVolume()/(DodgeValue
*GetBodyVolume()));
723 ToHitPercentage
= ModifyBodyPartToHitChance(E
.BodyPart
, ToHitPercentage
);
724 if (ToHitPercentage
< 1) ToHitPercentage
= 1;
725 else if (ToHitPercentage
> 95) ToHitPercentage
= 95;
726 if (ToHitPercentage
> RAND()%100) return E
.BodyPart
;
733 void character::Be () {
734 if (game::ForceJumpToPlayerBe()) {
735 if (!IsPlayer()) return;
736 game::SetForceJumpToPlayerBe(false);
738 truth ForceBe
= HP
!= MaxHP
|| AllowSpoil();
739 for (int c
= 0; c
< BodyParts
; ++c
) {
740 bodypart
*BodyPart
= GetBodyPart(c
);
741 if (BodyPart
&& (ForceBe
|| BodyPart
->NeedsBe())) BodyPart
->Be();
744 if (!IsEnabled()) return;
745 if (GetTeam() == PLAYER
->GetTeam()) {
746 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) {
747 if (CWeaponSkill
[c
].Tick() && IsPlayer()) CWeaponSkill
[c
].AddLevelDownMessage(c
);
752 if (GetHungerState() == STARVING
&& !(RAND()%50)) LoseConsciousness(250+RAND_N(250), true);
753 if (!Action
|| Action
->AllowFoodConsumption()) Hunger();
755 if (Stamina
!= MaxStamina
) RegenerateStamina();
756 if (HP
!= MaxHP
) Regenerate();
757 if (Action
&& AP
>= 1000) ActionAutoTermination();
758 if (Action
&& AP
>= 1000) {
760 if (!IsEnabled()) return;
762 EditAP(GetStateAPGain(100));
766 SpecialTurnHandler();
767 BlocksSinceLastTurn
= 0;
769 static int Timer
= 0;
771 if (ivanconfig::GetAutoSaveInterval() && !GetAction() && ++Timer
>= ivanconfig::GetAutoSaveInterval()) {
772 //fprintf(stderr, "autosaving..."); fflush(stderr);
773 game::Save(game::GetAutoSaveFileName());
774 //fprintf(stderr, "done\n"); fflush(stderr);
777 game::CalculateNextDanger();
778 if (!StateIsActivated(POLYMORPHED
)) game::UpdatePlayerAttributeAverage();
779 if (!game::IsInWilderness()) Search(GetAttribute(PERCEPTION
));
783 if (Action
->ShowEnvironment()) {
784 static int Counter
= 0;
785 if (++Counter
== 10) {
786 game::DrawEverything();
790 msgsystem::ThyMessagesAreNowOld();
791 if (Action
->IsVoluntary() && READ_KEY()) Action
->Terminate(false);
794 if (!Action
&& !game::IsInWilderness()) GetAICommand();
800 void character::Move (v2 MoveTo
, truth TeleportMove
, truth Run
) {
801 if (!IsEnabled()) return;
802 /* Test whether the player is stuck to something */
803 if (!TeleportMove
&& !TryToUnStickTraps(MoveTo
-GetPos())) return;
804 if (Run
&& !IsPlayer() && TorsoIsAlive() &&
805 (Stamina
<= 10000/Max(GetAttribute(LEG_STRENGTH
), 1) || (!StateIsActivated(PANIC
) && Stamina
< MaxStamina
>>2))) {
809 if (GetBurdenState() != OVER_LOADED
|| TeleportMove
) {
810 lsquare
*OldSquareUnder
[MAX_SQUARES_UNDER
];
812 if (!game::IsInWilderness()) {
815 area
*ca
= GetSquareUnder()->GetArea();
817 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
818 v2 np
= GetPos()+game::GetMoveVector(f
);
820 if (np
.X
>= 0 && np
.Y
>= 0 && np
.X
< ca
->GetXSize() && np
.Y
< ca
->GetYSize()) {
821 lsquare
*sq
= static_cast<lsquare
*>(ca
->GetSquare(np
.X
, np
.Y
));
828 for (int c
= 0; c
< GetSquaresUnder(); ++c
) OldSquareUnder
[c
] = GetLSquareUnder(c
);
833 /* Multitiled creatures should behave differently, maybe? */
835 int ED
= GetSquareUnder()->GetEntryDifficulty(), Base
= 10000;
837 EditAP(-GetMoveAPRequirement(ED
)>>1);
839 EditExperience(AGILITY
, 125, ED
<<7);
841 switch (GetHungerState()) {
842 case SATIATED
: Base
= 11000; break;
843 case BLOATED
: Base
= 12500; break;
844 case OVER_FED
: Base
= 15000; break;
847 EditStamina(-Base
/Max(GetAttribute(LEG_STRENGTH
), 1), true);
849 int ED
= GetSquareUnder()->GetEntryDifficulty();
851 EditAP(-GetMoveAPRequirement(ED
));
853 EditExperience(AGILITY
, 75, ED
<<7);
856 if (IsPlayer()) ShowNewPosInfo();
857 if (IsPlayer() && !game::IsInWilderness()) GetStackUnder()->SetSteppedOn(true);
858 if (!game::IsInWilderness()) SignalStepFrom(OldSquareUnder
);
861 cchar
*CrawlVerb
= StateIsActivated(LEVITATION
) ? "float" : "crawl";
863 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb
);
870 void character::GetAICommand () {
871 SeekLeader(GetLeader());
872 if (FollowLeader(GetLeader())) return;
873 if (CheckForEnemies(true, true, true)) return;
874 if (CheckForUsefulItemsOnGround()) return;
875 if (CheckForDoors()) return;
876 if (CheckSadism()) return;
877 if (MoveRandomly()) return;
882 truth
character::MoveTowardsTarget (truth Run
) {
887 if (!Route
.empty()) {
890 } else TPos
= GoingTo
;
892 MoveTo
[0] = v2(0, 0);
893 MoveTo
[1] = v2(0, 0);
894 MoveTo
[2] = v2(0, 0);
896 if (TPos
.X
< Pos
.X
) {
897 if (TPos
.Y
< Pos
.Y
) {
898 MoveTo
[0] = v2(-1, -1);
899 MoveTo
[1] = v2(-1, 0);
900 MoveTo
[2] = v2( 0, -1);
901 } else if (TPos
.Y
== Pos
.Y
) {
902 MoveTo
[0] = v2(-1, 0);
903 MoveTo
[1] = v2(-1, -1);
904 MoveTo
[2] = v2(-1, 1);
905 } else if (TPos
.Y
> Pos
.Y
) {
906 MoveTo
[0] = v2(-1, 1);
907 MoveTo
[1] = v2(-1, 0);
908 MoveTo
[2] = v2( 0, 1);
910 } else if (TPos
.X
== Pos
.X
) {
911 if (TPos
.Y
< Pos
.Y
) {
912 MoveTo
[0] = v2( 0, -1);
913 MoveTo
[1] = v2(-1, -1);
914 MoveTo
[2] = v2( 1, -1);
915 } else if (TPos
.Y
== Pos
.Y
) {
918 } else if (TPos
.Y
> Pos
.Y
) {
919 MoveTo
[0] = v2( 0, 1);
920 MoveTo
[1] = v2(-1, 1);
921 MoveTo
[2] = v2( 1, 1);
923 } else if (TPos
.X
> Pos
.X
) {
924 if (TPos
.Y
< Pos
.Y
) {
925 MoveTo
[0] = v2(1, -1);
926 MoveTo
[1] = v2(1, 0);
927 MoveTo
[2] = v2(0, -1);
928 } else if (TPos
.Y
== Pos
.Y
) {
929 MoveTo
[0] = v2(1, 0);
930 MoveTo
[1] = v2(1, -1);
931 MoveTo
[2] = v2(1, 1);
932 } else if (TPos
.Y
> Pos
.Y
) {
933 MoveTo
[0] = v2(1, 1);
934 MoveTo
[1] = v2(1, 0);
935 MoveTo
[2] = v2(0, 1);
939 v2 ModifiedMoveTo
= ApplyStateModification(MoveTo
[0]);
941 if (TryMove(ModifiedMoveTo
, true, Run
)) return true;
943 int L
= (Pos
-TPos
).GetManhattanLength();
945 if (RAND()&1) Swap(MoveTo
[1], MoveTo
[2]);
947 if (Pos
.IsAdjacent(TPos
)) {
952 if ((Pos
+MoveTo
[1]-TPos
).GetManhattanLength() <= L
&& TryMove(ApplyStateModification(MoveTo
[1]), true, Run
)) return true;
953 if ((Pos
+MoveTo
[2]-TPos
).GetManhattanLength() <= L
&& TryMove(ApplyStateModification(MoveTo
[2]), true, Run
)) return true;
954 Illegal
.insert(Pos
+ModifiedMoveTo
);
955 if (CreateRoute()) return true;
960 int character::CalculateNewSquaresUnder (lsquare
**NewSquare
, v2 Pos
) const {
961 if (GetLevel()->IsValidPos(Pos
)) {
962 *NewSquare
= GetNearLSquare(Pos
);
969 truth
character::TryMove (v2 MoveVector
, truth Important
, truth Run
) {
970 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
971 character
*Pet
[MAX_SQUARES_UNDER
];
972 character
*Neutral
[MAX_SQUARES_UNDER
];
973 character
*Hostile
[MAX_SQUARES_UNDER
];
974 v2 PetPos
[MAX_SQUARES_UNDER
];
975 v2 NeutralPos
[MAX_SQUARES_UNDER
];
976 v2 HostilePos
[MAX_SQUARES_UNDER
];
977 v2 MoveTo
= GetPos()+MoveVector
;
978 int Direction
= game::GetDirectionForVector(MoveVector
);
979 if (Direction
== DIR_ERROR
) ABORT("Direction fault.");
980 if (!game::IsInWilderness()) {
981 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, MoveTo
);
986 for (int c
= 0; c
< Squares
; ++c
) {
987 character
* Char
= MoveToSquare
[c
]->GetCharacter();
988 if (Char
&& Char
!= this) {
989 v2 Pos
= MoveToSquare
[c
]->GetPos();
992 PetPos
[Pets
++] = Pos
;
993 } else if (Char
->GetRelation(this) != HOSTILE
) {
994 Neutral
[Neutrals
] = Char
;
995 NeutralPos
[Neutrals
++] = Pos
;
997 Hostile
[Hostiles
] = Char
;
998 HostilePos
[Hostiles
++] = Pos
;
1003 if (Hostiles
== 1) return Hit(Hostile
[0], HostilePos
[0], Direction
);
1005 int Index
= RAND() % Hostiles
;
1006 return Hit(Hostile
[Index
], HostilePos
[Index
], Direction
);
1009 if (Neutrals
== 1) {
1010 if (!IsPlayer() && !Pets
&& Important
&& CanMoveOn(MoveToSquare
[0]))
1011 return HandleCharacterBlockingTheWay(Neutral
[0], NeutralPos
[0], Direction
);
1013 return IsPlayer() && Hit(Neutral
[0], NeutralPos
[0], Direction
);
1014 } else if (Neutrals
) {
1016 int Index
= RAND() % Neutrals
;
1017 return Hit(Neutral
[Index
], NeutralPos
[Index
], Direction
);
1023 for (int c
= 0; c
< Squares
; ++c
) if (MoveToSquare
[c
]->IsScary(this)) return false;
1027 if (IsPlayer() && !ivanconfig::GetBeNice() && Pet
[0]->IsMasochist() && HasSadistAttackMode() &&
1028 game::TruthQuestion("Do you want to punish " + Pet
[0]->GetObjectPronoun() + "?"))
1029 return Hit(Pet
[0], PetPos
[0], Direction
, SADIST_HIT
);
1031 return (Important
&& (CanMoveOn(MoveToSquare
[0]) ||
1032 (IsPlayer() && game::GoThroughWallsCheatIsActive())) && Displace(Pet
[0]));
1033 } else if (Pets
) return false;
1035 if ((CanMove() && CanMoveOn(MoveToSquare
[0])) || ((game::GoThroughWallsCheatIsActive() && IsPlayer()))) {
1036 Move(MoveTo
, false, Run
);
1037 if (IsEnabled() && GetPos() == GoingTo
) TerminateGoingTo();
1040 for (int c
= 0; c
< Squares
; ++c
) {
1041 olterrain
*Terrain
= MoveToSquare
[c
]->GetOLTerrain();
1042 if (Terrain
&& Terrain
->CanBeOpened()) {
1044 if (Terrain
->IsLocked()) {
1047 if (ivanconfig::GetKickDownDoors()) {
1048 if (game::TruthQuestion(CONST_S("Locked! Do you want to kick ")+Terrain
->GetName(DEFINITE
)+"?", true, game::GetMoveCommandKeyBetweenPoints(PLAYER
->GetPos(), MoveToSquare
[0]->GetPos()))) {
1049 Kick(MoveToSquare
[c
], Direction
);
1056 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 */
1058 } else if (Important
&& CheckKick()) {
1059 room
*Room
= MoveToSquare
[c
]->GetRoom();
1060 if (!Room
|| Room
->AllowKick(this, MoveToSquare
[c
])) {
1061 int HP
= Terrain
->GetHP();
1062 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s kicks %s.", CHAR_NAME(DEFINITE
), Terrain
->CHAR_NAME(DEFINITE
));
1063 Kick(MoveToSquare
[c
], Direction
);
1064 olterrain
*NewTerrain
= MoveToSquare
[c
]->GetOLTerrain();
1065 if (NewTerrain
== Terrain
&& Terrain
->GetHP() == HP
) { // BUG!
1066 Illegal
.insert(MoveTo
);
1072 } else { /* if (Terrain->IsLocked()) */
1073 /*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);*/
1074 /* Non-players always try to open it */
1075 if (!IsPlayer()) return MoveToSquare
[c
]->Open(this);
1076 if (game::TruthQuestion(CONST_S("Do you want to open ")+
1077 Terrain
->GetName(DEFINITE
)+"?", false, game::GetMoveCommandKeyBetweenPoints(PLAYER
->GetPos(),
1078 MoveToSquare
[0]->GetPos())))
1080 return MoveToSquare
[c
]->Open(this);
1083 } /* if (Terrain->IsLocked()) */
1084 } else { /* if (CanOpen()) */
1086 ADD_MESSAGE("This monster type cannot open doors.");
1090 Illegal
.insert(MoveTo
);
1091 return CreateRoute();
1093 } /* if (CanOpen()) */
1094 } /* if (Terrain && Terrain->CanBeOpened()) */
1099 if (IsPlayer() && !IsStuck() && GetLevel()->IsOnGround() && game::TruthQuestion(CONST_S("Do you want to leave ")+game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex())+"?")) {
1100 if (HasPetrussNut() && !HasGoldenEagleShirt()) {
1101 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!"));
1102 game::GetCurrentArea()->SendNewDrawRequest();
1103 game::DrawEverything();
1104 ShowAdventureInfo();
1105 festring Msg
= CONST_S("killed Petrus and became the Avatar of Chaos");
1106 PLAYER
->AddScoreEntry(Msg
, 3, false);
1110 if (game::TryTravel(WORLD_MAP
, WORLD_MAP
, game::GetCurrentDungeonIndex())) return true;
1115 /** No multitile support */
1116 if (CanMove() && GetArea()->IsValidPos(MoveTo
) && (CanMoveOn(GetNearWSquare(MoveTo
)) || game::GoThroughWallsCheatIsActive())) {
1117 if (!game::GoThroughWallsCheatIsActive()) {
1118 charactervector
&V
= game::GetWorldMap()->GetPlayerGroup();
1119 truth Discard
= false;
1120 for (uInt c
= 0; c
< V
.size(); ++c
) {
1121 if (!V
[c
]->CanMoveOn(GetNearWSquare(MoveTo
))) {
1123 ADD_MESSAGE("One or more of your team members cannot cross this terrain.");
1124 if (!game::TruthQuestion("Discard them?")) return false;
1127 if (Discard
) delete V
[c
];
1128 V
.erase(V
.begin() + c
--);
1132 Move(MoveTo
, false);
1141 void character::CreateCorpse (lsquare
*Square
) {
1142 if (!BodyPartsDisappearWhenSevered() && !game::AllBodyPartsVanish()) {
1143 corpse
*Corpse
= corpse::Spawn(0, NO_MATERIALS
);
1144 Corpse
->SetDeceased(this);
1145 Square
->AddItem(Corpse
);
1153 void character::Die (ccharacter
*Killer
, cfestring
&Msg
, feuLong DeathFlags
) {
1154 /* Note: This function musn't delete any objects, since one of these may be
1155 the one currently processed by pool::Be()! */
1156 if (!IsEnabled()) return;
1157 game::ClearEventData();
1158 game::mActor
= Killer
;
1159 if (game::RunOnCharEvent(this, CONST_S("die"))) { game::ClearEventData(); RemoveTraps(); return; }
1160 game::ClearEventData();
1163 ADD_MESSAGE("You die.");
1164 game::DrawEverything();
1165 if (game::TruthQuestion(CONST_S("Do you want to save screenshot?"), REQUIRES_ANSWER
)) {
1168 dir
<< ivanconfig::GetMyDir() << "/save";
1169 mkdir(dir
.CStr(), 0755);
1171 dir
<< getenv("HOME") << "/.ivan-save";
1172 mkdir(dir
.CStr(), 0755);
1174 dir
<< "/deathshots";
1175 mkdir(dir
.CStr(), 0755);
1177 time_t t
= time(NULL
);
1178 struct tm
*ts
= localtime(&t
);
1180 timestr
<< (int)(ts
->tm_year
%100);
1181 int t
= ts
->tm_mon
+1;
1182 if (t
< 10) timestr
<< '0'; timestr
<< t
;
1183 t
= ts
->tm_mday
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1185 t
= ts
->tm_hour
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1186 t
= ts
->tm_min
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1187 t
= ts
->tm_sec
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1191 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1192 festring ext
= ".png";
1194 festring ext
= ".bmp";
1196 festring fname
= dir
+"/deathshot_"+timestr
;
1197 if (inputfile::fileExists(fname
+ext
)) {
1198 for (int f
= 0; f
< 1000; f
++) {
1200 sprintf(buf
, "%03d", f
);
1201 festring fn
= fname
+buf
;
1202 if (!inputfile::fileExists(fn
+ext
)) {
1209 fprintf(stderr
, "deathshot: %s\n", fname
.CStr());
1210 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1211 DOUBLE_BUFFER
->SavePNG(fname
);
1213 DOUBLE_BUFFER
->SaveBMP(fname
);
1216 if (game::WizardModeIsActive()) {
1217 game::DrawEverything();
1218 if (!game::TruthQuestion(CONST_S("Do you want to do this, cheater?"), REQUIRES_ANSWER
)) {
1224 SetNP(SATIATED_LEVEL
);
1225 SendNewDrawRequest();
1229 } else if (CanBeSeenByPlayer() && !(DeathFlags
& DISALLOW_MSG
)) {
1230 ProcessAndAddMessage(GetDeathMessage());
1231 } else if (DeathFlags
& FORCE_MSG
) {
1232 ADD_MESSAGE("You sense the death of something.");
1235 if (!(DeathFlags
& FORBID_REINCARNATION
)) {
1236 if (StateIsActivated(LIFE_SAVED
) && CanMoveOn(!game::IsInWilderness() ? GetSquareUnder() : PLAYER
->GetSquareUnder())) {
1240 if (SpecialSaveLife()) return;
1241 } else if (StateIsActivated(LIFE_SAVED
)) {
1245 Flags
|= C_IN_NO_MSG_MODE
;
1246 character
*Ghost
= 0;
1248 game::RemoveSaves();
1249 if (!game::IsInWilderness()) {
1250 Ghost
= game::CreateGhost();
1255 square
*SquareUnder
[MAX_SQUARES_UNDER
];
1256 memset(SquareUnder
, 0, sizeof(SquareUnder
));
1258 if (IsPlayer() || !game::IsInWilderness()) {
1259 for (int c
= 0; c
< SquaresUnder
; ++c
) SquareUnder
[c
] = GetSquareUnder(c
);
1262 charactervector
& V
= game::GetWorldMap()->GetPlayerGroup();
1263 V
.erase(std::find(V
.begin(), V
.end(), this));
1265 //lsquare **LSquareUnder = reinterpret_cast<lsquare **>(SquareUnder); /* warning; wtf? */
1266 lsquare
*LSquareUnder
[MAX_SQUARES_UNDER
];
1267 memmove(LSquareUnder
, SquareUnder
, sizeof(SquareUnder
));
1269 if (!game::IsInWilderness()) {
1270 if (!StateIsActivated(POLYMORPHED
)) {
1271 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(this, Killer
, Msg
);
1272 if (!(DeathFlags
& DISALLOW_CORPSE
)) CreateCorpse(LSquareUnder
[0]); else SendToHell();
1274 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(GetPolymorphBackup(), Killer
, Msg
);
1275 GetPolymorphBackup()->CreateCorpse(LSquareUnder
[0]);
1276 GetPolymorphBackup()->Flags
&= ~C_POLYMORPHED
;
1277 SetPolymorphBackup(0);
1281 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(this, Killer
, Msg
);
1286 if (!game::IsInWilderness()) {
1287 for (int c
= 0; c
< GetSquaresUnder(); ++c
) LSquareUnder
[c
]->SetTemporaryEmitation(GetEmitation());
1289 ShowAdventureInfo();
1290 if (!game::IsInWilderness()) {
1291 for(int c
= 0; c
< GetSquaresUnder(); ++c
) LSquareUnder
[c
]->SetTemporaryEmitation(0);
1295 if (!game::IsInWilderness()) {
1296 if (GetSquaresUnder() == 1) {
1297 stack
*StackUnder
= LSquareUnder
[0]->GetStack();
1298 GetStack()->MoveItemsTo(StackUnder
);
1299 doforbodypartswithparam
<stack
*>()(this, &bodypart::DropEquipment
, StackUnder
);
1301 while (GetStack()->GetItems()) GetStack()->GetBottom()->MoveTo(LSquareUnder
[RAND_N(GetSquaresUnder())]->GetStack());
1302 for (int c
= 0; c
< BodyParts
; ++c
) {
1303 bodypart
*BodyPart
= GetBodyPart(c
);
1304 if (BodyPart
) BodyPart
->DropEquipment(LSquareUnder
[RAND_N(GetSquaresUnder())]->GetStack());
1309 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(0);
1311 Flags
&= ~C_IN_NO_MSG_MODE
;
1315 if (!game::IsInWilderness()) {
1316 Ghost
->PutTo(LSquareUnder
[0]->GetPos());
1320 game::TextScreen(CONST_S("Unfortunately you died."), ZERO_V2
, WHITE
, true, true, &game::ShowDeathSmiley
);
1326 void character::AddMissMessage (ccharacter
*Enemy
) const {
1328 if (Enemy
->IsPlayer()) Msg
= GetDescription(DEFINITE
)+" misses you!";
1329 else if (IsPlayer()) Msg
= CONST_S("You miss ")+Enemy
->GetDescription(DEFINITE
)+'!';
1330 else if (CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer()) Msg
= GetDescription(DEFINITE
)+" misses "+Enemy
->GetDescription(DEFINITE
)+'!';
1332 ADD_MESSAGE("%s", Msg
.CStr());
1336 void character::AddBlockMessage (ccharacter
*Enemy
, citem
*Blocker
, cfestring
&HitNoun
, truth Partial
) const {
1338 festring BlockVerb
= (Partial
? " to partially block the " : " to block the ")+HitNoun
;
1340 Msg
<< "You manage" << BlockVerb
<< " with your " << Blocker
->GetName(UNARTICLED
) << '!';
1341 } else if (Enemy
->IsPlayer() || Enemy
->CanBeSeenByPlayer()) {
1342 if (CanBeSeenByPlayer())
1343 Msg
<< GetName(DEFINITE
) << " manages" << BlockVerb
<< " with " << GetPossessivePronoun() << ' ' << Blocker
->GetName(UNARTICLED
) << '!';
1345 Msg
<< "Something manages" << BlockVerb
<< " with something!";
1349 ADD_MESSAGE("%s", Msg
.CStr());
1353 void character::AddPrimitiveHitMessage (ccharacter
*Enemy
, cfestring
&FirstPersonHitVerb
,
1354 cfestring
&ThirdPersonHitVerb
, int BodyPart
) const
1357 festring BodyPartDescription
;
1358 if (BodyPart
&& (Enemy
->CanBeSeenByPlayer() || Enemy
->IsPlayer()))
1359 BodyPartDescription
<< " in the " << Enemy
->GetBodyPartName(BodyPart
);
1360 if (Enemy
->IsPlayer())
1361 Msg
<< GetDescription(DEFINITE
) << ' ' << ThirdPersonHitVerb
<< " you" << BodyPartDescription
<< '!';
1362 else if (IsPlayer())
1363 Msg
<< "You " << FirstPersonHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
<< '!';
1364 else if (CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer())
1365 Msg
<< GetDescription(DEFINITE
) << ' ' << ThirdPersonHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) + BodyPartDescription
<< '!';
1368 ADD_MESSAGE("%s", Msg
.CStr());
1372 cchar
*const HitVerb
[] = { "strike", "slash", "stab" };
1373 cchar
*const HitVerb3rdPersonEnd
[] = { "s", "es", "s" };
1376 void character::AddWeaponHitMessage (ccharacter
*Enemy
, citem
*Weapon
, int BodyPart
, truth Critical
) const {
1378 festring BodyPartDescription
;
1380 if (BodyPart
&& (Enemy
->CanBeSeenByPlayer() || Enemy
->IsPlayer()))
1381 BodyPartDescription
<< " in the " << Enemy
->GetBodyPartName(BodyPart
);
1383 int FittingTypes
= 0;
1384 int DamageFlags
= Weapon
->GetDamageFlags();
1387 for (int c
= 0; c
< DAMAGE_TYPES
; ++c
) {
1388 if (1 << c
& DamageFlags
) {
1389 if (!FittingTypes
|| !RAND_N(FittingTypes
+1)) DamageType
= c
;
1394 if (!FittingTypes
) ABORT("No damage flags specified for %s!", Weapon
->CHAR_NAME(UNARTICLED
));
1396 festring NewHitVerb
= Critical
? " critically " : " ";
1397 NewHitVerb
<< HitVerb
[DamageType
];
1398 cchar
*const E
= HitVerb3rdPersonEnd
[DamageType
];
1400 if (Enemy
->IsPlayer()) {
1401 Msg
<< GetDescription(DEFINITE
) << NewHitVerb
<< E
<< " you" << BodyPartDescription
;
1402 if (CanBeSeenByPlayer()) Msg
<< " with " << GetPossessivePronoun() << ' ' << Weapon
->GetName(UNARTICLED
);
1404 } else if (IsPlayer()) {
1405 Msg
<< "You" << NewHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
<< '!';
1406 } else if(CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer()) {
1407 Msg
<< GetDescription(DEFINITE
) << NewHitVerb
<< E
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
;
1408 if (CanBeSeenByPlayer()) Msg
<< " with " << GetPossessivePronoun() << ' ' << Weapon
->GetName(UNARTICLED
);
1413 ADD_MESSAGE("%s", Msg
.CStr());
1417 item
*character::GeneralFindItem (ItemCheckerCB chk
) const {
1418 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1420 if (it
&& chk(it
)) return it
;
1426 static truth
isEncryptedScroll (item
*i
) { return i
->IsEncryptedScroll(); }
1427 truth
character::HasEncryptedScroll () const {
1428 if (GeneralFindItem(::isEncryptedScroll
)) return true;
1429 return combineequipmentpredicates()(this, &item::IsEncryptedScroll
, 1);
1433 static truth
isElpuriHead (item
*i
) { return i
->IsHeadOfElpuri(); }
1434 truth
character::HasHeadOfElpuri () const {
1435 if (GeneralFindItem(::isElpuriHead
)) return true;
1436 return combineequipmentpredicates()(this, &item::IsHeadOfElpuri
, 1);
1440 static truth
isPetrussNut (item
*i
) { return i
->IsPetrussNut(); }
1441 truth
character::HasPetrussNut () const {
1442 if (GeneralFindItem(::isPetrussNut
)) return true;
1443 return combineequipmentpredicates()(this, &item::IsPetrussNut
, 1);
1447 static truth
isGoldenEagleShirt (item
*i
) { return i
->IsGoldenEagleShirt(); }
1448 truth
character::HasGoldenEagleShirt () const {
1449 if (GeneralFindItem(::isGoldenEagleShirt
)) return true;
1450 return combineequipmentpredicates()(this, &item::IsGoldenEagleShirt
, 1);
1454 static truth
isShadowVeil (item
*i
) { return i
->IsShadowVeil(); }
1455 truth
character::HasShadowVeil () const {
1456 if (GeneralFindItem(::isShadowVeil
)) return true;
1457 return combineequipmentpredicates()(this, &item::IsShadowVeil
, 1);
1461 static truth
isLostRubyFlamingSword (item
*i
) { return i
->IsLostRubyFlamingSword(); }
1462 truth
character::HasLostRubyFlamingSword () const {
1463 if (GeneralFindItem(::isLostRubyFlamingSword
)) return true;
1464 return combineequipmentpredicates()(this, &item::IsLostRubyFlamingSword
, 1);
1468 truth
character::HasOmmelBlood () const {
1469 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
)
1470 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) return true;
1472 for (int c
= 0; c
< GetEquipments(); ++c
) {
1473 item
*Item
= GetEquipment(c
);
1475 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) return true;
1477 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1481 truth
character::HasCurdledBlood () const {
1482 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
)
1483 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) return true;
1485 for (int c
= 0; c
< GetEquipments(); ++c
) {
1486 item
*Item
= GetEquipment(c
);
1488 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) return true;
1490 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1494 truth
character::CurdleOmmelBlood () const {
1495 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1496 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) {
1497 i
->ChangeSecondaryMaterial(MAKE_MATERIAL(CURDLED_OMMEL_BLOOD
));
1502 for (int c
= 0; c
< GetEquipments(); ++c
) {
1503 item
*Item
= GetEquipment(c
);
1505 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) {
1506 Item
->ChangeSecondaryMaterial(MAKE_MATERIAL(CURDLED_OMMEL_BLOOD
));
1510 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1514 truth
character::RemoveCurdledOmmelBlood () {
1515 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1516 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) {
1517 (*i
)->RemoveFromSlot();
1523 for (int c
= 0; c
< GetEquipments(); ++c
) {
1524 item
*Item
= GetEquipment(c
);
1526 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) {
1527 Item
->RemoveFromSlot();
1536 int character::GeneralRemoveItem (ItemCheckerCB chk
, truth allItems
) {
1542 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1544 if (Item
&& chk(Item
)) {
1545 Item
->RemoveFromSlot();
1548 if (!allItems
) return cnt
;
1557 for (int c
= 0; c
< GetEquipments(); ++c
) {
1558 item
*Item
= GetEquipment(c
);
1559 if (Item
&& chk(Item
)) {
1560 Item
->RemoveFromSlot();
1563 if (!allItems
) return cnt
;
1573 //static truth isEncryptedScroll (item *i) { return i->IsEncryptedScroll(); }
1574 truth
character::RemoveEncryptedScroll () { return GeneralRemoveItem(::isEncryptedScroll
) != 0; }
1577 static truth
isMondedrPass (item
*i
) { return i
->IsMondedrPass(); }
1578 truth
character::RemoveMondedrPass () { return GeneralRemoveItem(::isMondedrPass
) != 0; }
1581 static truth
isRingOfThieves (item
*i
) { return i
->IsRingOfThieves(); }
1582 truth
character::RemoveRingOfThieves () { return GeneralRemoveItem(::isRingOfThieves
) != 0; }
1585 truth
character::RemoveShadowVeil () { return (GeneralRemoveItem(::isShadowVeil
) != 0); }
1588 truth
character::ReadItem (item
*ToBeRead
) {
1589 if (!ToBeRead
->CanBeRead(this)) {
1590 if (IsPlayer()) ADD_MESSAGE("You can't read this.");
1593 if (!GetLSquareUnder()->IsDark() || game::GetSeeWholeMapCheatMode()) {
1594 if (StateIsActivated(CONFUSED
) && !(RAND()&7)) {
1595 if (!ToBeRead
->IsDestroyable(this)) {
1596 ADD_MESSAGE("You read some words of %s and understand exactly nothing.", ToBeRead
->CHAR_NAME(DEFINITE
));
1598 ADD_MESSAGE("%s is very confusing. Or perhaps you are just too confused?", ToBeRead
->CHAR_NAME(DEFINITE
));
1599 ActivateRandomState(SRC_CONFUSE_READ
, 1000+RAND()%1500);
1600 ToBeRead
->RemoveFromSlot();
1601 ToBeRead
->SendToHell();
1606 if (ToBeRead
->Read(this)) {
1607 if (!game::WizardModeIsActive()) {
1608 /* This AP is used to take the stuff out of backpack */
1615 if (IsPlayer()) ADD_MESSAGE("It's too dark here to read.");
1620 void character::CalculateBurdenState () {
1621 int OldBurdenState
= BurdenState
;
1622 sLong SumOfMasses
= GetCarriedWeight();
1623 sLong CarryingStrengthUnits
= sLong(GetCarryingStrength())*2500;
1624 if (SumOfMasses
> (CarryingStrengthUnits
<< 1) + CarryingStrengthUnits
) BurdenState
= OVER_LOADED
;
1625 else if (SumOfMasses
> CarryingStrengthUnits
<< 1) BurdenState
= STRESSED
;
1626 else if (SumOfMasses
> CarryingStrengthUnits
) BurdenState
= BURDENED
;
1627 else BurdenState
= UNBURDENED
;
1628 if (!IsInitializing() && BurdenState
!= OldBurdenState
) CalculateBattleInfo();
1632 void character::Save (outputfile
&SaveFile
) const {
1633 SaveFile
<< (uShort
)GetType();
1634 Stack
->Save(SaveFile
);
1636 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) SaveFile
<< BaseExperience
[c
];
1638 SaveFile
<< ExpModifierMap
;
1639 SaveFile
<< NP
<< AP
<< Stamina
<< GenerationDanger
<< ScienceTalks
<< CounterToMindWormHatch
;
1640 SaveFile
<< TemporaryState
<< EquipmentState
<< Money
<< GoingTo
<< RegenerationCounter
<< Route
<< Illegal
;
1641 SaveFile
<< CurrentSweatMaterial
;
1642 SaveFile
.Put(!!IsEnabled());
1643 SaveFile
<< HomeData
<< BlocksSinceLastTurn
<< CommandFlags
;
1644 SaveFile
<< WarnFlags
<< (uShort
)Flags
;
1646 for (int c
= 0; c
< BodyParts
; ++c
) SaveFile
<< BodyPartSlot
[c
] << OriginalBodyPartID
[c
];
1648 SaveLinkedList(SaveFile
, TrapData
);
1651 for (int c
= 0; c
< STATES
; ++c
) SaveFile
<< TemporaryStateCounter
[c
];
1655 SaveFile
<< Team
->GetID(); // feuLong
1657 SaveFile
.Put(false);
1660 if (GetTeam() && GetTeam()->GetLeader() == this) SaveFile
.Put(true); else SaveFile
.Put(false);
1662 SaveFile
<< AssignedName
<< PolymorphBackup
;
1664 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) SaveFile
<< CWeaponSkill
[c
];
1666 SaveFile
<< (uShort
)GetConfig();
1670 void character::Load (inputfile
&SaveFile
) {
1672 Stack
->Load(SaveFile
);
1674 game::AddCharacterID(this, ID
);
1676 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) SaveFile
>> BaseExperience
[c
];
1678 SaveFile
>> ExpModifierMap
;
1679 SaveFile
>> NP
>> AP
>> Stamina
>> GenerationDanger
>> ScienceTalks
>> CounterToMindWormHatch
;
1680 SaveFile
>> TemporaryState
>> EquipmentState
>> Money
>> GoingTo
>> RegenerationCounter
>> Route
>> Illegal
;
1681 SaveFile
>> CurrentSweatMaterial
;
1683 if (!SaveFile
.Get()) Disable();
1685 SaveFile
>> HomeData
>> BlocksSinceLastTurn
>> CommandFlags
;
1686 SaveFile
>> WarnFlags
;
1687 WarnFlags
&= ~WARNED
;
1688 Flags
|= ReadType(uShort
, SaveFile
) & ~ENTITY_FLAGS
;
1690 for (int c
= 0; c
< BodyParts
; ++c
) {
1691 SaveFile
>> BodyPartSlot
[c
] >> OriginalBodyPartID
[c
];
1692 item
*BodyPart
= *BodyPartSlot
[c
];
1693 if (BodyPart
) BodyPart
->Disable();
1696 LoadLinkedList(SaveFile
, TrapData
);
1699 if (Action
) Action
->SetActor(this);
1701 for (int c
= 0; c
< STATES
; ++c
) SaveFile
>> TemporaryStateCounter
[c
];
1703 if (SaveFile
.Get()) SetTeam(game::GetTeam(ReadType(feuLong
, SaveFile
)));
1705 if (SaveFile
.Get()) GetTeam()->SetLeader(this);
1707 SaveFile
>> AssignedName
>> PolymorphBackup
;
1709 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) SaveFile
>> CWeaponSkill
[c
];
1711 databasecreator
<character
>::InstallDataBase(this, ReadType(uShort
, SaveFile
));
1713 if (IsEnabled() && !game::IsInWilderness()) {
1714 for (int c
= 1; c
< GetSquaresUnder(); ++c
) GetSquareUnder(c
)->SetCharacter(this);
1718 const fearray<festring> < = GetLevelTags();
1720 fprintf(stderr, "====\n");
1721 for (uInt f = 0; f < lt.Size; ++f) fprintf(stderr, " %u: [%s]\n", f, lt[f].CStr());
1727 truth
character::Engrave (cfestring
&What
) {
1728 GetLSquareUnder()->Engrave(What
);
1732 truth
character::MoveRandomly () {
1733 if (!IsEnabled()) return false;
1734 for (int c
= 0; c
< 10; ++c
) {
1735 v2 ToTry
= game::GetMoveVector(RAND()&7);
1736 if (GetLevel()->IsValidPos(GetPos()+ToTry
)) {
1737 lsquare
*Square
= GetNearLSquare(GetPos()+ToTry
);
1738 if (!Square
->IsDangerous(this) && !Square
->IsScary(this) && TryMove(ToTry
, false, false)) return true;
1745 truth
character::TestForPickup (item
*ToBeTested
) const {
1746 if (MakesBurdened(ToBeTested
->GetWeight()+GetCarriedWeight())) return false;
1751 void character::AddScoreEntry (cfestring
&Description
, double Multiplier
, truth AddEndLevel
) const {
1752 if (!game::WizardModeIsReallyActive()) {
1754 if (!HScore
.CheckVersion()) {
1755 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;
1758 festring Desc
= game::GetPlayerName();
1759 Desc
<< ", " << Description
;
1760 if (AddEndLevel
) Desc
<< " in "+(game::IsInWilderness() ? "the world map" : game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex()));
1761 HScore
.Add(sLong(game::GetScore()*Multiplier
), Desc
);
1767 truth
character::CheckDeath (cfestring
&Msg
, ccharacter
*Murderer
, feuLong DeathFlags
) {
1768 if (!IsEnabled()) return true;
1769 if (game::IsSumoWrestling() && IsDead()) {
1770 game::EndSumoWrestling(!!IsPlayer());
1773 if (DeathFlags
& FORCE_DEATH
|| IsDead()) {
1774 if (Murderer
&& Murderer
->IsPlayer() && GetTeam()->GetKillEvilness()) game::DoEvilDeed(GetTeam()->GetKillEvilness());
1775 festring SpecifierMsg
;
1776 int SpecifierParts
= 0;
1777 if (GetPolymorphBackup()) {
1778 SpecifierMsg
<< " polymorphed into ";
1779 id::AddName(SpecifierMsg
, INDEFINITE
);
1782 if (!(DeathFlags
& IGNORE_TRAPS
) && IsStuck()) {
1783 if (SpecifierParts
++) SpecifierMsg
<< " and";
1784 SpecifierMsg
<< " caught in " << GetTrapDescription();
1786 if (GetAction() && !(DeathFlags
& IGNORE_UNCONSCIOUSNESS
&& GetAction()->IsUnconsciousness())) {
1787 festring ActionMsg
= GetAction()->GetDeathExplanation();
1788 if (!ActionMsg
.IsEmpty()) {
1789 if (SpecifierParts
> 1) {
1790 SpecifierMsg
= ActionMsg
<< ',' << SpecifierMsg
;
1792 if (SpecifierParts
) SpecifierMsg
<< " and";
1793 SpecifierMsg
<< ActionMsg
;
1798 festring NewMsg
= Msg
;
1799 if (Murderer
== this) {
1800 SEARCH_N_REPLACE(NewMsg
, "@bkp", CONST_S("by ") + GetPossessivePronoun(false) + " own");
1801 SEARCH_N_REPLACE(NewMsg
, "@bk", CONST_S("by ") + GetObjectPronoun(false) + "self");
1802 SEARCH_N_REPLACE(NewMsg
, "@k", GetObjectPronoun(false) + "self");
1804 SEARCH_N_REPLACE(NewMsg
, "@bkp", CONST_S("by ") + Murderer
->GetName(INDEFINITE
) + "'s");
1805 SEARCH_N_REPLACE(NewMsg
, "@bk", CONST_S("by ") + Murderer
->GetName(INDEFINITE
));
1806 SEARCH_N_REPLACE(NewMsg
, "@k", CONST_S("by ") + Murderer
->GetName(INDEFINITE
));
1808 if (SpecifierParts
) NewMsg
<< " while" << SpecifierMsg
;
1809 if (IsPlayer() && game::WizardModeIsActive()) ADD_MESSAGE("Death message: %s. Score: %d.", NewMsg
.CStr(), game::GetScore());
1810 Die(Murderer
, NewMsg
, DeathFlags
);
1817 truth
character::CheckStarvationDeath (cfestring
&Msg
) {
1818 if (GetNP() < 1 && UsesNutrition()) return CheckDeath(Msg
, 0, FORCE_DEATH
);
1823 void character::ThrowItem (int Direction
, item
*ToBeThrown
) {
1824 if (Direction
> 7) ABORT("Throw in TOO odd direction...");
1825 ToBeThrown
->Fly(this, Direction
, GetAttribute(ARM_STRENGTH
));
1829 void character::HasBeenHitByItem (character
*Thrower
, item
*Thingy
, int Damage
, double ToHitValue
, int Direction
) {
1830 if (IsPlayer()) ADD_MESSAGE("%s hits you.", Thingy
->CHAR_NAME(DEFINITE
));
1831 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s.", Thingy
->CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
));
1832 int BodyPart
= ChooseBodyPartToReceiveHit(ToHitValue
, DodgeValue
);
1833 int WeaponSkillHits
= Thrower
? CalculateWeaponSkillHits(Thrower
) : 0;
1834 int DoneDamage
= ReceiveBodyPartDamage(Thrower
, Damage
, PHYSICAL_DAMAGE
, BodyPart
, Direction
);
1835 truth Succeeded
= (GetBodyPart(BodyPart
) && HitEffect(Thrower
, Thingy
, Thingy
->GetPos(), THROW_ATTACK
, BodyPart
, Direction
, !DoneDamage
, false, DoneDamage
)) || DoneDamage
;
1836 if (Succeeded
&& Thrower
) Thrower
->WeaponSkillHit(Thingy
, THROW_ATTACK
, WeaponSkillHits
);
1837 festring DeathMsg
= CONST_S("killed by a flying ")+Thingy
->GetName(UNARTICLED
);
1838 if (CheckDeath(DeathMsg
, Thrower
)) return;
1840 if (Thrower
->CanBeSeenByPlayer())
1841 DeActivateVoluntaryAction(CONST_S("The attack of ")+Thrower
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
1843 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
1845 DeActivateVoluntaryAction(CONST_S("The hit interrupts you."));
1850 truth
character::DodgesFlyingItem (item
*Item
, double ToHitValue
) {
1851 return !Item
->EffectIsGood() && RAND() % int(100+ToHitValue
/DodgeValue
*100) < 100;
1855 void character::GetPlayerCommand () {
1857 truth HasActed
= false;
1859 game::DrawEverything();
1860 if (game::GetDangerFound() && !StateIsActivated(FEARLESS
)) {
1861 if (game::GetDangerFound() > 500.) {
1862 if (game::GetCausePanicFlag()) {
1863 game::SetCausePanicFlag(false);
1864 BeginTemporaryState(PANIC
, 500+RAND_N(500));
1866 game::AskForEscPress(CONST_S("You are horrified by your situation!"));
1867 } else if (ivanconfig::GetWarnAboutDanger()) {
1868 if (game::GetDangerFound() > 50.) game::AskForEscPress(CONST_S("You sense great danger!"));
1869 else game::AskForEscPress(CONST_S("You sense danger!"));
1871 game::SetDangerFound(0);
1873 game::SetIsInGetCommand(true);
1874 int Key
= GET_KEY();
1875 game::SetIsInGetCommand(false);
1876 if (Key
!= '+' && Key
!= '-' && Key
!= 'M') msgsystem::ThyMessagesAreNowOld(); // gum
1877 truth ValidKeyPressed
= false;
1879 for (int c
= 0; c
< DIRECTION_COMMAND_KEYS
; ++c
) {
1880 if (Key
== game::GetMoveCommandKey(c
)) {
1881 HasActed
= TryMove(ApplyStateModification(game::GetMoveVector(c
)), true, game::PlayerIsRunning());
1882 ValidKeyPressed
= true;
1886 if (!ValidKeyPressed
) {
1887 for (int c
= 0; (cmd
= commandsystem::GetCommand(c
)); ++c
) {
1889 /* Numpad aliases for most commonly used commands */
1890 if (Key
== KEY_DEL
&& cmd
->GetName() == "Eat") Key
= cmd
->GetKey();
1891 if (Key
== KEY_INS
&& cmd
->GetName() == "PickUp") Key
= cmd
->GetKey();
1892 if (Key
== KEY_PLUS
&& cmd
->GetName() == "EquipmentScreen") Key
= cmd
->GetKey();
1894 if (Key
== cmd
->GetKey()) {
1895 if (game::IsInWilderness() && !commandsystem::GetCommand(c
)->IsUsableInWilderness()) {
1896 ADD_MESSAGE("This function cannot be used while in wilderness.");
1897 } else if (!game::WizardModeIsActive() && commandsystem::GetCommand(c
)->IsWizardModeFunction()) {
1898 ADD_MESSAGE("Activate wizardmode to use this function.");
1900 HasActed
= commandsystem::GetCommand(c
)->GetLinkedFunction()(this);
1902 ValidKeyPressed
= true;
1907 if (!ValidKeyPressed
) ADD_MESSAGE("Unknown key. Press '?' for a list of commands.");
1909 game::IncreaseTurn();
1913 void character::Vomit (v2 Pos
, int Amount
, truth ShowMsg
) {
1914 if (!CanVomit()) return;
1916 if (IsPlayer()) ADD_MESSAGE("You vomit.");
1917 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s vomits.", CHAR_NAME(DEFINITE
));
1919 if (VomittingIsUnhealthy()) {
1920 EditExperience(ARM_STRENGTH
, -75, 1 << 9);
1921 EditExperience(LEG_STRENGTH
, -75, 1 << 9);
1924 EditNP(-2500-RAND()%2501);
1925 CheckStarvationDeath(CONST_S("vomited himself to death"));
1927 if (StateIsActivated(PARASITIZED
) && !(RAND() & 7)) {
1928 if (IsPlayer()) ADD_MESSAGE("You notice a dead broad tapeworm among your former stomach contents.");
1929 DeActivateTemporaryState(PARASITIZED
);
1931 if (!game::IsInWilderness()) {
1932 GetNearLSquare(Pos
)->ReceiveVomit(this, liquid::Spawn(GetVomitMaterial(), sLong(sqrt(GetBodyVolume())*Amount
/1000)));
1937 truth
character::Polymorph (character
*NewForm
, int Counter
) {
1938 if (!IsPolymorphable() || (!IsPlayer() && game::IsInWilderness())) {
1943 if (GetAction()) GetAction()->Terminate(false);
1944 NewForm
->SetAssignedName("");
1946 ADD_MESSAGE("Your body glows in a crimson light. You transform into %s!", NewForm
->CHAR_NAME(INDEFINITE
));
1947 else if (CanBeSeenByPlayer())
1948 ADD_MESSAGE("%s glows in a crimson light and %s transforms into %s!", CHAR_NAME(DEFINITE
), GetPersonalPronoun().CStr(), NewForm
->CHAR_NAME(INDEFINITE
));
1950 Flags
|= C_IN_NO_MSG_MODE
;
1951 NewForm
->Flags
|= C_IN_NO_MSG_MODE
;
1952 NewForm
->ChangeTeam(GetTeam());
1953 NewForm
->GenerationDanger
= GenerationDanger
;
1954 NewForm
->mOnEvents
= this->mOnEvents
;
1956 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(NewForm
);
1960 NewForm
->PutToOrNear(Pos
);
1961 NewForm
->SetAssignedName(GetAssignedName());
1962 NewForm
->ActivateTemporaryState(POLYMORPHED
);
1963 NewForm
->SetTemporaryStateCounter(POLYMORPHED
, Counter
);
1965 if (TemporaryStateIsActivated(POLYMORPHED
)) {
1966 NewForm
->SetPolymorphBackup(GetPolymorphBackup());
1967 SetPolymorphBackup(0);
1970 NewForm
->SetPolymorphBackup(this);
1971 Flags
|= C_POLYMORPHED
;
1975 GetStack()->MoveItemsTo(NewForm
->GetStack());
1976 NewForm
->SetMoney(GetMoney());
1977 DonateEquipmentTo(NewForm
);
1978 Flags
&= ~C_IN_NO_MSG_MODE
;
1979 NewForm
->Flags
&= ~C_IN_NO_MSG_MODE
;
1980 NewForm
->CalculateAll();
1984 game::SetPlayer(NewForm
);
1985 game::SendLOSUpdateRequest();
1989 NewForm
->TestWalkability();
1994 void character::BeKicked (character
*Kicker
, item
*Boot
, bodypart
*Leg
, v2 HitPos
, double KickDamage
,
1995 double ToHitValue
, int Success
, int Direction
, truth Critical
, truth ForceHit
)
1998 game::ClearEventData();
1999 game::mActor
= Kicker
;
2000 if (game::RunOnCharEvent(this, CONST_S("before_be_kicked"))) { game::ClearEventData(); return; }
2001 game::ClearEventData();
2003 switch (TakeHit(Kicker
, Boot
, Leg
, HitPos
, KickDamage
, ToHitValue
, Success
, KICK_ATTACK
, Direction
, Critical
, ForceHit
)) {
2007 if (IsEnabled() && !CheckBalance(KickDamage
)) {
2008 if (IsPlayer()) ADD_MESSAGE("The kick throws you off balance.");
2009 else if (Kicker
->IsPlayer()) ADD_MESSAGE("The kick throws %s off balance.", CHAR_DESCRIPTION(DEFINITE
));
2010 v2 FallToPos
= GetPos()+game::GetMoveVector(Direction
);
2011 FallTo(Kicker
, FallToPos
);
2017 /* Return true if still in balance */
2018 truth
character::CheckBalance (double KickDamage
) {
2019 return !CanMove() || IsStuck() || !KickDamage
|| (!IsFlying() && KickDamage
*5 < RAND()%GetSize());
2023 void character::FallTo (character
*GuiltyGuy
, v2 Where
) {
2025 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
2026 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, Where
);
2028 truth NoRoom
= false;
2029 for (int c
= 0; c
< Squares
; ++c
) {
2030 olterrain
*Terrain
= MoveToSquare
[c
]->GetOLTerrain();
2031 if (Terrain
&& !CanMoveOn(Terrain
)) { NoRoom
= true; break; }
2035 if (IsPlayer()) ADD_MESSAGE("You hit your head on the wall.");
2036 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s head on the wall.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
2038 ReceiveDamage(GuiltyGuy
, 1+RAND()%5, PHYSICAL_DAMAGE
, HEAD
);
2039 CheckDeath(CONST_S("killed by hitting a wall due to being kicked @bk"), GuiltyGuy
);
2041 if (IsFreeForMe(MoveToSquare
[0])) Move(Where
, true);
2042 // Place code that handles characters bouncing to each other here
2048 truth
character::CheckCannibalism (cmaterial
*What
) const {
2049 return GetTorso()->GetMainMaterial()->IsSameAs(What
);
2053 void character::StandIdleAI () {
2054 SeekLeader(GetLeader());
2055 if (CheckForEnemies(true, true, true)) return;
2056 if (CheckForUsefulItemsOnGround()) return;
2057 if (FollowLeader(GetLeader())) return;
2058 if (CheckForDoors()) return;
2059 if (MoveTowardsHomePos()) return;
2060 if (CheckSadism()) return;
2065 truth
character::LoseConsciousness (int Counter
, truth HungerFaint
) {
2066 if (!AllowUnconsciousness()) return false;
2067 action
*Action
= GetAction();
2069 if (HungerFaint
&& !Action
->AllowUnconsciousness()) return false;
2070 if (Action
->IsUnconsciousness()) {
2071 static_cast<unconsciousness
*>(Action
)->RaiseCounterTo(Counter
);
2074 Action
->Terminate(false);
2076 if (IsPlayer()) ADD_MESSAGE("You lose consciousness.");
2077 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s loses consciousness.", CHAR_NAME(DEFINITE
));
2078 unconsciousness
*Unconsciousness
= unconsciousness::Spawn(this);
2079 Unconsciousness
->SetCounter(Counter
);
2080 SetAction(Unconsciousness
);
2085 void character::DeActivateVoluntaryAction (cfestring
&Reason
) {
2086 if (GetAction() && GetAction()->IsVoluntary()) {
2088 if (Reason
.GetSize()) ADD_MESSAGE("%s", Reason
.CStr());
2089 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"?")) GetAction()->ActivateInDNDMode();
2090 else GetAction()->Terminate(false);
2092 GetAction()->Terminate(false);
2098 void character::ActionAutoTermination () {
2099 if (!GetAction() || !GetAction()->IsVoluntary() || GetAction()->InDNDMode()) return;
2101 for (int c
= 0; c
< game::GetTeams(); ++c
) {
2102 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
2103 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
2105 if (ch
->IsEnabled() && ch
->CanBeSeenBy(this, false, true) && (ch
->CanMove() || ch
->GetPos().IsAdjacent(Pos
)) && ch
->CanAttack()) {
2107 ADD_MESSAGE("%s seems to be hostile.", ch
->CHAR_NAME(DEFINITE
));
2108 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"?")) GetAction()->ActivateInDNDMode();
2109 else GetAction()->Terminate(false);
2111 GetAction()->Terminate(false);
2121 truth
character::CheckForEnemies (truth CheckDoors
, truth CheckGround
, truth MayMoveRandomly
, truth RunTowardsTarget
) {
2122 if (!IsEnabled()) return false;
2123 truth HostileCharsNear
= false;
2124 character
*NearestChar
= 0;
2125 sLong NearestDistance
= 0x7FFFFFFF;
2127 for (int c
= 0; c
< game::GetTeams(); ++c
) {
2128 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
2129 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
2131 if (ch
->IsEnabled() && GetAttribute(WISDOM
) < ch
->GetAttackWisdomLimit()) {
2132 sLong ThisDistance
= Max
<sLong
>(abs(ch
->GetPos().X
- Pos
.X
), abs(ch
->GetPos().Y
- Pos
.Y
));
2133 if (ThisDistance
<= GetLOSRangeSquare()) HostileCharsNear
= true;
2134 if ((ThisDistance
< NearestDistance
|| (ThisDistance
== NearestDistance
&& !(RAND() % 3))) &&
2135 ch
->CanBeSeenBy(this, false, IsGoingSomeWhere()) &&
2136 (!IsGoingSomeWhere() || HasClearRouteTo(ch
->GetPos()))) {
2138 NearestDistance
= ThisDistance
;
2146 if (GetAttribute(INTELLIGENCE
) >= 10 || IsSpy()) game::CallForAttention(GetPos(), 100);
2147 if (SpecialEnemySightedReaction(NearestChar
)) return true;
2148 if (IsExtraCoward() && !StateIsActivated(PANIC
) && NearestChar
->GetRelativeDanger(this) >= 0.5 && !StateIsActivated(FEARLESS
)) {
2149 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s sees %s.", CHAR_NAME(DEFINITE
), NearestChar
->CHAR_DESCRIPTION(DEFINITE
));
2150 BeginTemporaryState(PANIC
, 500+RAND()%500);
2152 if (!IsRetreating()) {
2153 if (CheckGround
&& NearestDistance
> 2 && CheckForUsefulItemsOnGround(false)) return true;
2154 SetGoingTo(NearestChar
->GetPos());
2156 SetGoingTo(Pos
-((NearestChar
->GetPos()-Pos
)<<4));
2158 return MoveTowardsTarget(true);
2160 character
*Leader
= GetLeader();
2161 if (Leader
== this) Leader
= 0;
2162 if (!Leader
&& IsGoingSomeWhere()) {
2163 if (!MoveTowardsTarget(RunTowardsTarget
)) {
2167 if (!IsEnabled()) return true;
2168 if (GetPos() == GoingTo
) TerminateGoingTo();
2172 if ((!Leader
|| (Leader
&& !IsGoingSomeWhere())) && HostileCharsNear
) {
2173 if (CheckDoors
&& CheckForDoors()) return true;
2174 if (CheckGround
&& CheckForUsefulItemsOnGround()) return true;
2175 if (MayMoveRandomly
&& MoveRandomly()) return true; // one has heard that an enemy is near but doesn't know where
2183 truth
character::CheckForDoors () {
2184 if (!CanOpen() || !IsEnabled()) return false;
2185 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2186 lsquare
*Square
= GetNeighbourLSquare(d
);
2187 if (Square
&& Square
->GetOLTerrain() && Square
->GetOLTerrain()->Open(this)) return true;
2193 truth
character::CheckForUsefulItemsOnGround (truth CheckFood
) {
2194 if (StateIsActivated(PANIC
) || !IsEnabled()) return false;
2195 itemvector ItemVector
;
2196 GetStackUnder()->FillItemVector(ItemVector
);
2197 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
2198 if (ItemVector
[c
]->CanBeSeenBy(this) && ItemVector
[c
]->IsPickable(this)) {
2199 if (!(CommandFlags
& DONT_CHANGE_EQUIPMENT
) && TryToEquip(ItemVector
[c
])) return true;
2200 if (CheckFood
&& UsesNutrition() && !CheckIfSatiated() && TryToConsume(ItemVector
[c
])) return true;
2201 if (IsRangedAttacker() && (ItemVector
[c
])->GetThrowItemTypes() && TryToAddToInventory(ItemVector
[c
])) return true;
2208 truth
character::TryToAddToInventory (item
*Item
) {
2209 if (!(GetBurdenState() > STRESSED
) || !CanUseEquipment() || Item
->GetSquaresUnder() != 1) return false;
2210 room
*Room
= GetRoom();
2211 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
2212 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up %s from the ground.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(INDEFINITE
));
2213 Item
->MoveTo(GetStack());
2221 truth
character::CheckInventoryForItemToThrow (item
*ToBeChecked
) {
2222 return (ToBeChecked
->GetThrowItemTypes() & GetWhatThrowItemTypesToThrow()) ? true : false; //hehe
2226 truth
character::CheckThrowItemOpportunity () {
2227 if (!IsRangedAttacker() || !CanThrow() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2228 //fprintf(stderr, "character::CheckThrowItemOpportunity...\n");
2230 // (1) - Acquire target as nearest enemy
2231 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2232 // (3) - check inventory for throwing weapon, select this weapon
2233 // (4) - throw item in direction where the enemy is
2235 //Check the visible area for hostiles
2236 int ThrowDirection
= 0;
2237 int TargetFound
= 0;
2240 int RangeMax
= GetLOSRange();
2241 int CandidateDirections
[7] = {0, 0, 0, 0, 0, 0, 0};
2242 int HostileFound
= 0;
2243 item
*ToBeThrown
= 0;
2244 level
*Level
= GetLevel();
2246 for (int r
= 1; r
<= RangeMax
; ++r
) {
2247 for (int dir
= 0; dir
< MDIR_STAND
; ++dir
) {
2250 case 0: TestPos
= v2(Pos
.X
-r
, Pos
.Y
-r
); break;
2251 case 1: TestPos
= v2(Pos
.X
, Pos
.Y
-r
); break;
2252 case 2: TestPos
= v2(Pos
.X
+r
, Pos
.Y
-r
); break;
2253 case 3: TestPos
= v2(Pos
.X
-r
, Pos
.Y
); break;
2254 case 4: TestPos
= v2(Pos
.X
+r
, Pos
.Y
); break;
2255 case 5: TestPos
= v2(Pos
.X
-r
, Pos
.Y
+r
); break;
2256 case 6: TestPos
= v2(Pos
.X
, Pos
.Y
+r
); break;
2257 case 7: TestPos
= v2(Pos
.X
+r
, Pos
.Y
+r
); break;
2259 if (Level
->IsValidPos(TestPos
)) {
2260 square
*TestSquare
= GetNearSquare(TestPos
);
2261 character
*Dude
= TestSquare
->GetCharacter();
2263 if (Dude
&& Dude
->IsEnabled() && Dude
->CanBeSeenBy(this, false, true)) {
2264 if (GetRelation(Dude
) != HOSTILE
) CandidateDirections
[dir
] = BLOCKED
;
2265 else if (GetRelation(Dude
) == HOSTILE
&& CandidateDirections
[dir
] != BLOCKED
) {
2266 //then load this candidate position direction into the vector of possible throw directions
2267 CandidateDirections
[dir
] = SUCCESS
;
2276 for (int dir
= 0; dir
< MDIR_STAND
; ++dir
) {
2277 if (CandidateDirections
[dir
] == SUCCESS
&& !TargetFound
) {
2278 ThrowDirection
= dir
;
2283 if (!TargetFound
) return false;
2287 //fprintf(stderr, "throw: has target.\n");
2288 // check inventory for throwing weapon
2289 itemvector ItemVector
;
2290 GetStack()->FillItemVector(ItemVector
);
2291 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
2292 if (ItemVector
[c
]->IsThrowingWeapon()) {
2293 ToBeThrown
= ItemVector
[c
];
2297 if (!ToBeThrown
) return false;
2298 //fprintf(stderr, "throw: has throwing weapon.\n");
2299 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s throws %s.", CHAR_NAME(DEFINITE
), ToBeThrown
->CHAR_NAME(INDEFINITE
));
2300 ThrowItem(ThrowDirection
, ToBeThrown
);
2301 EditExperience(ARM_STRENGTH
, 75, 1<<8);
2302 EditExperience(DEXTERITY
, 75, 1<<8);
2303 EditExperience(PERCEPTION
, 75, 1<<8);
2311 truth
character::CheckAIZapOpportunity () {
2312 if (/*!IsRangedAttacker() || */ !CanZap() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2314 // (1) - Acquire target as nearest enemy
2315 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2316 // (3) - check inventory for zappable item
2317 // (4) - zap item in direction where the enemy is
2318 //Check the rest of the visible area for hostiles
2321 int SensibleRange
= 5;
2322 int RangeMax
= GetLOSRange();
2323 if (RangeMax
< SensibleRange
) SensibleRange
= RangeMax
;
2324 int CandidateDirections
[7] = {0, 0, 0, 0, 0, 0, 0};
2325 int HostileFound
= 0;
2326 int ZapDirection
= 0;
2327 int TargetFound
= 0;
2328 item
*ToBeZapped
= 0;
2329 level
*Level
= GetLevel();
2331 for (int r
= 2; r
<= SensibleRange
; ++r
) {
2332 for (int dir
= 0; dir
< MDIR_STAND
; ++dir
) {
2334 case 0: TestPos
= v2(Pos
.X
-r
, Pos
.Y
-r
); break;
2335 case 1: TestPos
= v2(Pos
.X
, Pos
.Y
-r
); break;
2336 case 2: TestPos
= v2(Pos
.X
+r
, Pos
.Y
-r
); break;
2337 case 3: TestPos
= v2(Pos
.X
-r
, Pos
.Y
); break;
2338 case 4: TestPos
= v2(Pos
.X
+r
, Pos
.Y
); break;
2339 case 5: TestPos
= v2(Pos
.X
-r
, Pos
.Y
+r
); break;
2340 case 6: TestPos
= v2(Pos
.X
, Pos
.Y
+r
); break;
2341 case 7: TestPos
= v2(Pos
.X
+r
, Pos
.Y
+r
); break;
2343 if (Level
->IsValidPos(TestPos
)) {
2344 square
*TestSquare
= GetNearSquare(TestPos
);
2345 character
*Dude
= TestSquare
->GetCharacter();
2347 if (Dude
&& Dude
->IsEnabled() && Dude
->CanBeSeenBy(this, false, true)) {
2348 if (GetRelation(Dude
) != HOSTILE
) CandidateDirections
[dir
] = BLOCKED
;
2349 else if (GetRelation(Dude
) == HOSTILE
&& CandidateDirections
[dir
] != BLOCKED
) {
2350 //then load this candidate position direction into the vector of possible zap directions
2351 CandidateDirections
[dir
] = SUCCESS
;
2360 for (int dir
= 0; dir
< MDIR_STAND
; ++dir
) {
2361 if (CandidateDirections
[dir
] == SUCCESS
&& !TargetFound
) {
2367 if (!TargetFound
) return false;
2371 // check inventory for zappable item
2372 itemvector ItemVector
;
2373 GetStack()->FillItemVector(ItemVector
);
2374 for (unsigned int c
= 0; c
< ItemVector
.size(); ++c
) {
2375 if (ItemVector
[c
]->GetMinCharges() > 0 && ItemVector
[c
]->GetPrice()) {
2376 // bald-faced gum solution for choosing zappables that have shots left.
2377 // MinCharges needs to be replaced. Empty wands have zero price!
2378 ToBeZapped
= ItemVector
[c
];
2382 if (!ToBeZapped
) return false;
2383 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s zaps %s.", CHAR_NAME(DEFINITE
), ToBeZapped
->CHAR_NAME(INDEFINITE
));
2384 if (ToBeZapped
->Zap(this, GetPos(), ZapDirection
)) {
2385 EditAP(-100000/APBonus(GetAttribute(PERCEPTION
)));
2395 truth
character::FollowLeader (character
*Leader
) {
2396 if (!Leader
|| Leader
== this || !IsEnabled()) return false;
2397 if ((CommandFlags
&FOLLOW_LEADER
) && Leader
->CanBeSeenBy(this) && Leader
->SquareUnderCanBeSeenBy(this, true)) {
2398 v2 Distance
= GetPos()-GoingTo
;
2399 if (abs(Distance
.X
) <= 2 && abs(Distance
.Y
) <= 2) return false;
2400 return MoveTowardsTarget(false);
2402 if (IsGoingSomeWhere()) {
2403 if (!MoveTowardsTarget(true)) {
2413 void character::SeekLeader (ccharacter
*Leader
) {
2414 if (Leader
&& Leader
!= this) {
2415 if (Leader
->CanBeSeenBy(this) && (Leader
->SquareUnderCanBeSeenBy(this, true) || !IsGoingSomeWhere())) {
2416 if (CommandFlags
&FOLLOW_LEADER
) SetGoingTo(Leader
->GetPos());
2417 } else if (!IsGoingSomeWhere()) {
2418 team
*Team
= GetTeam();
2419 for (std::list
<character
*>::const_iterator i
= Team
->GetMember().begin(); i
!= Team
->GetMember().end(); ++i
) {
2421 if (ch
->IsEnabled() && ch
->GetID() != GetID() &&
2422 (CommandFlags
& FOLLOW_LEADER
) == (ch
->CommandFlags
& FOLLOW_LEADER
) && ch
->CanBeSeenBy(this)) {
2423 v2 Pos
= ch
->GetPos();
2424 v2 Distance
= GetPos()-Pos
;
2425 if (abs(Distance
.X
) > 2 && abs(Distance
.Y
) > 2) {
2436 int character::GetMoveEase () const {
2437 switch (BurdenState
) {
2439 case STRESSED
: return 50;
2440 case BURDENED
: return 75;
2441 case UNBURDENED
: return 100;
2447 int character::GetLOSRange () const {
2448 if (!game::IsInWilderness()) return GetAttribute(PERCEPTION
)*GetLevel()->GetLOSModifier()/48;
2453 truth
character::Displace (character
*Who
, truth Forced
) {
2454 if (GetBurdenState() == OVER_LOADED
) {
2456 cchar
*CrawlVerb
= StateIsActivated(LEVITATION
) ? "float" : "crawl";
2457 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb
);
2464 double Danger
= GetRelativeDanger(Who
);
2465 int PriorityDifference
= Limit(GetDisplacePriority()-Who
->GetDisplacePriority(), -31, 31);
2467 if (IsPlayer()) ++PriorityDifference
;
2468 else if (Who
->IsPlayer()) --PriorityDifference
;
2470 if (PriorityDifference
>= 0) Danger
*= 1 << PriorityDifference
;
2471 else Danger
/= 1 << -PriorityDifference
;
2473 if (IsSmall() && Who
->IsSmall() &&
2474 (Forced
|| Danger
> 1.0 || !(Who
->IsPlayer() || Who
->IsBadPath(GetPos()))) &&
2475 !IsStuck() && !Who
->IsStuck() && (!Who
->GetAction() || Who
->GetAction()->TryDisplace()) &&
2476 CanMove() && Who
->CanMove() && Who
->CanMoveOn(GetLSquareUnder())) {
2477 if (IsPlayer()) ADD_MESSAGE("You displace %s!", Who
->CHAR_DESCRIPTION(DEFINITE
));
2478 else if (Who
->IsPlayer()) ADD_MESSAGE("%s displaces you!", CHAR_DESCRIPTION(DEFINITE
));
2479 else if (CanBeSeenByPlayer() || Who
->CanBeSeenByPlayer()) ADD_MESSAGE("%s displaces %s!", CHAR_DESCRIPTION(DEFINITE
), Who
->CHAR_DESCRIPTION(DEFINITE
));
2480 lsquare
*OldSquareUnder1
[MAX_SQUARES_UNDER
];
2481 lsquare
*OldSquareUnder2
[MAX_SQUARES_UNDER
];
2482 for (int c
= 0; c
< GetSquaresUnder(); ++c
) OldSquareUnder1
[c
] = GetLSquareUnder(c
);
2483 for (int c
= 0; c
< Who
->GetSquaresUnder(); ++c
) OldSquareUnder2
[c
] = Who
->GetLSquareUnder(c
);
2485 v2 WhoPos
= Who
->GetPos();
2490 EditAP(-GetMoveAPRequirement(GetSquareUnder()->GetEntryDifficulty()) - 500);
2491 EditNP(-12*GetSquareUnder()->GetEntryDifficulty());
2492 EditExperience(AGILITY
, 75, GetSquareUnder()->GetEntryDifficulty() << 7);
2493 if (IsPlayer()) ShowNewPosInfo();
2494 if (Who
->IsPlayer()) Who
->ShowNewPosInfo();
2495 SignalStepFrom(OldSquareUnder1
);
2496 Who
->SignalStepFrom(OldSquareUnder2
);
2500 ADD_MESSAGE("%s resists!", Who
->CHAR_DESCRIPTION(DEFINITE
));
2509 void character::SetNP (sLong What
) {
2510 int OldState
= GetHungerState();
2513 int NewState
= GetHungerState();
2514 if (NewState
== STARVING
&& OldState
> STARVING
) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
2515 else if (NewState
== VERY_HUNGRY
&& OldState
> VERY_HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting very hungry."));
2516 else if (NewState
== HUNGRY
&& OldState
> HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting hungry."));
2521 void character::ShowNewPosInfo () const {
2522 msgsystem::EnterBigMessageMode();
2525 if (ivanconfig::GetAutoCenterMap()) {
2526 game::UpdateCameraX();
2527 game::UpdateCameraY();
2529 if (Pos
.X
< game::GetCamera().X
+3 || Pos
.X
>= game::GetCamera().X
+game::GetScreenXSize()-3) game::UpdateCameraX();
2530 if (Pos
.Y
< game::GetCamera().Y
+3 || Pos
.Y
>= game::GetCamera().Y
+game::GetScreenYSize()-3) game::UpdateCameraY();
2533 game::SendLOSUpdateRequest();
2534 game::DrawEverythingNoBlit();
2537 if (!game::IsInWilderness()) {
2538 if (GetLSquareUnder()->IsDark() && !game::GetSeeWholeMapCheatMode()) ADD_MESSAGE("It's dark in here!");
2540 GetLSquareUnder()->ShowSmokeMessage();
2541 itemvectorvector PileVector
;
2542 GetStackUnder()->Pile(PileVector
, this, CENTER
);
2544 if (PileVector
.size()) {
2545 truth Feel
= !GetLSquareUnder()->IsTransparent() || GetLSquareUnder()->IsDark();
2547 if (PileVector
.size() == 1) {
2549 ADD_MESSAGE("You feel %s lying here.", PileVector
[0][0]->GetName(INDEFINITE
, PileVector
[0].size()).CStr());
2551 if (ivanconfig::GetShowFullItemDesc() && PileVector
[0][0]->AllowDetailedDescription()) {
2554 PileVector
[0][0]->AddInventoryEntry(PLAYER
, text
, PileVector
[0].size(), true);
2555 //fprintf(stderr, "invdsc : [%s]\n", text.CStr());
2556 ADD_MESSAGE("%s %s lying here.", text
.CStr(), PileVector
[0].size() == 1 ? "is" : "are");
2558 ADD_MESSAGE("%s %s lying here.", PileVector
[0][0]->GetName(INDEFINITE
, PileVector
[0].size()).CStr(), PileVector
[0].size() == 1 ? "is" : "are");
2561 fprintf(stderr, "description: [%s]\n", PileVector[0][0]->GetDescription(INDEFINITE).CStr());
2562 fprintf(stderr, "strength : [%s]\n", PileVector[0][0]->GetStrengthValueDescription());
2563 fprintf(stderr, "basetohit : [%s]\n", PileVector[0][0]->GetBaseToHitValueDescription());
2564 fprintf(stderr, "baseblock : [%s]\n", PileVector[0][0]->GetBaseBlockValueDescription());
2565 fprintf(stderr, "extdsc : [%s]\n", PileVector[0][0]->GetExtendedDescription().CStr());
2570 for (uInt c
= 0; c
< PileVector
.size(); ++c
) {
2571 if ((Items
+= PileVector
[c
].size()) > 3) break;
2574 if (Feel
) ADD_MESSAGE("You feel several items lying here.");
2575 else ADD_MESSAGE("Several items are lying here.");
2577 if (Feel
) ADD_MESSAGE("You feel a few items lying here.");
2578 else ADD_MESSAGE("A few items are lying here.");
2584 GetLSquareUnder()->GetSideItemDescription(SideItems
);
2586 if (!SideItems
.IsEmpty()) ADD_MESSAGE("There is %s.", SideItems
.CStr());
2588 if (GetLSquareUnder()->HasEngravings()) {
2589 if (CanRead()) ADD_MESSAGE("Something has been engraved here: \"%s\"", GetLSquareUnder()->GetEngraved());
2590 else ADD_MESSAGE("Something has been engraved here.");
2594 msgsystem::LeaveBigMessageMode();
2598 void character::Hostility (character
*Enemy
) {
2599 if (Enemy
== this || !Enemy
|| !Team
|| !Enemy
->Team
) return;
2600 if (Enemy
->IsMasochist() && GetRelation(Enemy
) == FRIEND
) return;
2601 if (!IsAlly(Enemy
)) {
2602 GetTeam()->Hostility(Enemy
->GetTeam());
2603 } else if (IsPlayer() && !Enemy
->IsPlayer()) {
2604 // I believe both may be players due to polymorph feature...
2605 if (Enemy
->CanBeSeenByPlayer()) ADD_MESSAGE("%s becomes enraged.", Enemy
->CHAR_NAME(DEFINITE
));
2606 Enemy
->ChangeTeam(game::GetTeam(BETRAYED_TEAM
));
2611 stack
*character::GetGiftStack () const {
2612 if (GetLSquareUnder()->GetRoomIndex() && !GetLSquareUnder()->GetRoom()->AllowDropGifts()) return GetStack();
2613 return GetStackUnder();
2617 truth
character::MoveRandomlyInRoom () {
2618 for (int c
= 0; c
< 10; ++c
) {
2619 v2 ToTry
= game::GetMoveVector(RAND()&7);
2620 if (GetLevel()->IsValidPos(GetPos()+ToTry
)) {
2621 lsquare
*Square
= GetNearLSquare(GetPos()+ToTry
);
2622 if (!Square
->IsDangerous(this) && !Square
->IsScary(this) &&
2623 (!Square
->GetOLTerrain() || !Square
->GetOLTerrain()->IsDoor()) &&
2624 TryMove(ToTry
, false, false)) return true;
2631 //#define dirlogf(...) do { fprintf(stderr, __VA_ARGS__); } while (0)
2632 #define dirlogf(...) ((void)0)
2635 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
};
2636 static const bool orthoDir
[MDIR_STAND
] = { false, true, false, true, true, false, true, false };
2639 // only for ortho moveDir
2640 static inline truth
IsDirExcluded (int moveDir
, int dir
) {
2641 if (moveDir
== dir
) return true;
2643 case MDIR_UP
: return (dir
== MDIR_UP_LEFT
|| dir
== MDIR_UP_RIGHT
);
2644 case MDIR_LEFT
: return (dir
== MDIR_UP_LEFT
|| dir
== MDIR_DOWN_LEFT
);
2645 case MDIR_RIGHT
: return (dir
== MDIR_UP_RIGHT
|| dir
== MDIR_DOWN_RIGHT
);
2646 case MDIR_DOWN
: return (dir
== MDIR_DOWN_LEFT
|| dir
== MDIR_DOWN_RIGHT
);
2652 truth
character::IsPassableSquare (int x
, int y
) const {
2653 if (x
>= 0 && y
>= 0) {
2654 area
*ca
= GetSquareUnder()->GetArea();
2657 if (x
>= ca
->GetXSize() || y
>= ca
->GetYSize()) return false;
2658 sq
= static_cast<lsquare
*>(ca
->GetSquare(x
, y
));
2659 return sq
&& CanMoveOn(sq
);
2665 void character::CountPossibleMoveDirs (cv2 pos
, int *odirs
, int *ndirs
, int exclideDir
) const {
2666 if (odirs
) *odirs
= 0;
2667 if (ndirs
) *ndirs
= 0;
2668 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2669 if (!IsDirExcluded(exclideDir
, f
)) {
2670 if (IsPassableSquare(pos
+game::GetMoveVector(f
))) {
2672 if (odirs
) ++(*odirs
);
2674 if (ndirs
) ++(*ndirs
);
2683 * in corridor (for orto-dirs):
2684 * count dirs excluding ortho-dir we going:
2685 * if there is one or less ortho-dirs and one or less non-ortho-dirs, we are in corridor
2687 // only for ortho-dirs
2688 truth
character::IsInCorridor (int x
, int y
, int moveDir
) const {
2691 dirlogf("IsInCorridor(%d,%d,%d)\n", x
, y
, moveDir
);
2693 moveDir
= (moveDir
>= 0 && moveDir
< MDIR_STAND
? revDir
[moveDir
] : -1);
2694 dirlogf(" reversedDir: %d\n", moveDir
);
2695 CountPossibleMoveDirs(v2(x
, y
), &od
, &nd
, moveDir
);
2696 dirlogf(" possibleDirs: (%d:%d)\n", od
, nd
);
2697 dirlogf(" IsInCorridor: %s\n", ((od
<= 1 && nd
<= 1) ? "yes" : "no"));
2698 return (od
<= 1 && nd
<= 1);
2702 cv2
character::GetDiagonalForDirs (int moveDir
, int newDir
) const {
2706 case MDIR_LEFT
: return game::GetMoveVector(MDIR_UP_LEFT
);
2707 case MDIR_RIGHT
: return game::GetMoveVector(MDIR_UP_RIGHT
);
2712 case MDIR_LEFT
: return game::GetMoveVector(MDIR_DOWN_LEFT
);
2713 case MDIR_RIGHT
: return game::GetMoveVector(MDIR_DOWN_RIGHT
);
2718 case MDIR_UP
: return game::GetMoveVector(MDIR_UP_LEFT
);
2719 case MDIR_DOWN
: return game::GetMoveVector(MDIR_DOWN_LEFT
);
2724 case MDIR_UP
: return game::GetMoveVector(MDIR_UP_RIGHT
);
2725 case MDIR_DOWN
: return game::GetMoveVector(MDIR_DOWN_RIGHT
);
2729 ABORT("wtf in character::GetDiagonalForDirs()");
2733 truth
character::IsInTunnelDeadEnd () const {
2736 CountPossibleMoveDirs(GetPos(), &od
, &nd
, -1);
2737 return (od
<= 1 && nd
== 0);
2742 * try to walk in the given dir
2743 * can do two steps without a turn and still in corridor?
2747 * go in non-ortho dir, set prevdir to last ortho-dir from corridor tracing
2749 // only for ortho-dirs; assume that the char is in corridor
2750 int character::CheckCorridorMove (v2
&moveVector
, cv2 pos
, int moveDir
, truth
*markAsTurn
) const {
2751 v2
ps1(pos
+(moveVector
= game::GetMoveVector(moveDir
)));
2753 if (markAsTurn
) *markAsTurn
= true;
2755 if (IsPassableSquare(ps1
)) {
2756 // we can do first step in the given dir
2757 // check if we will be in corridor after it
2758 dirlogf("CheckCorridorMove: can do first step\n");
2759 if (IsInCorridor(ps1
, moveDir
)) {
2760 // check second step
2761 v2
ps2(ps1
+moveVector
);
2762 dirlogf("CheckCorridorMove: still in corridor after the first step\n");
2763 if (IsPassableSquare(ps2
)) {
2764 // can do second step
2765 dirlogf("CheckCorridorMove: can do second step\n");
2768 // can't do second step; but we still in corridor, so we should make a turn
2769 int newDir
= -1; // direction to turn
2770 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2771 if (f
!= moveDir
&& orthoDir
[f
] && f
!= revDir
[moveDir
] && IsPassableSquare(ps1
+game::GetMoveVector(f
))) {
2776 dirlogf("CheckCorridorMove: can't do second step; moveDir=%d; newDir=%d\n", moveDir
, newDir
);
2778 // dead end, will stop
2779 //ABORT("wtd in character::CheckCorridorMove()");
2782 // we should do diagonal move
2783 moveVector
= GetDiagonalForDirs(moveDir
, newDir
);
2784 // if this is 'one-tile-turn', we should not change the direction to newDir
2785 if (IsPassableSquare(ps1
+game::GetMoveVector(newDir
)+game::GetMoveVector(moveDir
))) {
2786 // yes, this is 'one-tile-turn'
2787 dirlogf("CheckCorridorMove: one-tile-turn, don't change dir\n");
2795 * 'g'o right: should stop at '*', but it just goes right
2797 if (markAsTurn
) *markAsTurn
= IsInCorridor(ps1
+game::GetMoveVector(newDir
), newDir
);
2803 dirlogf("CheckCorridorMove: can do one or two steps; move forward\n");
2804 // can do one or two steps: check for T-junction
2805 // we should stop if we have more than two open dirs, or one of open dirs is not moveDir
2807 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2808 if (f
== revDir
[moveDir
]) continue; // skip "reverse dir" check
2809 v2
ps2(pos
+game::GetMoveVector(f
));
2810 if (IsPassableSquare(ps2
)) {
2812 if (dcount
> 2) return -1; // more than two open dirs, stop
2813 if (f
!= moveDir
) return -1; // one of open dirs is not moveDir
2816 // just move forward
2819 dirlogf("CheckCorridorMove: dead end\n");
2820 // can't go, assume invalid direction
2825 truth
character::IsDangerousSquare (v2 pos
) const {
2826 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
2827 auto Squares
= CalculateNewSquaresUnder(MoveToSquare
, pos
);
2828 for (decltype(Squares
) c
= 0; c
< Squares
; ++c
) {
2829 lsquare
*Square
= MoveToSquare
[c
];
2830 // check if someone is standing at the square
2831 if (Square
->GetCharacter() && GetTeam() != Square
->GetCharacter()->GetTeam() && Square
->GetCharacter()->CanBeSeenBy(this)) return true;
2832 if (Square
->IsDangerous(this) && Square
->CanBeSeenBy(this)) return true;
2838 void character::GoOn (go
*Go
, truth FirstStep
) {
2839 dirlogf("=== character::GoOn; dir=%d; pos=(%d,%d) ===\n", Go
->GetDirection(), GetPos().X
, GetPos().Y
);
2841 dirlogf("FirstStep\n");
2842 mPrevMoveDir
= Go
->GetDirection();
2843 Go
->SetIsWalkingInOpen(!IsInCorridor(Go
->GetDirection()));
2846 v2 MoveVector
= ApplyStateModification(game::GetMoveVector(Go
->GetDirection()));
2847 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
2848 lsquare
*MoveToSquare2
[MAX_SQUARES_UNDER
];
2849 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, GetPos()+MoveVector
);
2850 int moveDir
= game::MoveVectorToDirection(MoveVector
);
2852 if (!Squares
|| !CanMoveOn(MoveToSquare
[0])) {
2853 dirlogf("just can't move\n");
2854 Go
->Terminate(false);
2859 if (!Go
->GetPrevWasTurn() && Go
->IsWalkingInOpen() != !IsInCorridor(GetPos(), moveDir
)) {
2860 dirlogf("moved to/from open place\n");
2861 Go
->Terminate(false);
2865 uInt OldRoomIndex
= GetLSquareUnder()->GetRoomIndex();
2866 uInt CurrentRoomIndex
= MoveToSquare
[0]->GetRoomIndex();
2868 if (OldRoomIndex
&& (CurrentRoomIndex
!= OldRoomIndex
)) {
2869 // room about to be changed, stop here
2870 dirlogf("room about to be changed\n");
2871 Go
->Terminate(false);
2874 // stop near the dangerous square
2875 if (IsDangerousSquare(GetPos()+MoveVector
)) {
2876 dirlogf("sense the danger\n");
2877 Go
->Terminate(false);
2881 // if the state modified the direction, move and stop
2882 if (moveDir
!= Go
->GetDirection()) {
2883 dirlogf("move affected by state\n");
2884 if (TryMove(MoveVector
, true, game::PlayerIsRunning())) {
2885 game::DrawEverything();
2886 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2888 Go
->Terminate(false);
2892 truth doStop
= false, markAsTurn
= false;
2895 // continuous walking
2896 if (Go
->IsWalkingInOpen() || !orthoDir
[moveDir
]) {
2897 // walking in open space or diagonal walking
2898 v2
newPos(GetPos()+MoveVector
);
2899 int ood
, ond
, nod
, nnd
;
2901 * open: stop if # of possible dirs in next step != # of possible dirs in current step
2902 * (or next step is in corridor)
2904 dirlogf("open walking\n");
2905 if (IsInCorridor(newPos
, moveDir
)) {
2906 // trying to enter the corridor, stop right here
2907 dirlogf("entering the corridor\n");
2908 Go
->Terminate(false);
2911 CountPossibleMoveDirs(GetPos(), &ood
, &ond
);
2912 CountPossibleMoveDirs(newPos
, &nod
, &nnd
);
2913 if (ood
!= nod
|| ond
!= nnd
) {
2914 // # of directions to walk to changed, stop right here
2915 dirlogf("# of directions changed from (%d:%d) to (%d:%d)\n", ood
, ond
, nod
, nnd
);
2916 //Go->Terminate(false);
2920 // ok, we can do this move
2922 // ortho-walking thru the corridor
2923 int newDir
= CheckCorridorMove(MoveVector
, GetPos(), moveDir
, &markAsTurn
);
2925 // ah, something weird; stop right here
2926 Go
->Terminate(false);
2929 Go
->SetDirection(newDir
); // perform possible turn
2931 // stop near the dangerous square
2932 for (int mdv
= 0; mdv
< MDIR_STAND
; ++mdv
) {
2933 if (IsDangerousSquare(GetPos()+MoveVector
+game::GetMoveVector(mdv
))) {
2934 dirlogf(" danger!\n");
2935 Go
->Terminate(false);
2940 // first step, just do it
2943 // now try to perform the move
2944 dirlogf("trying to make the move\n");
2946 square
*BeginSquare
= GetSquareUnder();
2947 uInt OldRoomIndex
= GetLSquareUnder()->GetRoomIndex();
2948 uInt CurrentRoomIndex
= MoveToSquare
[0]->GetRoomIndex();
2950 // stop on the square with something interesting
2953 area
*ca
= GetSquareUnder()->GetArea();
2954 v2 npos
= GetPos()+MoveVector
;
2955 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2956 v2 np
= npos
+game::GetMoveVector(f
);
2957 if (np
.X
>= 0 && np
.Y
>= 0 && np
.X
< ca
->GetXSize() && np
.Y
< ca
->GetYSize()) {
2958 lsquare
*sq
= static_cast<lsquare
*>(ca
->GetSquare(np
.X
, np
.Y
));
2959 if (IsPlayer() && !sq
->HasBeenSeen()) continue;
2960 olterrain
*terra
= sq
->GetOLTerrain();
2962 dirlogf("** OK terra at %d; door: %s; seen: %s\n", f
, (terra
->IsDoor() ? "yes" : "no"), (sq
->IsGoSeen() ? "yes" : "no"));
2963 if (terra
->IsDoor()) {
2964 if (ivanconfig::GetStopOnSeenDoors() || !sq
->IsGoSeen()) {
2965 dirlogf(" *** stop near the door\n");
2975 for (int c
= 0; c
< Squares
; ++c
) {
2976 lsquare
*Square
= MoveToSquare
[c
];
2977 if (IsPlayer() && !Square
->HasBeenSeen()) continue;
2978 if (Square
->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
2979 dirlogf(" stepped near something interesting\n");
2985 // check items in adjacent squares too, so diagonal move won't miss any
2987 for (int f
= 0; f
< MDIR_STAND
&& !doStop
; ++f
) {
2988 v2 np
= game::GetMoveVector(f
);
2989 if (np
== MoveVector
) continue; // this will be checked on the next move
2990 if (!IsPassableSquare(GetPos()+np
)) continue;
2991 int sq2
= CalculateNewSquaresUnder(MoveToSquare2
, GetPos()+np
);
2992 for (int c
= 0; c
< sq2
; ++c
) {
2993 lsquare
*Square
= MoveToSquare2
[c
];
2994 if (!Square
->CanBeSeenBy(this)) continue;
2995 if (Square
->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
2996 dirlogf(" stepped near something interesting\n");
2997 //HACK: mark all items as stepped on
2998 for (int d
= 0; d
< MDIR_STAND
; ++d
) {
2999 np
= game::GetMoveVector(d
);
3000 if (!IsPassableSquare(GetPos()+np
)) continue;
3001 sq2
= CalculateNewSquaresUnder(MoveToSquare2
, GetPos()+np
);
3002 for (int n
= 0; n
< sq2
; ++n
) MoveToSquare2
[n
]->GetStack()->SetSteppedOn(true);
3005 Go
->Terminate(false);
3013 Go
->SetPrevWasTurn(markAsTurn
&& MoveVector
.X
&& MoveVector
.Y
); // diagonal move?
3015 truth moveOk
= TryMove(MoveVector
, true, game::PlayerIsRunning());
3017 if (!moveOk
|| BeginSquare
== GetSquareUnder() || (CurrentRoomIndex
&& (OldRoomIndex
!= CurrentRoomIndex
))) {
3018 dirlogf(" stopped\n");
3020 game::DrawEverything();
3021 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
3023 Go
->Terminate(false);
3028 mPrevMoveDir
= Go
->GetDirection();
3029 Go
->SetIsWalkingInOpen(!IsInCorridor(moveDir
));
3032 game::DrawEverything();
3033 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
3034 if (doStop
) Go
->Terminate(false);
3038 void character::SetTeam (team
*What
) {
3044 void character::ChangeTeam (team
*What
) {
3045 if (Team
) Team
->Remove(this);
3047 SendNewDrawRequest();
3048 if (Team
) Team
->Add(this);
3052 truth
character::ChangeRandomAttribute (int HowMuch
) {
3053 for (int c
= 0; c
< 50; ++c
) {
3054 int AttribID
= RAND()%ATTRIBUTES
;
3055 if (EditAttribute(AttribID
, HowMuch
)) return true;
3061 int character::RandomizeReply (sLong
&Said
, int Replies
) {
3062 truth NotSaid
= false;
3063 for (int c
= 0; c
< Replies
; ++c
) {
3064 if (!(Said
& (1 << c
))) {
3069 if (!NotSaid
) Said
= 0;
3071 while (Said
& 1 << (ToSay
= RAND() % Replies
));
3077 void character::DisplayInfo (festring
&Msg
) {
3079 Msg
<< " You are " << GetStandVerb() << " here.";
3081 Msg
<< ' ' << GetName(INDEFINITE
).CapitalizeCopy() << " is " << GetStandVerb() << " here. " << GetPersonalPronoun().CapitalizeCopy();
3082 cchar
*Separator1
= GetAction() ? "," : " and";
3083 cchar
*Separator2
= " and";
3084 if (GetTeam() == PLAYER
->GetTeam()) {
3087 int Relation
= GetRelation(PLAYER
);
3088 if (Relation
== HOSTILE
) Msg
<< " is hostile";
3089 else if (Relation
== UNCARING
) {
3090 Msg
<< " does not care about you";
3091 Separator1
= Separator2
= " and is";
3093 Msg
<< " is friendly";
3096 if (StateIsActivated(PANIC
)) {
3097 Msg
<< Separator1
<< " panicked";
3098 Separator2
= " and";
3100 if (GetAction()) Msg
<< Separator2
<< ' ' << GetAction()->GetDescription();
3106 void character::TestWalkability () {
3107 if (!IsEnabled()) return;
3108 square
*SquareUnder
= !game::IsInWilderness() ? GetSquareUnder() : PLAYER
->GetSquareUnder();
3109 if (SquareUnder
->IsFatalToStay() && !CanMoveOn(SquareUnder
)) {
3110 truth Alive
= false;
3111 if (!game::IsInWilderness() || IsPlayer()) {
3112 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
3113 square
*Square
= GetNeighbourSquare(d
);
3114 if (Square
&& CanMoveOn(Square
) && IsFreeForMe(Square
)) {
3115 if (IsPlayer()) ADD_MESSAGE("%s.", SquareUnder
->SurviveMessage(this));
3116 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE
), SquareUnder
->MonsterSurviveMessage(this));
3117 Move(Square
->GetPos(), true); // actually, this shouldn't be a teleport move
3118 SquareUnder
->SurviveEffect(this);
3128 festring DeathMsg
= festring(SquareUnder
->DeathMessage(this));
3129 game::AskForEscPress(DeathMsg
+".");
3130 festring Msg
= SquareUnder
->ScoreEntry(this);
3131 PLAYER
->AddScoreEntry(Msg
);
3134 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE
), SquareUnder
->MonsterDeathVerb(this));
3135 Die(0, SquareUnder
->ScoreEntry(this), DISALLOW_MSG
);
3142 int character::GetSize () const {
3143 if (GetTorso()->GetSize() < 1) {
3144 fprintf(stderr
, "WARNING: character::GetSize() is %d for %s!\n", GetTorso()->GetSize(), GetNameSingular().CStr());
3147 return GetTorso()->GetSize();
3151 void character::SetMainMaterial (material
*NewMaterial
, int SpecialFlags
) {
3152 NewMaterial
->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
3153 GetBodyPart(0)->SetMainMaterial(NewMaterial
, SpecialFlags
);
3154 for (int c
= 1; c
< BodyParts
; ++c
) {
3155 NewMaterial
= NewMaterial
->SpawnMore(GetBodyPart(c
)->GetMainMaterial()->GetVolume());
3156 GetBodyPart(c
)->SetMainMaterial(NewMaterial
, SpecialFlags
);
3161 void character::ChangeMainMaterial (material
*NewMaterial
, int SpecialFlags
) {
3162 NewMaterial
->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
3163 GetBodyPart(0)->ChangeMainMaterial(NewMaterial
, SpecialFlags
);
3164 for (int c
= 1; c
< BodyParts
; ++c
) {
3165 NewMaterial
= NewMaterial
->SpawnMore(GetBodyPart(c
)->GetMainMaterial()->GetVolume());
3166 GetBodyPart(c
)->ChangeMainMaterial(NewMaterial
, SpecialFlags
);
3171 void character::SetSecondaryMaterial (material
*, int) {
3172 ABORT("Illegal character::SetSecondaryMaterial call!");
3176 void character::ChangeSecondaryMaterial (material
*, int) {
3177 ABORT("Illegal character::ChangeSecondaryMaterial call!");
3181 void character::TeleportRandomly (truth Intentional
) {
3182 v2 TelePos
= ERROR_V2
;
3183 if (StateIsActivated(TELEPORT_CONTROL
)) {
3185 v2 Input
= game::PositionQuestion(CONST_S("Where do you wish to teleport? [direction keys move cursor, space accepts]"), GetPos(), &game::TeleportHandler
, 0, false);
3186 if (Input
== ERROR_V2
) Input
= GetPos(); // esc pressed
3187 lsquare
*Square
= GetNearLSquare(Input
);
3188 if (CanMoveOn(Square
) || game::GoThroughWallsCheatIsActive()) {
3189 if (Square
->GetPos() == GetPos()) {
3190 ADD_MESSAGE("You disappear and reappear.");
3193 if (IsFreeForMe(Square
)) {
3194 if ((Input
-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
3195 EditExperience(INTELLIGENCE
, 100, 1 << 10);
3198 ADD_MESSAGE("You cannot concentrate yourself enough to control a teleport that far.");
3201 character
*C
= Square
->GetCharacter();
3202 if (C
) ADD_MESSAGE("For a moment you feel very much like %s.", C
->CHAR_NAME(INDEFINITE
));
3203 else ADD_MESSAGE("You feel that something weird has happened, but can't really tell what exactly.");
3206 ADD_MESSAGE("You feel like having been hit by something really hard from the inside.");
3208 } else if (!Intentional
) {
3209 if (IsGoingSomeWhere() && GetLevel()->IsValidPos(GoingTo
)) {
3210 v2 Where
= GetLevel()->GetNearestFreeSquare(this, GoingTo
);
3211 if (Where
!= ERROR_V2
&& (Where
-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
3212 EditExperience(INTELLIGENCE
, 100, 1 << 10);
3220 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.");
3223 //if (TelePos != ERROR_V2) Move(TelePos, true);
3224 //else Move(GetLevel()->GetRandomSquare(this), true);
3225 //if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE));
3226 //if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3228 if (TelePos
== ERROR_V2
) TelePos
= GetLevel()->GetRandomSquare(this);
3230 room
*PossibleRoom
= game::GetCurrentLevel()->GetLSquare(TelePos
)->GetRoom();
3232 if (!PossibleRoom
) {
3233 //if it's outside of a room
3234 if (TelePos
!= ERROR_V2
) Move(TelePos
, true); else Move(GetLevel()->GetRandomSquare(this), true);
3235 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE
));
3236 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3237 } else if (PossibleRoom
&& PossibleRoom
->IsOKToTeleportInto()) {
3238 // If it's inside of a room, check whether a ward is active that might impede the player
3239 if (TelePos
!= ERROR_V2
) Move(TelePos
, true); else Move(GetLevel()->GetRandomSquare(this), true);
3240 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE
));
3241 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3244 ADD_MESSAGE("A mighty force blasts you back to where you were standing. A ward prevents you from teleporting.");
3246 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);
3251 CONST_S("killed by an explosion triggered when attempting to teleport into room protected by a ward"),
3255 lsquare* Square = GetNearLSquare(GetPos());
3256 Square->DrawParticles(RED);
3257 Square->FireBall(Beam);*/
3262 void character::DoDetecting () {
3263 material
*TempMaterial
;
3266 festring Temp
= game::DefaultQuestion(CONST_S("What material do you want to detect?"), game::GetDefaultDetectMaterial());
3267 TempMaterial
= protosystem::CreateMaterial(Temp
);
3268 if (TempMaterial
) break;
3269 game::DrawEverythingNoBlit();
3272 level
*Level
= GetLevel();
3273 int Squares
= Level
->DetectMaterial(TempMaterial
);
3275 if (Squares
> GetAttribute(INTELLIGENCE
) * (25+RAND()%51)) {
3276 ADD_MESSAGE("An enormous burst of geographical information overwhelms your consciousness. Your mind cannot cope with it and your memories blur.");
3277 Level
->BlurMemory();
3278 BeginTemporaryState(CONFUSED
, 1000 + RAND() % 1000);
3279 EditExperience(INTELLIGENCE
, -100, 1 << 12);
3280 } else if (!Squares
) {
3281 ADD_MESSAGE("You feel a sudden urge to imagine the dark void of a starless night sky.");
3282 EditExperience(INTELLIGENCE
, 200, 1 << 12);
3284 ADD_MESSAGE("You feel attracted to all things made of %s.", TempMaterial
->GetName(false, false).CStr());
3285 game::PositionQuestion(CONST_S("Detecting material [direction keys move cursor, space exits]"), GetPos(), 0, 0, false);
3286 EditExperience(INTELLIGENCE
, 300, 1 << 12);
3289 delete TempMaterial
;
3290 Level
->CalculateLuminances();
3291 game::SendLOSUpdateRequest();
3295 void character::RestoreHP () {
3296 doforbodyparts()(this, &bodypart::FastRestoreHP
);
3301 void character::RestoreLivingHP () {
3303 for (int c
= 0; c
< BodyParts
; ++c
) {
3304 bodypart
*BodyPart
= GetBodyPart(c
);
3305 if (BodyPart
&& BodyPart
->CanRegenerate()) {
3306 BodyPart
->FastRestoreHP();
3307 HP
+= BodyPart
->GetHP();
3313 truth
character::AllowDamageTypeBloodSpill (int Type
) {
3314 switch (Type
&0xFFF) {
3315 case PHYSICAL_DAMAGE
:
3324 case MUSTARD_GAS_DAMAGE
:
3328 ABORT("Unknown blood effect destroyed the dungeon!");
3333 /* Returns truly done damage */
3334 int character::ReceiveBodyPartDamage (character
*Damager
, int Damage
, int Type
, int BodyPartIndex
,
3335 int Direction
, truth PenetrateResistance
, truth Critical
, truth ShowNoDamageMsg
, truth CaptureBodyPart
)
3337 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
3338 if (!Damager
|| Damager
->AttackMayDamageArmor()) BodyPart
->DamageArmor(Damager
, Damage
, Type
);
3339 if (!PenetrateResistance
) {
3340 Damage
-= (BodyPart
->GetTotalResistance(Type
)>>1)+RAND()%((BodyPart
->GetTotalResistance(Type
)>>1)+1);
3342 if (int(Damage
) < 1) {
3346 if (ShowNoDamageMsg
) {
3347 if (IsPlayer()) ADD_MESSAGE("You are not hurt.");
3348 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is not hurt.", GetPersonalPronoun().CStr());
3354 if (Critical
&& AllowDamageTypeBloodSpill(Type
) && !game::IsInWilderness()) {
3355 BodyPart
->SpillBlood(2+(RAND()&1));
3356 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
3357 lsquare
*Square
= GetNeighbourLSquare(d
);
3358 if (Square
&& Square
->IsFlyable()) BodyPart
->SpillBlood(1, Square
->GetPos());
3362 if (BodyPart
->ReceiveDamage(Damager
, Damage
, Type
, Direction
) && BodyPartCanBeSevered(BodyPartIndex
)) {
3363 if (DamageTypeDestroysBodyPart(Type
)) {
3364 if (IsPlayer()) ADD_MESSAGE("Your %s is destroyed!", BodyPart
->GetBodyPartName().CStr());
3365 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is destroyed!", GetPossessivePronoun().CStr(), BodyPart
->GetBodyPartName().CStr());
3366 GetBodyPart(BodyPartIndex
)->DropEquipment();
3367 item
*Severed
= SevereBodyPart(BodyPartIndex
);
3368 if (Severed
) Severed
->DestroyBodyPart(!game::IsInWilderness() ? GetStackUnder() : GetStack());
3369 SendNewDrawRequest();
3370 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart destroyed!"));
3372 if (IsPlayer()) ADD_MESSAGE("Your %s is severed off!", BodyPart
->GetBodyPartName().CStr());
3373 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is severed off!", GetPossessivePronoun().CStr(), BodyPart
->GetBodyPartName().CStr());
3374 item
*Severed
= SevereBodyPart(BodyPartIndex
);
3375 SendNewDrawRequest();
3377 if (CaptureBodyPart
) {
3378 Damager
->GetLSquareUnder()->AddItem(Severed
);
3379 } else if (!game::IsInWilderness()) {
3380 /** No multi-tile humanoid support! */
3381 GetStackUnder()->AddItem(Severed
);
3382 if (Direction
!= YOURSELF
) Severed
->Fly(0, Direction
, Damage
);
3384 GetStack()->AddItem(Severed
);
3386 Severed
->DropEquipment();
3387 } else if (IsPlayer() || CanBeSeenByPlayer()) {
3388 ADD_MESSAGE("It vanishes.");
3390 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart severed!"));
3392 if (CanPanicFromSeveredBodyPart() && RAND()%100 < GetPanicLevel() && !StateIsActivated(PANIC
) && !IsDead() && !StateIsActivated(FEARLESS
)) {
3393 BeginTemporaryState(PANIC
, 1000+RAND()%1001);
3395 SpecialBodyPartSeverReaction();
3398 if (!IsDead()) CheckPanic(500);
3404 /* Returns 0 if bodypart disappears */
3405 item
*character::SevereBodyPart (int BodyPartIndex
, truth ForceDisappearance
, stack
*EquipmentDropStack
) {
3406 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
3407 if (StateIsActivated(LEPROSY
)) BodyPart
->GetMainMaterial()->SetIsInfectedByLeprosy(true);
3408 if (ForceDisappearance
|| BodyPartsDisappearWhenSevered() || StateIsActivated(POLYMORPHED
) || game::AllBodyPartsVanish()) {
3409 BodyPart
->DropEquipment(EquipmentDropStack
);
3410 BodyPart
->RemoveFromSlot();
3411 CalculateAttributeBonuses();
3412 CalculateBattleInfo();
3413 BodyPart
->SendToHell();
3414 SignalPossibleTransparencyChange();
3415 RemoveTraps(BodyPartIndex
);
3418 BodyPart
->SetOwnerDescription("of " + GetName(INDEFINITE
));
3419 BodyPart
->SetIsUnique(LeftOversAreUnique());
3420 UpdateBodyPartPicture(BodyPartIndex
, true);
3421 BodyPart
->RemoveFromSlot();
3422 BodyPart
->RandomizePosition();
3423 CalculateAttributeBonuses();
3424 CalculateBattleInfo();
3426 SignalPossibleTransparencyChange();
3427 RemoveTraps(BodyPartIndex
);
3432 /* The second int is actually TargetFlags, which is not used here, but seems to be used in humanoid::ReceiveDamage.
3433 * Returns true if the character really receives damage */
3434 truth
character::ReceiveDamage (character
*Damager
, int Damage
, int Type
, int, int Direction
,
3435 truth
, truth PenetrateArmor
, truth Critical
, truth ShowMsg
)
3437 truth Affected
= ReceiveBodyPartDamage(Damager
, Damage
, Type
, 0, Direction
, PenetrateArmor
, Critical
, ShowMsg
);
3438 if (DamageTypeAffectsInventory(Type
)) {
3439 for (int c
= 0; c
< GetEquipments(); ++c
) {
3440 item
*Equipment
= GetEquipment(c
);
3441 if (Equipment
) Equipment
->ReceiveDamage(Damager
, Damage
, Type
);
3443 GetStack()->ReceiveDamage(Damager
, Damage
, Type
);
3449 festring
character::GetDescription (int Case
) const {
3450 if (IsPlayer()) return CONST_S("you");
3451 if (CanBeSeenByPlayer()) return GetName(Case
);
3452 return CONST_S("something");
3456 festring
character::GetPersonalPronoun (truth PlayersView
) const {
3457 if (IsPlayer() && PlayersView
) return CONST_S("you");
3458 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3459 if (GetSex() == MALE
) return CONST_S("he");
3460 return CONST_S("she");
3464 festring
character::GetPossessivePronoun (truth PlayersView
) const {
3465 if (IsPlayer() && PlayersView
) return CONST_S("your");
3466 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("its");
3467 if (GetSex() == MALE
) return CONST_S("his");
3468 return CONST_S("her");
3472 festring
character::GetObjectPronoun (truth PlayersView
) const {
3473 if (IsPlayer() && PlayersView
) return CONST_S("you");
3474 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3475 if (GetSex() == MALE
) return CONST_S("him");
3476 return CONST_S("her");
3480 void character::AddName (festring
&String
, int Case
) const {
3481 if (AssignedName
.IsEmpty()) {
3482 id::AddName(String
, Case
);
3483 } else if (!(Case
& PLURAL
)) {
3484 if (!ShowClassDescription()) {
3485 String
<< AssignedName
;
3487 String
<< AssignedName
<< ' ';
3488 id::AddName(String
, (Case
|ARTICLE_BIT
)&~INDEFINE_BIT
);
3491 id::AddName(String
, Case
);
3492 String
<< " named " << AssignedName
;
3497 int character::GetHungerState () const {
3498 if (!UsesNutrition()) return NOT_HUNGRY
;
3499 if (GetNP() > OVER_FED_LEVEL
) return OVER_FED
;
3500 if (GetNP() > BLOATED_LEVEL
) return BLOATED
;
3501 if (GetNP() > SATIATED_LEVEL
) return SATIATED
;
3502 if (GetNP() > NOT_HUNGER_LEVEL
) return NOT_HUNGRY
;
3503 if (GetNP() > HUNGER_LEVEL
) return HUNGRY
;
3504 if (GetNP() > VERY_HUNGER_LEVEL
) return VERY_HUNGRY
;
3509 truth
character::CanConsume (material
*Material
) const {
3510 return GetConsumeFlags() & Material
->GetConsumeType();
3514 void character::SetTemporaryStateCounter (sLong State
, int What
) {
3515 for (int c
= 0; c
< STATES
; ++c
) {
3516 if ((1 << c
) & State
) TemporaryStateCounter
[c
] = What
;
3521 void character::EditTemporaryStateCounter (sLong State
, int What
) {
3522 for (int c
= 0; c
< STATES
; ++c
) {
3523 if ((1 << c
) & State
) TemporaryStateCounter
[c
] += What
;
3528 int character::GetTemporaryStateCounter (sLong State
) const {
3529 for (int c
= 0; c
< STATES
; ++c
) {
3530 if ((1 << c
) & State
) return TemporaryStateCounter
[c
];
3532 ABORT("Illegal GetTemporaryStateCounter request!");
3537 truth
character::CheckKick () const {
3539 if (IsPlayer()) ADD_MESSAGE("This race can't kick.");
3546 int character::GetResistance (int Type
) const {
3547 switch (Type
&0xFFF) {
3548 case PHYSICAL_DAMAGE
:
3550 case MUSTARD_GAS_DAMAGE
:
3553 case ENERGY
: return GetEnergyResistance();
3554 case FIRE
: return GetFireResistance();
3555 case POISON
: return GetPoisonResistance();
3556 case ELECTRICITY
: return GetElectricityResistance();
3557 case ACID
: return GetAcidResistance();
3558 case SOUND
: return GetSoundResistance();
3560 ABORT("Resistance lack detected!");
3565 void character::Regenerate () {
3566 if (HP
== MaxHP
) return;
3567 sLong RegenerationBonus
= 0;
3568 truth NoHealableBodyParts
= true;
3569 for (int c
= 0; c
< BodyParts
; ++c
) {
3570 bodypart
*BodyPart
= GetBodyPart(c
);
3571 if (BodyPart
&& BodyPart
->CanRegenerate()) {
3572 RegenerationBonus
+= BodyPart
->GetMaxHP();
3573 if (NoHealableBodyParts
&& BodyPart
->GetHP() < BodyPart
->GetMaxHP()) NoHealableBodyParts
= false;
3576 if (!RegenerationBonus
|| NoHealableBodyParts
) return;
3577 RegenerationBonus
*= (50+GetAttribute(ENDURANCE
));
3579 if (Action
&& Action
->IsRest()) {
3580 if (SquaresUnder
== 1) RegenerationBonus
*= GetSquareUnder()->GetRestModifier() << 1;
3582 int Lowest
= GetSquareUnder(0)->GetRestModifier();
3583 for (int c
= 1; c
< GetSquaresUnder(); ++c
) {
3584 int Mod
= GetSquareUnder(c
)->GetRestModifier();
3585 if (Mod
< Lowest
) Lowest
= Mod
;
3587 RegenerationBonus
*= Lowest
<< 1;
3591 RegenerationCounter
+= RegenerationBonus
;
3593 while (RegenerationCounter
> 1250000) {
3594 bodypart
*BodyPart
= HealHitPoint();
3595 if (!BodyPart
) break;
3596 EditNP(-Max(7500/MaxHP
, 1));
3597 RegenerationCounter
-= 1250000;
3598 int HP
= BodyPart
->GetHP();
3599 EditExperience(ENDURANCE
, Min(1000*BodyPart
->GetMaxHP()/(HP
*HP
), 300), 1000);
3604 void character::PrintInfo () const {
3605 felist
Info(CONST_S("Information about ")+GetName(DEFINITE
));
3606 for (int c
= 0; c
< GetEquipments(); ++c
) {
3607 item
*Equipment
= GetEquipment(c
);
3608 if ((EquipmentEasilyRecognized(c
) || game::WizardModeIsActive()) && Equipment
) {
3609 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
3610 Info
.AddEntry(festring(GetEquipmentName(c
))+": "+Equipment
->GetName(INDEFINITE
), LIGHT_GRAY
, 0, ImageKey
, true);
3613 if (Info
.IsEmpty()) {
3614 ADD_MESSAGE("There's nothing special to tell about %s.", CHAR_NAME(DEFINITE
));
3616 game::SetStandardListAttributes(Info
);
3617 Info
.SetEntryDrawer(game::ItemEntryDrawer
);
3620 game::ClearItemDrawVector();
3624 truth
character::TryToRiseFromTheDead () {
3625 for (int c
= 0; c
< BodyParts
; ++c
) {
3626 bodypart
*BodyPart
= GetBodyPart(c
);
3628 BodyPart
->ResetSpoiling();
3629 if (BodyPart
->CanRegenerate() || BodyPart
->GetHP() < 1) BodyPart
->SetHP(1);
3637 truth
character::RaiseTheDead (character
*) {
3638 truth Useful
= false;
3639 for (int c
= 0; c
< BodyParts
; ++c
) {
3640 bodypart
*BodyPart
= GetBodyPart(c
);
3641 if (!BodyPart
&& CanCreateBodyPart(c
)) {
3642 CreateBodyPart(c
)->SetHP(1);
3643 if (IsPlayer()) ADD_MESSAGE("Suddenly you grow a new %s.", GetBodyPartName(c
).CStr());
3644 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), GetBodyPartName(c
).CStr());
3646 } else if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < 1) {
3651 if (IsPlayer()) ADD_MESSAGE("You shudder.");
3652 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s shudders.", CHAR_NAME(DEFINITE
));
3658 void character::SetSize (int Size
) {
3659 for (int c
= 0; c
< BodyParts
; ++c
) {
3660 bodypart
*BodyPart
= GetBodyPart(c
);
3661 if (BodyPart
) BodyPart
->SetSize(GetBodyPartSize(c
, Size
));
3666 sLong
character::GetBodyPartSize (int I
, int TotalSize
) const {
3667 if (I
== TORSO_INDEX
) return TotalSize
;
3668 ABORT("Weird bodypart size request for a character!");
3673 sLong
character::GetBodyPartVolume (int I
) const {
3674 if (I
== TORSO_INDEX
) return GetTotalVolume();
3675 ABORT("Weird bodypart volume request for a character!");
3680 void character::CreateBodyParts (int SpecialFlags
) {
3681 for (int c
= 0; c
< BodyParts
; ++c
) if (CanCreateBodyPart(c
)) CreateBodyPart(c
, SpecialFlags
);
3685 void character::RestoreBodyParts () {
3686 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) CreateBodyPart(c
);
3690 void character::UpdatePictures () {
3691 if (!PictureUpdatesAreForbidden()) for (int c
= 0; c
< BodyParts
; ++c
) UpdateBodyPartPicture(c
, false);
3695 bodypart
*character::MakeBodyPart (int I
) const {
3696 if (I
== TORSO_INDEX
) return normaltorso::Spawn(0, NO_MATERIALS
);
3697 ABORT("Weird bodypart to make for a character!");
3702 bodypart
*character::CreateBodyPart (int I
, int SpecialFlags
) {
3703 bodypart
*BodyPart
= MakeBodyPart(I
);
3704 material
*Material
= CreateBodyPartMaterial(I
, GetBodyPartVolume(I
));
3705 BodyPart
->InitMaterials(Material
, false);
3706 BodyPart
->SetSize(GetBodyPartSize(I
, GetTotalSize()));
3707 BodyPart
->SetBloodMaterial(GetBloodMaterial());
3708 BodyPart
->SetNormalMaterial(Material
->GetConfig());
3710 SetBodyPart(I
, BodyPart
);
3711 BodyPart
->InitSpecialAttributes();
3712 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdateBodyPartPicture(I
, false);
3713 if (!IsInitializing()) {
3714 CalculateBattleInfo();
3715 SendNewDrawRequest();
3716 SignalPossibleTransparencyChange();
3722 v2
character::GetBodyPartBitmapPos (int I
, truth
) const {
3723 if (I
== TORSO_INDEX
) return GetTorsoBitmapPos();
3724 ABORT("Weird bodypart BitmapPos request for a character!");
3729 void character::UpdateBodyPartPicture (int I
, truth Severed
) {
3730 bodypart
*BP
= GetBodyPart(I
);
3732 BP
->SetBitmapPos(GetBodyPartBitmapPos(I
, Severed
));
3733 BP
->GetMainMaterial()->SetSkinColor(GetBodyPartColorA(I
, Severed
));
3734 BP
->GetMainMaterial()->SetSkinColorIsSparkling(GetBodyPartSparkleFlags(I
) & SPARKLING_A
);
3735 BP
->SetMaterialColorB(GetBodyPartColorB(I
, Severed
));
3736 BP
->SetMaterialColorC(GetBodyPartColorC(I
, Severed
));
3737 BP
->SetMaterialColorD(GetBodyPartColorD(I
, Severed
));
3738 BP
->SetSparkleFlags(GetBodyPartSparkleFlags(I
));
3739 BP
->SetSpecialFlags(GetSpecialBodyPartFlags(I
));
3740 BP
->SetWobbleData(GetBodyPartWobbleData(I
));
3741 BP
->UpdatePictures();
3746 void character::LoadDataBaseStats () {
3747 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) {
3748 BaseExperience
[c
] = DataBase
->NaturalExperience
[c
];
3749 if (BaseExperience
[c
]) LimitRef(BaseExperience
[c
], MIN_EXP
, MAX_EXP
);
3751 SetMoney(GetDefaultMoney());
3752 SetInitialSweatMaterial(GetSweatMaterial());
3753 const fearray
<sLong
> &Skills
= GetKnownCWeaponSkills();
3755 const fearray
<sLong
> &Hits
= GetCWeaponSkillHits();
3756 if (Hits
.Size
== 1) {
3757 for (uInt c
= 0; c
< Skills
.Size
; ++c
) {
3758 if (Skills
[c
] < AllowedWeaponSkillCategories
) CWeaponSkill
[Skills
[c
]].AddHit(Hits
[0]*100);
3760 } else if (Hits
.Size
== Skills
.Size
) {
3761 for (uInt c
= 0; c
< Skills
.Size
; ++c
) {
3762 if (Skills
[c
] < AllowedWeaponSkillCategories
) CWeaponSkill
[Skills
[c
]].AddHit(Hits
[c
]*100);
3765 ABORT("Illegal weapon skill hit array size detected!");
3771 character
*characterprototype::SpawnAndLoad (inputfile
&SaveFile
) const {
3772 character
*Char
= Spawner(0, LOAD
);
3773 Char
->Load(SaveFile
);
3774 Char
->CalculateAll();
3779 void character::Initialize (int NewConfig
, int SpecialFlags
) {
3780 Flags
|= C_INITIALIZING
|C_IN_NO_MSG_MODE
;
3781 CalculateBodyParts();
3782 CalculateAllowedWeaponSkillCategories();
3783 CalculateSquaresUnder();
3784 BodyPartSlot
= new bodypartslot
[BodyParts
];
3785 OriginalBodyPartID
= new std::list
<feuLong
>[BodyParts
];
3786 CWeaponSkill
= new cweaponskill
[AllowedWeaponSkillCategories
];
3787 SquareUnder
= new square
*[SquaresUnder
];
3789 if (SquaresUnder
== 1) *SquareUnder
= 0; else memset(SquareUnder
, 0, SquaresUnder
*sizeof(square
*));
3791 for (int c
= 0; c
< BodyParts
; ++c
) BodyPartSlot
[c
].SetMaster(this);
3793 if (!(SpecialFlags
& LOAD
)) {
3794 ID
= game::CreateNewCharacterID(this);
3795 databasecreator
<character
>::InstallDataBase(this, NewConfig
);
3796 LoadDataBaseStats();
3797 TemporaryState
|= GetClassStates();
3798 if (TemporaryState
) {
3799 for (int c
= 0; c
< STATES
; ++c
) if (TemporaryState
& (1 << c
)) TemporaryStateCounter
[c
] = PERMANENT
;
3802 CreateBodyParts(SpecialFlags
| NO_PIC_UPDATE
);
3803 InitSpecialAttributes();
3804 CommandFlags
= GetDefaultCommandFlags();
3806 if (GetAttribute(INTELLIGENCE
, false) < 8) CommandFlags
&= ~DONT_CONSUME_ANYTHING_VALUABLE
; // gum
3807 if (!GetDefaultName().IsEmpty()) SetAssignedName(GetDefaultName());
3810 if (!(SpecialFlags
& LOAD
)) PostConstruct();
3812 if (!(SpecialFlags
& LOAD
)) {
3813 if (!(SpecialFlags
& NO_EQUIPMENT
)) CreateInitialEquipment((SpecialFlags
& NO_EQUIPMENT_PIC_UPDATE
) >> 1);
3814 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdatePictures();
3820 Flags
&= ~(C_INITIALIZING
|C_IN_NO_MSG_MODE
);
3824 truth
character::TeleportNear (character
*Caller
) {
3825 v2 Where
= GetLevel()->GetNearestFreeSquare(this, Caller
->GetPos());
3826 if (Where
== ERROR_V2
) return false;
3832 void character::ReceiveHeal (sLong Amount
) {
3834 for (c
= 0; c
< Amount
/ 10; ++c
) if (!HealHitPoint()) break;
3836 if (RAND()%10 < Amount
) HealHitPoint();
3837 if (Amount
>= 250 || RAND()%250 < Amount
) {
3838 bodypart
*NewBodyPart
= GenerateRandomBodyPart();
3839 if (!NewBodyPart
) return;
3840 NewBodyPart
->SetHP(1);
3841 if (IsPlayer()) ADD_MESSAGE("You grow a new %s.", NewBodyPart
->GetBodyPartName().CStr());
3842 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), NewBodyPart
->GetBodyPartName().CStr());
3847 void character::AddHealingLiquidConsumeEndMessage () const {
3848 if (IsPlayer()) ADD_MESSAGE("You feel better.");
3849 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks healthier.", CHAR_NAME(DEFINITE
));
3853 void character::ReceiveSchoolFood (sLong SizeOfEffect
) {
3854 SizeOfEffect
+= RAND()%SizeOfEffect
;
3855 if (SizeOfEffect
>= 250) VomitAtRandomDirection(SizeOfEffect
);
3856 if (!(RAND() % 3) && SizeOfEffect
>= 500 && EditAttribute(ENDURANCE
, SizeOfEffect
/500)) {
3857 if (IsPlayer()) ADD_MESSAGE("You gain a little bit of toughness for surviving this stuff.");
3858 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks tougher.", CHAR_NAME(DEFINITE
));
3860 BeginTemporaryState(POISONED
, (SizeOfEffect
>>1));
3864 void character::AddSchoolFoodConsumeEndMessage () const {
3865 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff tasted like vomit and old mousepads.");
3869 void character::AddSchoolFoodHitMessage () const {
3870 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff feels like vomit and old mousepads.");
3874 void character::ReceiveNutrition (sLong SizeOfEffect
) {
3875 EditNP(SizeOfEffect
);
3879 void character::ReceiveOmmelUrine (sLong Amount
) {
3880 EditExperience(ARM_STRENGTH
, 500, Amount
<<4);
3881 EditExperience(LEG_STRENGTH
, 500, Amount
<<4);
3882 if (IsPlayer()) game::DoEvilDeed(Amount
/25);
3886 void character::ReceiveOmmelCerumen (sLong Amount
) {
3887 EditExperience(INTELLIGENCE
, 500, Amount
<< 5);
3888 EditExperience(WISDOM
, 500, Amount
<< 5);
3889 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3893 void character::ReceiveOmmelSweat (sLong Amount
) {
3894 EditExperience(AGILITY
, 500, Amount
<< 4);
3895 EditExperience(DEXTERITY
, 500, Amount
<< 4);
3897 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3901 void character::ReceiveOmmelTears (sLong Amount
) {
3902 EditExperience(PERCEPTION
, 500, Amount
<< 4);
3903 EditExperience(CHARISMA
, 500, Amount
<< 4);
3904 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3908 void character::ReceiveOmmelSnot (sLong Amount
) {
3909 EditExperience(ENDURANCE
, 500, Amount
<< 5);
3911 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3915 void character::ReceiveOmmelBone (sLong Amount
) {
3916 EditExperience(ARM_STRENGTH
, 500, Amount
<< 6);
3917 EditExperience(LEG_STRENGTH
, 500, Amount
<< 6);
3918 EditExperience(DEXTERITY
, 500, Amount
<< 6);
3919 EditExperience(AGILITY
, 500, Amount
<< 6);
3920 EditExperience(ENDURANCE
, 500, Amount
<< 6);
3921 EditExperience(PERCEPTION
, 500, Amount
<< 6);
3922 EditExperience(INTELLIGENCE
, 500, Amount
<< 6);
3923 EditExperience(WISDOM
, 500, Amount
<< 6);
3924 EditExperience(CHARISMA
, 500, Amount
<< 6);
3927 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3931 void character::AddOmmelConsumeEndMessage () const {
3932 if (IsPlayer()) ADD_MESSAGE("You feel a primitive force coursing through your veins.");
3933 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks more powerful.", CHAR_NAME(DEFINITE
));
3937 void character::ReceivePepsi (sLong Amount
) {
3938 ReceiveDamage(0, Amount
/ 100, POISON
, TORSO
);
3939 EditExperience(PERCEPTION
, Amount
, 1 << 14);
3940 if (CheckDeath(CONST_S("was poisoned by pepsi"), 0)) return;
3941 if (IsPlayer()) game::DoEvilDeed(Amount
/ 10);
3945 void character::AddPepsiConsumeEndMessage () const {
3946 if (IsPlayer()) ADD_MESSAGE("Urgh. You feel your guruism fading away.");
3947 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very lame.", CHAR_NAME(DEFINITE
));
3951 void character::ReceiveDarkness (sLong Amount
) {
3952 EditExperience(INTELLIGENCE
, -Amount
/ 5, 1 << 13);
3953 EditExperience(WISDOM
, -Amount
/ 5, 1 << 13);
3954 EditExperience(CHARISMA
, -Amount
/ 5, 1 << 13);
3955 if (IsPlayer()) game::DoEvilDeed(int(Amount
/ 50));
3959 void character::AddFrogFleshConsumeEndMessage () const {
3960 if (IsPlayer()) ADD_MESSAGE("Arg. You feel the fate of a navastater placed upon you...");
3961 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks like a navastater.", CHAR_NAME(DEFINITE
));
3965 void character::ReceiveKoboldFlesh (sLong
) {
3966 /* As it is commonly known, the possibility of fainting per 500 cubic
3967 centimeters of kobold flesh is exactly 5%. */
3968 if (!(RAND() % 20)) {
3969 if (IsPlayer()) ADD_MESSAGE("You lose control of your legs and fall down.");
3970 LoseConsciousness(250 + RAND_N(250));
3975 void character::AddKoboldFleshConsumeEndMessage () const {
3976 if (IsPlayer()) ADD_MESSAGE("This stuff tasted really funny.");
3980 void character::AddKoboldFleshHitMessage () const {
3981 if (IsPlayer()) ADD_MESSAGE("You feel very funny.");
3985 void character::AddBoneConsumeEndMessage () const {
3986 if (IsPlayer()) ADD_MESSAGE("You feel like a hippie.");
3987 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s barks happily.", CHAR_NAME(DEFINITE
)); // this suspects that nobody except dogs can eat bones
3990 truth
character::RawEditAttribute (double &Experience
, int Amount
) const {
3991 /* Check if the attribute is disabled for creature */
3992 if (!Experience
) return false;
3993 if ((Amount
< 0 && Experience
< 2 * EXP_MULTIPLIER
) || (Amount
> 0 && Experience
> 999 * EXP_MULTIPLIER
)) return false;
3994 Experience
+= Amount
* EXP_MULTIPLIER
;
3995 LimitRef
<double>(Experience
, MIN_EXP
, MAX_EXP
);
4000 void character::DrawPanel (truth AnimationDraw
) const {
4001 if (AnimationDraw
) { DrawStats(true); return; }
4002 igraph::BlitBackGround(v2(19 + (game::GetScreenXSize() << 4), 0), v2(RES
.X
- 19 - (game::GetScreenXSize() << 4), RES
.Y
));
4003 igraph::BlitBackGround(v2(16, 45 + (game::GetScreenYSize() << 4)), v2(game::GetScreenXSize() << 4, 9));
4004 FONT
->Printf(DOUBLE_BUFFER
, v2(16, 45 + (game::GetScreenYSize() << 4)), WHITE
, "%s", GetPanelName().CStr());
4005 game::UpdateAttributeMemory();
4006 int PanelPosX
= RES
.X
- 96;
4007 int PanelPosY
= DrawStats(false);
4008 PrintAttribute("End", ENDURANCE
, PanelPosX
, PanelPosY
++);
4009 PrintAttribute("Per", PERCEPTION
, PanelPosX
, PanelPosY
++);
4010 PrintAttribute("Int", INTELLIGENCE
, PanelPosX
, PanelPosY
++);
4011 PrintAttribute("Wis", WISDOM
, PanelPosX
, PanelPosY
++);
4012 PrintAttribute("Wil", WILL_POWER
, PanelPosX
, PanelPosY
++);
4013 PrintAttribute("Cha", CHARISMA
, PanelPosX
, PanelPosY
++);
4014 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Siz %d", GetSize());
4015 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), IsInBadCondition() ? RED
: WHITE
, "HP %d/%d", GetHP(), GetMaxHP());
4017 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Gold: %d", GetMoney());
4020 if (game::IsInWilderness())
4021 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Worldmap");
4023 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", game::GetCurrentDungeon()->GetShortLevelDescription(game::GetCurrentLevelIndex()).CapitalizeCopy().CStr());
4026 game::GetTime(Time
);
4027 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Day %d", Time
.Day
);
4028 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Time %d:%s%d", Time
.Hour
, Time
.Min
< 10 ? "0" : "", Time
.Min
);
4029 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Turn %d", game::GetTurn());
4034 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", festring(GetAction()->GetDescription()).CapitalizeCopy().CStr());
4037 //printf("========= STATES =========\n");
4038 for (int c
= 0; c
< STATES
; ++c
) {
4039 //printf(" %d: %s (%s)\n", c, StateData[c].Description, (StateIsActivated(1<<c) ? "TAN" : "ona"));
4040 if (!(StateData
[c
].Flags
& SECRET
) && StateIsActivated(1 << c
) && (1 << c
!= HASTE
|| !StateIsActivated(SLOW
)) && (1 << c
!= SLOW
|| !StateIsActivated(HASTE
))) {
4041 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), (1 << c
) & EquipmentState
|| TemporaryStateCounter
[c
] >= PERMANENT
? BLUE
: WHITE
, "%s", StateData
[c
].Description
);
4045 /* Make this more elegant!!! */
4046 switch (GetHungerState()) {
4047 case STARVING
: FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Starving"); break;
4048 case VERY_HUNGRY
: FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Very hungry"); break;
4049 case HUNGRY
: FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Hungry"); break;
4050 case SATIATED
: FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Satiated"); break;
4051 case BLOATED
: FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Bloated"); break;
4052 case OVER_FED
: FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Overfed!"); break;
4055 switch (GetBurdenState()) {
4056 case OVER_LOADED
: FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Overload!"); break;
4057 case STRESSED
: FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Stressed"); break;
4058 case BURDENED
: FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Burdened"); break;
4061 switch (GetTirednessState()) {
4062 case FAINTING
: FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Fainting"); break;
4063 case EXHAUSTED
: FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Exhausted"); break;
4066 if (game::PlayerIsRunning()) {
4067 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", GetRunDescriptionLine(0));
4068 cchar
*SecondLine
= GetRunDescriptionLine(1);
4069 if (strlen(SecondLine
)) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", SecondLine
);
4074 void character::CalculateDodgeValue () {
4075 DodgeValue
= 0.05 * GetMoveEase() * GetAttribute(AGILITY
) / sqrt(GetSize());
4076 if (IsFlying()) DodgeValue
*= 2;
4077 if (DodgeValue
< 1) DodgeValue
= 1;
4081 truth
character::DamageTypeAffectsInventory (int Type
) {
4082 switch (Type
&0xFFF) {
4089 case PHYSICAL_DAMAGE
:
4092 case MUSTARD_GAS_DAMAGE
:
4096 ABORT("Unknown reaping effect destroyed dungeon!");
4101 int character::CheckForBlockWithArm (character
*Enemy
, item
*Weapon
, arm
*Arm
,
4102 double WeaponToHitValue
, int Damage
, int Success
, int Type
)
4104 int BlockStrength
= Arm
->GetBlockCapability();
4105 double BlockValue
= Arm
->GetBlockValue();
4106 if (BlockStrength
&& BlockValue
) {
4107 item
*Blocker
= Arm
->GetWielded();
4108 if (RAND() % int(100+WeaponToHitValue
/BlockValue
/(1<<BlocksSinceLastTurn
)*(100+Success
)) < 100) {
4109 int NewDamage
= BlockStrength
< Damage
? Damage
-BlockStrength
: 0;
4111 case UNARMED_ATTACK
: AddBlockMessage(Enemy
, Blocker
, Enemy
->UnarmedHitNoun(), NewDamage
); break;
4112 case WEAPON_ATTACK
: AddBlockMessage(Enemy
, Blocker
, "attack", NewDamage
); break;
4113 case KICK_ATTACK
: AddBlockMessage(Enemy
, Blocker
, Enemy
->KickNoun(), NewDamage
); break;
4114 case BITE_ATTACK
: AddBlockMessage(Enemy
, Blocker
, Enemy
->BiteNoun(), NewDamage
); break;
4116 sLong Weight
= Blocker
->GetWeight();
4117 sLong StrExp
= Limit(15 * Weight
/ 200, 75, 300);
4118 sLong DexExp
= Weight
? Limit(75000 / Weight
, 75, 300) : 300;
4119 Arm
->EditExperience(ARM_STRENGTH
, StrExp
, 1 << 8);
4120 Arm
->EditExperience(DEXTERITY
, DexExp
, 1 << 8);
4121 EditStamina(-10000 / GetAttribute(ARM_STRENGTH
), false);
4122 if (Arm
->TwoHandWieldIsActive()) {
4123 arm
*PairArm
= Arm
->GetPairArm();
4124 PairArm
->EditExperience(ARM_STRENGTH
, StrExp
, 1 << 8);
4125 PairArm
->EditExperience(DEXTERITY
, DexExp
, 1 << 8);
4127 Blocker
->WeaponSkillHit(Enemy
->CalculateWeaponSkillHits(this));
4128 Blocker
->ReceiveDamage(this, Damage
, PHYSICAL_DAMAGE
);
4129 Blocker
->BlockEffect(this, Enemy
, Weapon
, Type
);
4130 if (Weapon
) Weapon
->ReceiveDamage(Enemy
, Damage
- NewDamage
, PHYSICAL_DAMAGE
);
4131 if (BlocksSinceLastTurn
< 16) ++BlocksSinceLastTurn
;
4139 sLong
character::GetStateAPGain (sLong BaseAPGain
) const {
4140 if (!StateIsActivated(HASTE
) == !StateIsActivated(SLOW
)) return BaseAPGain
;
4141 if (StateIsActivated(HASTE
)) return (BaseAPGain
* 5) >> 2;
4142 return (BaseAPGain
<< 2) / 5;
4146 void character::SignalEquipmentAdd (int EquipmentIndex
) {
4147 item
*Equipment
= GetEquipment(EquipmentIndex
);
4148 if (Equipment
->IsInCorrectSlot(EquipmentIndex
)) {
4149 sLong AddedStates
= Equipment
->GetGearStates();
4151 for (int c
= 0; c
< STATES
; ++c
) {
4152 if (AddedStates
& (1 << c
)) {
4153 if (!StateIsActivated(1 << c
)) {
4154 if (!IsInNoMsgMode()) (this->*StateData
[c
].PrintBeginMessage
)();
4155 EquipmentState
|= 1 << c
;
4156 if (StateData
[c
].BeginHandler
) (this->*StateData
[c
].BeginHandler
)();
4158 EquipmentState
|= 1 << c
;
4164 if (!IsInitializing() && Equipment
->IsInCorrectSlot(EquipmentIndex
)) ApplyEquipmentAttributeBonuses(Equipment
);
4168 void character::SignalEquipmentRemoval (int, citem
*Item
) {
4169 CalculateEquipmentState();
4170 if (CalculateAttributeBonuses()) CheckDeath(festring("lost ")+GetPossessivePronoun(false)+" vital "+Item
->GetName(INDEFINITE
));
4174 void character::CalculateEquipmentState () {
4175 sLong Back
= EquipmentState
;
4177 for (int c
= 0; c
< GetEquipments(); ++c
) {
4178 item
*Equipment
= GetEquipment(c
);
4179 if (Equipment
&& Equipment
->IsInCorrectSlot(c
)) EquipmentState
|= Equipment
->GetGearStates();
4181 for (int c
= 0; c
< STATES
; ++c
) {
4182 if (Back
& (1 << c
) && !StateIsActivated(1 << c
)) {
4183 if (StateData
[c
].EndHandler
) {
4184 (this->*StateData
[c
].EndHandler
)();
4185 if (!IsEnabled()) return;
4187 if (!IsInNoMsgMode()) (this->*StateData
[c
].PrintEndMessage
)();
4193 /* Counter = duration in ticks */
4194 void character::BeginTemporaryState (sLong State
, int Counter
) {
4195 if (!Counter
) return;
4197 if (State
== POLYMORPHED
) ABORT("No Polymorphing with BeginTemporaryState!");
4198 for (Index
= 0; Index
< STATES
; ++Index
) if (1 << Index
== State
) break;
4199 if (Index
== STATES
) ABORT("BeginTemporaryState works only when State == 2^n!");
4200 if (TemporaryStateIsActivated(State
)) {
4201 int OldCounter
= GetTemporaryStateCounter(State
);
4202 if (OldCounter
!= PERMANENT
) EditTemporaryStateCounter(State
, Max(Counter
, 50-OldCounter
));
4203 } else if (StateData
[Index
].IsAllowed
== 0 || (this->*StateData
[Index
].IsAllowed
)()) {
4204 SetTemporaryStateCounter(State
, Max(Counter
, 50));
4205 if (!EquipmentStateIsActivated(State
)) {
4206 if (!IsInNoMsgMode()) (this->*StateData
[Index
].PrintBeginMessage
)();
4207 ActivateTemporaryState(State
);
4208 if (StateData
[Index
].BeginHandler
) (this->*StateData
[Index
].BeginHandler
)();
4210 ActivateTemporaryState(State
);
4216 void character::HandleStates () {
4217 if (!TemporaryState
&& !EquipmentState
) return;
4218 for (int c
= 0; c
< STATES
; ++c
) {
4219 if (TemporaryState
& (1 << c
) && TemporaryStateCounter
[c
] != PERMANENT
) {
4220 if (!--TemporaryStateCounter
[c
]) {
4221 TemporaryState
&= ~(1 << c
);
4222 if (!(EquipmentState
& (1 << c
))) {
4223 if (StateData
[c
].EndHandler
) {
4224 (this->*StateData
[c
].EndHandler
)();
4225 if (!IsEnabled()) return;
4227 if (!TemporaryStateCounter
[c
]) (this->*StateData
[c
].PrintEndMessage
)();
4231 if (StateIsActivated(1 << c
)) {
4232 if (StateData
[c
].Handler
) (this->*StateData
[c
].Handler
)();
4234 if (!IsEnabled()) return;
4239 void character::PrintBeginPolymorphControlMessage () const {
4240 if (IsPlayer()) ADD_MESSAGE("You feel your mind has total control over your body.");
4244 void character::PrintEndPolymorphControlMessage () const {
4245 if (IsPlayer()) ADD_MESSAGE("You are somehow uncertain of your willpower.");
4249 void character::PrintBeginLifeSaveMessage () const {
4250 if (IsPlayer()) ADD_MESSAGE("You hear Hell's gates being locked just now.");
4254 void character::PrintEndLifeSaveMessage () const {
4255 if (IsPlayer()) ADD_MESSAGE("You feel the Afterlife is welcoming you once again.");
4259 void character::PrintBeginLycanthropyMessage () const {
4260 if (IsPlayer()) ADD_MESSAGE("You suddenly notice you've always loved full moons.");
4264 void character::PrintEndLycanthropyMessage () const {
4265 if (IsPlayer()) ADD_MESSAGE("You feel the wolf inside you has had enough of your bad habits.");
4269 void character::PrintBeginVampirismMessage () const {
4270 if (IsPlayer()) ADD_MESSAGE("You suddenly decide you have always hated garlic.");
4274 void character::PrintEndVampirismMessage () const {
4275 if (IsPlayer()) ADD_MESSAGE("You recall your delight of the morning sunshine back in New Attnam. You are a vampire no longer.");
4279 void character::PrintBeginInvisibilityMessage () const {
4280 if ((PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm()) || (PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5)) {
4281 if (IsPlayer()) ADD_MESSAGE("You seem somehow transparent.");
4282 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems somehow transparent.", CHAR_NAME(DEFINITE
));
4284 if (IsPlayer()) ADD_MESSAGE("You fade away.");
4285 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears!", CHAR_NAME(DEFINITE
));
4290 void character::PrintEndInvisibilityMessage () const {
4291 if ((PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm()) || (PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5)) {
4292 if (IsPlayer()) ADD_MESSAGE("Your notice your transparency has ended.");
4293 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The appearance of %s seems far more solid now.", CHAR_NAME(INDEFINITE
));
4295 if (IsPlayer()) ADD_MESSAGE("You reappear.");
4296 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s appears from nowhere!", CHAR_NAME(INDEFINITE
));
4301 void character::PrintBeginInfraVisionMessage () const {
4303 if (StateIsActivated(INVISIBLE
) && IsWarm() && !(StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5))
4304 ADD_MESSAGE("You reappear.");
4306 ADD_MESSAGE("You feel your perception being magically altered.");
4311 void character::PrintEndInfraVisionMessage () const {
4313 if (StateIsActivated(INVISIBLE
) && IsWarm() && !(StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5))
4314 ADD_MESSAGE("You disappear.");
4316 ADD_MESSAGE("You feel your perception returning to normal.");
4321 void character::PrintBeginESPMessage () const {
4322 if (IsPlayer()) ADD_MESSAGE("You suddenly feel like being only a tiny part of a great network of intelligent minds.");
4326 void character::PrintEndESPMessage () const {
4327 if (IsPlayer()) ADD_MESSAGE("You are filled with desire to be just yourself from now on.");
4331 void character::PrintBeginHasteMessage () const {
4332 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4333 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE
));
4337 void character::PrintEndHasteMessage () const {
4338 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4339 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE
));
4343 void character::PrintBeginSlowMessage () const {
4344 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4345 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE
));
4349 void character::PrintEndSlowMessage () const {
4350 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4351 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE
));
4355 void character::EndPolymorph () {
4356 ForceEndPolymorph();
4360 character
*character::ForceEndPolymorph () {
4362 ADD_MESSAGE("You return to your true form.");
4363 } else if (game::IsInWilderness()) {
4364 ActivateTemporaryState(POLYMORPHED
);
4365 SetTemporaryStateCounter(POLYMORPHED
, 10);
4366 return this; // fast gum solution, state ends when the player enters a dungeon
4368 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s returns to %s true form.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
4370 if (GetAction()) GetAction()->Terminate(false);
4374 character
*Char
= GetPolymorphBackup();
4375 Flags
|= C_IN_NO_MSG_MODE
;
4376 Char
->Flags
|= C_IN_NO_MSG_MODE
;
4377 Char
->PutToOrNear(Pos
);
4378 Char
->ChangeTeam(GetTeam());
4379 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(Char
);
4380 SetPolymorphBackup(0);
4382 Char
->Flags
&= ~C_POLYMORPHED
;
4383 GetStack()->MoveItemsTo(Char
->GetStack());
4384 DonateEquipmentTo(Char
);
4385 Char
->SetMoney(GetMoney());
4386 Flags
&= ~C_IN_NO_MSG_MODE
;
4387 Char
->Flags
&= ~C_IN_NO_MSG_MODE
;
4388 Char
->CalculateAll();
4389 Char
->SetAssignedName(GetAssignedName());
4392 game::SetPlayer(Char
);
4393 game::SendLOSUpdateRequest();
4396 Char
->TestWalkability();
4401 void character::LycanthropyHandler () {
4402 if (!(RAND() % 2000)) {
4403 if (StateIsActivated(POLYMORPH_CONTROL
) && !game::TruthQuestion(CONST_S("Do you wish to change into a werewolf?"))) return;
4404 Polymorph(werewolfwolf::Spawn(), 1000 + RAND() % 2000);
4409 void character::SaveLife () {
4410 if (TemporaryStateIsActivated(LIFE_SAVED
)) {
4412 ADD_MESSAGE("But wait! You glow briefly red and seem to be in a better shape!");
4413 else if (CanBeSeenByPlayer())
4414 ADD_MESSAGE("But wait, suddenly %s glows briefly red and seems to be in a better shape!", GetPersonalPronoun().CStr());
4415 DeActivateTemporaryState(LIFE_SAVED
);
4417 item
*LifeSaver
= 0;
4418 for (int c
= 0; c
< GetEquipments(); ++c
) {
4419 item
*Equipment
= GetEquipment(c
);
4420 if (Equipment
&& Equipment
->IsInCorrectSlot(c
) && Equipment
->GetGearStates() & LIFE_SAVED
) LifeSaver
= Equipment
;
4422 if (!LifeSaver
) ABORT("The Universe can only kill you once!");
4424 ADD_MESSAGE("But wait! Your %s glows briefly red and disappears and you seem to be in a better shape!", LifeSaver
->CHAR_NAME(UNARTICLED
));
4425 else if (CanBeSeenByPlayer())
4426 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());
4427 LifeSaver
->RemoveFromSlot();
4428 LifeSaver
->SendToHell();
4431 if (IsPlayer()) game::AskForEscPress(CONST_S("Life saved!"));
4439 if (GetNP() < SATIATED_LEVEL
) SetNP(SATIATED_LEVEL
);
4441 SendNewDrawRequest();
4443 if (GetAction()) GetAction()->Terminate(false);
4447 character
*character::PolymorphRandomly (int MinDanger
, int MaxDanger
, int Time
) {
4448 character
*NewForm
= 0;
4449 if (StateIsActivated(POLYMORPH_CONTROL
)) {
4451 if (!GetNewFormForPolymorphWithControl(NewForm
)) return NewForm
;
4453 NewForm
= protosystem::CreateMonster(MinDanger
*10, MaxDanger
*10, NO_EQUIPMENT
);
4456 NewForm
= protosystem::CreateMonster(MinDanger
, MaxDanger
, NO_EQUIPMENT
);
4458 Polymorph(NewForm
, Time
);
4463 /* In reality, the reading takes Time / (Intelligence * 10) turns */
4464 void character::StartReading (item
*Item
, sLong Time
) {
4465 study
*Read
= study::Spawn(this);
4466 Read
->SetLiteratureID(Item
->GetID());
4467 if (game::WizardModeIsActive()) Time
= 1;
4468 Read
->SetCounter(Time
);
4470 if (IsPlayer()) ADD_MESSAGE("You start reading %s.", Item
->CHAR_NAME(DEFINITE
));
4471 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s starts reading %s.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(DEFINITE
));
4475 /* Call when one makes something with his/her/its hands.
4476 * Difficulty of 5 takes about one turn, so it's the most common to use. */
4477 void character::DexterityAction (int Difficulty
) {
4478 EditAP(-20000 * Difficulty
/ APBonus(GetAttribute(DEXTERITY
)));
4479 EditExperience(DEXTERITY
, Difficulty
* 15, 1 << 7);
4483 /* If Theoretically != false, range is not a factor. */
4484 truth
character::CanBeSeenByPlayer (truth Theoretically
, truth IgnoreESP
) const {
4485 if (IsEnabled() && !game::IsGenerating() && (Theoretically
|| GetSquareUnder())) {
4486 truth MayBeESPSeen
= PLAYER
->IsEnabled() && !IgnoreESP
&& PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5;
4487 truth MayBeInfraSeen
= PLAYER
->IsEnabled() && PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm();
4488 truth Visible
= !StateIsActivated(INVISIBLE
) || MayBeESPSeen
|| MayBeInfraSeen
;
4489 if (game::IsInWilderness()) return Visible
;
4490 if (MayBeESPSeen
&& (Theoretically
|| GetDistanceSquareFrom(PLAYER
) <= PLAYER
->GetESPRangeSquare())) return true;
4491 if (!Visible
) return false;
4492 return (Theoretically
|| SquareUnderCanBeSeenByPlayer(MayBeInfraSeen
));
4498 truth
character::CanBeSeenBy (ccharacter
*Who
, truth Theoretically
, truth IgnoreESP
) const {
4499 if (Who
->IsPlayer()) return CanBeSeenByPlayer(Theoretically
, IgnoreESP
);
4500 if (IsEnabled() && !game::IsGenerating() && (Theoretically
|| GetSquareUnder())) {
4501 truth MayBeESPSeen
= Who
->IsEnabled() && !IgnoreESP
&& Who
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5;
4502 truth MayBeInfraSeen
= Who
->IsEnabled() && Who
->StateIsActivated(INFRA_VISION
) && IsWarm();
4503 truth Visible
= !StateIsActivated(INVISIBLE
) || MayBeESPSeen
|| MayBeInfraSeen
;
4504 if (game::IsInWilderness()) return Visible
;
4505 if (MayBeESPSeen
&& (Theoretically
|| GetDistanceSquareFrom(Who
) <= Who
->GetESPRangeSquare())) return true;
4506 if (!Visible
) return false;
4507 return (Theoretically
|| SquareUnderCanBeSeenBy(Who
, MayBeInfraSeen
));
4513 truth
character::SquareUnderCanBeSeenByPlayer (truth IgnoreDarkness
) const {
4514 if (!GetSquareUnder()) return false;
4515 int S1
= SquaresUnder
, S2
= PLAYER
->SquaresUnder
;
4516 if (S1
== 1 && S2
== 1) {
4517 if (GetSquareUnder()->CanBeSeenByPlayer(IgnoreDarkness
)) return true;
4518 if (IgnoreDarkness
) {
4519 int LOSRangeSquare
= PLAYER
->GetLOSRangeSquare();
4520 if ((GetPos() - PLAYER
->GetPos()).GetLengthSquare() <= LOSRangeSquare
) {
4521 eyecontroller::Map
= GetLevel()->GetMap();
4522 return mapmath
<eyecontroller
>::DoLine(PLAYER
->GetPos().X
, PLAYER
->GetPos().Y
, GetPos().X
, GetPos().Y
, SKIP_FIRST
);
4527 for (int c1
= 0; c1
< S1
; ++c1
) {
4528 lsquare
*Square
= GetLSquareUnder(c1
);
4529 if (Square
->CanBeSeenByPlayer(IgnoreDarkness
)) return true;
4530 else if (IgnoreDarkness
) {
4531 v2 Pos
= Square
->GetPos();
4532 int LOSRangeSquare
= PLAYER
->GetLOSRangeSquare();
4533 for (int c2
= 0; c2
< S2
; ++c2
) {
4534 v2 PlayerPos
= PLAYER
->GetPos(c2
);
4535 if ((Pos
-PlayerPos
).GetLengthSquare() <= LOSRangeSquare
) {
4536 eyecontroller::Map
= GetLevel()->GetMap();
4537 if (mapmath
<eyecontroller
>::DoLine(PlayerPos
.X
, PlayerPos
.Y
, Pos
.X
, Pos
.Y
, SKIP_FIRST
)) return true;
4547 truth
character::SquareUnderCanBeSeenBy (ccharacter
*Who
, truth IgnoreDarkness
) const {
4548 int S1
= SquaresUnder
, S2
= Who
->SquaresUnder
;
4549 int LOSRangeSquare
= Who
->GetLOSRangeSquare();
4550 if (S1
== 1 && S2
== 1) return GetSquareUnder()->CanBeSeenFrom(Who
->GetPos(), LOSRangeSquare
, IgnoreDarkness
);
4551 for (int c1
= 0; c1
< S1
; ++c1
) {
4552 lsquare
*Square
= GetLSquareUnder(c1
);
4553 for (int c2
= 0; c2
< S2
; ++c2
) if (Square
->CanBeSeenFrom(Who
->GetPos(c2
), LOSRangeSquare
, IgnoreDarkness
)) return true;
4559 int character::GetDistanceSquareFrom (ccharacter
*Who
) const {
4560 int S1
= SquaresUnder
, S2
= Who
->SquaresUnder
;
4561 if (S1
== 1 && S2
== 1) return (GetPos() - Who
->GetPos()).GetLengthSquare();
4562 v2
MinDist(0x7FFF, 0x7FFF);
4563 int MinLength
= 0xFFFF;
4564 for (int c1
= 0; c1
< S1
; ++c1
) {
4565 for (int c2
= 0; c2
< S2
; ++c2
) {
4566 v2 Dist
= GetPos(c1
)-Who
->GetPos(c2
);
4567 if (Dist
.X
< 0) Dist
.X
= -Dist
.X
;
4568 if (Dist
.Y
< 0) Dist
.Y
= -Dist
.Y
;
4569 if (Dist
.X
<= MinDist
.X
&& Dist
.Y
<= MinDist
.Y
) {
4571 MinLength
= Dist
.GetLengthSquare();
4572 } else if (Dist
.X
< MinDist
.X
|| Dist
.Y
< MinDist
.Y
) {
4573 int Length
= Dist
.GetLengthSquare();
4574 if (Length
< MinLength
) {
4585 void character::AttachBodyPart (bodypart
*BodyPart
) {
4586 SetBodyPart(BodyPart
->GetBodyPartIndex(), BodyPart
);
4587 if (!AllowSpoil()) BodyPart
->ResetSpoiling();
4588 BodyPart
->ResetPosition();
4589 BodyPart
->UpdatePictures();
4590 CalculateAttributeBonuses();
4591 CalculateBattleInfo();
4592 SendNewDrawRequest();
4593 SignalPossibleTransparencyChange();
4597 /* Returns true if the character has all bodyparts, false if not. */
4598 truth
character::HasAllBodyParts () const {
4599 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) return false;
4604 bodypart
*character::GenerateRandomBodyPart () {
4605 int NeededBodyPart
[MAX_BODYPARTS
];
4607 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) NeededBodyPart
[Index
++] = c
;
4608 return Index
? CreateBodyPart(NeededBodyPart
[RAND() % Index
]) : 0;
4612 /* Searches the character's Stack and if it find some bodyparts there that are the character's
4613 * old bodyparts returns a stackiterator to one of them (choosen in random).
4614 * If no fitting bodyparts are found the function returns 0 */
4615 bodypart
*character::FindRandomOwnBodyPart (truth AllowNonLiving
) const {
4616 itemvector LostAndFound
;
4617 for (int c
= 0; c
< BodyParts
; ++c
) {
4618 if (!GetBodyPart(c
)) {
4619 for (std::list
<feuLong
>::iterator i
= OriginalBodyPartID
[c
].begin(); i
!= OriginalBodyPartID
[c
].end(); ++i
) {
4620 bodypart
*Found
= static_cast<bodypart
*>(SearchForItem(*i
));
4621 if (Found
&& (AllowNonLiving
|| Found
->CanRegenerate())) LostAndFound
.push_back(Found
);
4625 if (LostAndFound
.empty()) return 0;
4626 return static_cast<bodypart
*>(LostAndFound
[RAND() % LostAndFound
.size()]);
4630 void character::PrintBeginPoisonedMessage () const {
4631 if (IsPlayer()) ADD_MESSAGE("You seem to be very ill.");
4632 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very ill.", CHAR_NAME(DEFINITE
));
4636 void character::PrintEndPoisonedMessage () const {
4637 if (IsPlayer()) ADD_MESSAGE("You feel better again.");
4638 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks better.", CHAR_NAME(DEFINITE
));
4642 void character::PoisonedHandler () {
4643 if (!(RAND() % 100)) VomitAtRandomDirection(500 + RAND_N(250));
4645 for (int Used
= 0; Used
< GetTemporaryStateCounter(POISONED
); Used
+= 100) if (!(RAND() % 100)) ++Damage
;
4647 ReceiveDamage(0, Damage
, POISON
, ALL
, 8, false, false, false, false);
4648 CheckDeath(CONST_S("died of acute poisoning"), 0);
4653 truth
character::IsWarm () const {
4654 return combinebodypartpredicates()(this, &bodypart::IsWarm
, 1);
4658 truth
character::IsWarmBlooded() const
4660 return combinebodypartpredicates()(this, &bodypart::IsWarmBlooded
, 1);
4664 void character::BeginInvisibility () {
4666 SendNewDrawRequest();
4667 SignalPossibleTransparencyChange();
4671 void character::BeginInfraVision () {
4672 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4676 void character::BeginESP () {
4677 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4681 void character::EndInvisibility () {
4683 SendNewDrawRequest();
4684 SignalPossibleTransparencyChange();
4688 void character::EndInfraVision () {
4689 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4693 void character::EndESP () {
4694 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4698 void character::Draw (blitdata
&BlitData
) const {
4699 col24 L
= BlitData
.Luminance
;
4700 if (PLAYER
->IsEnabled() &&
4701 ((PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5 &&
4702 (PLAYER
->GetPos() - GetPos()).GetLengthSquare() <= PLAYER
->GetESPRangeSquare()) ||
4703 (PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm())))
4704 BlitData
.Luminance
= ivanconfig::GetContrastLuminance();
4706 DrawBodyParts(BlitData
);
4707 BlitData
.Luminance
= ivanconfig::GetContrastLuminance();
4708 BlitData
.Src
.Y
= 16;
4709 cint SquareIndex
= BlitData
.CustomData
& SQUARE_INDEX_MASK
;
4711 if (GetTeam() == PLAYER
->GetTeam() && !IsPlayer() && SquareIndex
== GetTameSymbolSquareIndex()) {
4712 BlitData
.Src
.X
= 32;
4713 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4716 if (IsFlying() && SquareIndex
== GetFlySymbolSquareIndex()) {
4717 BlitData
.Src
.X
= 128;
4718 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4721 if (IsSwimming() && SquareIndex
== GetSwimmingSymbolSquareIndex()) {
4722 BlitData
.Src
.X
= 240;
4723 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4726 if (GetAction() && GetAction()->IsUnconsciousness() && SquareIndex
== GetUnconsciousSymbolSquareIndex()) {
4727 BlitData
.Src
.X
= 224;
4728 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4731 BlitData
.Src
.X
= BlitData
.Src
.Y
= 0;
4732 BlitData
.Luminance
= L
;
4736 void character::DrawBodyParts (blitdata
&BlitData
) const {
4737 GetTorso()->Draw(BlitData
);
4741 void character::PrintBeginTeleportMessage () const {
4742 if (IsPlayer()) ADD_MESSAGE("You feel jumpy.");
4746 void character::PrintEndTeleportMessage () const {
4747 if (IsPlayer()) ADD_MESSAGE("You suddenly realize you've always preferred walking to jumping.");
4751 void character::PrintBeginDetectMessage () const {
4752 if (IsPlayer()) ADD_MESSAGE("You feel curious about your surroundings.");
4756 void character::PrintEndDetectMessage () const {
4757 if (IsPlayer()) ADD_MESSAGE("You decide to rely on your intuition from now on.");
4761 void character::TeleportHandler () {
4762 if (!(RAND() % 1500) && !game::IsInWilderness()) {
4763 if (IsPlayer()) ADD_MESSAGE("You feel an urgent spatial relocation is now appropriate.");
4764 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears.", CHAR_NAME(DEFINITE
));
4770 void character::DetectHandler () {
4772 //the AI can't be asked position questions! So only the player can hav this state really :/ a bit daft of me
4773 if (!(RAND()%3000) && !game::IsInWilderness()) {
4774 ADD_MESSAGE("Your mind wanders in search of something.");
4775 DoDetecting(); //in fact, who knows what would happen if a dark frog had the detecting state?
4781 void character::PrintBeginPolymorphMessage () const {
4782 if (IsPlayer()) ADD_MESSAGE("An unconfortable uncertainty of who you really are overwhelms you.");
4786 void character::PrintEndPolymorphMessage () const {
4787 if (IsPlayer()) ADD_MESSAGE("You feel you are you and no one else.");
4791 void character::PolymorphHandler () {
4792 if (!(RAND() % 1500)) PolymorphRandomly(1, 999999, 200 + RAND() % 800);
4795 void character::PrintBeginTeleportControlMessage () const {
4796 if (IsPlayer()) ADD_MESSAGE("You feel very controlled.");
4800 void character::PrintEndTeleportControlMessage () const {
4801 if (IsPlayer()) ADD_MESSAGE("You feel your control slipping.");
4805 void character::DisplayStethoscopeInfo (character
*) const {
4806 felist
Info(CONST_S("Information about ") + GetDescription(DEFINITE
));
4807 AddSpecialStethoscopeInfo(Info
);
4808 Info
.AddEntry(CONST_S("Endurance: ") + GetAttribute(ENDURANCE
), LIGHT_GRAY
);
4809 Info
.AddEntry(CONST_S("Perception: ") + GetAttribute(PERCEPTION
), LIGHT_GRAY
);
4810 Info
.AddEntry(CONST_S("Intelligence: ") + GetAttribute(INTELLIGENCE
), LIGHT_GRAY
);
4811 Info
.AddEntry(CONST_S("Wisdom: ") + GetAttribute(WISDOM
), LIGHT_GRAY
);
4812 //Info.AddEntry(CONST_S("Willpower: ") + GetAttribute(WILL_POWER), LIGHT_GRAY);
4813 Info
.AddEntry(CONST_S("Charisma: ") + GetAttribute(CHARISMA
), LIGHT_GRAY
);
4814 Info
.AddEntry(CONST_S("HP: ") + GetHP() + "/" + GetMaxHP(), IsInBadCondition() ? RED
: LIGHT_GRAY
);
4815 if (GetAction()) Info
.AddEntry(festring(GetAction()->GetDescription()).CapitalizeCopy(), LIGHT_GRAY
);
4816 for (int c
= 0; c
< STATES
; ++c
) {
4817 if (StateIsActivated(1 << c
) && (1 << c
!= HASTE
|| !StateIsActivated(SLOW
)) && (1 << c
!= SLOW
|| !StateIsActivated(HASTE
)))
4818 Info
.AddEntry(StateData
[c
].Description
, LIGHT_GRAY
);
4820 switch (GetTirednessState()) {
4821 case FAINTING
: Info
.AddEntry("Fainting", RED
); break;
4822 case EXHAUSTED
: Info
.AddEntry("Exhausted", LIGHT_GRAY
); break;
4824 game::SetStandardListAttributes(Info
);
4829 truth
character::CanUseStethoscope (truth PrintReason
) const {
4830 if (PrintReason
) ADD_MESSAGE("This type of monster can't use a stethoscope.");
4835 /* Effect used by at least Sophos.
4836 * NOTICE: Doesn't check for death! */
4837 void character::TeleportSomePartsAway (int NumberToTeleport
) {
4838 for (int c
= 0; c
< NumberToTeleport
; ++c
) {
4839 int RandomBodyPart
= GetRandomNonVitalBodyPart();
4840 if (RandomBodyPart
== NONE_INDEX
) {
4841 for (; c
< NumberToTeleport
; ++c
) {
4842 GetTorso()->SetHP((GetTorso()->GetHP() << 2) / 5);
4843 sLong TorsosVolume
= GetTorso()->GetMainMaterial()->GetVolume() / 10;
4844 if (!TorsosVolume
) break;
4845 sLong Amount
= (RAND() % TorsosVolume
)+1;
4846 item
*Lump
= GetTorso()->GetMainMaterial()->CreateNaturalForm(Amount
);
4847 GetTorso()->GetMainMaterial()->EditVolume(-Amount
);
4848 Lump
->MoveTo(GetNearLSquare(GetLevel()->GetRandomSquare())->GetStack());
4849 if (IsPlayer()) ADD_MESSAGE("Parts of you teleport away.");
4850 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Parts of %s teleport away.", CHAR_NAME(DEFINITE
));
4853 item
*SeveredBodyPart
= SevereBodyPart(RandomBodyPart
);
4854 if (SeveredBodyPart
) {
4855 GetNearLSquare(GetLevel()->GetRandomSquare())->AddItem(SeveredBodyPart
);
4856 SeveredBodyPart
->DropEquipment();
4857 if (IsPlayer()) ADD_MESSAGE("Your %s teleports away.", GetBodyPartName(RandomBodyPart
).CStr());
4858 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s teleports away.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4860 if (IsPlayer()) ADD_MESSAGE("Your %s disappears.", GetBodyPartName(RandomBodyPart
).CStr());
4861 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s disappears.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4868 /* Returns an index of a random bodypart that is not vital. If no non-vital bodypart is found returns NONE_INDEX */
4869 int character::GetRandomNonVitalBodyPart () const {
4870 int OKBodyPart
[MAX_BODYPARTS
];
4871 int OKBodyParts
= 0;
4872 for (int c
= 0; c
< BodyParts
; ++c
) if (GetBodyPart(c
) && !BodyPartIsVital(c
)) OKBodyPart
[OKBodyParts
++] = c
;
4873 return OKBodyParts
? OKBodyPart
[RAND() % OKBodyParts
] : NONE_INDEX
;
4877 void character::CalculateVolumeAndWeight () {
4878 Volume
= Stack
->GetVolume();
4879 Weight
= Stack
->GetWeight();
4881 CarriedWeight
= Weight
;
4882 for (int c
= 0; c
< BodyParts
; ++c
) {
4883 bodypart
*BodyPart
= GetBodyPart(c
);
4885 BodyVolume
+= BodyPart
->GetBodyPartVolume();
4886 Volume
+= BodyPart
->GetVolume();
4887 CarriedWeight
+= BodyPart
->GetCarriedWeight();
4888 Weight
+= BodyPart
->GetWeight();
4894 void character::SignalVolumeAndWeightChange () {
4895 if (!IsInitializing()) {
4896 CalculateVolumeAndWeight();
4897 if (IsEnabled()) CalculateBurdenState();
4898 if (MotherEntity
) MotherEntity
->SignalVolumeAndWeightChange();
4903 void character::SignalEmitationIncrease (col24 EmitationUpdate
) {
4904 if (game::CompareLights(EmitationUpdate
, Emitation
) > 0) {
4905 game::CombineLights(Emitation
, EmitationUpdate
);
4906 if (MotherEntity
) MotherEntity
->SignalEmitationIncrease(EmitationUpdate
);
4907 else if (SquareUnder
[0] && !game::IsInWilderness()) {
4908 for(int c
= 0; c
< GetSquaresUnder(); ++c
) GetLSquareUnder()->SignalEmitationIncrease(EmitationUpdate
);
4914 void character::SignalEmitationDecrease (col24 EmitationUpdate
) {
4915 if (game::CompareLights(EmitationUpdate
, Emitation
) >= 0 && Emitation
) {
4916 col24 Backup
= Emitation
;
4917 CalculateEmitation();
4918 if (Backup
!= Emitation
) {
4919 if (MotherEntity
) MotherEntity
->SignalEmitationDecrease(EmitationUpdate
);
4920 else if (SquareUnder
[0] && !game::IsInWilderness()) {
4921 for (int c
= 0; c
< GetSquaresUnder(); ++c
) GetLSquareUnder(c
)->SignalEmitationDecrease(EmitationUpdate
);
4928 void character::CalculateEmitation () {
4929 Emitation
= GetBaseEmitation();
4930 for (int c
= 0; c
< BodyParts
; ++c
) {
4931 bodypart
*BodyPart
= GetBodyPart(c
);
4932 if (BodyPart
) game::CombineLights(Emitation
, BodyPart
->GetEmitation());
4934 game::CombineLights(Emitation
, Stack
->GetEmitation());
4938 void character::CalculateAll () {
4939 Flags
|= C_INITIALIZING
;
4940 CalculateAttributeBonuses();
4941 CalculateVolumeAndWeight();
4942 CalculateEmitation();
4943 CalculateBodyPartMaxHPs(0);
4944 CalculateMaxStamina();
4945 CalculateBurdenState();
4946 CalculateBattleInfo();
4947 Flags
&= ~C_INITIALIZING
;
4951 void character::CalculateHP () {
4952 HP
= sumbodypartproperties()(this, &bodypart::GetHP
);
4956 void character::CalculateMaxHP () {
4957 MaxHP
= sumbodypartproperties()(this, &bodypart::GetMaxHP
);
4961 void character::CalculateBodyPartMaxHPs (feuLong Flags
) {
4962 doforbodypartswithparam
<feuLong
>()(this, &bodypart::CalculateMaxHP
, Flags
);
4968 truth
character::EditAttribute (int Identifier
, int Value
) {
4969 if (Identifier
== ENDURANCE
&& UseMaterialAttributes()) return false;
4970 if (RawEditAttribute(BaseExperience
[Identifier
], Value
)) {
4971 if (!IsInitializing()) {
4972 if (Identifier
== LEG_STRENGTH
) CalculateBurdenState();
4973 else if (Identifier
== ENDURANCE
) CalculateBodyPartMaxHPs();
4974 else if (IsPlayer() && Identifier
== PERCEPTION
) game::SendLOSUpdateRequest();
4975 else if (IsPlayerKind() && (Identifier
== INTELLIGENCE
|| Identifier
== WISDOM
|| Identifier
== CHARISMA
)) UpdatePictures();
4976 CalculateBattleInfo();
4984 truth
character::ActivateRandomState (int Flags
, int Time
, sLong Seed
) {
4986 if (Seed
) femath::SetSeed(Seed
);
4987 sLong ToBeActivated
= GetRandomState(Flags
|DUR_TEMPORARY
);
4989 if (!ToBeActivated
) return false;
4990 BeginTemporaryState(ToBeActivated
, Time
);
4995 truth
character::GainRandomIntrinsic (int Flags
) {
4996 sLong ToBeActivated
= GetRandomState(Flags
|DUR_PERMANENT
);
4997 if (!ToBeActivated
) return false;
4998 GainIntrinsic(ToBeActivated
);
5003 /* Returns 0 if state not found */
5004 sLong
character::GetRandomState (int Flags
) const {
5005 sLong OKStates
[STATES
];
5006 int NumberOfOKStates
= 0;
5007 for (int c
= 0; c
< STATES
; ++c
) {
5008 if (StateData
[c
].Flags
& Flags
& DUR_FLAGS
&& StateData
[c
].Flags
& Flags
& SRC_FLAGS
) OKStates
[NumberOfOKStates
++] = 1 << c
;
5010 return NumberOfOKStates
? OKStates
[RAND() % NumberOfOKStates
] : 0;
5014 int characterprototype::CreateSpecialConfigurations (characterdatabase
**TempConfig
, int Configs
, int Level
) {
5015 if (Level
== 0 && TempConfig
[0]->CreateDivineConfigurations
) {
5016 Configs
= databasecreator
<character
>::CreateDivineConfigurations(this, TempConfig
, Configs
);
5018 if (Level
== 1 && TempConfig
[0]->CreateUndeadConfigurations
) {
5019 for (int c
= 1; c
< protocontainer
<character
>::GetSize(); ++c
) {
5020 const character::prototype
*Proto
= protocontainer
<character
>::GetProto(c
);
5021 const character::database
*const *CharacterConfigData
= Proto
->GetConfigData();
5022 if (!CharacterConfigData
) ABORT("No database entry for character <%s>!", Proto
->GetClassID());
5023 const character::database
*const* End
= CharacterConfigData
+ Proto
->GetConfigSize();
5024 for (++CharacterConfigData
; CharacterConfigData
!= End
; ++CharacterConfigData
) {
5025 const character::database
*CharacterDataBase
= *CharacterConfigData
;
5026 if (CharacterDataBase
->UndeadVersions
) {
5027 character::database
* ConfigDataBase
= new character::database(**TempConfig
);
5028 festring ucfgname
= "undead ";
5029 ucfgname
<< CharacterDataBase
->CfgStrName
;
5030 ConfigDataBase
->InitDefaults(this, (c
<< 8) | CharacterDataBase
->Config
, ucfgname
);
5031 ConfigDataBase
->PostFix
<< "of ";
5032 if (CharacterDataBase
->Adjective
.GetSize()) {
5033 if (CharacterDataBase
->UsesLongAdjectiveArticle
) ConfigDataBase
->PostFix
<< "an ";
5034 else ConfigDataBase
->PostFix
<< "a ";
5035 ConfigDataBase
->PostFix
<< CharacterDataBase
->Adjective
<< ' ';
5037 if (CharacterDataBase
->UsesLongArticle
) ConfigDataBase
->PostFix
<< "an ";
5038 else ConfigDataBase
->PostFix
<< "a ";
5040 ConfigDataBase
->PostFix
<< CharacterDataBase
->NameSingular
;
5041 if (CharacterDataBase
->PostFix
.GetSize()) ConfigDataBase
->PostFix
<< ' ' << CharacterDataBase
->PostFix
;
5042 int P1
= TempConfig
[0]->UndeadAttributeModifier
;
5043 int P2
= TempConfig
[0]->UndeadVolumeModifier
;
5045 for (c2
= 0; c2
< ATTRIBUTES
; ++c2
) ConfigDataBase
->*ExpPtr
[c2
] = CharacterDataBase
->*ExpPtr
[c2
] * P1
/ 100;
5046 for (c2
= 0; c2
< EQUIPMENT_DATAS
; ++c2
) ConfigDataBase
->*EquipmentDataPtr
[c2
] = contentscript
<item
>();
5047 ConfigDataBase
->DefaultIntelligence
= 5;
5048 ConfigDataBase
->DefaultWisdom
= 5;
5049 ConfigDataBase
->DefaultCharisma
= 5;
5050 ConfigDataBase
->TotalSize
= CharacterDataBase
->TotalSize
;
5051 ConfigDataBase
->Sex
= CharacterDataBase
->Sex
;
5052 ConfigDataBase
->AttributeBonus
= CharacterDataBase
->AttributeBonus
;
5053 ConfigDataBase
->TotalVolume
= CharacterDataBase
->TotalVolume
* P2
/ 100;
5054 if (TempConfig
[0]->UndeadCopyMaterials
) {
5055 ConfigDataBase
->HeadBitmapPos
= CharacterDataBase
->HeadBitmapPos
;
5056 ConfigDataBase
->HairColor
= CharacterDataBase
->HairColor
;
5057 ConfigDataBase
->EyeColor
= CharacterDataBase
->EyeColor
;
5058 ConfigDataBase
->CapColor
= CharacterDataBase
->CapColor
;
5059 ConfigDataBase
->FleshMaterial
= CharacterDataBase
->FleshMaterial
;
5060 ConfigDataBase
->BloodMaterial
= CharacterDataBase
->BloodMaterial
;
5061 ConfigDataBase
->VomitMaterial
= CharacterDataBase
->VomitMaterial
;
5062 ConfigDataBase
->SweatMaterial
= CharacterDataBase
->SweatMaterial
;
5064 ConfigDataBase
->KnownCWeaponSkills
= CharacterDataBase
->KnownCWeaponSkills
;
5065 ConfigDataBase
->CWeaponSkillHits
= CharacterDataBase
->CWeaponSkillHits
;
5066 ConfigDataBase
->PostProcess();
5067 TempConfig
[Configs
++] = ConfigDataBase
;
5072 if (Level
== 0 && TempConfig
[0]->CreateGolemMaterialConfigurations
) {
5073 for (int c
= 1; c
< protocontainer
<material
>::GetSize(); ++c
) {
5074 const material::prototype
* Proto
= protocontainer
<material
>::GetProto(c
);
5075 const material::database
*const* MaterialConfigData
= Proto
->GetConfigData();
5076 const material::database
*const* End
= MaterialConfigData
+ Proto
->GetConfigSize();
5077 for (++MaterialConfigData
; MaterialConfigData
!= End
; ++MaterialConfigData
) {
5078 const material::database
* MaterialDataBase
= *MaterialConfigData
;
5079 if (MaterialDataBase
->CategoryFlags
& IS_GOLEM_MATERIAL
) {
5080 character::database
* ConfigDataBase
= new character::database(**TempConfig
);
5082 gcfgname
<< MaterialDataBase
->CfgStrName
;
5083 gcfgname
<< " golem";
5084 ConfigDataBase
->InitDefaults(this, MaterialDataBase
->Config
, gcfgname
);
5085 ConfigDataBase
->Adjective
= MaterialDataBase
->NameStem
;
5086 ConfigDataBase
->UsesLongAdjectiveArticle
= MaterialDataBase
->NameFlags
& USE_AN
;
5087 ConfigDataBase
->AttachedGod
= MaterialDataBase
->AttachedGod
;
5088 TempConfig
[Configs
++] = ConfigDataBase
;
5097 double character::GetTimeToDie (ccharacter
*Enemy
, int Damage
, double ToHitValue
, truth AttackIsBlockable
, truth UseMaxHP
) const {
5098 double DodgeValue
= GetDodgeValue();
5099 if (!Enemy
->CanBeSeenBy(this, true)) ToHitValue
*= 2;
5100 if (!CanBeSeenBy(Enemy
, true)) DodgeValue
*= 2;
5101 double MinHits
= 1000;
5103 for (int c
= 0; c
< BodyParts
; ++c
) {
5104 if (BodyPartIsVital(c
) && GetBodyPart(c
)) {
5105 double Hits
= GetBodyPart(c
)->GetTimeToDie(Damage
, ToHitValue
, DodgeValue
, AttackIsBlockable
, UseMaxHP
);
5106 if (First
) { MinHits
= Hits
; First
= false; } else MinHits
= 1/(1/MinHits
+1/Hits
);
5113 double character::GetRelativeDanger (ccharacter
*Enemy
, truth UseMaxHP
) const {
5114 double Danger
= Enemy
->GetTimeToKill(this, UseMaxHP
)/GetTimeToKill(Enemy
, UseMaxHP
);
5115 int EnemyAP
= Enemy
->GetMoveAPRequirement(1);
5116 int ThisAP
= GetMoveAPRequirement(1);
5117 if (EnemyAP
> ThisAP
) Danger
*= 1.25; else if (ThisAP
> EnemyAP
) Danger
*= 0.80;
5118 if (!Enemy
->CanBeSeenBy(this, true)) Danger
*= (Enemy
->IsPlayer() ? 0.2 : 0.5);
5119 if (!CanBeSeenBy(Enemy
, true)) Danger
*= (IsPlayer() ? 5.0 : 2.0);
5120 if (GetAttribute(INTELLIGENCE
) < 10 && !IsPlayer()) Danger
*= 0.80;
5121 if (Enemy
->GetAttribute(INTELLIGENCE
) < 10 && !Enemy
->IsPlayer()) Danger
*= 1.25;
5122 return Limit(Danger
, 0.001, 1000.0);
5126 festring
character::GetBodyPartName (int I
, truth Articled
) const {
5127 if (I
== TORSO_INDEX
) return Articled
? CONST_S("a torso") : CONST_S("torso");
5128 ABORT("Illegal character bodypart name request!");
5133 item
*character::SearchForItem (feuLong ID
) const {
5134 item
*Equipment
= findequipment
<feuLong
>()(this, &item::HasID
, ID
);
5135 if (Equipment
) return Equipment
;
5136 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (i
->GetID() == ID
) return *i
;
5141 truth
character::ContentsCanBeSeenBy (ccharacter
*Viewer
) const {
5142 return (Viewer
== this);
5146 truth
character::HitEffect (character
*Enemy
, item
* Weapon
, v2 HitPos
, int Type
, int BodyPartIndex
,
5147 int Direction
, truth BlockedByArmour
, truth Critical
, int DoneDamage
)
5149 if (Weapon
) return Weapon
->HitEffect(this, Enemy
, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5151 case UNARMED_ATTACK
: return Enemy
->SpecialUnarmedEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5152 case KICK_ATTACK
: return Enemy
->SpecialKickEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5153 case BITE_ATTACK
: return Enemy
->SpecialBiteEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
, Critical
, DoneDamage
);
5159 void character::WeaponSkillHit (item
*Weapon
, int Type
, int Hits
) {
5162 case UNARMED_ATTACK
: Category
= UNARMED
; break;
5163 case WEAPON_ATTACK
: Weapon
->WeaponSkillHit(Hits
); return;
5164 case KICK_ATTACK
: Category
= KICK
; break;
5165 case BITE_ATTACK
: Category
= BITE
; break;
5167 if (!IsHumanoid()) return;
5168 Category
= Weapon
->GetWeaponCategory();
5171 ABORT("Illegal Type %d passed to character::WeaponSkillHit()!", Type
);
5174 if (GetCWeaponSkill(Category
)->AddHit(Hits
)) {
5175 CalculateBattleInfo();
5176 if (IsPlayer()) GetCWeaponSkill(Category
)->AddLevelUpMessage(Category
);
5181 /* Returns 0 if character cannot be duplicated */
5182 character
*character::Duplicate (feuLong Flags
) {
5183 if (!(Flags
& IGNORE_PROHIBITIONS
) && !CanBeCloned()) return 0;
5184 character
*Char
= GetProtoType()->Clone(this);
5185 if (Flags
& MIRROR_IMAGE
) {
5186 DuplicateEquipment(Char
, Flags
& ~IGNORE_PROHIBITIONS
);
5187 Char
->SetLifeExpectancy(Flags
>> LE_BASE_SHIFT
& LE_BASE_RANGE
, Flags
>> LE_RAND_SHIFT
& LE_RAND_RANGE
);
5189 Char
->CalculateAll();
5190 Char
->CalculateEmitation();
5191 Char
->UpdatePictures();
5192 Char
->Flags
&= ~(C_INITIALIZING
|C_IN_NO_MSG_MODE
);
5197 truth
character::TryToEquip (item
*Item
) {
5198 if (!Item
->AllowEquip() || !CanUseEquipment() || GetAttribute(WISDOM
) >= Item
->GetWearWisdomLimit() || Item
->GetSquaresUnder() != 1) {
5202 for (int e
= 0; e
< GetEquipments(); ++e
) {
5203 if (GetBodyPartOfEquipment(e
) && EquipmentIsAllowed(e
)) {
5204 sorter Sorter
= EquipmentSorter(e
);
5205 if ((Sorter
== 0 || (Item
->*Sorter
)(this)) &&
5206 ((e
!= RIGHT_WIELDED_INDEX
&& e
!= LEFT_WIELDED_INDEX
) ||
5207 Item
->IsWeapon(this) || Item
->IsShield(this)) && AllowEquipment(Item
, e
))
5209 item
*OldEquipment
= GetEquipment(e
);
5210 if (BoundToUse(OldEquipment
, e
)) continue;
5211 lsquare
*LSquareUnder
= GetLSquareUnder();
5212 stack
*StackUnder
= LSquareUnder
->GetStack();
5213 msgsystem::DisableMessages();
5214 Flags
|= C_PICTURE_UPDATES_FORBIDDEN
;
5215 LSquareUnder
->Freeze();
5216 StackUnder
->Freeze();
5217 double Danger
= GetRelativeDanger(PLAYER
);
5218 if (OldEquipment
) OldEquipment
->RemoveFromSlot();
5219 Item
->RemoveFromSlot();
5220 SetEquipment(e
, Item
);
5221 double NewDanger
= GetRelativeDanger(PLAYER
);
5222 Item
->RemoveFromSlot();
5223 StackUnder
->AddItem(Item
);
5224 if (OldEquipment
) SetEquipment(e
, OldEquipment
);
5225 msgsystem::EnableMessages();
5226 Flags
&= ~C_PICTURE_UPDATES_FORBIDDEN
;
5227 LSquareUnder
->UnFreeze();
5228 StackUnder
->UnFreeze();
5230 if (NewDanger
> Danger
|| BoundToUse(Item
, e
)) {
5231 room
*Room
= GetRoom();
5232 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
5233 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
));
5234 if (Room
) Room
->DropItem(this, OldEquipment
, 1);
5235 OldEquipment
->MoveTo(StackUnder
);
5236 Item
->RemoveFromSlot();
5237 SetEquipment(e
, Item
);
5243 if (NewDanger
> Danger
|| (NewDanger
== Danger
&& e
!= RIGHT_WIELDED_INDEX
&& e
!= LEFT_WIELDED_INDEX
) || BoundToUse(Item
, e
)) {
5244 room
*Room
= GetRoom();
5245 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
5246 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up and equips %s.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(INDEFINITE
));
5247 Item
->RemoveFromSlot();
5248 SetEquipment(e
, Item
);
5261 truth
character::TryToConsume (item
*Item
) {
5262 return Item
->CanBeEatenByAI(this) && ConsumeItem(Item
, Item
->GetConsumeMaterial(this)->GetConsumeVerb());
5266 void character::UpdateESPLOS () const {
5267 if (StateIsActivated(ESP
) && !game::IsInWilderness()) {
5268 for (int c
= 0; c
< game::GetTeams(); ++c
) {
5269 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
5270 const character
*ch
= *i
;
5271 if (ch
->IsEnabled()) ch
->SendNewDrawRequest();
5278 int character::GetCWeaponSkillLevel (citem
*Item
) const {
5279 if (Item
->GetWeaponCategory() < GetAllowedWeaponSkillCategories()) return GetCWeaponSkill(Item
->GetWeaponCategory())->GetLevel();
5284 void character::PrintBeginPanicMessage () const {
5285 if (IsPlayer()) ADD_MESSAGE("You panic!");
5286 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s panics.", CHAR_NAME(DEFINITE
));
5290 void character::PrintEndPanicMessage () const {
5291 if (IsPlayer()) ADD_MESSAGE("You finally calm down.");
5292 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s calms down.", CHAR_NAME(DEFINITE
));
5296 void character::CheckPanic (int Ticks
) {
5297 if (GetPanicLevel() > 1 && !StateIsActivated(PANIC
) && GetHP()*100 < RAND()%(GetPanicLevel()*GetMaxHP()<<1) && !StateIsActivated(FEARLESS
)) {
5298 BeginTemporaryState(PANIC
, ((Ticks
* 3) >> 2) + RAND() % ((Ticks
>> 1) + 1)); // 25% randomness to ticks...
5303 /* returns 0 if fails else the newly created character */
5304 character
*character::DuplicateToNearestSquare (character
*Cloner
, feuLong Flags
) {
5305 character
*NewlyCreated
= Duplicate(Flags
);
5306 if (!NewlyCreated
) return 0;
5307 if (Flags
& CHANGE_TEAM
&& Cloner
) NewlyCreated
->ChangeTeam(Cloner
->GetTeam());
5308 NewlyCreated
->PutNear(GetPos());
5309 return NewlyCreated
;
5313 void character::SignalSpoil (material
*m
) {
5314 if (GetMotherEntity()) GetMotherEntity()->SignalSpoil(m
);
5315 else Disappear(0, "spoil", &item::IsVeryCloseToSpoiling
);
5319 truth
character::CanHeal () const {
5320 for (int c
= 0; c
< BodyParts
; ++c
) {
5321 bodypart
*BodyPart
= GetBodyPart(c
);
5322 if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < BodyPart
->GetMaxHP()) return true;
5328 int character::GetRelation (ccharacter
*Who
) const {
5329 return GetTeam()->GetRelation(Who
->GetTeam());
5333 truth (item::*AffectTest
[BASE_ATTRIBUTES
])() const = {
5334 &item::AffectsEndurance
,
5335 &item::AffectsPerception
,
5336 &item::AffectsIntelligence
,
5337 &item::AffectsWisdom
,
5338 &item::AffectsWillPower
,
5339 &item::AffectsCharisma
,
5344 /* Returns nonzero if endurance has decreased and death may occur */
5345 truth
character::CalculateAttributeBonuses () {
5346 doforbodyparts()(this, &bodypart::CalculateAttributeBonuses
);
5347 int BackupBonus
[BASE_ATTRIBUTES
];
5348 int BackupCarryingBonus
= CarryingBonus
;
5351 for (c1
= 0; c1
< BASE_ATTRIBUTES
; ++c1
) {
5352 BackupBonus
[c1
] = AttributeBonus
[c1
];
5353 AttributeBonus
[c1
] = 0;
5355 for (c1
= 0; c1
< GetEquipments(); ++c1
) {
5356 item
*Equipment
= GetEquipment(c1
);
5357 if (!Equipment
|| !Equipment
->IsInCorrectSlot(c1
)) continue;
5358 for (int c2
= 0; c2
< BASE_ATTRIBUTES
; ++c2
) {
5359 if ((Equipment
->*AffectTest
[c2
])()) AttributeBonus
[c2
] += Equipment
->GetEnchantment();
5361 if (Equipment
->AffectsCarryingCapacity()) CarryingBonus
+= Equipment
->GetCarryingBonus();
5364 ApplySpecialAttributeBonuses();
5366 if (IsPlayer() && !IsInitializing() && AttributeBonus
[PERCEPTION
] != BackupBonus
[PERCEPTION
]) game::SendLOSUpdateRequest();
5367 if (IsPlayer() && !IsInitializing() && AttributeBonus
[INTELLIGENCE
] != BackupBonus
[INTELLIGENCE
]) UpdateESPLOS();
5369 if (!IsInitializing() && CarryingBonus
!= BackupCarryingBonus
) CalculateBurdenState();
5371 if (!IsInitializing() && AttributeBonus
[ENDURANCE
] != BackupBonus
[ENDURANCE
]) {
5372 CalculateBodyPartMaxHPs();
5373 CalculateMaxStamina();
5374 return AttributeBonus
[ENDURANCE
] < BackupBonus
[ENDURANCE
];
5381 void character::ApplyEquipmentAttributeBonuses (item
*Equipment
) {
5382 if (Equipment
->AffectsEndurance()) {
5383 AttributeBonus
[ENDURANCE
] += Equipment
->GetEnchantment();
5384 CalculateBodyPartMaxHPs();
5385 CalculateMaxStamina();
5387 if (Equipment
->AffectsPerception()) {
5388 AttributeBonus
[PERCEPTION
] += Equipment
->GetEnchantment();
5389 if (IsPlayer()) game::SendLOSUpdateRequest();
5391 if (Equipment
->AffectsIntelligence()) {
5392 AttributeBonus
[INTELLIGENCE
] += Equipment
->GetEnchantment();
5393 if (IsPlayer()) UpdateESPLOS();
5395 if (Equipment
->AffectsWisdom()) AttributeBonus
[WISDOM
] += Equipment
->GetEnchantment();
5396 if (Equipment
->AffectsWillPower()) AttributeBonus
[WILL_POWER
] += Equipment
->GetEnchantment();
5397 if (Equipment
->AffectsCharisma()) AttributeBonus
[CHARISMA
] += Equipment
->GetEnchantment();
5398 if (Equipment
->AffectsMana()) AttributeBonus
[MANA
] += Equipment
->GetEnchantment();
5399 if (Equipment
->AffectsCarryingCapacity()) {
5400 CarryingBonus
+= Equipment
->GetCarryingBonus();
5401 CalculateBurdenState();
5406 void character::ReceiveAntidote (sLong Amount
) {
5407 if (StateIsActivated(POISONED
)) {
5408 if (GetTemporaryStateCounter(POISONED
) > Amount
) {
5409 EditTemporaryStateCounter(POISONED
, -Amount
);
5412 if (IsPlayer()) ADD_MESSAGE("Aaaah... You feel much better.");
5413 Amount
-= GetTemporaryStateCounter(POISONED
);
5414 DeActivateTemporaryState(POISONED
);
5417 if ((Amount
>= 100 || RAND_N(100) < Amount
) && StateIsActivated(PARASITIZED
)) {
5418 if (IsPlayer()) ADD_MESSAGE("Something in your belly didn't seem to like this stuff.");
5419 DeActivateTemporaryState(PARASITIZED
);
5420 Amount
-= Min(100, Amount
);
5422 if ((Amount
>= 100 || RAND_N(100) < Amount
) && StateIsActivated(LEPROSY
)) {
5423 if (IsPlayer()) ADD_MESSAGE("You are not falling to pieces anymore.");
5424 DeActivateTemporaryState(LEPROSY
);
5425 Amount
-= Min(100, Amount
);
5430 void character::AddAntidoteConsumeEndMessage () const {
5431 if (StateIsActivated(POISONED
)) {
5432 // true only if the antidote didn't cure the poison completely
5433 if (IsPlayer()) ADD_MESSAGE("Your body processes the poison in your veins with rapid speed.");
5438 truth
character::IsDead () const {
5439 for (int c
= 0; c
< BodyParts
; ++c
) {
5440 bodypart
*BodyPart
= GetBodyPart(c
);
5441 if (BodyPartIsVital(c
) && (!BodyPart
|| BodyPart
->GetHP() < 1)) return true;
5447 void character::SignalSpoilLevelChange (material
*m
) {
5448 if (GetMotherEntity()) GetMotherEntity()->SignalSpoilLevelChange(m
); else UpdatePictures();
5452 void character::AddOriginalBodyPartID (int I
, feuLong What
) {
5453 if (std::find(OriginalBodyPartID
[I
].begin(), OriginalBodyPartID
[I
].end(), What
) == OriginalBodyPartID
[I
].end()) {
5454 OriginalBodyPartID
[I
].push_back(What
);
5455 if (OriginalBodyPartID
[I
].size() > 100) OriginalBodyPartID
[I
].erase(OriginalBodyPartID
[I
].begin());
5460 void character::AddToInventory (const fearray
<contentscript
<item
> > &ItemArray
, int SpecialFlags
) {
5461 for (uInt c1
= 0; c1
< ItemArray
.Size
; ++c1
) {
5462 if (ItemArray
[c1
].IsValid()) {
5463 const interval
*TimesPtr
= ItemArray
[c1
].GetTimes();
5464 int Times
= TimesPtr
? TimesPtr
->Randomize() : 1;
5465 for (int c2
= 0; c2
< Times
; ++c2
) {
5466 item
*Item
= ItemArray
[c1
].Instantiate(SpecialFlags
);
5468 Stack
->AddItem(Item
);
5469 Item
->SpecialGenerationHandler();
5477 truth
character::HasHadBodyPart (citem
*Item
) const {
5478 for (int c
= 0; c
< BodyParts
; ++c
)
5479 if (std::find(OriginalBodyPartID
[c
].begin(), OriginalBodyPartID
[c
].end(), Item
->GetID()) != OriginalBodyPartID
[c
].end())
5481 return GetPolymorphBackup() && GetPolymorphBackup()->HasHadBodyPart(Item
);
5485 festring
&character::ProcessMessage (festring
&Msg
) const {
5486 SEARCH_N_REPLACE(Msg
, "@nu", GetName(UNARTICLED
));
5487 SEARCH_N_REPLACE(Msg
, "@ni", GetName(INDEFINITE
));
5488 SEARCH_N_REPLACE(Msg
, "@nd", GetName(DEFINITE
));
5489 SEARCH_N_REPLACE(Msg
, "@du", GetDescription(UNARTICLED
));
5490 SEARCH_N_REPLACE(Msg
, "@di", GetDescription(INDEFINITE
));
5491 SEARCH_N_REPLACE(Msg
, "@dd", GetDescription(DEFINITE
));
5492 SEARCH_N_REPLACE(Msg
, "@pp", GetPersonalPronoun());
5493 SEARCH_N_REPLACE(Msg
, "@sp", GetPossessivePronoun());
5494 SEARCH_N_REPLACE(Msg
, "@op", GetObjectPronoun());
5495 SEARCH_N_REPLACE(Msg
, "@Nu", GetName(UNARTICLED
).CapitalizeCopy());
5496 SEARCH_N_REPLACE(Msg
, "@Ni", GetName(INDEFINITE
).CapitalizeCopy());
5497 SEARCH_N_REPLACE(Msg
, "@Nd", GetName(DEFINITE
).CapitalizeCopy());
5498 SEARCH_N_REPLACE(Msg
, "@Du", GetDescription(UNARTICLED
).CapitalizeCopy());
5499 SEARCH_N_REPLACE(Msg
, "@Di", GetDescription(INDEFINITE
).CapitalizeCopy());
5500 SEARCH_N_REPLACE(Msg
, "@Dd", GetDescription(DEFINITE
).CapitalizeCopy());
5501 SEARCH_N_REPLACE(Msg
, "@Pp", GetPersonalPronoun().CapitalizeCopy());
5502 SEARCH_N_REPLACE(Msg
, "@Sp", GetPossessivePronoun().CapitalizeCopy());
5503 SEARCH_N_REPLACE(Msg
, "@Op", GetObjectPronoun().CapitalizeCopy());
5504 SEARCH_N_REPLACE(Msg
, "@Gd", GetMasterGod()->GetName());
5509 void character::ProcessAndAddMessage (festring Msg
) const {
5510 ADD_MESSAGE("%s", ProcessMessage(Msg
).CStr());
5514 void character::BeTalkedTo () {
5516 if (GetRelation(PLAYER
) == HOSTILE
)
5517 ProcessAndAddMessage(GetHostileReplies()[RandomizeReply(Said
, GetHostileReplies().Size
)]);
5519 ProcessAndAddMessage(GetFriendlyReplies()[RandomizeReply(Said
, GetFriendlyReplies().Size
)]);
5523 truth
character::CheckZap () {
5525 ADD_MESSAGE("This monster type can't zap.");
5532 void character::DamageAllItems (character
*Damager
, int Damage
, int Type
) {
5533 GetStack()->ReceiveDamage(Damager
, Damage
, Type
);
5534 for (int c
= 0; c
< GetEquipments(); ++c
) {
5535 item
*Equipment
= GetEquipment(c
);
5536 if (Equipment
) Equipment
->ReceiveDamage(Damager
, Damage
, Type
);
5541 truth
character::Equips (citem
*Item
) const {
5542 return combineequipmentpredicateswithparam
<feuLong
>()(this, &item::HasID
, Item
->GetID(), 1);
5546 void character::PrintBeginConfuseMessage () const {
5547 if (IsPlayer()) ADD_MESSAGE("You feel quite happy.");
5551 void character::PrintEndConfuseMessage () const {
5552 if (IsPlayer()) ADD_MESSAGE("The world is boring again.");
5556 v2
character::ApplyStateModification (v2 TryDirection
) const {
5557 if (!StateIsActivated(CONFUSED
) || RAND() & 15 || game::IsInWilderness()) return TryDirection
;
5558 v2 To
= GetLevel()->GetFreeAdjacentSquare(this, GetPos(), true);
5559 if (To
== ERROR_V2
) return TryDirection
;
5561 if (To
!= TryDirection
&& IsPlayer()) ADD_MESSAGE("Whoa! You somehow don't manage to walk straight.");
5566 void character::AddConfuseHitMessage () const {
5567 if (IsPlayer()) ADD_MESSAGE("This stuff is confusing.");
5571 item
*character::SelectFromPossessions (cfestring
&Topic
, sorter Sorter
) {
5572 itemvector ReturnVector
;
5573 SelectFromPossessions(ReturnVector
, Topic
, NO_MULTI_SELECT
, Sorter
);
5574 return !ReturnVector
.empty() ? ReturnVector
[0] : 0;
5578 truth
character::SelectFromPossessions (itemvector
&ReturnVector
, cfestring
&Topic
, int Flags
, sorter Sorter
) {
5580 truth InventoryPossible
= GetStack()->SortedItems(this, Sorter
);
5581 if (InventoryPossible
) List
.AddEntry(CONST_S("choose from inventory"), LIGHT_GRAY
, 20, game::AddToItemDrawVector(itemvector()));
5586 for (c
= 0; c
< BodyParts
; ++c
) {
5587 bodypart
*BodyPart
= GetBodyPart(c
);
5588 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) {
5589 Item
.push_back(BodyPart
);
5591 BodyPart
->AddName(Entry
, UNARTICLED
);
5592 int ImageKey
= game::AddToItemDrawVector(itemvector(1, BodyPart
));
5593 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
5597 for (c
= 0; c
< GetEquipments(); ++c
) {
5598 item
*Equipment
= GetEquipment(c
);
5599 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) {
5600 Item
.push_back(Equipment
);
5601 Entry
= GetEquipmentName(c
);
5604 Equipment
->AddInventoryEntry(this, Entry
, 1, true);
5605 AddSpecialEquipmentInfo(Entry
, c
);
5606 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
5607 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
5612 game::SetStandardListAttributes(List
);
5613 List
.SetFlags(SELECTABLE
|DRAW_BACKGROUND_AFTERWARDS
);
5614 List
.SetEntryDrawer(game::ItemEntryDrawer
);
5615 game::DrawEverythingNoBlit();
5616 int Chosen
= List
.Draw();
5617 game::ClearItemDrawVector();
5618 if (Chosen
!= ESCAPED
) {
5619 if ((InventoryPossible
&& !Chosen
) || Chosen
& FELIST_ERROR_BIT
) {
5620 GetStack()->DrawContents(ReturnVector
, this, Topic
, Flags
, Sorter
);
5622 ReturnVector
.push_back(Item
[InventoryPossible
? Chosen
- 1 : Chosen
]);
5623 if (Flags
& SELECT_PAIR
&& ReturnVector
[0]->HandleInPairs()) {
5624 item
*PairEquipment
= GetPairEquipment(ReturnVector
[0]->GetEquipmentIndex());
5625 if (PairEquipment
&& PairEquipment
->CanBePiledWith(ReturnVector
[0], this)) ReturnVector
.push_back(PairEquipment
);
5630 if (!GetStack()->SortedItems(this, Sorter
)) return false;
5631 game::ClearItemDrawVector();
5632 GetStack()->DrawContents(ReturnVector
, this, Topic
, Flags
, Sorter
);
5638 truth
character::EquipsSomething (sorter Sorter
) {
5639 for (int c
= 0; c
< GetEquipments(); ++c
) {
5640 item
*Equipment
= GetEquipment(c
);
5641 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) return true;
5647 material
*character::CreateBodyPartMaterial (int, sLong Volume
) const {
5648 return MAKE_MATERIAL(GetFleshMaterial(), Volume
);
5652 truth
character::CheckTalk () {
5654 ADD_MESSAGE("This monster does not know the art of talking.");
5661 truth
character::MoveTowardsHomePos () {
5662 if (HomeDataIsValid() && IsEnabled()) {
5663 SetGoingTo(HomeData
->Pos
);
5664 return MoveTowardsTarget(false) || (!GetPos().IsAdjacent(HomeData
->Pos
) && MoveRandomly());
5670 truth
character::TryToChangeEquipment (stack
*MainStack
, stack
*SecStack
, int Chosen
) {
5671 if (!GetBodyPartOfEquipment(Chosen
)) {
5672 ADD_MESSAGE("Bodypart missing!");
5676 item
*OldEquipment
= GetEquipment(Chosen
);
5677 if (!IsPlayer() && BoundToUse(OldEquipment
, Chosen
)) {
5678 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE
), OldEquipment
->CHAR_NAME(DEFINITE
));
5681 if (OldEquipment
) OldEquipment
->MoveTo(MainStack
);
5683 sorter Sorter
= EquipmentSorter(Chosen
);
5684 if (!MainStack
->SortedItems(this, Sorter
) && (!SecStack
|| !SecStack
->SortedItems(this, Sorter
))) {
5685 ADD_MESSAGE("You haven't got any item that could be used for this purpose.");
5689 game::DrawEverythingNoBlit();
5690 itemvector ItemVector
;
5691 int Return
= MainStack
->DrawContents(ItemVector
, SecStack
, this,
5692 CONST_S("Choose ")+GetEquipmentName(Chosen
)+':',
5693 (SecStack
? CONST_S("Items in your inventory") : CONST_S("")),
5694 (SecStack
? festring(CONST_S("Items in ")+GetPossessivePronoun()+" inventory") : CONST_S("")),
5695 (SecStack
? festring(GetDescription(DEFINITE
)+" is "+GetVerbalBurdenState()) : CONST_S("")),
5696 GetVerbalBurdenStateColor(),
5697 NONE_AS_CHOICE
|NO_MULTI_SELECT
|SELECT_PAIR
|SKIP_FIRST_IF_NO_OLD
,
5698 Sorter
, OldEquipment
);
5699 if (Return
== ESCAPED
) {
5701 OldEquipment
->RemoveFromSlot();
5702 SetEquipment(Chosen
, OldEquipment
);
5707 item
*Item
= (ItemVector
.empty() ? 0 : ItemVector
[0]);
5710 if (!IsPlayer() && !AllowEquipment(Item
, Chosen
)) {
5711 ADD_MESSAGE("%s refuses to equip %s.", CHAR_DESCRIPTION(DEFINITE
), Item
->CHAR_NAME(DEFINITE
));
5714 int otherChosen
= -1;
5715 if (ItemVector
[0]->HandleInPairs() && ItemVector
.size() > 1) {
5717 case RIGHT_GAUNTLET_INDEX
: otherChosen
= LEFT_GAUNTLET_INDEX
; break;
5718 case LEFT_GAUNTLET_INDEX
: otherChosen
= RIGHT_GAUNTLET_INDEX
; break;
5719 case RIGHT_BOOT_INDEX
: otherChosen
= LEFT_BOOT_INDEX
; break;
5720 case LEFT_BOOT_INDEX
: otherChosen
= RIGHT_BOOT_INDEX
; break;
5723 if (otherChosen
!= -1) {
5724 if (GetBodyPartOfEquipment(otherChosen
)) {
5725 if (!game::TruthQuestion("Wear both items?", NO
)) otherChosen
= -1;
5731 // wear/wield first item
5732 Item
->RemoveFromSlot();
5733 SetEquipment(Chosen
, Item
);
5734 if (CheckIfEquipmentIsNotUsable(Chosen
)) { Item
->MoveTo(MainStack
); Item
= 0; } // small bug?
5735 // wear/wield possible second item
5736 if (Item
&& otherChosen
!= -1 && ItemVector
[0]->HandleInPairs() && ItemVector
.size() > 1 && GetBodyPartOfEquipment(otherChosen
)) {
5737 item
*otherOld
= GetEquipment(otherChosen
);
5738 if (otherOld
&& !IsPlayer() && BoundToUse(otherOld
, otherChosen
)) {
5739 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE
), otherOld
->CHAR_NAME(DEFINITE
));
5740 } else if (otherOld
) {
5741 otherOld
->MoveTo(MainStack
);
5743 ItemVector
[1]->RemoveFromSlot();
5744 SetEquipment(otherChosen
, ItemVector
[1]);
5745 if (CheckIfEquipmentIsNotUsable(otherChosen
)) { ItemVector
[1]->MoveTo(MainStack
); } // small bug?
5749 return (Item
!= OldEquipment
);
5753 void character::PrintBeginParasitizedMessage () const {
5754 if (IsPlayer()) ADD_MESSAGE("You feel you are no longer alone.");
5758 void character::PrintEndParasitizedMessage () const {
5759 if (IsPlayer()) ADD_MESSAGE("A feeling of long welcome emptiness overwhelms you.");
5763 void character::ParasitizedHandler () {
5765 if (!(RAND() % 250)) {
5766 if (IsPlayer()) ADD_MESSAGE("Ugh. You feel something violently carving its way through your intestines.");
5767 ReceiveDamage(0, 1, POISON
, TORSO
, 8, false, false, false, false);
5768 CheckDeath(CONST_S("killed by a vile parasite"), 0);
5773 truth
character::CanFollow () const {
5774 return CanMove() && !StateIsActivated(PANIC
) && !IsStuck();
5778 festring
character::GetKillName () const {
5779 if (!GetPolymorphBackup()) return GetName(INDEFINITE
);
5781 GetPolymorphBackup()->AddName(KillName
, INDEFINITE
);
5782 KillName
<< " polymorphed into ";
5783 id::AddName(KillName
, INDEFINITE
);
5788 festring
character::GetPanelName () const {
5790 Name
<< AssignedName
<< " the " << game::GetVerbalPlayerAlignment() << ' ';
5791 id::AddName(Name
, UNARTICLED
);
5796 sLong
character::GetMoveAPRequirement (int Difficulty
) const {
5797 return (!StateIsActivated(PANIC
) ? 10000000 : 8000000) * Difficulty
/ (APBonus(GetAttribute(AGILITY
)) * GetMoveEase());
5801 bodypart
*character::HealHitPoint() {
5802 int NeedHeal
= 0, NeedHealIndex
[MAX_BODYPARTS
];
5803 for (int c
= 0; c
< BodyParts
; ++c
) {
5804 bodypart
*BodyPart
= GetBodyPart(c
);
5805 if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < BodyPart
->GetMaxHP()) NeedHealIndex
[NeedHeal
++] = c
;
5808 bodypart
*BodyPart
= GetBodyPart(NeedHealIndex
[RAND() % NeedHeal
]);
5809 BodyPart
->IncreaseHP();
5817 void character::CreateHomeData () {
5818 HomeData
= new homedata
;
5819 lsquare
*Square
= GetLSquareUnder();
5820 HomeData
->Pos
= Square
->GetPos();
5821 HomeData
->Dungeon
= Square
->GetDungeonIndex();
5822 HomeData
->Level
= Square
->GetLevelIndex();
5823 HomeData
->Room
= Square
->GetRoomIndex();
5827 room
*character::GetHomeRoom() const {
5828 if (HomeDataIsValid() && HomeData
->Room
) return GetLevel()->GetRoom(HomeData
->Room
);
5833 void character::RemoveHomeData () {
5839 void character::AddESPConsumeMessage () const {
5840 if (IsPlayer()) ADD_MESSAGE("You feel a strange mental activity.");
5844 void character::SetBodyPart (int I
, bodypart
*What
) {
5845 BodyPartSlot
[I
].PutInItem(What
);
5847 What
->SignalPossibleUsabilityChange();
5849 AddOriginalBodyPartID(I
, What
->GetID());
5850 if (What
->GetMainMaterial()->IsInfectedByLeprosy()) GainIntrinsic(LEPROSY
);
5851 else if (StateIsActivated(LEPROSY
)) What
->GetMainMaterial()->SetIsInfectedByLeprosy(true);
5856 truth
character::ConsumeItem (item
*Item
, cfestring
&ConsumeVerb
) {
5857 if (IsPlayer() && HasHadBodyPart(Item
) && !game::TruthQuestion(CONST_S("Are you sure? You may be able to put it back...")))
5859 if (Item
->IsOnGround() && GetRoom() && !GetRoom()->ConsumeItem(this, Item
, 1))
5861 if (IsPlayer()) ADD_MESSAGE("You begin %s %s.", ConsumeVerb
.CStr(), Item
->CHAR_NAME(DEFINITE
));
5862 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins %s %s.", CHAR_NAME(DEFINITE
), ConsumeVerb
.CStr(), Item
->CHAR_NAME(DEFINITE
));
5863 consume
*Consume
= consume::Spawn(this);
5864 Consume
->SetDescription(ConsumeVerb
);
5865 Consume
->SetConsumingID(Item
->GetID());
5872 truth
character::CheckThrow () const {
5874 ADD_MESSAGE("This monster type cannot throw.");
5881 void character::GetHitByExplosion (const explosion
*Explosion
, int Damage
) {
5882 int DamageDirection
= GetPos() == Explosion
->Pos
? RANDOM_DIR
: game::CalculateRoughDirection(GetPos() - Explosion
->Pos
);
5883 if (!IsPet() && Explosion
->Terrorist
&& Explosion
->Terrorist
->IsPet()) Explosion
->Terrorist
->Hostility(this);
5884 GetTorso()->SpillBlood((8 - Explosion
->Size
+ RAND() % (8 - Explosion
->Size
)) >> 1);
5885 if (DamageDirection
== RANDOM_DIR
) DamageDirection
= RAND()&7;
5886 v2 SpillPos
= GetPos() + game::GetMoveVector(DamageDirection
);
5887 if (SquareUnder
[0] && GetArea()->IsValidPos(SpillPos
)) GetTorso()->SpillBlood((8-Explosion
->Size
+RAND()%(8-Explosion
->Size
))>>1, SpillPos
);
5888 if (IsPlayer()) ADD_MESSAGE("You are hit by the explosion!");
5889 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is hit by the explosion.", CHAR_NAME(DEFINITE
));
5890 truth WasUnconscious
= GetAction() && GetAction()->IsUnconsciousness();
5891 ReceiveDamage(Explosion
->Terrorist
, Damage
>> 1, FIRE
, ALL
, DamageDirection
, true, false, false, false);
5893 ReceiveDamage(Explosion
->Terrorist
, Damage
>> 1, PHYSICAL_DAMAGE
, ALL
, DamageDirection
, true, false, false, false);
5894 CheckDeath(Explosion
->DeathMsg
, Explosion
->Terrorist
, !WasUnconscious
? IGNORE_UNCONSCIOUSNESS
: 0);
5899 void character::SortAllItems (const sortdata
&SortData
) {
5900 GetStack()->SortAllItems(SortData
);
5901 doforequipmentswithparam
<const sortdata
&>()(this, &item::SortAllItems
, SortData
);
5905 void character::PrintBeginSearchingMessage () const {
5906 if (IsPlayer()) ADD_MESSAGE("You feel you can now notice even the very smallest details around you.");
5910 void character::PrintEndSearchingMessage () const {
5911 if (IsPlayer()) ADD_MESSAGE("You feel less perceptive.");
5915 void character::SearchingHandler () {
5916 if (!game::IsInWilderness()) Search(15);
5920 void character::Search (int Perception
) {
5921 for (int d
= 0; d
< GetExtendedNeighbourSquares(); ++d
) {
5922 lsquare
*LSquare
= GetNeighbourLSquare(d
);
5923 if (LSquare
) LSquare
->GetStack()->Search(this, Min(Perception
, 200));
5928 // surprisingly returns 0 if fails
5929 character
*character::GetRandomNeighbour (int RelationFlags
) const {
5930 character
*Chars
[MAX_NEIGHBOUR_SQUARES
];
5932 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
5933 lsquare
*LSquare
= GetNeighbourLSquare(d
);
5935 character
*Char
= LSquare
->GetCharacter();
5936 if (Char
&& (GetRelation(Char
) & RelationFlags
)) Chars
[Index
++] = Char
;
5939 return Index
? Chars
[RAND() % Index
] : 0;
5943 void character::ResetStates () {
5944 for (int c
= 0; c
< STATES
; ++c
) {
5945 if (1 << c
!= POLYMORPHED
&& TemporaryStateIsActivated(1 << c
) && TemporaryStateCounter
[c
] != PERMANENT
) {
5946 TemporaryState
&= ~(1 << c
);
5947 if (StateData
[c
].EndHandler
) {
5948 (this->*StateData
[c
].EndHandler
)();
5949 if (!IsEnabled())return;
5956 void characterdatabase::InitDefaults (const characterprototype
*NewProtoType
, int NewConfig
, cfestring
&acfgstrname
) {
5958 ProtoType
= NewProtoType
;
5960 CfgStrName
= acfgstrname
;
5965 void character::PrintBeginGasImmunityMessage () const {
5966 if (IsPlayer()) ADD_MESSAGE("All smells fade away.");
5970 void character::PrintEndGasImmunityMessage () const {
5971 if (IsPlayer()) ADD_MESSAGE("Yuck! The world smells bad again.");
5975 void character::ShowAdventureInfo () const {
5976 static const char *lists
[4][4] = {
5977 { "Show massacre history",
5979 "Show message history",
5982 "Show message history",
5985 { "Show message history",
5989 { "Show massacre history",
5990 "Show message history",
5994 // massacre, inventory, messages
5995 static const int nums
[4][3] = {
6002 if (GetStack()->GetItems()) {
6003 idx
= game::MassacreListsEmpty() ? 1 : 0;
6005 idx
= game::MassacreListsEmpty() ? 2 : 3;
6009 sel
= game::ListSelectorArray(sel
, CONST_S("Do you want to see some funny history?"), lists
[idx
]);
6011 if (sel
== nums
[idx
][0] && !game::MassacreListsEmpty()) {
6012 game::DisplayMassacreLists();
6014 if (sel
== nums
[idx
][1] && GetStack()->GetItems()) {
6015 GetStack()->DrawContents(this, CONST_S("Your inventory"), NO_SELECT
);
6016 for(stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) i
->DrawContents(this);
6017 doforequipmentswithparam
<ccharacter
*>()(this, &item::DrawContents
, this);
6019 if (sel
== nums
[idx
][2]) {
6020 msgsystem::DrawMessageHistory();
6026 truth
character::EditAllAttributes (int Amount
) {
6027 if (!Amount
) return true;
6029 truth MayEditMore
= false;
6030 for (c
= 0; c
< BodyParts
; ++c
) {
6031 bodypart
*BodyPart
= GetBodyPart(c
);
6032 if (BodyPart
&& BodyPart
->EditAllAttributes(Amount
)) MayEditMore
= true;
6034 for (c
= 0; c
< BASE_ATTRIBUTES
; ++c
) {
6035 if (BaseExperience
[c
]) {
6036 BaseExperience
[c
] += Amount
* EXP_MULTIPLIER
;
6037 LimitRef(BaseExperience
[c
], MIN_EXP
, MAX_EXP
);
6038 if ((Amount
< 0 && BaseExperience
[c
] != MIN_EXP
) || (Amount
> 0 && BaseExperience
[c
] != MAX_EXP
)) MayEditMore
= true;
6045 game::SendLOSUpdateRequest();
6048 if (IsPlayerKind()) UpdatePictures();
6054 void character::AddAttributeInfo (festring
&Entry
) const {
6056 Entry
<< GetAttribute(ENDURANCE
);
6058 Entry
<< GetAttribute(PERCEPTION
);
6060 Entry
<< GetAttribute(INTELLIGENCE
);
6062 Entry
<< GetAttribute(WISDOM
);
6064 Entry
<< GetAttribute(CHARISMA
);
6066 Entry
<< GetAttribute(MANA
);
6070 void character::AddDefenceInfo (felist
&List
) const {
6072 for (int c
= 0; c
< BodyParts
; ++c
) {
6073 bodypart
*BodyPart
= GetBodyPart(c
);
6075 Entry
= CONST_S(" ");
6076 BodyPart
->AddName(Entry
, UNARTICLED
);
6078 Entry
<< BodyPart
->GetMaxHP();
6080 Entry
<< BodyPart
->GetTotalResistance(PHYSICAL_DAMAGE
);
6081 List
.AddEntry(Entry
, LIGHT_GRAY
);
6087 void character::DetachBodyPart () {
6088 ADD_MESSAGE("You haven't got any extra bodyparts.");
6093 void character::ReceiveHolyBanana (sLong Amount
) {
6095 EditExperience(ARM_STRENGTH
, Amount
, 1 << 13);
6096 EditExperience(LEG_STRENGTH
, Amount
, 1 << 13);
6097 EditExperience(DEXTERITY
, Amount
, 1 << 13);
6098 EditExperience(AGILITY
, Amount
, 1 << 13);
6099 EditExperience(ENDURANCE
, Amount
, 1 << 13);
6100 EditExperience(PERCEPTION
, Amount
, 1 << 13);
6101 EditExperience(INTELLIGENCE
, Amount
, 1 << 13);
6102 EditExperience(WISDOM
, Amount
, 1 << 13);
6103 EditExperience(CHARISMA
, Amount
, 1 << 13);
6108 void character::AddHolyBananaConsumeEndMessage () const {
6109 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
6110 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE
));
6114 void character::ReceiveHolyMango (sLong Amount
) {
6116 EditExperience(ARM_STRENGTH
, Amount
, 1 << 13);
6117 EditExperience(LEG_STRENGTH
, Amount
, 1 << 13);
6118 EditExperience(DEXTERITY
, Amount
, 1 << 13);
6119 EditExperience(AGILITY
, Amount
, 1 << 13);
6120 EditExperience(ENDURANCE
, Amount
, 1 << 13);
6121 EditExperience(PERCEPTION
, Amount
, 1 << 13);
6122 EditExperience(INTELLIGENCE
, Amount
, 1 << 13);
6123 EditExperience(WISDOM
, Amount
, 1 << 13);
6124 EditExperience(CHARISMA
, Amount
, 1 << 13);
6129 void character::AddHolyMangoConsumeEndMessage () const {
6130 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
6131 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE
));
6135 truth
character::PreProcessForBone () {
6136 if (IsPet() && IsEnabled()) {
6137 Die(0, CONST_S(""), FORBID_REINCARNATION
);
6140 if (GetAction()) GetAction()->Terminate(false);
6141 if (TemporaryStateIsActivated(POLYMORPHED
)) {
6142 character
*PolymorphBackup
= GetPolymorphBackup();
6144 PolymorphBackup
->PreProcessForBone();
6147 if (MustBeRemovedFromBone()) return false;
6148 if (IsUnique() && !CanBeGenerated()) game::SignalQuestMonsterFound();
6152 GetStack()->PreProcessForBone();
6153 doforequipments()(this, &item::PreProcessForBone
);
6154 doforbodyparts()(this, &bodypart::PreProcessForBone
);
6155 game::RemoveCharacterID(ID
);
6157 game::AddCharacterID(this, ID
);
6162 truth
character::PostProcessForBone (double &DangerSum
, int& Enemies
) {
6163 if (PostProcessForBone()) {
6164 if (GetRelation(PLAYER
) == HOSTILE
) {
6165 double Danger
= GetRelativeDanger(PLAYER
, true);
6166 if (Danger
> 99.0) game::SetTooGreatDangerFound(true);
6167 else if (!IsUnique() && !IgnoreDanger()) {
6168 DangerSum
+= Danger
;
6178 truth
character::PostProcessForBone () {
6179 feuLong NewID
= game::CreateNewCharacterID(this);
6180 game::GetBoneCharacterIDMap().insert(std::make_pair(-ID
, NewID
));
6181 game::RemoveCharacterID(ID
);
6183 if (IsUnique() && CanBeGenerated()) {
6184 if (DataBase
->Flags
& HAS_BEEN_GENERATED
) return false;
6187 GetStack()->PostProcessForBone();
6188 doforequipments()(this, &item::PostProcessForBone
);
6189 doforbodyparts()(this, &bodypart::PostProcessForBone
);
6194 void character::FinalProcessForBone () {
6196 GetStack()->FinalProcessForBone();
6197 doforequipments()(this, &item::FinalProcessForBone
);
6199 for (c
= 0; c
< BodyParts
; ++c
) {
6200 for (std::list
<feuLong
>::iterator i
= OriginalBodyPartID
[c
].begin(); i
!= OriginalBodyPartID
[c
].end();) {
6201 boneidmap::iterator BI
= game::GetBoneItemIDMap().find(*i
);
6202 if (BI
== game::GetBoneItemIDMap().end()) {
6203 std::list
<feuLong
>::iterator Dirt
= i
++;
6204 OriginalBodyPartID
[c
].erase(Dirt
);
6214 void character::SetSoulID (feuLong What
) {
6215 if (GetPolymorphBackup()) GetPolymorphBackup()->SetSoulID(What
);
6219 truth
character::SearchForItem (citem
*Item
) const {
6220 if (combineequipmentpredicateswithparam
<feuLong
>()(this, &item::HasID
, Item
->GetID(), 1)) return true;
6221 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (*i
== Item
) return true;
6226 item
*character::SearchForItem (const sweaponskill
*SWeaponSkill
) const {
6227 for (int c
= 0; c
< GetEquipments(); ++c
) {
6228 item
*Equipment
= GetEquipment(c
);
6229 if (Equipment
&& SWeaponSkill
->IsSkillOf(Equipment
)) return Equipment
;
6231 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (SWeaponSkill
->IsSkillOf(*i
)) return *i
;
6236 void character::PutNear (v2 Pos
) {
6237 v2 NewPos
= game::GetCurrentLevel()->GetNearestFreeSquare(this, Pos
, false);
6238 if (NewPos
== ERROR_V2
) {
6239 do { NewPos
= game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos
== Pos
);
6245 void character::PutToOrNear (v2 Pos
) {
6246 if (game::IsInWilderness() || (CanMoveOn(game::GetCurrentLevel()->GetLSquare(Pos
)) && IsFreeForMe(game::GetCurrentLevel()->GetLSquare(Pos
))))
6253 void character::PutTo (v2 Pos
) {
6254 SquareUnder
[0] = game::GetCurrentArea()->GetSquare(Pos
);
6255 SquareUnder
[0]->AddCharacter(this);
6259 void character::Remove () {
6260 SquareUnder
[0]->RemoveCharacter();
6265 void character::SendNewDrawRequest () const {
6266 for (int c
= 0; c
< SquaresUnder
; ++c
) {
6267 square
*Square
= GetSquareUnder(c
);
6268 if (Square
) Square
->SendNewDrawRequest();
6273 truth
character::IsOver (v2 Pos
) const {
6274 for (int c
= 0; c
< SquaresUnder
; ++c
) {
6275 square
*Square
= GetSquareUnder(c
);
6276 if (Square
&& Square
->GetPos() == Pos
) return true;
6282 truth
character::CanTheoreticallyMoveOn (const lsquare
*LSquare
) const { return GetMoveType() & LSquare
->GetTheoreticalWalkability(); }
6283 truth
character::CanMoveOn (const lsquare
*LSquare
) const { return GetMoveType() & LSquare
->GetWalkability(); }
6284 truth
character::CanMoveOn (const square
*Square
) const { return GetMoveType() & Square
->GetSquareWalkability(); }
6285 truth
character::CanMoveOn (const olterrain
*OLTerrain
) const { return GetMoveType() & OLTerrain
->GetWalkability(); }
6286 truth
character::CanMoveOn (const oterrain
*OTerrain
) const { return GetMoveType() & OTerrain
->GetWalkability(); }
6287 truth
character::IsFreeForMe(square
*Square
) const { return !Square
->GetCharacter() || Square
->GetCharacter() == this; }
6288 void character::LoadSquaresUnder () { SquareUnder
[0] = game::GetSquareInLoad(); }
6290 truth
character::AttackAdjacentEnemyAI () {
6291 if (!IsEnabled()) return false;
6292 character
*Char
[MAX_NEIGHBOUR_SQUARES
];
6293 v2 Pos
[MAX_NEIGHBOUR_SQUARES
];
6294 int Dir
[MAX_NEIGHBOUR_SQUARES
];
6296 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
6297 square
*Square
= GetNeighbourSquare(d
);
6299 character
*Enemy
= Square
->GetCharacter();
6300 if (Enemy
&& (GetRelation(Enemy
) == HOSTILE
|| StateIsActivated(CONFUSED
))) {
6302 Pos
[Index
] = Square
->GetPos();
6303 Char
[Index
++] = Enemy
;
6308 int ChosenIndex
= RAND() % Index
;
6309 Hit(Char
[ChosenIndex
], Pos
[ChosenIndex
], Dir
[ChosenIndex
]);
6316 void character::SignalStepFrom (lsquare
**OldSquareUnder
) {
6318 lsquare
*NewSquareUnder
[MAX_SQUARES_UNDER
];
6319 for (c
= 0; c
< GetSquaresUnder(); ++c
) NewSquareUnder
[c
] = GetLSquareUnder(c
);
6320 for (c
= 0; c
< GetSquaresUnder(); ++c
) {
6321 if (IsEnabled() && GetLSquareUnder(c
) == NewSquareUnder
[c
]) NewSquareUnder
[c
]->StepOn(this, OldSquareUnder
);
6326 int character::GetSumOfAttributes () const {
6327 return GetAttribute(ENDURANCE
) + GetAttribute(PERCEPTION
) + GetAttribute(INTELLIGENCE
) + GetAttribute(WISDOM
) + GetAttribute(CHARISMA
) + GetAttribute(ARM_STRENGTH
) + GetAttribute(AGILITY
);
6331 void character::IntelligenceAction (int Difficulty
) {
6332 EditAP(-20000 * Difficulty
/ APBonus(GetAttribute(INTELLIGENCE
)));
6333 EditExperience(INTELLIGENCE
, Difficulty
* 15, 1 << 7);
6337 struct walkabilitycontroller
{
6338 static truth
Handler (int x
, int y
) {
6339 return x
>= 0 && y
>= 0 && x
< LevelXSize
&& y
< LevelYSize
&& Map
[x
][y
]->GetTheoreticalWalkability() & MoveType
;
6341 static lsquare
***Map
;
6342 static int LevelXSize
, LevelYSize
;
6343 static int MoveType
;
6347 lsquare
***walkabilitycontroller::Map
;
6348 int walkabilitycontroller::LevelXSize
, walkabilitycontroller::LevelYSize
;
6349 int walkabilitycontroller::MoveType
;
6352 truth
character::CreateRoute () {
6354 if (GetAttribute(INTELLIGENCE
) >= 10 && !StateIsActivated(CONFUSED
)) {
6356 walkabilitycontroller::Map
= GetLevel()->GetMap();
6357 walkabilitycontroller::LevelXSize
= GetLevel()->GetXSize();
6358 walkabilitycontroller::LevelYSize
= GetLevel()->GetYSize();
6359 walkabilitycontroller::MoveType
= GetMoveType();
6361 for (int c
= 0; c
< game::GetTeams(); ++c
)
6362 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
6363 character
*Char
= *i
;
6364 if (Char
->IsEnabled() && !Char
->Route
.empty() && (Char
->GetMoveType()&GetMoveType()) == Char
->GetMoveType()) {
6365 v2 CharGoingTo
= Char
->Route
[0];
6366 v2 iPos
= Char
->Route
.back();
6367 if ((GoingTo
-CharGoingTo
).GetLengthSquare() <= 100 && (Pos
- iPos
).GetLengthSquare() <= 100 &&
6368 mapmath
<walkabilitycontroller
>::DoLine(CharGoingTo
.X
, CharGoingTo
.Y
, GoingTo
.X
, GoingTo
.Y
, SKIP_FIRST
) &&
6369 mapmath
<walkabilitycontroller
>::DoLine(Pos
.X
, Pos
.Y
, iPos
.X
, iPos
.Y
, SKIP_FIRST
)) {
6370 if (!Illegal
.empty() && Illegal
.find(Char
->Route
.back()) != Illegal
.end()) continue;
6371 Node
= GetLevel()->FindRoute(CharGoingTo
, GoingTo
, Illegal
, GetMoveType());
6372 if (Node
) { while(Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
6373 else { Route
.clear(); continue; }
6374 Route
.insert(Route
.end(), Char
->Route
.begin(), Char
->Route
.end());
6375 Node
= GetLevel()->FindRoute(Pos
, iPos
, Illegal
, GetMoveType());
6376 if (Node
) { while (Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
6377 else { Route
.clear(); continue; }
6378 IntelligenceAction(1);
6383 Node
= GetLevel()->FindRoute(Pos
, GoingTo
, Illegal
, GetMoveType());
6384 if (Node
) { while(Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
6385 else TerminateGoingTo();
6386 IntelligenceAction(5);
6393 void character::SetGoingTo (v2 What
) {
6394 if (GoingTo
!= What
) {
6402 void character::TerminateGoingTo () {
6409 truth
character::CheckForFood (int Radius
) {
6410 if (StateIsActivated(PANIC
) || !UsesNutrition() || !IsEnabled()) return false;
6413 for (int r
= 1; r
<= Radius
; ++r
) {
6416 for (y
= Pos
.Y
-r
; y
<= Pos
.Y
+r
; ++y
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6419 if (x
< GetLevel()->GetXSize()) {
6420 for (y
= Pos
.Y
-r
; y
<= Pos
.Y
+r
; ++y
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6424 for (x
= Pos
.X
-r
; x
<= Pos
.X
+r
; ++x
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6427 if (y
< GetLevel()->GetYSize()) {
6428 for (x
= Pos
.X
-r
; x
<= Pos
.X
+r
; ++x
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6435 truth
character::CheckForFoodInSquare (v2 Pos
) {
6436 level
*Level
= GetLevel();
6437 if (Level
->IsValidPos(Pos
)) {
6438 lsquare
*Square
= Level
->GetLSquare(Pos
);
6439 stack
*Stack
= Square
->GetStack();
6440 if (Stack
->GetItems()) {
6441 for (stackiterator i
= Stack
->GetBottom(); i
.HasItem(); ++i
) {
6442 if (i
->IsPickable(this) && i
->CanBeSeenBy(this) && i
->CanBeEatenByAI(this) && (!Square
->GetRoomIndex() || Square
->GetRoom()->AllowFoodSearch())) {
6444 return MoveTowardsTarget(false);
6453 void character::SetConfig (int NewConfig
, int SpecialFlags
) {
6454 databasecreator
<character
>::InstallDataBase(this, NewConfig
);
6457 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdatePictures();
6461 truth
character::IsOver (citem
*Item
) const {
6462 for (int c1
= 0; c1
< Item
->GetSquaresUnder(); ++c1
)
6463 for (int c2
= 0; c2
< SquaresUnder
; ++c2
)
6464 if (Item
->GetPos(c1
) == GetPos(c2
)) return true;
6469 truth
character::CheckConsume (cfestring
&Verb
) const {
6470 if (!UsesNutrition()) {
6471 if (IsPlayer()) ADD_MESSAGE("In this form you can't and don't need to %s.", Verb
.CStr());
6478 void character::PutTo (lsquare
*To
) {
6479 PutTo(To
->GetPos());
6483 double character::RandomizeBabyExperience (double SumE
) {
6484 if (!SumE
) return 0;
6485 double E
= (SumE
/ 4) - (SumE
/ 32) + (double(RAND()) / MAX_RAND
) * (SumE
/ 16 + 1);
6486 return Limit(E
, MIN_EXP
, MAX_EXP
);
6490 liquid
*character::CreateBlood (sLong Volume
) const {
6491 return liquid::Spawn(GetBloodMaterial(), Volume
);
6495 void character::SpillFluid (character
*Spiller
, liquid
*Liquid
, int SquareIndex
) {
6496 sLong ReserveVolume
= Liquid
->GetVolume() >> 1;
6497 Liquid
->EditVolume(-ReserveVolume
);
6498 GetStack()->SpillFluid(Spiller
, Liquid
, sLong(Liquid
->GetVolume() * sqrt(double(GetStack()->GetVolume()) / GetVolume())));
6499 Liquid
->EditVolume(ReserveVolume
);
6501 sLong Modifier
[MAX_BODYPARTS
], ModifierSum
= 0;
6502 for (c
= 0; c
< BodyParts
; ++c
) {
6503 if (GetBodyPart(c
)) {
6504 Modifier
[c
] = sLong(sqrt(GetBodyPart(c
)->GetVolume()));
6505 if (Modifier
[c
]) Modifier
[c
] *= 1 + (RAND() & 3);
6506 ModifierSum
+= Modifier
[c
];
6511 for (c
= 1; c
< GetBodyParts(); ++c
) {
6512 if (GetBodyPart(c
) && IsEnabled())
6513 GetBodyPart(c
)->SpillFluid(Spiller
, Liquid
->SpawnMoreLiquid(Liquid
->GetVolume() * Modifier
[c
] / ModifierSum
), SquareIndex
);
6516 Liquid
->SetVolume(Liquid
->GetVolume() * Modifier
[TORSO_INDEX
] / ModifierSum
);
6517 GetTorso()->SpillFluid(Spiller
, Liquid
, SquareIndex
);
6522 void character::StayOn (liquid
*Liquid
) {
6523 Liquid
->TouchEffect(this, TORSO_INDEX
);
6527 truth
character::IsAlly (ccharacter
*Char
) const {
6528 return Char
->GetTeam()->GetID() == GetTeam()->GetID();
6532 void character::ResetSpoiling () {
6533 doforbodyparts()(this, &bodypart::ResetSpoiling
);
6537 item
*character::SearchForItem (ccharacter
*Char
, sorter Sorter
) const {
6538 item
*Equipment
= findequipment
<ccharacter
*>()(this, Sorter
, Char
);
6539 if (Equipment
) return Equipment
;
6540 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (((*i
)->*Sorter
)(Char
)) return *i
;
6545 truth
character::DetectMaterial (cmaterial
*Material
) const {
6546 return GetStack()->DetectMaterial(Material
) ||
6547 combinebodypartpredicateswithparam
<cmaterial
*>()(this, &bodypart::DetectMaterial
, Material
, 1) ||
6548 combineequipmentpredicateswithparam
<cmaterial
*>()(this, &item::DetectMaterial
, Material
, 1);
6552 truth
character::DamageTypeDestroysBodyPart (int Type
) {
6553 return (Type
&0xFFF) != PHYSICAL_DAMAGE
;
6557 truth
character::CheckIfTooScaredToHit (ccharacter
*Enemy
) const {
6558 if (IsPlayer() && StateIsActivated(PANIC
)) {
6559 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
6560 square
*Square
= GetNeighbourSquare(d
);
6562 if(CanMoveOn(Square
) && (!Square
->GetCharacter() || Square
->GetCharacter()->IsPet())) {
6563 ADD_MESSAGE("You are too scared to attack %s.", Enemy
->CHAR_DESCRIPTION(DEFINITE
));
6573 void character::PrintBeginLevitationMessage () const {
6575 if (IsPlayer()) ADD_MESSAGE("You rise into the air like a small hot-air balloon.");
6576 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins to float.", CHAR_NAME(DEFINITE
));
6581 void character::PrintEndLevitationMessage () const {
6583 if (IsPlayer()) ADD_MESSAGE("You descend gently onto the ground.");
6584 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s drops onto the ground.", CHAR_NAME(DEFINITE
));
6589 truth
character::IsLimbIndex (int I
) {
6591 case RIGHT_ARM_INDEX
:
6592 case LEFT_ARM_INDEX
:
6593 case RIGHT_LEG_INDEX
:
6594 case LEFT_LEG_INDEX
:
6601 void character::EditExperience (int Identifier
, double Value
, double Speed
) {
6602 if (!AllowExperience() || (Identifier
== ENDURANCE
&& UseMaterialAttributes())) return;
6603 int Change
= RawEditExperience(BaseExperience
[Identifier
], GetNaturalExperience(Identifier
), Value
, Speed
);
6604 if (!Change
) return;
6605 cchar
*PlayerMsg
= 0, *NPCMsg
= 0;
6606 switch (Identifier
) {
6609 PlayerMsg
= "You feel tougher than anything!";
6610 if (IsPet()) NPCMsg
= "Suddenly %s looks tougher.";
6612 PlayerMsg
= "You feel less healthy.";
6613 if (IsPet()) NPCMsg
= "Suddenly %s looks less healthy.";
6615 CalculateBodyPartMaxHPs();
6616 CalculateMaxStamina();
6621 PlayerMsg
= "You now see the world in much better detail than before.";
6623 PlayerMsg
= "You feel very guru.";
6624 game::GetGod(VALPURUS
)->AdjustRelation(100);
6626 game::SendLOSUpdateRequest();
6631 if (Change
> 0) PlayerMsg
= "Suddenly the inner structure of the Multiverse around you looks quite simple.";
6632 else PlayerMsg
= "It surely is hard to think today.";
6635 if (IsPlayerKind()) UpdatePictures();
6639 if (Change
> 0) PlayerMsg
= "You feel your life experience increasing all the time.";
6640 else PlayerMsg
= "You feel like having done something unwise.";
6642 if (IsPlayerKind()) UpdatePictures();
6646 PlayerMsg
= "You feel very confident of your social skills.";
6648 if (GetAttribute(CHARISMA
) <= 15) NPCMsg
= "%s looks less ugly.";
6649 else NPCMsg
= "%s looks more attractive.";
6652 PlayerMsg
= "You feel somehow disliked.";
6654 if (GetAttribute(CHARISMA
) < 15) NPCMsg
= "%s looks more ugly.";
6655 else NPCMsg
= "%s looks less attractive.";
6658 if (IsPlayerKind()) UpdatePictures();
6662 PlayerMsg
= "You feel magical forces coursing through your body!";
6663 NPCMsg
= "You notice an odd glow around %s.";
6665 PlayerMsg
= "You feel your magical abilities withering slowly.";
6666 NPCMsg
= "You notice strange vibrations in the air around %s. But they disappear rapidly.";
6671 if (IsPlayer()) ADD_MESSAGE("%s", PlayerMsg
);
6672 else if (NPCMsg
&& CanBeSeenByPlayer()) ADD_MESSAGE(NPCMsg
, CHAR_NAME(DEFINITE
));
6674 CalculateBattleInfo();
6678 int character::RawEditExperience (double &Exp
, double NaturalExp
, double Value
, double Speed
) const {
6679 double OldExp
= Exp
;
6684 if(!OldExp
|| !Value
|| (Value
> 0 && OldExp
>= NaturalExp
* (100 + Value
) / 100) ||
6685 (Value
< 0 && OldExp
<= NaturalExp
* (100 + Value
) / 100)) return 0;
6686 if (!IsPlayer()) Speed
*= 1.5;
6687 Exp
+= (NaturalExp
* (100 + Value
) - 100 * OldExp
) * Speed
* EXP_DIVISOR
;
6688 LimitRef(Exp
, MIN_EXP
, MAX_EXP
);
6689 int NewA
= int(Exp
* EXP_DIVISOR
);
6690 int OldA
= int(OldExp
* EXP_DIVISOR
);
6691 int Delta
= NewA
- OldA
;
6692 if (Delta
> 0) Exp
= Max(Exp
, (NewA
+ 0.05) * EXP_MULTIPLIER
);
6693 else if (Delta
< 0) Exp
= Min(Exp
, (NewA
+ 0.95) * EXP_MULTIPLIER
);
6694 LimitRef(Exp
, MIN_EXP
, MAX_EXP
);
6699 int character::GetAttribute (int Identifier
, truth AllowBonus
) const {
6700 int A
= int(BaseExperience
[Identifier
] * EXP_DIVISOR
);
6701 if (AllowBonus
&& Identifier
== INTELLIGENCE
&& BrainsHurt()) return Max((A
+ AttributeBonus
[INTELLIGENCE
]) / 3, 1);
6702 return A
&& AllowBonus
? Max(A
+ AttributeBonus
[Identifier
], 1) : A
;
6706 void characterdatabase::PostProcess () {
6707 double AM
= (100 + AttributeBonus
) * EXP_MULTIPLIER
/ 100;
6708 for (int c
= 0; c
< ATTRIBUTES
; ++c
) NaturalExperience
[c
] = this->*ExpPtr
[c
] * AM
;
6712 void character::EditDealExperience (sLong Price
) {
6713 EditExperience(CHARISMA
, sqrt(Price
) / 5, 1 << 9);
6717 void character::PrintBeginLeprosyMessage () const {
6718 if (IsPlayer()) ADD_MESSAGE("You feel you're falling in pieces.");
6722 void character::PrintEndLeprosyMessage () const {
6723 if (IsPlayer()) ADD_MESSAGE("You feel your limbs are stuck in place tightly."); // CHANGE OR DIE
6727 void character::TryToInfectWithLeprosy (ccharacter
*Infector
) {
6728 if (!IsImmuneToLeprosy() &&
6729 ((GetRelation(Infector
) == HOSTILE
&& !RAND_N(50 * GetAttribute(ENDURANCE
))) ||
6730 !RAND_N(500 * GetAttribute(ENDURANCE
)))) GainIntrinsic(LEPROSY
);
6734 void character::SignalGeneration () {
6735 const_cast<database
*>(DataBase
)->Flags
|= HAS_BEEN_GENERATED
;
6739 void character::CheckIfSeen () {
6740 if (IsPlayer() || CanBeSeenByPlayer()) SignalSeen();
6744 void character::SignalSeen () {
6745 if (!(WarnFlags
& WARNED
) && GetRelation(PLAYER
) == HOSTILE
&& !StateIsActivated(FEARLESS
)) {
6746 double Danger
= GetRelativeDanger(PLAYER
);
6748 game::SetDangerFound(Max(game::GetDangerFound(), Danger
));
6749 if (Danger
> 500.0 && !(WarnFlags
& HAS_CAUSED_PANIC
)) {
6750 WarnFlags
|= HAS_CAUSED_PANIC
;
6751 game::SetCausePanicFlag(true);
6753 WarnFlags
|= WARNED
;
6756 const_cast<database
*>(DataBase
)->Flags
|= HAS_BEEN_SEEN
;
6760 int character::GetPolymorphIntelligenceRequirement () const {
6761 if (DataBase
->PolymorphIntelligenceRequirement
== DEPENDS_ON_ATTRIBUTES
) return Max(GetAttributeAverage() - 5, 0);
6762 return DataBase
->PolymorphIntelligenceRequirement
;
6766 void character::RemoveAllItems () {
6767 GetStack()->Clean();
6768 for (int c
= 0; c
< GetEquipments(); ++c
) {
6769 item
*Equipment
= GetEquipment(c
);
6771 Equipment
->RemoveFromSlot();
6772 Equipment
->SendToHell();
6778 int character::CalculateWeaponSkillHits (ccharacter
*Enemy
) const {
6779 if (Enemy
->IsPlayer()) {
6780 configid
ConfigID(GetType(), GetConfig());
6781 const dangerid
& DangerID
= game::GetDangerMap().find(ConfigID
)->second
;
6782 return Min(int(DangerID
.EquippedDanger
* 2000), 1000);
6784 return Min(int(GetRelativeDanger(Enemy
, true) * 2000), 1000);
6788 truth
character::CanUseEquipment (int I
) const {
6789 return CanUseEquipment() && I
< GetEquipments() && GetBodyPartOfEquipment(I
) && EquipmentIsAllowed(I
);
6793 /* Target mustn't have any equipment */
6794 void character::DonateEquipmentTo (character
*Character
) {
6796 feuLong
*EquipmentMemory
= game::GetEquipmentMemory();
6797 for (int c
= 0; c
< MAX_EQUIPMENT_SLOTS
; ++c
) {
6798 item
*Item
= GetEquipment(c
);
6800 if (Character
->CanUseEquipment(c
)) {
6801 Item
->RemoveFromSlot();
6802 Character
->SetEquipment(c
, Item
);
6804 EquipmentMemory
[c
] = Item
->GetID();
6805 Item
->MoveTo(Character
->GetStack());
6807 } else if (CanUseEquipment(c
)) {
6808 EquipmentMemory
[c
] = 0;
6809 } else if (EquipmentMemory
[c
] && Character
->CanUseEquipment(c
)) {
6810 for (stackiterator i
= Character
->GetStack()->GetBottom(); i
.HasItem(); ++i
) {
6811 if (i
->GetID() == EquipmentMemory
[c
]) {
6813 Item
->RemoveFromSlot();
6814 Character
->SetEquipment(c
, Item
);
6818 EquipmentMemory
[c
] = 0;
6822 for (int c
= 0; c
< GetEquipments(); ++c
) {
6823 item
*Item
= GetEquipment(c
);
6825 if (Character
->CanUseEquipment(c
)) {
6826 Item
->RemoveFromSlot();
6827 Character
->SetEquipment(c
, Item
);
6829 Item
->MoveTo(Character
->GetStackUnder());
6837 void character::ReceivePeaSoup (sLong
) {
6838 if (!game::IsInWilderness()) {
6839 lsquare
*Square
= GetLSquareUnder();
6840 if (Square
->IsFlyable()) Square
->AddSmoke(gas::Spawn(FART
, 250));
6845 void character::AddPeaSoupConsumeEndMessage () const {
6847 if (CanHear()) ADD_MESSAGE("Mmmh! The soup is very tasty. You hear a small puff.");
6848 else ADD_MESSAGE("Mmmh! The soup is very tasty.");
6849 } else if (CanBeSeenByPlayer() && PLAYER
->CanHear()) {
6851 ADD_MESSAGE("You hear a small puff.");
6856 void character::CalculateMaxStamina () {
6857 MaxStamina
= TorsoIsAlive() ? GetAttribute(ENDURANCE
) * 10000 : 0;
6861 void character::EditStamina (int Amount
, truth CanCauseUnconsciousness
) {
6862 if (!TorsoIsAlive()) return;
6863 int UnconsciousnessStamina
= MaxStamina
>> 3;
6864 if (!CanCauseUnconsciousness
&& Amount
< 0) {
6865 if (Stamina
> UnconsciousnessStamina
) {
6867 if (Stamina
< UnconsciousnessStamina
) Stamina
= UnconsciousnessStamina
;
6871 int OldStamina
= Stamina
;
6873 if (Stamina
> MaxStamina
) {
6874 Stamina
= MaxStamina
;
6875 } else if (Stamina
< 0) {
6877 LoseConsciousness(250 + RAND_N(250));
6878 } else if (IsPlayer()) {
6879 if (OldStamina
>= MaxStamina
>> 2 && Stamina
< MaxStamina
>> 2) {
6880 ADD_MESSAGE("You are getting a little tired.");
6881 } else if(OldStamina
>= UnconsciousnessStamina
&& Stamina
< UnconsciousnessStamina
) {
6882 ADD_MESSAGE("You are seriously out of breath!");
6883 game::SetPlayerIsRunning(false);
6886 if (IsPlayer() && StateIsActivated(PANIC
) && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6890 void character::RegenerateStamina () {
6891 if (GetTirednessState() != UNTIRED
) {
6892 EditExperience(ENDURANCE
, 50, 1);
6893 if (Sweats() && TorsoIsAlive() && !RAND_N(30) && !game::IsInWilderness()) {
6894 // Sweat amount proportional to endurance also
6895 //sLong Volume = sLong(0.05 * sqrt(GetBodyVolume()));
6896 sLong Volume
= long(0.05*sqrt(GetBodyVolume()*GetAttribute(ENDURANCE
)/10));
6897 if (GetTirednessState() == FAINTING
) Volume
<<= 1;
6898 for (int c
= 0; c
< SquaresUnder
; ++c
) GetLSquareUnder(c
)->SpillFluid(0, CreateSweat(Volume
), false, false);
6903 if (Action
->IsRest()) {
6904 if (SquaresUnder
== 1) {
6905 Bonus
= GetSquareUnder()->GetRestModifier() << 1;
6907 int Lowest
= GetSquareUnder(0)->GetRestModifier();
6908 for (int c
= 1; c
< GetSquaresUnder(); ++c
) {
6909 int Mod
= GetSquareUnder(c
)->GetRestModifier();
6910 if (Mod
< Lowest
) Lowest
= Mod
;
6912 Bonus
= Lowest
<< 1;
6914 } else if (Action
->IsUnconsciousness()) Bonus
= 2;
6917 switch (GetBurdenState()) {
6918 case OVER_LOADED
: Plus1
= 25; break;
6919 case STRESSED
: Plus1
= 50; break;
6920 case BURDENED
: Plus1
= 75; break;
6924 switch (GetHungerState()) {
6925 case STARVING
: Plus2
= 25; break;
6926 case VERY_HUNGRY
: Plus2
= 50; break;
6927 case HUNGRY
: Plus2
= 75; break;
6930 Stamina
+= Plus1
* Plus2
* Bonus
/ 1000;
6931 if (Stamina
> MaxStamina
) Stamina
= MaxStamina
;
6932 if (IsPlayer() && StateIsActivated(PANIC
) && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6936 void character::BeginPanic () {
6937 if (IsPlayer() && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6938 DeActivateVoluntaryAction();
6942 void character::EndPanic () {
6943 if (IsPlayer()) game::SetPlayerIsRunning(false);
6947 int character::GetTirednessState () const {
6948 if (Stamina
>= MaxStamina
>> 2) return UNTIRED
;
6949 if (Stamina
>= MaxStamina
>> 3) return EXHAUSTED
;
6954 void character::ReceiveBlackUnicorn (sLong Amount
) {
6955 if (!(RAND() % 160)) game::DoEvilDeed(Amount
/ 50);
6956 BeginTemporaryState(TELEPORT
, Amount
/ 100);
6957 for (int c
= 0; c
< STATES
; ++c
) {
6958 if (StateData
[c
].Flags
& DUR_TEMPORARY
) {
6959 BeginTemporaryState(1 << c
, Amount
/ 100);
6960 if (!IsEnabled()) return;
6961 } else if (StateData
[c
].Flags
& DUR_PERMANENT
) {
6962 GainIntrinsic(1 << c
);
6963 if (!IsEnabled()) return;
6969 void character::ReceiveGrayUnicorn (sLong Amount
) {
6970 if (!(RAND() % 80)) game::DoEvilDeed(Amount
/ 50);
6971 BeginTemporaryState(TELEPORT
, Amount
/ 100);
6972 for (int c
= 0; c
< STATES
; ++c
) {
6973 if (1 << c
!= TELEPORT
) {
6974 DecreaseStateCounter(1 << c
, -Amount
/ 100);
6975 if (!IsEnabled()) return;
6981 void character::ReceiveWhiteUnicorn (sLong Amount
) {
6982 if (!(RAND() % 40)) game::DoEvilDeed(Amount
/ 50);
6983 BeginTemporaryState(TELEPORT
, Amount
/ 100);
6984 DecreaseStateCounter(LYCANTHROPY
, -Amount
/ 100);
6985 DecreaseStateCounter(POISONED
, -Amount
/ 100);
6986 DecreaseStateCounter(PARASITIZED
, -Amount
/ 100);
6987 DecreaseStateCounter(LEPROSY
, -Amount
/ 100);
6988 DecreaseStateCounter(VAMPIRISM
, -Amount
/ 100);
6992 /* Counter should be negative. Removes intrinsics. */
6993 void character::DecreaseStateCounter (sLong State
, int Counter
) {
6995 for (Index
= 0; Index
< STATES
; ++Index
) if (1 << Index
== State
) break;
6996 if (Index
== STATES
) ABORT("DecreaseTemporaryStateCounter works only when State == 2^n!");
6997 if (TemporaryState
& State
) {
6998 if (TemporaryStateCounter
[Index
] == PERMANENT
|| (TemporaryStateCounter
[Index
] += Counter
) <= 0) {
6999 TemporaryState
&= ~State
;
7000 if (!(EquipmentState
& State
)) {
7001 if (StateData
[Index
].EndHandler
) {
7002 (this->*StateData
[Index
].EndHandler
)();
7003 if (!IsEnabled()) return;
7005 (this->*StateData
[Index
].PrintEndMessage
)();
7012 truth
character::IsImmuneToLeprosy () const {
7013 return DataBase
->IsImmuneToLeprosy
|| UseMaterialAttributes();
7017 void character::LeprosyHandler () {
7018 EditExperience(ARM_STRENGTH
, -25, 1 << 1);
7019 EditExperience(LEG_STRENGTH
, -25, 1 << 1);
7020 EditExperience(DEXTERITY
, -25, 1 << 1);
7021 EditExperience(AGILITY
, -25, 1 << 1);
7022 EditExperience(ENDURANCE
, -25, 1 << 1);
7023 EditExperience(CHARISMA
, -25, 1 << 1);
7024 CheckDeath(CONST_S("killed by leprosy"));
7028 bodypart
*character::SearchForOriginalBodyPart (int I
) const {
7029 for (stackiterator i1
= GetStackUnder()->GetBottom(); i1
.HasItem(); ++i1
) {
7030 for (std::list
<feuLong
>::iterator i2
= OriginalBodyPartID
[I
].begin(); i2
!= OriginalBodyPartID
[I
].end(); ++i2
)
7031 if (i1
->GetID() == *i2
) return static_cast<bodypart
*>(*i1
);
7037 void character::SetLifeExpectancy (int Base
, int RandPlus
) {
7039 for (c
= 0; c
< BodyParts
; ++c
) {
7040 bodypart
*BodyPart
= GetBodyPart(c
);
7041 if (BodyPart
) BodyPart
->SetLifeExpectancy(Base
, RandPlus
);
7043 for (c
= 0; c
< GetEquipments(); ++c
) {
7044 item
*Equipment
= GetEquipment(c
);
7045 if (Equipment
) Equipment
->SetLifeExpectancy(Base
, RandPlus
);
7050 /* Receiver should be a fresh duplicate of this */
7051 void character::DuplicateEquipment (character
*Receiver
, feuLong Flags
) {
7052 for (int c
= 0; c
< GetEquipments(); ++c
) {
7053 item
*Equipment
= GetEquipment(c
);
7055 item
*Duplicate
= Equipment
->Duplicate(Flags
);
7056 Receiver
->SetEquipment(c
, Duplicate
);
7062 void character::Disappear (corpse
*Corpse
, cchar
*Verb
, truth (item::*ClosePredicate
)() const) {
7063 truth TorsoDisappeared
= false;
7064 truth CanBeSeen
= Corpse
? Corpse
->CanBeSeenByPlayer() : IsPlayer() || CanBeSeenByPlayer();
7066 if ((GetTorso()->*ClosePredicate
)()) {
7068 if (Corpse
) ADD_MESSAGE("%s %ss.", Corpse
->CHAR_NAME(DEFINITE
), Verb
);
7069 else if (IsPlayer()) ADD_MESSAGE("You %s.", Verb
);
7070 else ADD_MESSAGE("%s %ss.", CHAR_NAME(DEFINITE
), Verb
);
7072 TorsoDisappeared
= true;
7073 for (c
= 0; c
< GetEquipments(); ++c
) {
7074 item
*Equipment
= GetEquipment(c
);
7075 if (Equipment
&& (Equipment
->*ClosePredicate
)()) {
7076 Equipment
->RemoveFromSlot();
7077 Equipment
->SendToHell();
7080 itemvector ItemVector
;
7081 GetStack()->FillItemVector(ItemVector
);
7082 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
7083 if (ItemVector
[c
] && (ItemVector
[c
]->*ClosePredicate
)()) {
7084 ItemVector
[c
]->RemoveFromSlot();
7085 ItemVector
[c
]->SendToHell();
7089 for (c
= 1; c
< GetBodyParts(); ++c
) {
7090 bodypart
*BodyPart
= GetBodyPart(c
);
7092 if ((BodyPart
->*ClosePredicate
)()) {
7093 if (!TorsoDisappeared
&& CanBeSeen
) {
7094 if(IsPlayer()) ADD_MESSAGE("Your %s %ss.", GetBodyPartName(c
).CStr(), Verb
);
7095 else ADD_MESSAGE("The %s of %s %ss.", GetBodyPartName(c
).CStr(), CHAR_NAME(DEFINITE
), Verb
);
7097 BodyPart
->DropEquipment();
7098 item
*BodyPart
= SevereBodyPart(c
);
7099 if (BodyPart
) BodyPart
->SendToHell();
7100 } else if (TorsoDisappeared
) {
7101 BodyPart
->DropEquipment();
7102 item
*BodyPart
= SevereBodyPart(c
);
7104 if (Corpse
) Corpse
->GetSlot()->AddFriendItem(BodyPart
);
7105 else if (!game::IsInWilderness()) GetStackUnder()->AddItem(BodyPart
);
7106 else BodyPart
->SendToHell();
7111 if (TorsoDisappeared
) {
7113 Corpse
->RemoveFromSlot();
7114 Corpse
->SendToHell();
7116 CheckDeath(festring(Verb
) + "ed", 0, FORCE_DEATH
|DISALLOW_CORPSE
|DISALLOW_MSG
);
7119 CheckDeath(festring(Verb
) + "ed", 0, DISALLOW_MSG
);
7124 void character::SignalDisappearance () {
7125 if (GetMotherEntity()) GetMotherEntity()->SignalDisappearance();
7126 else Disappear(0, "disappear", &item::IsVeryCloseToDisappearance
);
7130 truth
character::HornOfFearWorks () const {
7131 return CanHear() && GetPanicLevel() > RAND()%33 && !StateIsActivated(FEARLESS
);
7135 void character::BeginLeprosy () {
7136 doforbodypartswithparam
<truth
>()(this, &bodypart::SetIsInfectedByLeprosy
, true);
7140 void character::EndLeprosy () {
7141 doforbodypartswithparam
<truth
>()(this, &bodypart::SetIsInfectedByLeprosy
, false);
7145 truth
character::IsSameAs (ccharacter
*What
) const {
7146 return What
->GetType() == GetType() && What
->GetConfig() == GetConfig();
7150 feuLong
character::GetCommandFlags () const {
7151 return !StateIsActivated(PANIC
) ? CommandFlags
: CommandFlags
|FLEE_FROM_ENEMIES
;
7155 feuLong
character::GetConstantCommandFlags () const {
7156 return !StateIsActivated(PANIC
) ? DataBase
->ConstantCommandFlags
: DataBase
->ConstantCommandFlags
|FLEE_FROM_ENEMIES
;
7160 feuLong
character::GetPossibleCommandFlags () const {
7161 int Int
= GetAttribute(INTELLIGENCE
);
7162 feuLong Flags
= ALL_COMMAND_FLAGS
;
7163 if (!CanMove() || Int
< 4) Flags
&= ~FOLLOW_LEADER
;
7164 if (!CanMove() || Int
< 6) Flags
&= ~FLEE_FROM_ENEMIES
;
7165 if (!CanUseEquipment() || Int
< 8) Flags
&= ~DONT_CHANGE_EQUIPMENT
;
7166 if (!UsesNutrition() || Int
< 8) Flags
&= ~DONT_CONSUME_ANYTHING_VALUABLE
;
7171 truth
character::IsRetreating () const {
7172 return StateIsActivated(PANIC
) || (CommandFlags
& FLEE_FROM_ENEMIES
&& IsPet());
7176 truth
character::ChatMenu () {
7177 if (GetAction() && !GetAction()->CanBeTalkedTo()) {
7178 ADD_MESSAGE("%s is silent.", CHAR_DESCRIPTION(DEFINITE
));
7179 PLAYER
->EditAP(-200);
7182 feuLong ManagementFlags
= GetManagementFlags();
7183 if (ManagementFlags
== CHAT_IDLY
|| !IsPet()) return ChatIdly();
7184 static cchar
*const ChatMenuEntry
[CHAT_MENU_ENTRIES
] = {
7191 static const petmanagementfunction PMF
[CHAT_MENU_ENTRIES
] = {
7192 &character::ChangePetEquipment
,
7193 &character::TakePetItems
,
7194 &character::GivePetItems
,
7195 &character::IssuePetCommands
,
7196 &character::ChatIdly
7198 felist
List(CONST_S("Choose action:"));
7199 game::SetStandardListAttributes(List
);
7200 List
.AddFlags(SELECTABLE
);
7202 for (c
= 0; c
< CHAT_MENU_ENTRIES
; ++c
) if (1 << c
& ManagementFlags
) List
.AddEntry(ChatMenuEntry
[c
], LIGHT_GRAY
);
7203 int Chosen
= List
.Draw();
7204 if (Chosen
& FELIST_ERROR_BIT
) return false;
7205 for (c
= 0, i
= 0; c
< CHAT_MENU_ENTRIES
; ++c
) {
7206 if (1 << c
& ManagementFlags
&& i
++ == Chosen
) return (this->*PMF
[c
])();
7208 return false; // dummy
7212 truth
character::ChangePetEquipment () {
7213 if (EquipmentScreen(PLAYER
->GetStack(), GetStack())) {
7221 truth
character::TakePetItems () {
7222 truth Success
= false;
7223 stack::SetSelected(0);
7226 game::DrawEverythingNoBlit();
7227 GetStack()->DrawContents(
7231 CONST_S("What do you want to take from ") + CHAR_DESCRIPTION(DEFINITE
) + '?',
7234 GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState(),
7235 GetVerbalBurdenStateColor(),
7237 if (ToTake
.empty()) break;
7238 for (uInt c
= 0; c
< ToTake
.size(); ++c
) ToTake
[c
]->MoveTo(PLAYER
->GetStack());
7239 ADD_MESSAGE("You take %s.", ToTake
[0]->GetName(DEFINITE
, ToTake
.size()).CStr());
7244 PLAYER
->DexterityAction(2);
7250 truth
character::GivePetItems () {
7251 truth Success
= false;
7252 stack::SetSelected(0);
7255 game::DrawEverythingNoBlit();
7256 PLAYER
->GetStack()->DrawContents(
7260 CONST_S("What do you want to give to ") + CHAR_DESCRIPTION(DEFINITE
) + '?',
7263 GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState(),
7264 GetVerbalBurdenStateColor(),
7266 if (ToGive
.empty()) break;
7267 for (uInt c
= 0; c
< ToGive
.size(); ++c
) ToGive
[c
]->MoveTo(GetStack());
7268 ADD_MESSAGE("You give %s to %s.", ToGive
[0]->GetName(DEFINITE
, ToGive
.size()).CStr(), CHAR_DESCRIPTION(DEFINITE
));
7273 PLAYER
->DexterityAction(2);
7279 truth
character::IssuePetCommands () {
7280 if (!IsConscious()) {
7281 ADD_MESSAGE("%s is unconscious.", CHAR_DESCRIPTION(DEFINITE
));
7284 feuLong PossibleC
= GetPossibleCommandFlags();
7286 ADD_MESSAGE("%s cannot be commanded.", CHAR_DESCRIPTION(DEFINITE
));
7289 feuLong OldC
= GetCommandFlags();
7290 feuLong NewC
= OldC
, VaryFlags
= 0;
7291 game::CommandScreen(CONST_S("Issue commands to ")+GetDescription(DEFINITE
), PossibleC
, GetConstantCommandFlags(), VaryFlags
, NewC
);
7292 if (NewC
== OldC
) return false;
7293 SetCommandFlags(NewC
);
7294 PLAYER
->EditAP(-500);
7295 PLAYER
->EditExperience(CHARISMA
, 25, 1 << 7);
7300 truth
character::ChatIdly () {
7301 if (!TryToTalkAboutScience()) {
7303 PLAYER
->EditExperience(CHARISMA
, 75, 1 << 7);
7305 PLAYER
->EditAP(-1000);
7310 int character::HasSomethingToEquipAt (int chosen
, truth equippedIsTrue
) {
7311 if (!GetBodyPartOfEquipment(chosen
)) return 0;
7313 item
*oldEquipment
= GetEquipment(chosen
);
7314 if (!IsPlayer() && oldEquipment
&& BoundToUse(oldEquipment
, chosen
)) return 0;
7316 stack
*mainStack
= GetStack();
7317 sorter Sorter
= EquipmentSorter(chosen
);
7318 auto count
= mainStack
->SortedItemsCount(this, Sorter
);
7320 if (equippedIsTrue
&& oldEquipment
) ++count
;
7326 truth
character::EquipmentScreen (stack
*MainStack
, stack
*SecStack
) {
7327 if (!CanUseEquipment()) {
7328 ADD_MESSAGE("%s cannot use equipment.", CHAR_DESCRIPTION(DEFINITE
));
7332 truth EquipmentChanged
= false;
7333 felist
List(CONST_S("Equipment menu [ESC exits]"));
7337 List
.EmptyDescription();
7339 List
.AddDescription(CONST_S(""));
7340 List
.AddDescription(festring(GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState()).CapitalizeCopy(), GetVerbalBurdenStateColor());
7342 int selected
= -1, curit
= 0;
7343 truth selectedIsEmpty
= false;
7344 for (int c
= 0; c
< GetEquipments(); ++c
, ++curit
) {
7345 int bpidx
= (GetBodyPartOfEquipment(c
) ? GetBodyPartOfEquipment(c
)->GetBodyPartIndex() : -1);
7346 Entry
= GetEquipmentName(c
);
7349 item
*Equipment
= GetEquipment(c
);
7351 Equipment
->AddInventoryEntry(this, Entry
, 1, true);
7352 AddSpecialEquipmentInfo(Entry
, c
);
7353 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
7354 if (selected
< 0 && bpidx
>= 0 && bpidx
!= RIGHT_ARM_INDEX
&& bpidx
!= LEFT_ARM_INDEX
) selected
= curit
;
7355 List
.AddEntry(Entry
, (HasSomethingToEquipAt(c
, false) ? ORANGE
: LIGHT_GRAY
), 20, ImageKey
, true);
7357 truth canUse
= !!GetBodyPartOfEquipment(c
);
7358 Entry
<< (canUse
? "-" : "can't use");
7361 switch (HasSomethingToEquipAt(c
, false)) {
7362 case 0: color
= RED
; break;
7363 case 1: color
= LIGHT_GRAY
; break;
7364 default: color
= ORANGE
; break;
7367 if (color
!= RED
&& bpidx
>= 0 && bpidx
!= RIGHT_ARM_INDEX
&& bpidx
!= LEFT_ARM_INDEX
) {
7368 if (selected
< 0 || !selectedIsEmpty
) { selected
= curit
; selectedIsEmpty
= true; }
7370 List
.AddEntry(Entry
, color
, 20, game::AddToItemDrawVector(itemvector()));
7373 game::DrawEverythingNoBlit();
7374 game::SetStandardListAttributes(List
);
7375 if (selected
>= 0) List
.SetSelected(selected
);
7376 List
.SetFlags(SELECTABLE
|DRAW_BACKGROUND_AFTERWARDS
);
7377 List
.SetEntryDrawer(game::ItemEntryDrawer
);
7378 Chosen
= List
.Draw();
7379 game::ClearItemDrawVector();
7380 if (Chosen
>= GetEquipments()) break;
7381 EquipmentChanged
= TryToChangeEquipment(MainStack
, SecStack
, Chosen
);
7383 if (EquipmentChanged
) DexterityAction(5);
7384 return EquipmentChanged
;
7388 feuLong
character::GetManagementFlags () const {
7389 feuLong Flags
= ALL_MANAGEMENT_FLAGS
;
7390 if (!CanUseEquipment() || !AllowPlayerToChangeEquipment()) Flags
&= ~CHANGE_EQUIPMENT
;
7391 if (!GetStack()->GetItems()) Flags
&= ~TAKE_ITEMS
;
7392 if (!WillCarryItems()) Flags
&= ~GIVE_ITEMS
;
7393 if (!GetPossibleCommandFlags()) Flags
&= ~ISSUE_COMMANDS
;
7398 cchar
*VerbalBurdenState
[] = { "overloaded", "stressed", "burdened", "unburdened" };
7399 col16 VerbalBurdenStateColor
[] = { RED
, BLUE
, BLUE
, WHITE
};
7401 cchar
*character::GetVerbalBurdenState () const { return VerbalBurdenState
[BurdenState
]; }
7402 col16
character::GetVerbalBurdenStateColor () const { return VerbalBurdenStateColor
[BurdenState
]; }
7403 int character::GetAttributeAverage () const { return GetSumOfAttributes()/7; }
7405 cfestring
&character::GetStandVerb() const {
7406 if (ForceCustomStandVerb()) return DataBase
->StandVerb
;
7407 static festring Hovering
= "hovering";
7408 static festring Swimming
= "swimming";
7409 if (StateIsActivated(LEVITATION
)) return Hovering
;
7410 if (IsSwimming()) return Swimming
;
7411 return DataBase
->StandVerb
;
7415 truth
character::CheckApply () const {
7417 ADD_MESSAGE("This monster type cannot apply.");
7424 void character::EndLevitation () {
7425 if (!IsFlying() && GetSquareUnder()) {
7426 if (!game::IsInWilderness()) SignalStepFrom(0);
7427 if (game::IsInWilderness() || !GetLSquareUnder()->IsFreezed()) TestWalkability();
7432 truth
character::CanMove () const {
7433 return !IsRooted() || StateIsActivated(LEVITATION
);
7437 void character::CalculateEnchantments () {
7438 doforequipments()(this, &item::CalculateEnchantment
);
7439 GetStack()->CalculateEnchantments();
7443 truth
character::GetNewFormForPolymorphWithControl (character
*&NewForm
) {
7444 festring Topic
, Temp
;
7447 festring Temp
= game::DefaultQuestion(CONST_S("What do you want to become? [press '?' for a list]"), game::GetDefaultPolymorphTo(), &game::PolymorphControlKeyHandler
);
7448 NewForm
= protosystem::CreateMonster(Temp
);
7450 if (NewForm
->IsSameAs(this)) {
7452 ADD_MESSAGE("You choose not to polymorph.");
7456 if (PolymorphBackup
&& NewForm
->IsSameAs(PolymorphBackup
)) {
7458 NewForm
= ForceEndPolymorph();
7461 if (NewForm
->GetPolymorphIntelligenceRequirement() > GetAttribute(INTELLIGENCE
) && !game::WizardModeIsActive()) {
7462 ADD_MESSAGE("You feel your mind isn't yet powerful enough to call forth the form of %s.", NewForm
->CHAR_NAME(INDEFINITE
));
7466 NewForm
->RemoveAllItems();
7474 liquid
*character::CreateSweat(sLong Volume
) const {
7475 //return liquid::Spawn(GetSweatMaterial(), Volume);
7476 return liquid::Spawn(GetCurrentSweatMaterial(), Volume
);
7480 truth
character::TeleportRandomItem (truth TryToHinderVisibility
) {
7481 if (IsImmuneToItemTeleport()) return false;
7482 itemvector ItemVector
;
7483 std::vector
<sLong
> PossibilityVector
;
7484 int TotalPossibility
= 0;
7485 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
7486 ItemVector
.push_back(*i
);
7487 int Possibility
= i
->GetTeleportPriority();
7488 if (TryToHinderVisibility
) Possibility
+= i
->GetHinderVisibilityBonus(this);
7489 PossibilityVector
.push_back(Possibility
);
7490 TotalPossibility
+= Possibility
;
7492 for (int c
= 0; c
< GetEquipments(); ++c
) {
7493 item
*Equipment
= GetEquipment(c
);
7495 ItemVector
.push_back(Equipment
);
7496 int Possibility
= Equipment
->GetTeleportPriority();
7497 if (TryToHinderVisibility
) Possibility
+= Equipment
->GetHinderVisibilityBonus(this);
7498 PossibilityVector
.push_back(Possibility
<<= 1);
7499 TotalPossibility
+= Possibility
;
7502 if (!TotalPossibility
) return false;
7503 int Chosen
= femath::WeightedRand(PossibilityVector
, TotalPossibility
);
7504 item
*Item
= ItemVector
[Chosen
];
7505 truth Equipped
= PLAYER
->Equips(Item
);
7506 truth Seen
= Item
->CanBeSeenByPlayer();
7507 Item
->RemoveFromSlot();
7508 if (Seen
) ADD_MESSAGE("%s disappears.", Item
->CHAR_NAME(DEFINITE
));
7509 if (Equipped
) game::AskForEscPress(CONST_S("Equipment lost!"));
7511 int Range
= Item
->GetEmitation() && TryToHinderVisibility
? 25 : 5;
7512 rect
Border(Pos
+ v2(-Range
, -Range
), Pos
+ v2(Range
, Range
));
7513 Pos
= GetLevel()->GetRandomSquare(this, 0, &Border
);
7514 if (Pos
== ERROR_V2
) Pos
= GetLevel()->GetRandomSquare();
7515 GetNearLSquare(Pos
)->GetStack()->AddItem(Item
);
7516 if (Item
->CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", Item
->CHAR_NAME(INDEFINITE
));
7521 truth
character::HasClearRouteTo (v2 Pos
) const {
7522 pathcontroller::Map
= GetLevel()->GetMap();
7523 pathcontroller::Character
= this;
7524 v2 ThisPos
= GetPos();
7525 return mapmath
<pathcontroller
>::DoLine(ThisPos
.X
, ThisPos
.Y
, Pos
.X
, Pos
.Y
, SKIP_FIRST
);
7529 truth
character::IsTransparent () const {
7530 return !IsEnormous() || GetTorso()->GetMainMaterial()->IsTransparent() || StateIsActivated(INVISIBLE
);
7534 void character::SignalPossibleTransparencyChange () {
7535 if (!game::IsInWilderness()) {
7536 for (int c
= 0; c
< SquaresUnder
; ++c
) {
7537 lsquare
*Square
= GetLSquareUnder(c
);
7538 if (Square
) Square
->SignalPossibleTransparencyChange();
7544 int character::GetCursorData () const {
7546 int Color
= game::PlayerIsRunning() ? BLUE_CURSOR
: DARK_CURSOR
;
7547 for (int c
= 0; c
< BodyParts
; ++c
) {
7548 bodypart
*BodyPart
= GetBodyPart(c
);
7549 if (BodyPart
&& BodyPart
->IsUsable()) {
7550 int ConditionColorIndex
= BodyPart
->GetConditionColorIndex();
7551 if ((BodyPartIsVital(c
) && !ConditionColorIndex
) || (ConditionColorIndex
<= 1 && ++Bad
== 2)) return Color
|CURSOR_FLASH
;
7552 } else if (++Bad
== 2) return Color
|CURSOR_FLASH
;
7554 Color
= game::PlayerIsRunning() ? YELLOW_CURSOR
: RED_CURSOR
;
7555 return Bad
? Color
|CURSOR_FLASH
: Color
;
7559 void character::TryToName () {
7560 if (!IsPet()) ADD_MESSAGE("%s refuses to let YOU decide what %s's called.", CHAR_NAME(DEFINITE
), CHAR_PERSONAL_PRONOUN
);
7561 else if (IsPlayer()) ADD_MESSAGE("You can't rename yourself.");
7562 else if (!IsNameable()) ADD_MESSAGE("%s refuses to be called anything else but %s.", CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
));
7564 festring Topic
= CONST_S("What name will you give to ")+GetName(DEFINITE
)+'?';
7565 festring Name
= game::StringQuestion(Topic
, WHITE
, 0, 80, true);
7566 if (Name
.GetSize()) SetAssignedName(Name
);
7571 double character::GetSituationDanger (ccharacter
*Enemy
, v2 ThisPos
, v2 EnemyPos
, truth SeesEnemy
) const {
7573 if (IgnoreDanger() && !IsPlayer()) {
7574 if (Enemy
->IgnoreDanger() && !Enemy
->IsPlayer()) {
7575 Danger
= double(GetHP())*GetHPRequirementForGeneration()/(Enemy
->GetHP()*Enemy
->GetHPRequirementForGeneration());
7578 Danger
= 0.25*GetHPRequirementForGeneration()/Enemy
->GetHP();
7580 } else if (Enemy
->IgnoreDanger() && !Enemy
->IsPlayer()) {
7581 Danger
= 4.0*GetHP()/Enemy
->GetHPRequirementForGeneration();
7583 Danger
= GetRelativeDanger(Enemy
);
7585 Danger
*= 3.0/((EnemyPos
-ThisPos
).GetManhattanLength()+2);
7586 if (!SeesEnemy
) Danger
*= 0.2;
7587 if (StateIsActivated(PANIC
)) Danger
*= 0.2;
7588 Danger
*= double(GetHP())*Enemy
->GetMaxHP()/(Enemy
->GetHP()*GetMaxHP());
7593 void character::ModifySituationDanger (double &Danger
) const {
7594 switch (GetTirednessState()) {
7595 case FAINTING
: Danger
*= 1.5;
7596 case EXHAUSTED
: Danger
*= 1.25;
7598 for (int c
= 0; c
< STATES
; ++c
) {
7599 if (StateIsActivated(1 << c
) && StateData
[c
].SituationDangerModifier
!= 0) (this->*StateData
[c
].SituationDangerModifier
)(Danger
);
7604 void character::LycanthropySituationDangerModifier (double &Danger
) const {
7605 character
*Wolf
= werewolfwolf::Spawn();
7606 double DangerToWolf
= GetRelativeDanger(Wolf
);
7607 Danger
*= pow(DangerToWolf
, 0.1);
7612 void character::PoisonedSituationDangerModifier (double &Danger
) const {
7613 int C
= GetTemporaryStateCounter(POISONED
);
7614 Danger
*= (1+(C
*C
)/(GetHP()*10000.0*(GetGlobalResistance(POISON
)+1)));
7618 void character::PolymorphingSituationDangerModifier (double &Danger
) const {
7619 if (!StateIsActivated(POLYMORPH_CONTROL
)) Danger
*= 1.5;
7623 void character::PanicSituationDangerModifier (double &Danger
) const {
7628 void character::ConfusedSituationDangerModifier (double &Danger
) const {
7633 void character::ParasitizedSituationDangerModifier (double &Danger
) const {
7638 void character::LeprosySituationDangerModifier (double &Danger
) const {
7643 void character::AddRandomScienceName (festring
&String
) const {
7644 festring Science
= GetScienceTalkName().GetRandomElement().CStr();
7645 if (Science
[0] == '!') {
7646 String
<< Science
.CStr()+1;
7649 festring Attribute
= GetScienceTalkAdjectiveAttribute().GetRandomElement();
7651 truth NoAttrib
= Attribute
.IsEmpty(), NoSecondAdjective
= false;
7652 if (!Attribute
.IsEmpty() && Attribute
[0] == '!') {
7653 NoSecondAdjective
= true;
7654 Attribute
.Erase(0, 1);
7656 if (!Science
.Find("the ")) {
7657 Science
.Erase(0, 4);
7658 if (!Attribute
.Find("the ", 0, 4)) Attribute
<< " the"; else Attribute
.Insert(0, "the ", 4);
7660 if (islower(Science
[0]) && Science
.Find(' ') == festring::NPos
&& Science
.Find('-') == festring::NPos
&&
7661 Science
.Find("phobia") == festring::NPos
) {
7662 Prefix
= GetScienceTalkPrefix().GetRandomElement();
7663 if (!Prefix
.IsEmpty() && Science
.Find(Prefix
) != festring::NPos
) Prefix
.Empty();
7665 int L
= Prefix
.GetSize();
7666 if (L
&& Prefix
[L
-1] == Science
[0]) Science
.Erase(0, 1);
7667 if (!NoAttrib
&& !NoSecondAdjective
== !RAND_GOOD(3)) {
7668 int S1
= NoSecondAdjective
? 0 : GetScienceTalkAdjectiveAttribute().Size
;
7669 int S2
= GetScienceTalkSubstantiveAttribute().Size
;
7670 festring OtherAttribute
;
7671 int Chosen
= RAND_GOOD(S1
+S2
);
7672 if (Chosen
< S1
) OtherAttribute
= GetScienceTalkAdjectiveAttribute()[Chosen
];
7673 else OtherAttribute
= GetScienceTalkSubstantiveAttribute()[Chosen
- S1
];
7674 if (!OtherAttribute
.IsEmpty() && OtherAttribute
.Find("the ", 0, 4) && Attribute
.Find(OtherAttribute
) == festring::NPos
) {
7675 String
<< Attribute
<< ' ' << OtherAttribute
<< ' ' << Prefix
<< Science
;
7679 String
<< Attribute
;
7680 if (!NoAttrib
) String
<< ' ';
7681 String
<< Prefix
<< Science
;
7685 truth
character::TryToTalkAboutScience () {
7686 if (GetRelation(PLAYER
) == HOSTILE
||
7687 GetScienceTalkPossibility() <= RAND_GOOD(100) ||
7688 PLAYER
->GetAttribute(INTELLIGENCE
) < GetScienceTalkIntelligenceRequirement() ||
7689 PLAYER
->GetAttribute(WISDOM
) < GetScienceTalkWisdomRequirement() ||
7690 PLAYER
->GetAttribute(CHARISMA
) < GetScienceTalkCharismaRequirement())
7694 AddRandomScienceName(Science
);
7697 AddRandomScienceName(S1
);
7698 AddRandomScienceName(S2
);
7699 if (S1
.Find(S2
) == festring::NPos
&& S2
.Find(S1
) == festring::NPos
) {
7700 switch (RAND_GOOD(3)) {
7701 case 0: Science
= "the relation of "; break;
7702 case 1: Science
= "the differences of "; break;
7703 case 2: Science
= "the similarities of "; break;
7705 Science
<< S1
<< " and " << S2
;
7708 AddRandomScienceName(Science
);
7711 switch ((RAND() + GET_TICK()) % 10) {
7713 ADD_MESSAGE("You have a rather pleasant chat about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7716 ADD_MESSAGE("%s explains a few of %s opinions regarding %s to you.", CHAR_DESCRIPTION(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, Science
.CStr());
7719 ADD_MESSAGE("%s reveals a number of %s insightful views of %s to you.", CHAR_DESCRIPTION(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, Science
.CStr());
7722 ADD_MESSAGE("You exhange some information pertaining to %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7725 ADD_MESSAGE("You engage in a pretty intriguing conversation about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7728 ADD_MESSAGE("You discuss at length about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7731 ADD_MESSAGE("You have a somewhat boring talk concerning %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7734 ADD_MESSAGE("You are drawn into a heated argument regarding %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7737 ADD_MESSAGE("%s delivers a long monologue concerning eg. %s.", CHAR_DESCRIPTION(DEFINITE
), Science
.CStr());
7740 ADD_MESSAGE("You dive into a brief but thought-provoking debate over %s with %s", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7743 PLAYER
->EditExperience(INTELLIGENCE
, 1000, 50. * GetScienceTalkIntelligenceModifier() / ++ScienceTalks
);
7744 PLAYER
->EditExperience(WISDOM
, 1000, 50. * GetScienceTalkWisdomModifier() / ++ScienceTalks
);
7745 PLAYER
->EditExperience(CHARISMA
, 1000, 50. * GetScienceTalkCharismaModifier() / ++ScienceTalks
);
7750 truth
character::IsUsingWeaponOfCategory (int Category
) const {
7752 ((GetMainWielded() && GetMainWielded()->GetWeaponCategory() == Category
) ||
7753 (GetSecondaryWielded() && GetSecondaryWielded()->GetWeaponCategory() == Category
));
7757 truth
character::TryToUnStickTraps (v2 Dir
) {
7758 if (!TrapData
) return true;
7759 std::vector
<trapdata
> TrapVector
;
7760 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) TrapVector
.push_back(*TrapData
);
7761 for (uInt c
= 0; c
< TrapVector
.size(); ++c
) {
7763 entity
*Trap
= game::SearchTrap(TrapVector
[c
].TrapID
);
7764 /*k8:??? if(!Trap->Exists()) int esko = esko = 2; */
7765 if (!Trap
->Exists()) continue; /*k8: ??? added by me; what this means? */
7766 if (Trap
->GetVictimID() == GetID() && Trap
->TryToUnStick(this, Dir
)) break;
7769 return !TrapData
&& IsEnabled();
7773 struct trapidcomparer
{
7774 trapidcomparer (feuLong ID
) : ID(ID
) {}
7775 truth
operator () (const trapdata
*T
) const { return T
->TrapID
== ID
; }
7780 void character::RemoveTrap (feuLong ID
) {
7781 trapdata
*&T
= ListFind(TrapData
, trapidcomparer(ID
));
7783 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7787 void character::AddTrap (feuLong ID
, feuLong BodyParts
) {
7788 trapdata
*&T
= ListFind(TrapData
, trapidcomparer(ID
));
7789 if (T
) T
->BodyParts
|= BodyParts
;
7790 else T
= new trapdata(ID
, GetID(), BodyParts
);
7791 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7795 truth
character::IsStuckToTrap (feuLong ID
) const {
7796 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) if (T
->TrapID
== ID
) return true;
7801 void character::RemoveTraps () {
7802 for (trapdata
*T
= TrapData
; T
; T
= T
->Next
) {
7803 entity
*Trap
= game::SearchTrap(T
->TrapID
);
7804 if (Trap
) Trap
->UnStick();
7806 deleteList(TrapData
);
7807 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7811 void character::RemoveTraps (int BodyPartIndex
) {
7812 feuLong Flag
= 1 << BodyPartIndex
;
7813 for (trapdata
**T
= &TrapData
; *T
;) {
7814 if ((*T
)->BodyParts
& Flag
) {
7815 entity
*Trap
= game::SearchTrap((*T
)->TrapID
);
7816 if (!((*T
)->BodyParts
&= ~Flag
)) {
7817 if (Trap
) Trap
->UnStick();
7818 trapdata
*ToDel
= *T
;
7822 if (Trap
) Trap
->UnStick(BodyPartIndex
);
7830 if (GetBodyPart(BodyPartIndex
)) GetBodyPart(BodyPartIndex
)->SignalPossibleUsabilityChange();
7834 festring
character::GetTrapDescription () const {
7836 std::pair
<entity
*, int> TrapStack
[3];
7838 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) {
7840 entity
*Trap
= game::SearchTrap(T
->TrapID
);
7843 for (c
= 0; c
< Index
; ++c
) if (TrapStack
[c
].first
->GetTrapType() == Trap
->GetTrapType()) ++TrapStack
[c
].second
;
7844 if (c
== Index
) TrapStack
[Index
++] = std::make_pair(Trap
, 1);
7852 TrapStack
[0].first
->AddTrapName(Desc
, TrapStack
[0].second
);
7855 TrapStack
[1].first
->AddTrapName(Desc
, TrapStack
[1].second
);
7856 } else if (Index
== 3) {
7858 TrapStack
[1].first
->AddTrapName(Desc
, TrapStack
[1].second
);
7860 TrapStack
[2].first
->AddTrapName(Desc
, TrapStack
[2].second
);
7863 Desc
<< "lots of traps";
7869 int character::RandomizeHurtBodyPart (feuLong BodyParts
) const {
7870 int BodyPartIndex
[MAX_BODYPARTS
];
7872 for (int c
= 0; c
< GetBodyParts(); ++c
) {
7873 if (1 << c
& BodyParts
) {
7874 /*k8: ??? if(!GetBodyPart(c)) int esko = esko = 2; */
7875 if (!GetBodyPart(c
)) continue;
7876 BodyPartIndex
[Index
++] = c
;
7878 /*k8: ??? if(!Index) int esko = esko = 2;*/
7881 fprintf(stderr
, "FATAL: RandomizeHurtBodyPart -- Index==0\n");
7884 return BodyPartIndex
[RAND_N(Index
)];
7888 truth
character::BodyPartIsStuck (int I
) const {
7889 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) if (1 << I
& T
->BodyParts
) return true;
7894 void character::PrintAttribute (cchar
*Desc
, int I
, int PanelPosX
, int PanelPosY
) const {
7895 int Attribute
= GetAttribute(I
);
7896 int NoBonusAttribute
= GetAttribute(I
, false);
7897 col16 C
= game::GetAttributeColor(I
);
7898 festring String
= Desc
;
7900 String
<< Attribute
;
7902 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
* 10), C
, "%s", String
.CStr());
7903 if (Attribute
!= NoBonusAttribute
) {
7904 int Where
= PanelPosX
+ ((String
.GetSize() + 1) << 3);
7905 FONT
->Printf(DOUBLE_BUFFER
, v2(Where
, PanelPosY
* 10), LIGHT_GRAY
, "%d", NoBonusAttribute
);
7910 truth
character::AllowUnconsciousness () const {
7911 return DataBase
->AllowUnconsciousness
&& TorsoIsAlive();
7915 truth
character::CanPanic () const {
7916 return !Action
|| !Action
->IsUnconsciousness() || !StateIsActivated(FEARLESS
);
7920 int character::GetRandomBodyPart (feuLong Possible
) const {
7921 int OKBodyPart
[MAX_BODYPARTS
];
7922 int OKBodyParts
= 0;
7923 for (int c
= 0; c
< BodyParts
; ++c
) if (1 << c
& Possible
&& GetBodyPart(c
)) OKBodyPart
[OKBodyParts
++] = c
;
7924 return OKBodyParts
? OKBodyPart
[RAND_N(OKBodyParts
)] : NONE_INDEX
;
7928 void character::EditNP (sLong What
) {
7929 int OldState
= GetHungerState();
7931 int NewState
= GetHungerState();
7932 if (OldState
> VERY_HUNGRY
&& NewState
== VERY_HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
7933 if (OldState
> STARVING
&& NewState
== STARVING
) DeActivateVoluntaryAction(CONST_S("You are getting extremely hungry."));
7937 truth
character::IsSwimming () const {
7938 return !IsFlying() && GetSquareUnder() && GetSquareUnder()->GetSquareWalkability() & SWIM
;
7942 void character::AddBlackUnicornConsumeEndMessage () const {
7943 if (IsPlayer()) ADD_MESSAGE("You feel dirty and loathsome.");
7947 void character::AddGrayUnicornConsumeEndMessage () const {
7948 if (IsPlayer()) ADD_MESSAGE("You feel neutralized.");
7952 void character::AddWhiteUnicornConsumeEndMessage () const {
7953 if (IsPlayer()) ADD_MESSAGE("You feel purified.");
7957 void character::AddOmmelBoneConsumeEndMessage () const {
7958 if (IsPlayer()) ADD_MESSAGE("You feel the power of all your canine ancestors combining in your body.");
7959 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s looks extremely ferocious. You shudder.", CHAR_NAME(DEFINITE
));
7963 void character::AddLiquidHorrorConsumeEndMessage () const {
7964 if (IsPlayer()) ADD_MESSAGE("Untold horrors flash before your eyes. The melancholy of the world is on your shoulders!");
7965 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks as if the melancholy of the world is on %s shoulders!.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
7969 void character::AddAlienFleshConsumeEndMessage() const
7971 if (IsPlayer()) ADD_MESSAGE("You feel somehow sick by eating such acidic corpse...");
7972 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks like he eat something bad.", CHAR_NAME(DEFINITE
));
7976 int character::GetBodyPartSparkleFlags (int) const {
7978 ((GetNaturalSparkleFlags() & SKIN_COLOR
? SPARKLING_A
: 0) |
7979 (GetNaturalSparkleFlags() & TORSO_MAIN_COLOR
? SPARKLING_B
: 0) |
7980 (GetNaturalSparkleFlags() & TORSO_SPECIAL_COLOR
? SPARKLING_D
: 0));
7984 truth
character::IsAnimated () const {
7985 return combinebodypartpredicates()(this, &bodypart::IsAnimated
, 1);
7989 double character::GetNaturalExperience (int Identifier
) const {
7990 return DataBase
->NaturalExperience
[Identifier
];
7994 truth
character::HasBodyPart (sorter Sorter
) const {
7995 if (Sorter
== 0) return true;
7996 return combinebodypartpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1);
8000 truth
character::PossessesItem (sorter Sorter
) const {
8001 if (Sorter
== 0) return true;
8003 (GetStack()->SortedItems(this, Sorter
) ||
8004 combinebodypartpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1) ||
8005 combineequipmentpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1));
8009 truth
character::MoreThanOnePossessesItem (sorter Sorter
) const {
8013 for (int c
= 0; c
< BodyParts
; ++c
) {
8014 bodypart
*BodyPart
= GetBodyPart(c
);
8016 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) {
8017 if (++count
> 1) return true;
8020 for (int c
= 0; c
< GetEquipments(); ++c
) {
8021 item
*Equipment
= GetEquipment(c
);
8023 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) {
8024 if (++count
> 1) return true;
8027 for (int c
= 0; c
< GetStack()->GetItems(); ++c
) {
8028 item
*Stk
= GetStack()->GetItem(c
);
8030 if (Stk
&& (Sorter
== 0 || (Stk
->*Sorter
)(this))) {
8031 if (++count
> 1) return true;
8040 item
*character::FirstPossessesItem (sorter Sorter
) const {
8042 for (int c
= 0; c
< BodyParts
; ++c
) {
8043 bodypart
*BodyPart
= GetBodyPart(c
);
8045 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) return BodyPart
;
8047 for (int c
= 0; c
< GetEquipments(); ++c
) {
8048 item
*Equipment
= GetEquipment(c
);
8050 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) return Equipment
;
8052 for (int c
= 0; c
< GetStack()->GetItems(); ++c
) {
8053 item
*Stk
= GetStack()->GetItem(c
);
8055 if (Stk
&& (Sorter
== 0 || (Stk
->*Sorter
)(this))) return Stk
;
8063 cchar
*character::GetRunDescriptionLine (int I
) const {
8064 if (!GetRunDescriptionLineOne().IsEmpty()) return !I
? GetRunDescriptionLineOne().CStr() : GetRunDescriptionLineTwo().CStr();
8065 if (IsFlying()) return !I
? "Flying" : "very fast";
8066 if (IsSwimming()) return !I
? "Swimming" : "very fast";
8067 return !I
? "Running" : "";
8071 void character::VomitAtRandomDirection (int Amount
) {
8072 if (game::IsInWilderness()) return;
8073 /* Lacks support of multitile monsters */
8076 for (int d
= 0; d
< 9; ++d
) {
8077 lsquare
*Square
= GetLSquareUnder()->GetNeighbourLSquare(d
);
8078 if (Square
&& !Square
->VomitingIsDangerous(this)) Possible
[Index
++] = Square
->GetPos();
8080 if (Index
) Vomit(Possible
[RAND_N(Index
)], Amount
);
8081 else Vomit(GetPos(), Amount
);
8085 void character::RemoveLifeSavers () {
8086 for (int c
= 0; c
< GetEquipments(); ++c
) {
8087 item
*Equipment
= GetEquipment(c
);
8088 if (Equipment
&& Equipment
->IsInCorrectSlot(c
) && Equipment
->GetGearStates() & LIFE_SAVED
) {
8089 Equipment
->SendToHell();
8090 Equipment
->RemoveFromSlot();
8096 ccharacter
*character::FindCarrier () const {
8097 return this; //check
8101 void character::PrintBeginHiccupsMessage () const {
8102 if (IsPlayer()) ADD_MESSAGE("Your diaphragm is spasming vehemently.");
8106 void character::PrintEndHiccupsMessage () const {
8107 if (IsPlayer()) ADD_MESSAGE("You feel your annoying hiccoughs have finally subsided.");
8111 void character::HiccupsHandler () {
8113 if (!(RAND() % 2000)) {
8114 if (IsPlayer()) ADD_MESSAGE("");
8115 else if (CanBeSeenByPlayer()) ADD_MESSAGE("");
8116 else if ((PLAYER->GetPos()-GetPos()).GetLengthSquare() <= 400) ADD_MESSAGE("");
8117 game::CallForAttention(GetPos(), 400);
8123 void character::VampirismHandler () {
8124 //EditExperience(ARM_STRENGTH, -25, 1 << 1);
8125 //EditExperience(LEG_STRENGTH, -25, 1 << 1);
8126 //EditExperience(DEXTERITY, -25, 1 << 1);
8127 //EditExperience(AGILITY, -25, 1 << 1);
8128 //EditExperience(ENDURANCE, -25, 1 << 1);
8129 EditExperience(CHARISMA
, -25, 1 << 1);
8130 EditExperience(WISDOM
, -25, 1 << 1);
8131 EditExperience(INTELLIGENCE
, -25, 1 << 1);
8132 CheckDeath(CONST_S("killed by vampirism"));
8136 void character::HiccupsSituationDangerModifier (double &Danger
) const {
8141 void character::VampirismSituationDangerModifier (double &Danger
) const {
8142 character
*Vampire
= vampire::Spawn();
8143 double DangerToVampire
= GetRelativeDanger(Vampire
);
8144 Danger
*= pow(DangerToVampire
, 0.1);
8149 bool character::IsConscious () const {
8150 return !Action
|| !Action
->IsUnconsciousness();
8154 wsquare
*character::GetNearWSquare (v2 Pos
) const {
8155 return static_cast<wsquare
*>(GetSquareUnder()->GetArea()->GetSquare(Pos
));
8159 wsquare
*character::GetNearWSquare (int x
, int y
) const {
8160 return static_cast<wsquare
*>(GetSquareUnder()->GetArea()->GetSquare(x
, y
));
8164 void character::ForcePutNear (v2 Pos
) {
8165 /* GUM SOLUTION!!! */
8166 v2 NewPos
= game::GetCurrentLevel()->GetNearestFreeSquare(PLAYER
, Pos
, false);
8167 if (NewPos
== ERROR_V2
) do { NewPos
= game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos
== Pos
);
8172 void character::ReceiveMustardGas (int BodyPart
, sLong Volume
) {
8173 if (Volume
) GetBodyPart(BodyPart
)->AddFluid(liquid::Spawn(MUSTARD_GAS_LIQUID
, Volume
), CONST_S("skin"), 0, true);
8177 void character::ReceiveMustardGasLiquid (int BodyPartIndex
, sLong Modifier
) {
8178 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
8179 if (BodyPart
->GetMainMaterial()->GetInteractionFlags() & IS_AFFECTED_BY_MUSTARD_GAS
) {
8180 sLong Tries
= Modifier
;
8181 Modifier
-= Tries
; //opt%?
8183 for (sLong c
= 0; c
< Tries
; ++c
) if (!(RAND() % 100)) ++Damage
;
8184 if (Modifier
&& !(RAND() % 1000 / Modifier
)) ++Damage
;
8186 feuLong Minute
= game::GetTotalMinutes();
8187 if (GetLastAcidMsgMin() != Minute
&& (CanBeSeenByPlayer() || IsPlayer())) {
8188 SetLastAcidMsgMin(Minute
);
8189 if (IsPlayer()) ADD_MESSAGE("Mustard gas dissolves the skin of your %s.", BodyPart
->GetBodyPartName().CStr());
8190 else ADD_MESSAGE("Mustard gas dissolves %s.", CHAR_NAME(DEFINITE
));
8192 ReceiveBodyPartDamage(0, Damage
, MUSTARD_GAS_DAMAGE
, BodyPartIndex
, YOURSELF
, false, false, false);
8193 CheckDeath(CONST_S("killed by a fatal exposure to mustard gas"));
8199 truth
character::IsBadPath (v2 Pos
) const {
8200 if (!IsGoingSomeWhere()) return false;
8201 v2 TPos
= !Route
.empty() ? Route
.back() : GoingTo
;
8202 return ((TPos
- Pos
).GetManhattanLength() > (TPos
- GetPos()).GetManhattanLength());
8206 double &character::GetExpModifierRef (expid E
) {
8207 return ExpModifierMap
.insert(std::make_pair(E
, 1.)).first
->second
;
8211 /* Should probably do more. Now only makes Player forget gods */
8212 truth
character::ForgetRandomThing () {
8214 /* hopefully this code isn't some where else */
8215 std::vector
<god
*> Known
;
8216 for (int c
= 1; c
<= GODS
; ++c
) if (game::GetGod(c
)->IsKnown()) Known
.push_back(game::GetGod(c
));
8217 if (Known
.empty()) return false;
8218 int RandomGod
= RAND_N(Known
.size());
8219 Known
.at(RAND_N(Known
.size()))->SetIsKnown(false);
8220 ADD_MESSAGE("You forget how to pray to %s.", Known
.at(RandomGod
)->GetName());
8227 int character::CheckForBlock (character
*Enemy
, item
*Weapon
, double ToHitValue
, int Damage
, int Success
, int Type
) {
8232 void character::ApplyAllGodsKnownBonus () {
8233 stack
*AddPlace
= GetStackUnder();
8234 if (game::IsInWilderness()) AddPlace
= GetStack(); else AddPlace
= GetStackUnder();
8235 pantheonbook
*NewBook
= pantheonbook::Spawn();
8236 AddPlace
->AddItem(NewBook
);
8237 ADD_MESSAGE("\"MORTAL! BEHOLD THE HOLY SAGA\"");
8238 ADD_MESSAGE("%s materializes near your feet.", NewBook
->CHAR_NAME(INDEFINITE
));
8242 void character::ReceiveSirenSong (character
*Siren
) {
8243 if (Siren
->GetTeam() == GetTeam()) return;
8245 if (IsPlayer()) ADD_MESSAGE("The beautiful melody of %s makes you feel sleepy.", Siren
->CHAR_NAME(DEFINITE
));
8246 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The beautiful melody of %s makes %s look sleepy.", Siren
->CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
)); /*k8*/
8247 Stamina
-= (1 + RAND_N(4)) * 10000;
8250 if (!IsPlayer() && IsCharmable() && !RAND_N(5)) {
8251 ChangeTeam(Siren
->GetTeam());
8252 ADD_MESSAGE("%s seems to be totally brainwashed by %s melodies.", CHAR_NAME(DEFINITE
), Siren
->CHAR_NAME(DEFINITE
));
8256 item
*What
= GiveMostExpensiveItem(Siren
);
8259 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
);
8261 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
);
8264 if (IsPlayer()) ADD_MESSAGE("You would like to give something to %s.", Siren
->CHAR_NAME(DEFINITE
));
8271 // return 0, if no item found
8272 item
*character::FindMostExpensiveItem () const {
8274 item
*MostExpensive
= 0;
8275 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
8276 if ((*i
)->GetPrice() > MaxPrice
) {
8277 MaxPrice
= (*i
)->GetPrice();
8278 MostExpensive
= (*i
);
8281 for (int c
= 0; c
< GetEquipments(); ++c
) {
8282 item
*Equipment
= GetEquipment(c
);
8283 if (Equipment
&& Equipment
->GetPrice() > MaxPrice
) {
8284 MaxPrice
= Equipment
->GetPrice();
8285 MostExpensive
= Equipment
;
8288 return MostExpensive
;
8292 // returns 0 if no items available
8293 item
*character::GiveMostExpensiveItem(character
*ToWhom
) {
8294 item
*ToGive
= FindMostExpensiveItem();
8295 if (!ToGive
) return 0;
8296 truth Equipped
= PLAYER
->Equips(ToGive
);
8297 ToGive
->RemoveFromSlot();
8298 if (Equipped
) game::AskForEscPress(CONST_S("Equipment lost!"));
8299 ToWhom
->ReceiveItemAsPresent(ToGive
);
8305 void character::ReceiveItemAsPresent (item
*Present
) {
8306 if (TestForPickup(Present
)) GetStack()->AddItem(Present
); else GetStackUnder()->AddItem(Present
);
8310 /* returns 0 if no enemies in sight */
8311 character
*character::GetNearestEnemy () const {
8312 character
*NearestEnemy
= 0;
8313 sLong NearestEnemyDistance
= 0x7FFFFFFF;
8315 for (int c
= 0; c
< game::GetTeams(); ++c
) {
8316 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
8317 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
8318 if ((*i
)->IsEnabled()) {
8319 sLong ThisDistance
= Max
<sLong
>(abs((*i
)->GetPos().X
- Pos
.X
), abs((*i
)->GetPos().Y
- Pos
.Y
));
8320 if ((ThisDistance
< NearestEnemyDistance
|| (ThisDistance
== NearestEnemyDistance
&& !(RAND() % 3))) && (*i
)->CanBeSeenBy(this)) {
8322 NearestEnemyDistance
= ThisDistance
;
8328 return NearestEnemy
;
8332 truth
character::MindWormCanPenetrateSkull (mindworm
*) const {
8337 truth
character::CanTameWithDulcis (const character
*Tamer
) const {
8338 int TamingDifficulty
= GetTamingDifficulty();
8339 if (TamingDifficulty
== NO_TAMING
) return false;
8340 if (GetAttachedGod() == DULCIS
) return true;
8341 int Modifier
= Tamer
->GetAttribute(WISDOM
) + Tamer
->GetAttribute(CHARISMA
);
8342 if (Tamer
->IsPlayer()) Modifier
+= game::GetGod(DULCIS
)->GetRelation() / 20;
8343 else if (Tamer
->GetAttachedGod() == DULCIS
) Modifier
+= 50;
8344 if (TamingDifficulty
== 0) {
8345 if (!IgnoreDanger()) TamingDifficulty
= int(10 * GetRelativeDanger(Tamer
));
8346 else TamingDifficulty
= 10 * GetHPRequirementForGeneration()/Max(Tamer
->GetHP(), 1);
8348 return Modifier
>= TamingDifficulty
* 3;
8352 truth
character::CanTameWithLyre (const character
*Tamer
) const {
8353 int TamingDifficulty
= GetTamingDifficulty();
8354 if (TamingDifficulty
== NO_TAMING
) return false;
8355 if (TamingDifficulty
== 0) {
8356 if (!IgnoreDanger()) TamingDifficulty
= int(10 * GetRelativeDanger(Tamer
));
8357 else TamingDifficulty
= 10*GetHPRequirementForGeneration()/Max(Tamer
->GetHP(), 1);
8359 return Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
;
8363 truth
character::CanTameWithScroll (const character
*Tamer
) const {
8364 int TamingDifficulty
= GetTamingDifficulty();
8366 (TamingDifficulty
!= NO_TAMING
&&
8367 (TamingDifficulty
== 0 ||
8368 Tamer
->GetAttribute(INTELLIGENCE
) * 4 + Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
* 5));
8372 truth
character::CheckSadism () {
8373 if (!IsSadist() || !HasSadistAttackMode() || !IsSmall()) return false; // gum
8375 for (int d
= 0; d
< MDIR_STAND
; ++d
) {
8376 square
*Square
= GetNeighbourSquare(d
);
8378 character
*Char
= Square
->GetCharacter();
8379 if (Char
&& Char
->IsMasochist() && GetRelation(Char
) == FRIEND
&&
8380 Char
->GetHP() * 3 >= Char
->GetMaxHP() * 2 && Hit(Char
, Square
->GetPos(), d
, SADIST_HIT
)) {
8391 truth
character::CheckForBeverage () {
8392 if (StateIsActivated(PANIC
) || !IsEnabled() || !UsesNutrition() || CheckIfSatiated()) return false;
8393 itemvector ItemVector
;
8394 GetStack()->FillItemVector(ItemVector
);
8395 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) if (ItemVector
[c
]->IsBeverage(this) && TryToConsume(ItemVector
[c
])) return true;
8400 void character::Haste () {
8401 doforbodyparts()(this, &bodypart::Haste
);
8402 doforequipments()(this, &item::Haste
);
8403 BeginTemporaryState(HASTE
, 500 + RAND() % 1000);
8407 void character::Slow () {
8408 doforbodyparts()(this, &bodypart::Slow
);
8409 doforequipments()(this, &item::Slow
);
8410 //BeginTemporaryState(HASTE, 500 + RAND() % 1000); // this seems to be a bug
8411 BeginTemporaryState(SLOW
, 500 + RAND() % 1000);
8415 void character::SurgicallyDetachBodyPart () {
8416 ADD_MESSAGE("You haven't got any extra bodyparts.");
8420 truth
character::CanHear() const
8422 return DataBase
->CanHear
&& HasHead();
8426 truth
character::IsAllowedInDungeon (int dunIndex
) {
8427 const fearray
<int> &dlist
= GetAllowedDungeons();
8429 for (uInt f
= 0; f
< dlist
.Size
; ++f
) {
8430 if (dlist
[f
] == ALL_DUNGEONS
|| dlist
[f
] == dunIndex
) {
8431 fprintf(stderr
, "OK!\n");
8435 fprintf(stderr
, "NO!\n");
8440 truth
character::IsESPBlockedByEquipment () const {
8441 for (int c
= 0; c
< GetEquipments(); ++c
) {
8442 item
*Item
= GetEquipment(c
);
8443 if (Item
&& Item
->IsHelmet(this) &&
8444 ((Item
->GetMainMaterial() && Item
->GetMainMaterial()->BlockESP()) ||
8445 (Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->BlockESP()))) return true;
8451 truth
character::TemporaryStateIsActivated (sLong What
) const {
8452 if ((What
&ESP
) && (TemporaryState
&ESP
) && IsESPBlockedByEquipment()) {
8453 return ((TemporaryState
&What
)&(~ESP
));
8455 return (TemporaryState
& What
);
8459 truth
character::StateIsActivated (sLong What
) const {
8460 if ((What
&ESP
) && ((TemporaryState
|EquipmentState
)&ESP
) && IsESPBlockedByEquipment()) {
8461 return ((TemporaryState
&What
)&(~ESP
)) || ((EquipmentState
&What
)&(~ESP
));
8463 return (TemporaryState
& What
) || (EquipmentState
& What
);
8467 void character::PrintBeginFearlessMessage () const {
8468 if (!StateIsActivated(FEARLESS
)) {
8469 if (IsPlayer()) ADD_MESSAGE("You feel very comfortable.");
8470 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems very comfortable.", CHAR_NAME(DEFINITE
));
8474 void character::PrintEndFearlessMessage () const {
8475 if (!StateIsActivated(FEARLESS
)) {
8476 if (IsPlayer()) ADD_MESSAGE("Everything looks more dangerous now.");
8477 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems to have lost his confidence.", CHAR_NAME(DEFINITE
));
8481 void character::BeginFearless () {
8482 DeActivateTemporaryState(PANIC
);
8485 void character::EndFearless () {
8490 void character::PrintBeginEtherealityMessage () const {
8491 if (IsPlayer()) ADD_MESSAGE("You feel like many miscible droplets of ether.");
8492 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s melds into the surroundings.", CHAR_NAME(DEFINITE
));
8495 void character::PrintEndEtherealityMessage () const {
8496 if (IsPlayer()) ADD_MESSAGE("You drop out of the firmament, feeling suddenly quite dense.");
8497 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s displaces the air with a puff.", CHAR_NAME(INDEFINITE
));
8500 void character::BeginEthereality () {}
8502 void character::EndEthereality () {}