3 * Iter Vehemens ad Necem (IVAN)
4 * Copyright (C) Timo Kiviluoto
5 * Released under the GNU General
8 * See LICENSING which should be included
9 * along with this file for more details
12 /* Compiled through charset.cpp */
15 #include <sys/types.h>
43 /* These statedata structs contain functions and values used for handling
44 * states. Remember to update them. All normal states must have
45 * PrintBeginMessage and PrintEndMessage functions and a Description string.
46 * BeginHandler, EndHandler, Handler (called each tick) and IsAllowed are
47 * optional, enter zero if the state doesn't need one. If the SECRET flag
48 * is set, Description is not shown in the panel without magical means.
49 * You can also set some source (SRC_*) and duration (DUR_*) flags, which
50 * control whether the state can be randomly activated in certain situations.
51 * These flags can be found in ivandef.h. RANDOMIZABLE sets all source
52 * & duration flags at once. */
54 const char *Description
;
56 void (character::*PrintBeginMessage
) () const;
57 void (character::*PrintEndMessage
) () const;
58 void (character::*BeginHandler
) ();
59 void (character::*EndHandler
) ();
60 void (character::*Handler
) ();
61 truth (character::*IsAllowed
) () const;
62 void (character::*SituationDangerModifier
) (double &) const;
66 //WARNING! state count and state order MUST be synced with "define.dat"
67 const statedata StateData
[STATES
] =
75 &character::EndPolymorph
,
81 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
82 &character::PrintBeginHasteMessage
,
83 &character::PrintEndHasteMessage
,
91 RANDOMIZABLE
&~SRC_GOOD
,
92 &character::PrintBeginSlowMessage
,
93 &character::PrintEndSlowMessage
,
101 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
|SRC_GOOD
),
102 &character::PrintBeginPolymorphControlMessage
,
103 &character::PrintEndPolymorphControlMessage
,
112 &character::PrintBeginLifeSaveMessage
,
113 &character::PrintEndLifeSaveMessage
,
121 SECRET
|SRC_FOUNTAIN
|SRC_CONFUSE_READ
|DUR_FLAGS
,
122 &character::PrintBeginLycanthropyMessage
,
123 &character::PrintEndLycanthropyMessage
,
126 &character::LycanthropyHandler
,
128 &character::LycanthropySituationDangerModifier
131 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
132 &character::PrintBeginInvisibilityMessage
,
133 &character::PrintEndInvisibilityMessage
,
134 &character::BeginInvisibility
,
135 &character::EndInvisibility
,
141 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
142 &character::PrintBeginInfraVisionMessage
,
143 &character::PrintEndInfraVisionMessage
,
144 &character::BeginInfraVision
,
145 &character::EndInfraVision
,
151 RANDOMIZABLE
&~SRC_EVIL
,
152 &character::PrintBeginESPMessage
,
153 &character::PrintEndESPMessage
,
154 &character::BeginESP
,
162 &character::PrintBeginPoisonedMessage
,
163 &character::PrintEndPoisonedMessage
,
166 &character::PoisonedHandler
,
167 &character::CanBePoisoned
,
168 &character::PoisonedSituationDangerModifier
171 SECRET
|(RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_GOOD
)),
172 &character::PrintBeginTeleportMessage
,
173 &character::PrintEndTeleportMessage
,
176 &character::TeleportHandler
,
181 SECRET
|(RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_GOOD
)),
182 &character::PrintBeginPolymorphMessage
,
183 &character::PrintEndPolymorphMessage
,
186 &character::PolymorphHandler
,
188 &character::PolymorphingSituationDangerModifier
191 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
192 &character::PrintBeginTeleportControlMessage
,
193 &character::PrintEndTeleportControlMessage
,
202 &character::PrintBeginPanicMessage
,
203 &character::PrintEndPanicMessage
,
204 &character::BeginPanic
,
205 &character::EndPanic
,
207 &character::CanPanic
,
208 &character::PanicSituationDangerModifier
211 SECRET
|(RANDOMIZABLE
&~(DUR_PERMANENT
|SRC_GOOD
)),
212 &character::PrintBeginConfuseMessage
,
213 &character::PrintEndConfuseMessage
,
217 &character::CanBeConfused
,
218 &character::ConfusedSituationDangerModifier
221 SECRET
|(RANDOMIZABLE
&~DUR_TEMPORARY
),
222 &character::PrintBeginParasitizedMessage
,
223 &character::PrintEndParasitizedMessage
,
226 &character::ParasitizedHandler
,
227 &character::CanBeParasitized
,
228 &character::ParasitizedSituationDangerModifier
232 &character::PrintBeginSearchingMessage
,
233 &character::PrintEndSearchingMessage
,
236 &character::SearchingHandler
,
241 SECRET
|(RANDOMIZABLE
&~(SRC_GOOD
|SRC_EVIL
)),
242 &character::PrintBeginGasImmunityMessage
,
243 &character::PrintEndGasImmunityMessage
,
251 RANDOMIZABLE
&~SRC_EVIL
,
252 &character::PrintBeginLevitationMessage
,
253 &character::PrintEndLevitationMessage
,
255 &character::EndLevitation
,
261 SECRET
|(RANDOMIZABLE
&~DUR_TEMPORARY
),
262 &character::PrintBeginLeprosyMessage
,
263 &character::PrintEndLeprosyMessage
,
264 &character::BeginLeprosy
,
265 &character::EndLeprosy
,
266 &character::LeprosyHandler
,
268 &character::LeprosySituationDangerModifier
271 SRC_FOUNTAIN
|SRC_CONFUSE_READ
|DUR_FLAGS
,
272 &character::PrintBeginHiccupsMessage
,
273 &character::PrintEndHiccupsMessage
,
276 &character::HiccupsHandler
,
278 &character::HiccupsSituationDangerModifier
281 DUR_FLAGS
, //perhaps no fountain, no secret and no confuse read either: SECRET|SRC_FOUNTAIN|SRC_CONFUSE_READ|
282 &character::PrintBeginVampirismMessage
,
283 &character::PrintEndVampirismMessage
,
286 &character::VampirismHandler
,
288 &character::VampirismSituationDangerModifier
292 &character::PrintBeginSwimmingMessage
,
293 &character::PrintEndSwimmingMessage
,
294 &character::BeginSwimming
, &character::EndSwimming
,
300 SECRET
|(RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
)),
301 &character::PrintBeginDetectMessage
,
302 &character::PrintEndDetectMessage
,
305 &character::DetectHandler
,
311 &character::PrintBeginEtherealityMessage
,
312 &character::PrintEndEtherealityMessage
,
313 &character::BeginEthereality
, &character::EndEthereality
,
319 RANDOMIZABLE
&~SRC_EVIL
,
320 &character::PrintBeginFearlessMessage
,
321 &character::PrintEndFearlessMessage
,
322 &character::BeginFearless
,
323 &character::EndFearless
,
330 &character::PrintBeginPolymorphLockMessage
,
331 &character::PrintEndPolymorphLockMessage
,
334 &character::PolymorphLockHandler
,
339 SECRET
|(RANDOMIZABLE
&~SRC_EVIL
),
340 &character::PrintBeginRegenerationMessage
,
341 &character::PrintEndRegenerationMessage
,
349 SECRET
|(RANDOMIZABLE
&~SRC_EVIL
),
350 &character::PrintBeginDiseaseImmunityMessage
,
351 &character::PrintEndDiseaseImmunityMessage
,
360 &character::PrintBeginTeleportLockMessage
,
361 &character::PrintEndTeleportLockMessage
,
364 &character::TeleportLockHandler
,
371 characterprototype::characterprototype (const characterprototype
*Base
, characterspawner Spawner
,
372 charactercloner Cloner
, cchar
*ClassID
)
378 Index
= protocontainer
<character
>::Add(this);
382 void character::CreateInitialEquipment (int SpecialFlags
) { AddToInventory(DataBase
->Inventory
, SpecialFlags
); }
383 void character::EditAP (sLong What
) { AP
= Limit
<sLong
>(AP
+What
, -12000, 1200); }
384 int character::GetRandomStepperBodyPart () const { return TORSO_INDEX
; }
385 void character::GainIntrinsic (sLong What
) { BeginTemporaryState(What
, PERMANENT
); }
386 truth
character::IsUsingArms () const { return GetAttackStyle() & USE_ARMS
; }
387 truth
character::IsUsingLegs () const { return GetAttackStyle() & USE_LEGS
; }
388 truth
character::IsUsingHead () const { return GetAttackStyle() & USE_HEAD
; }
389 void character::CalculateAllowedWeaponSkillCategories () { AllowedWeaponSkillCategories
= MARTIAL_SKILL_CATEGORIES
; }
390 festring
character::GetBeVerb () const { return IsPlayer() ? CONST_S("are") : CONST_S("is"); }
391 void character::SetEndurance (int What
) { BaseExperience
[ENDURANCE
] = What
* EXP_MULTIPLIER
; }
392 void character::SetPerception (int What
) { BaseExperience
[PERCEPTION
] = What
* EXP_MULTIPLIER
; }
393 void character::SetIntelligence (int What
) { BaseExperience
[INTELLIGENCE
] = What
* EXP_MULTIPLIER
; }
394 void character::SetWisdom (int What
) { BaseExperience
[WISDOM
] = What
* EXP_MULTIPLIER
; }
395 void character::SetWillPower (int What
) { BaseExperience
[WILL_POWER
] = What
* EXP_MULTIPLIER
; }
396 void character::SetCharisma (int What
) { BaseExperience
[CHARISMA
] = What
* EXP_MULTIPLIER
; }
397 void character::SetMana (int What
) { BaseExperience
[MANA
] = What
* EXP_MULTIPLIER
; }
398 truth
character::IsOnGround () const { return MotherEntity
&& MotherEntity
->IsOnGround(); }
399 truth
character::LeftOversAreUnique () const { return GetArticleMode() || AssignedName
.GetSize(); }
400 truth
character::HomeDataIsValid () const { return (HomeData
&& HomeData
->Level
== GetLSquareUnder()->GetLevelIndex() && HomeData
->Dungeon
== GetLSquareUnder()->GetDungeonIndex()); }
401 void character::SetHomePos (v2 Pos
) { HomeData
->Pos
= Pos
; }
402 cchar
*character::FirstPersonUnarmedHitVerb () const { return "hit"; }
403 cchar
*character::FirstPersonCriticalUnarmedHitVerb () const { return "critically hit"; }
404 cchar
*character::ThirdPersonUnarmedHitVerb () const { return "hits"; }
405 cchar
*character::ThirdPersonCriticalUnarmedHitVerb () const { return "critically hits"; }
406 cchar
*character::FirstPersonKickVerb () const { return "kick"; }
407 cchar
*character::FirstPersonCriticalKickVerb () const { return "critically kick"; }
408 cchar
*character::ThirdPersonKickVerb () const { return "kicks"; }
409 cchar
*character::ThirdPersonCriticalKickVerb () const { return "critically kicks"; }
410 cchar
*character::FirstPersonBiteVerb () const { return "bite"; }
411 cchar
*character::FirstPersonCriticalBiteVerb () const { return "critically bite"; }
412 cchar
*character::ThirdPersonBiteVerb () const { return "bites"; }
413 cchar
*character::ThirdPersonCriticalBiteVerb () const { return "critically bites"; }
414 cchar
*character::UnarmedHitNoun () const { return "attack"; }
415 cchar
*character::KickNoun () const { return "kick"; }
416 cchar
*character::BiteNoun () const { return "attack"; }
417 cchar
*character::GetEquipmentName (int) const { return ""; }
418 const std::list
<feuLong
> &character::GetOriginalBodyPartID (int I
) const { return OriginalBodyPartID
[I
]; }
419 square
*character::GetNeighbourSquare (int I
) const { return GetSquareUnder()->GetNeighbourSquare(I
); }
420 lsquare
*character::GetNeighbourLSquare (int I
) const { return static_cast<lsquare
*>(GetSquareUnder())->GetNeighbourLSquare(I
); }
421 //wsquare *character::GetNeighbourWSquare (int I) const { return static_cast<wsquare *>(GetSquareUnder())->GetNeighbourWSquare(I); }
422 god
*character::GetMasterGod () const { return game::GetGod(GetConfig()); }
423 col16
character::GetBodyPartColorA (int, truth
) const { return GetSkinColor(); }
424 col16
character::GetBodyPartColorB (int, truth
) const { return GetTorsoMainColor(); }
425 col16
character::GetBodyPartColorC (int, truth
) const { return GetBeltColor(); } // sorry...
426 col16
character::GetBodyPartColorD (int, truth
) const { return GetTorsoSpecialColor(); }
427 int character::GetRandomApplyBodyPart () const { return TORSO_INDEX
; }
428 truth
character::IsPet () const { return GetTeam()->GetID() == PLAYER_TEAM
; }
429 character
* character::GetLeader () const { return GetTeam()->GetLeader(); }
430 festring
character::GetZombieDescription () const { return " of "+GetName(INDEFINITE
); }
431 truth
character::BodyPartCanBeSevered (int I
) const { return I
; }
432 truth
character::HasBeenSeen () const { return DataBase
->Flags
& HAS_BEEN_SEEN
; }
433 truth
character::IsTemporary () const { return GetTorso()->GetLifeExpectancy(); }
434 cchar
*character::GetNormalDeathMessage () const { return "killed @k"; }
437 truth
character::IsHomeLevel (level
*lvl
) const {
438 if (!lvl
) return false;
440 const fearray
<festring
> &hlist
= DataBase
->HomeLevel
;
441 if (hlist
.Size
== 0) return false;
443 for (uInt f
= 0; f
< hlist
.Size
; ++f
) { if (hlist
[f
] == "*") return true; }
444 // taken from unique characters code
445 cfestring
*tag
= lvl
->GetLevelScript()->GetTag();
446 if (!tag
) return false; // not a possible home level
447 // check for home level
448 for (uInt f
= 0; f
< hlist
.Size
; ++f
) { if (hlist
[f
] == *tag
) return true; }
453 truth
character::MustBeRemovedFromBone () const {
454 if (IsUnique() && !CanBeGenerated()) return true;
456 const fearray
<festring
> &hlist
= DataBase
->HomeLevel
;
457 if (hlist
.Size
== 0) return false;
459 for (uInt f
= 0; f
< hlist
.Size
; ++f
) { if (hlist
[f
] == "*") return true; }
460 // taken from unique characters code
461 if (!IsEnabled() || GetTeam()->GetID() != DataBase
->NaturalTeam
) return true;
462 return IsHomeLevel(GetLevel());
466 int character::GetMoveType () const {// return (!StateIsActivated(LEVITATION) ? DataBase->MoveType : DataBase->MoveType | FLY); }
468 (!StateIsActivated(LEVITATION
) ? DataBase
->MoveType
: DataBase
->MoveType
|FLY
)|
469 (!StateIsActivated(ETHEREAL_MOVING
) ? DataBase
->MoveType
: DataBase
->MoveType
|ETHEREAL
)|
470 (!StateIsActivated(SWIMMING
) ? DataBase
->MoveType
: DataBase
->MoveType
|WALK
|SWIM
)|
476 int characterdatabase::*ExpPtr
[ATTRIBUTES
] = {
477 &characterdatabase::DefaultEndurance
,
478 &characterdatabase::DefaultPerception
,
479 &characterdatabase::DefaultIntelligence
,
480 &characterdatabase::DefaultWisdom
,
481 &characterdatabase::DefaultWillPower
,
482 &characterdatabase::DefaultCharisma
,
483 &characterdatabase::DefaultMana
,
484 &characterdatabase::DefaultArmStrength
,
485 &characterdatabase::DefaultLegStrength
,
486 &characterdatabase::DefaultDexterity
,
487 &characterdatabase::DefaultAgility
491 contentscript
<item
> characterdatabase::*EquipmentDataPtr
[EQUIPMENT_DATAS
] = {
492 &characterdatabase::Helmet
,
493 &characterdatabase::Amulet
,
494 &characterdatabase::Cloak
,
495 &characterdatabase::BodyArmor
,
496 &characterdatabase::Belt
,
497 &characterdatabase::RightWielded
,
498 &characterdatabase::LeftWielded
,
499 &characterdatabase::RightRing
,
500 &characterdatabase::LeftRing
,
501 &characterdatabase::RightGauntlet
,
502 &characterdatabase::LeftGauntlet
,
503 &characterdatabase::RightBoot
,
504 &characterdatabase::LeftBoot
508 character::character (ccharacter
&Char
) :
509 entity(Char
), id(Char
), NP(Char
.NP
), AP(Char
.AP
),
510 TemporaryState(Char
.TemporaryState
&~POLYMORPHED
),
511 Team(Char
.Team
), GoingTo(ERROR_V2
), Money(0),
512 AssignedName(Char
.AssignedName
), Action(0),
513 DataBase(Char
.DataBase
), MotherEntity(0),
514 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
515 AllowedWeaponSkillCategories(Char
.AllowedWeaponSkillCategories
),
516 BodyParts(Char
.BodyParts
),
517 RegenerationCounter(Char
.RegenerationCounter
),
518 SquaresUnder(Char
.SquaresUnder
), LastAcidMsgMin(0),
519 Stamina(Char
.Stamina
), MaxStamina(Char
.MaxStamina
),
520 BlocksSinceLastTurn(0), GenerationDanger(Char
.GenerationDanger
),
521 CommandFlags(Char
.CommandFlags
), WarnFlags(0),
522 ScienceTalks(Char
.ScienceTalks
), TrapData(0), CounterToMindWormHatch(0)
526 Flags
|= C_INITIALIZING
|C_IN_NO_MSG_MODE
;
527 Stack
= new stack(0, this, HIDDEN
);
528 for (c
= 0; c
< STATES
; ++c
) TemporaryStateCounter
[c
] = Char
.TemporaryStateCounter
[c
];
529 if (Team
) Team
->Add(this);
530 for (c
= 0; c
< BASE_ATTRIBUTES
; ++c
) BaseExperience
[c
] = Char
.BaseExperience
[c
];
531 BodyPartSlot
= new bodypartslot
[BodyParts
];
532 OriginalBodyPartID
= new std::list
<feuLong
>[BodyParts
];
533 CWeaponSkill
= new cweaponskill
[AllowedWeaponSkillCategories
];
534 SquareUnder
= new square
*[SquaresUnder
];
535 if (SquaresUnder
== 1) *SquareUnder
= 0; else memset(SquareUnder
, 0, SquaresUnder
*sizeof(square
*));
536 for (c
= 0; c
< BodyParts
; ++c
) {
537 BodyPartSlot
[c
].SetMaster(this);
538 bodypart
*CharBodyPart
= Char
.GetBodyPart(c
);
539 OriginalBodyPartID
[c
] = Char
.OriginalBodyPartID
[c
];
541 bodypart
*BodyPart
= static_cast<bodypart
*>(CharBodyPart
->Duplicate());
542 SetBodyPart(c
, BodyPart
);
543 BodyPart
->CalculateEmitation();
546 for (c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) CWeaponSkill
[c
] = Char
.CWeaponSkill
[c
];
547 HomeData
= Char
.HomeData
? new homedata(*Char
.HomeData
) : 0;
548 ID
= game::CreateNewCharacterID(this);
552 character::character () :
553 entity(HAS_BE
), NP(50000), AP(0), TemporaryState(0), Team(0),
554 GoingTo(ERROR_V2
), Money(0), Action(0), MotherEntity(0),
555 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
556 RegenerationCounter(0), HomeData(0), LastAcidMsgMin(0),
557 BlocksSinceLastTurn(0), GenerationDanger(DEFAULT_GENERATION_DANGER
),
558 WarnFlags(0), ScienceTalks(0), TrapData(0), CounterToMindWormHatch(0)
560 Stack
= new stack(0, this, HIDDEN
);
564 character::~character () {
565 if (Action
) delete Action
;
566 if (Team
) Team
->Remove(this);
568 for (int c
= 0; c
< BodyParts
; ++c
) delete GetBodyPart(c
);
569 delete [] BodyPartSlot
;
570 delete [] OriginalBodyPartID
;
571 delete PolymorphBackup
;
572 delete [] SquareUnder
;
573 delete [] CWeaponSkill
;
575 deleteList(TrapData
);
576 game::RemoveCharacterID(ID
);
580 void character::Hunger () {
581 auto bst
= GetBurdenState();
582 if (bst
== OVER_LOADED
|| bst
== STRESSED
) {
584 EditExperience(LEG_STRENGTH
, 150, 1 << 2);
585 EditExperience(AGILITY
, -50, 1 << 2);
586 } else if (bst
== BURDENED
) {
588 EditExperience(LEG_STRENGTH
, 75, 1 << 1);
589 EditExperience(AGILITY
, -25, 1 << 1);
590 } else if (bst
== UNBURDENED
) {
594 auto hst
= GetHungerState();
595 if (hst
== STARVING
) {
596 EditExperience(ARM_STRENGTH
, -75, 1 << 3);
597 EditExperience(LEG_STRENGTH
, -75, 1 << 3);
598 } else if (hst
== VERY_HUNGRY
) {
599 EditExperience(ARM_STRENGTH
, -50, 1 << 2);
600 EditExperience(LEG_STRENGTH
, -50, 1 << 2);
601 } else if (hst
== HUNGRY
) {
602 EditExperience(ARM_STRENGTH
, -25, 1 << 1);
603 EditExperience(LEG_STRENGTH
, -25, 1 << 1);
604 } else if (hst
== SATIATED
) {
605 EditExperience(AGILITY
, -25, 1 << 1);
606 } else if (hst
== BLOATED
) {
607 EditExperience(AGILITY
, -50, 1 << 2);
608 } else if (hst
== OVER_FED
) {
609 EditExperience(AGILITY
, -75, 1 << 3);
612 CheckStarvationDeath(CONST_S("starved to death"));
616 int character::TakeHit (character
*Enemy
, item
*Weapon
, bodypart
*EnemyBodyPart
, v2 HitPos
, double Damage
,
617 double ToHitValue
, int Success
, int Type
, int GivenDir
, truth Critical
, truth ForceHit
)
620 if (!game::RunCharEvent("before_take_hit", this, Enemy
, Weapon
)) return DID_NO_DAMAGE
;
622 int Dir
= (Type
== BITE_ATTACK
? YOURSELF
: GivenDir
);
623 double DodgeValue
= GetDodgeValue();
624 if (!Enemy
->IsPlayer() && GetAttackWisdomLimit() != NO_LIMIT
) Enemy
->EditExperience(WISDOM
, 75, 1 << 13);
625 if (!Enemy
->CanBeSeenBy(this)) ToHitValue
*= 2;
626 if (!CanBeSeenBy(Enemy
)) DodgeValue
*= 2;
627 if (Enemy
->StateIsActivated(CONFUSED
)) ToHitValue
*= 0.75;
629 switch (Enemy
->GetTirednessState()) {
635 switch (GetTirednessState()) {
643 if (!IsRetreating()) SetGoingTo(Enemy
->GetPos());
644 else SetGoingTo(GetPos()-((Enemy
->GetPos()-GetPos())<<4));
645 if (!Enemy
->IsRetreating()) Enemy
->SetGoingTo(GetPos());
646 else Enemy
->SetGoingTo(Enemy
->GetPos()-((GetPos()-Enemy
->GetPos())<<4));
649 /* Effectively, the average chance to hit is 100% / (DV/THV + 1). */
650 if (RAND() % int(100+ToHitValue
/DodgeValue
*(100+Success
)) < 100 && !Critical
&& !ForceHit
) {
651 Enemy
->AddMissMessage(this);
652 EditExperience(AGILITY
, 150, 1 << 7);
653 EditExperience(PERCEPTION
, 75, 1 << 7);
654 if (Enemy
->CanBeSeenByPlayer())
655 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
657 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
661 int TrueDamage
= int(Damage
*(100+Success
)/100)+(RAND()%3 ? 1 : 0);
663 TrueDamage
+= TrueDamage
>> 1;
667 int BodyPart
= ChooseBodyPartToReceiveHit(ToHitValue
, DodgeValue
);
669 if (Type
== UNARMED_ATTACK
) Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalUnarmedHitVerb(), Enemy
->ThirdPersonCriticalUnarmedHitVerb(), BodyPart
);
670 else if (Type
== WEAPON_ATTACK
) Enemy
->AddWeaponHitMessage(this, Weapon
, BodyPart
, true);
671 else if (Type
== KICK_ATTACK
) Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalKickVerb(), Enemy
->ThirdPersonCriticalKickVerb(), BodyPart
);
672 else if (Type
== BITE_ATTACK
) Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalBiteVerb(), Enemy
->ThirdPersonCriticalBiteVerb(), BodyPart
);
674 if (Type
== UNARMED_ATTACK
) Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonUnarmedHitVerb(), Enemy
->ThirdPersonUnarmedHitVerb(), BodyPart
);
675 else if (Type
== WEAPON_ATTACK
) Enemy
->AddWeaponHitMessage(this, Weapon
, BodyPart
, false);
676 else if (Type
== KICK_ATTACK
) Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonKickVerb(), Enemy
->ThirdPersonKickVerb(), BodyPart
);
677 else if (Type
== BITE_ATTACK
) Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonBiteVerb(), Enemy
->ThirdPersonBiteVerb(), BodyPart
);
680 if (!Critical
&& TrueDamage
&& Enemy
->AttackIsBlockable(Type
)) {
681 TrueDamage
= CheckForBlock(Enemy
, Weapon
, ToHitValue
, TrueDamage
, Success
, Type
);
682 if (!TrueDamage
|| (Weapon
&& !Weapon
->Exists())) {
683 if (Enemy
->CanBeSeenByPlayer())
684 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
686 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
691 int WeaponSkillHits
= CalculateWeaponSkillHits(Enemy
);
692 int DoneDamage
= ReceiveBodyPartDamage(Enemy
, TrueDamage
, PHYSICAL_DAMAGE
, BodyPart
, Dir
, false, Critical
, true, Type
== BITE_ATTACK
&& Enemy
->BiteCapturesBodyPart());
693 truth Succeeded
= (GetBodyPart(BodyPart
) && HitEffect(Enemy
, Weapon
, HitPos
, Type
, BodyPart
, Dir
, !DoneDamage
, Critical
, DoneDamage
)) || DoneDamage
;
694 if (Succeeded
) Enemy
->WeaponSkillHit(Weapon
, Type
, WeaponSkillHits
);
697 if (Weapon
->Exists() && DoneDamage
< TrueDamage
) Weapon
->ReceiveDamage(Enemy
, TrueDamage
-DoneDamage
, PHYSICAL_DAMAGE
);
698 if (Weapon
->Exists() && DoneDamage
&& SpillsBlood() && GetBodyPart(BodyPart
) &&
699 (GetBodyPart(BodyPart
)->IsAlive() || GetBodyPart(BodyPart
)->GetMainMaterial()->IsLiquid()))
700 Weapon
->SpillFluid(0, CreateBlood(15+RAND()%15));
703 if (Enemy
->AttackIsBlockable(Type
)) SpecialBodyDefenceEffect(Enemy
, EnemyBodyPart
, Type
);
706 if (Enemy
->CanBeSeenByPlayer())
707 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
709 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
711 return DID_NO_DAMAGE
;
714 if (CheckDeath(GetNormalDeathMessage(), Enemy
, Enemy
->IsPlayer() ? FORCE_MSG
: 0)) return HAS_DIED
;
716 if (Enemy
->CanBeSeenByPlayer())
717 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
719 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
725 struct svpriorityelement
{
726 svpriorityelement (int BodyPart
, int StrengthValue
) : BodyPart(BodyPart
), StrengthValue(StrengthValue
) {}
727 bool operator < (const svpriorityelement
&AnotherPair
) const { return StrengthValue
> AnotherPair
.StrengthValue
; }
733 int character::ChooseBodyPartToReceiveHit (double ToHitValue
, double DodgeValue
) {
734 if (BodyParts
== 1) return 0;
735 std::priority_queue
<svpriorityelement
> SVQueue
;
736 for (int c
= 0; c
< BodyParts
; ++c
) {
737 bodypart
*BodyPart
= GetBodyPart(c
);
738 if (BodyPart
&& (BodyPart
->GetHP() != 1 || BodyPart
->CanBeSevered(PHYSICAL_DAMAGE
)))
739 SVQueue
.push(svpriorityelement(c
, ModifyBodyPartHitPreference(c
, BodyPart
->GetStrengthValue()+BodyPart
->GetHP())));
741 while (SVQueue
.size()) {
742 svpriorityelement E
= SVQueue
.top();
743 int ToHitPercentage
= int(GLOBAL_WEAK_BODYPART_HIT_MODIFIER
*ToHitValue
*GetBodyPart(E
.BodyPart
)->GetBodyPartVolume()/(DodgeValue
*GetBodyVolume()));
744 ToHitPercentage
= ModifyBodyPartToHitChance(E
.BodyPart
, ToHitPercentage
);
745 if (ToHitPercentage
< 1) ToHitPercentage
= 1;
746 else if (ToHitPercentage
> 95) ToHitPercentage
= 95;
747 if (ToHitPercentage
> RAND()%100) return E
.BodyPart
;
754 void character::Be () {
755 if (game::ForceJumpToPlayerBe()) {
756 if (!IsPlayer()) return;
757 game::SetForceJumpToPlayerBe(false);
759 truth ForceBe
= HP
!= MaxHP
|| AllowSpoil();
760 for (int c
= 0; c
< BodyParts
; ++c
) {
761 bodypart
*BodyPart
= GetBodyPart(c
);
762 if (BodyPart
&& (ForceBe
|| BodyPart
->NeedsBe())) BodyPart
->Be();
765 if (!IsEnabled()) return;
766 if (GetTeam() == PLAYER
->GetTeam()) {
767 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) {
768 if (CWeaponSkill
[c
].Tick() && IsPlayer()) CWeaponSkill
[c
].AddLevelDownMessage(c
);
773 if (GetHungerState() == STARVING
&& !(RAND()%50)) LoseConsciousness(250+RAND_N(250), true);
774 if (!Action
|| Action
->AllowFoodConsumption()) Hunger();
776 if (Stamina
!= MaxStamina
) RegenerateStamina();
777 if (HP
!= MaxHP
|| StateIsActivated(REGENERATION
)) Regenerate();
778 if (Action
&& AP
>= 1000) ActionAutoTermination();
779 if (Action
&& AP
>= 1000) {
781 if (!IsEnabled()) return;
783 EditAP(GetStateAPGain(100));
787 SpecialTurnHandler();
788 BlocksSinceLastTurn
= 0;
790 static int Timer
= 0;
792 if (ivanconfig::GetAutoSaveInterval() && !GetAction() && ++Timer
>= ivanconfig::GetAutoSaveInterval()) {
793 //fprintf(stderr, "autosaving..."); fflush(stderr);
794 game::Save(game::GetAutoSaveFileName());
795 //fprintf(stderr, "done\n"); fflush(stderr);
798 game::CalculateNextDanger();
799 if (!StateIsActivated(POLYMORPHED
)) game::UpdatePlayerAttributeAverage();
800 if (!game::IsInWilderness()) Search(GetAttribute(PERCEPTION
));
804 if (Action
->ShowEnvironment()) {
805 static int Counter
= 0;
806 if (++Counter
== 10) {
807 game::DrawEverything();
811 msgsystem::ThyMessagesAreNowOld();
812 if (Action
->IsVoluntary() && READ_KEY()) Action
->Terminate(false);
815 if (!Action
&& !game::IsInWilderness()) GetAICommand();
821 void character::Move (v2 MoveTo
, truth TeleportMove
, truth Run
) {
822 if (!IsEnabled()) return;
823 /* Test whether the player is stuck to something */
824 if (!TeleportMove
&& !TryToUnStickTraps(MoveTo
-GetPos())) return;
825 if (Run
&& !IsPlayer() && TorsoIsAlive() &&
826 (Stamina
<= 10000/Max(GetAttribute(LEG_STRENGTH
), 1) || (!StateIsActivated(PANIC
) && Stamina
< MaxStamina
>>2))) {
830 if (GetBurdenState() != OVER_LOADED
|| TeleportMove
) {
831 lsquare
*OldSquareUnder
[MAX_SQUARES_UNDER
];
833 if (!game::IsInWilderness()) {
836 area
*ca
= GetSquareUnder()->GetArea();
838 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
839 v2 np
= GetPos()+game::GetMoveVector(f
);
841 if (np
.X
>= 0 && np
.Y
>= 0 && np
.X
< ca
->GetXSize() && np
.Y
< ca
->GetYSize()) {
842 lsquare
*sq
= static_cast<lsquare
*>(ca
->GetSquare(np
.X
, np
.Y
));
849 for (int c
= 0; c
< GetSquaresUnder(); ++c
) OldSquareUnder
[c
] = GetLSquareUnder(c
);
854 /* Multitiled creatures should behave differently, maybe? */
856 int ED
= GetSquareUnder()->GetEntryDifficulty(), Base
= 10000;
858 EditAP(-GetMoveAPRequirement(ED
)>>1);
860 EditExperience(AGILITY
, 125, ED
<<7);
862 auto hst
= GetHungerState();
863 if (hst
== SATIATED
) Base
= 11000;
864 else if (hst
== BLOATED
) Base
= 12500;
865 else if (hst
== OVER_FED
) Base
= 15000;
867 EditStamina(-Base
/Max(GetAttribute(LEG_STRENGTH
), 1), true);
869 int ED
= GetSquareUnder()->GetEntryDifficulty();
871 EditAP(-GetMoveAPRequirement(ED
));
873 EditExperience(AGILITY
, 75, ED
<<7);
876 if (IsPlayer()) ShowNewPosInfo();
877 if (IsPlayer() && !game::IsInWilderness()) GetStackUnder()->SetSteppedOn(true);
878 if (!game::IsInWilderness()) SignalStepFrom(OldSquareUnder
);
881 cchar
*CrawlVerb
= StateIsActivated(LEVITATION
) ? "float" : "crawl";
883 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb
);
890 void character::GetAICommand () {
891 SeekLeader(GetLeader());
892 if (FollowLeader(GetLeader())) return;
893 if (CheckForEnemies(true, true, true)) return;
894 if (CheckForUsefulItemsOnGround()) return;
895 if (CheckForDoors()) return;
896 if (CheckSadism()) return;
897 if (MoveRandomly()) return;
902 truth
character::MoveTowardsTarget (truth Run
) {
907 if (!Route
.empty()) {
910 } else TPos
= GoingTo
;
912 MoveTo
[0] = v2(0, 0);
913 MoveTo
[1] = v2(0, 0);
914 MoveTo
[2] = v2(0, 0);
916 if (TPos
.X
< Pos
.X
) {
917 if (TPos
.Y
< Pos
.Y
) {
918 MoveTo
[0] = v2(-1, -1);
919 MoveTo
[1] = v2(-1, 0);
920 MoveTo
[2] = v2( 0, -1);
921 } else if (TPos
.Y
== Pos
.Y
) {
922 MoveTo
[0] = v2(-1, 0);
923 MoveTo
[1] = v2(-1, -1);
924 MoveTo
[2] = v2(-1, 1);
925 } else if (TPos
.Y
> Pos
.Y
) {
926 MoveTo
[0] = v2(-1, 1);
927 MoveTo
[1] = v2(-1, 0);
928 MoveTo
[2] = v2( 0, 1);
930 } else if (TPos
.X
== Pos
.X
) {
931 if (TPos
.Y
< Pos
.Y
) {
932 MoveTo
[0] = v2( 0, -1);
933 MoveTo
[1] = v2(-1, -1);
934 MoveTo
[2] = v2( 1, -1);
935 } else if (TPos
.Y
== Pos
.Y
) {
938 } else if (TPos
.Y
> Pos
.Y
) {
939 MoveTo
[0] = v2( 0, 1);
940 MoveTo
[1] = v2(-1, 1);
941 MoveTo
[2] = v2( 1, 1);
943 } else if (TPos
.X
> Pos
.X
) {
944 if (TPos
.Y
< Pos
.Y
) {
945 MoveTo
[0] = v2(1, -1);
946 MoveTo
[1] = v2(1, 0);
947 MoveTo
[2] = v2(0, -1);
948 } else if (TPos
.Y
== Pos
.Y
) {
949 MoveTo
[0] = v2(1, 0);
950 MoveTo
[1] = v2(1, -1);
951 MoveTo
[2] = v2(1, 1);
952 } else if (TPos
.Y
> Pos
.Y
) {
953 MoveTo
[0] = v2(1, 1);
954 MoveTo
[1] = v2(1, 0);
955 MoveTo
[2] = v2(0, 1);
959 v2 ModifiedMoveTo
= ApplyStateModification(MoveTo
[0]);
961 if (TryMove(ModifiedMoveTo
, true, Run
)) return true;
963 int L
= (Pos
-TPos
).GetManhattanLength();
965 if (RAND()&1) Swap(MoveTo
[1], MoveTo
[2]);
967 if (Pos
.IsAdjacent(TPos
)) {
972 if ((Pos
+MoveTo
[1]-TPos
).GetManhattanLength() <= L
&& TryMove(ApplyStateModification(MoveTo
[1]), true, Run
)) return true;
973 if ((Pos
+MoveTo
[2]-TPos
).GetManhattanLength() <= L
&& TryMove(ApplyStateModification(MoveTo
[2]), true, Run
)) return true;
974 Illegal
.insert(Pos
+ModifiedMoveTo
);
975 if (CreateRoute()) return true;
980 int character::CalculateNewSquaresUnder (lsquare
**NewSquare
, v2 Pos
) const {
981 if (GetLevel()->IsValidPos(Pos
)) {
982 *NewSquare
= GetNearLSquare(Pos
);
989 truth
character::TryMove (v2 MoveVector
, truth Important
, truth Run
) {
990 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
991 character
*Pet
[MAX_SQUARES_UNDER
];
992 character
*Neutral
[MAX_SQUARES_UNDER
];
993 character
*Hostile
[MAX_SQUARES_UNDER
];
994 v2 PetPos
[MAX_SQUARES_UNDER
];
995 v2 NeutralPos
[MAX_SQUARES_UNDER
];
996 v2 HostilePos
[MAX_SQUARES_UNDER
];
997 v2 MoveTo
= GetPos()+MoveVector
;
998 int Direction
= game::GetDirectionForVector(MoveVector
);
999 if (Direction
== DIR_ERROR
) ABORT("Direction fault.");
1000 if (!game::IsInWilderness()) {
1001 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, MoveTo
);
1006 for (int c
= 0; c
< Squares
; ++c
) {
1007 character
* Char
= MoveToSquare
[c
]->GetCharacter();
1008 if (Char
&& Char
!= this) {
1009 v2 Pos
= MoveToSquare
[c
]->GetPos();
1012 PetPos
[Pets
++] = Pos
;
1013 } else if (Char
->GetRelation(this) != HOSTILE
) {
1014 Neutral
[Neutrals
] = Char
;
1015 NeutralPos
[Neutrals
++] = Pos
;
1017 Hostile
[Hostiles
] = Char
;
1018 HostilePos
[Hostiles
++] = Pos
;
1023 if (Hostiles
== 1) return Hit(Hostile
[0], HostilePos
[0], Direction
);
1025 int Index
= RAND() % Hostiles
;
1026 return Hit(Hostile
[Index
], HostilePos
[Index
], Direction
);
1029 if (Neutrals
== 1) {
1030 if (!IsPlayer() && !Pets
&& Important
&& CanMoveOn(MoveToSquare
[0]))
1031 return HandleCharacterBlockingTheWay(Neutral
[0], NeutralPos
[0], Direction
);
1033 return IsPlayer() && Hit(Neutral
[0], NeutralPos
[0], Direction
);
1034 } else if (Neutrals
) {
1036 int Index
= RAND() % Neutrals
;
1037 return Hit(Neutral
[Index
], NeutralPos
[Index
], Direction
);
1043 for (int c
= 0; c
< Squares
; ++c
) if (MoveToSquare
[c
]->IsScary(this)) return false;
1047 if (IsPlayer() && !ivanconfig::GetBeNice() && Pet
[0]->IsMasochist() && HasSadistAttackMode() &&
1048 game::TruthQuestion("Do you want to punish " + Pet
[0]->GetObjectPronoun() + "?"))
1049 return Hit(Pet
[0], PetPos
[0], Direction
, SADIST_HIT
);
1051 return (Important
&& (CanMoveOn(MoveToSquare
[0]) ||
1052 (IsPlayer() && game::GoThroughWallsCheatIsActive())) && Displace(Pet
[0]));
1053 } else if (Pets
) return false;
1055 if ((CanMove() && CanMoveOn(MoveToSquare
[0])) || ((game::GoThroughWallsCheatIsActive() && IsPlayer()))) {
1056 Move(MoveTo
, false, Run
);
1057 if (IsEnabled() && GetPos() == GoingTo
) TerminateGoingTo();
1060 for (int c
= 0; c
< Squares
; ++c
) {
1061 olterrain
*Terrain
= MoveToSquare
[c
]->GetOLTerrain();
1062 if (Terrain
&& Terrain
->CanBeOpened()) {
1064 if (Terrain
->IsLocked()) {
1067 if (ivanconfig::GetKickDownDoors()) {
1068 if (game::TruthQuestion(CONST_S("Locked! Do you want to kick ")+Terrain
->GetName(DEFINITE
)+"?", true, game::GetMoveCommandKeyBetweenPoints(PLAYER
->GetPos(), MoveToSquare
[0]->GetPos()))) {
1069 Kick(MoveToSquare
[c
], Direction
);
1076 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 */
1078 } else if (Important
&& CheckKick()) {
1079 room
*Room
= MoveToSquare
[c
]->GetRoom();
1080 if (!Room
|| Room
->AllowKick(this, MoveToSquare
[c
])) {
1081 int HP
= Terrain
->GetHP();
1082 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s kicks %s.", CHAR_NAME(DEFINITE
), Terrain
->CHAR_NAME(DEFINITE
));
1083 Kick(MoveToSquare
[c
], Direction
);
1084 olterrain
*NewTerrain
= MoveToSquare
[c
]->GetOLTerrain();
1085 if (NewTerrain
== Terrain
&& Terrain
->GetHP() == HP
) { // BUG!
1086 Illegal
.insert(MoveTo
);
1092 } else { /* if (Terrain->IsLocked()) */
1093 /*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);*/
1094 /* Non-players always try to open it */
1095 if (!IsPlayer()) return MoveToSquare
[c
]->Open(this);
1096 if (game::TruthQuestion(CONST_S("Do you want to open ")+Terrain
->GetName(DEFINITE
)+"?", false, game::GetMoveCommandKeyBetweenPoints(PLAYER
->GetPos(), MoveToSquare
[0]->GetPos()))) {
1097 return MoveToSquare
[c
]->Open(this);
1100 } /* if (Terrain->IsLocked()) */
1101 } else { /* if (CanOpen()) */
1103 ADD_MESSAGE("This monster type cannot open doors.");
1107 Illegal
.insert(MoveTo
);
1108 return CreateRoute();
1110 } /* if (CanOpen()) */
1111 } /* if (Terrain && Terrain->CanBeOpened()) */
1116 if (IsPlayer() && !IsStuck() && GetLevel()->IsOnGround() && game::TruthQuestion(CONST_S("Do you want to leave ")+game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex())+"?")) {
1117 if (HasPetrussNut() && !HasGoldenEagleShirt()) {
1118 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!"));
1119 game::GetCurrentArea()->SendNewDrawRequest();
1120 game::DrawEverything();
1121 ShowAdventureInfo();
1122 festring Msg
= CONST_S("killed Petrus and became the Avatar of Chaos");
1123 PLAYER
->AddScoreEntry(Msg
, 3, false);
1127 if (game::TryTravel(WORLD_MAP
, WORLD_MAP
, game::GetCurrentDungeonIndex())) return true;
1132 /** No multitile support */
1133 if (CanMove() && GetArea()->IsValidPos(MoveTo
) && (CanMoveOn(GetNearWSquare(MoveTo
)) || game::GoThroughWallsCheatIsActive())) {
1134 if (!game::GoThroughWallsCheatIsActive()) {
1135 charactervector
&V
= game::GetWorldMap()->GetPlayerGroup();
1136 truth Discard
= false;
1137 for (uInt c
= 0; c
< V
.size(); ++c
) {
1138 if (!V
[c
]->CanMoveOn(GetNearWSquare(MoveTo
))) {
1140 ADD_MESSAGE("One or more of your team members cannot cross this terrain.");
1141 if (!game::TruthQuestion("Discard them?")) return false;
1144 if (Discard
) delete V
[c
];
1145 V
.erase(V
.begin() + c
--);
1149 Move(MoveTo
, false);
1158 void character::CreateCorpse (lsquare
*Square
) {
1159 if (!BodyPartsDisappearWhenSevered() && !game::AllBodyPartsVanish()) {
1160 corpse
*Corpse
= corpse::Spawn(0, NO_MATERIALS
);
1161 Corpse
->SetDeceased(this);
1162 Square
->AddItem(Corpse
);
1170 void character::Die (character
*Killer
, cfestring
&Msg
, feuLong DeathFlags
) {
1171 /* Note: This function musn't delete any objects, since one of these may be
1172 the one currently processed by pool::Be()! */
1173 if (!IsEnabled()) return;
1175 if (!game::RunCharEvent("before_die", this, Killer
)) return;
1179 ADD_MESSAGE("You die.");
1180 game::DrawEverything();
1181 if (game::TruthQuestion(CONST_S("Do you want to save screenshot?"), REQUIRES_ANSWER
)) {
1184 dir
<< ivanconfig::GetMyDir() << "/save";
1185 mkdir(dir
.CStr(), 0755);
1187 dir
<< getenv("HOME") << "/.ivan-save";
1188 mkdir(dir
.CStr(), 0755);
1190 dir
<< "/deathshots";
1191 mkdir(dir
.CStr(), 0755);
1193 time_t t
= time(NULL
);
1194 struct tm
*ts
= localtime(&t
);
1196 timestr
<< (int)(ts
->tm_year
%100);
1197 int t
= ts
->tm_mon
+1;
1198 if (t
< 10) timestr
<< '0'; timestr
<< t
;
1199 t
= ts
->tm_mday
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1201 t
= ts
->tm_hour
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1202 t
= ts
->tm_min
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1203 t
= ts
->tm_sec
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1207 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1208 festring ext
= ".png";
1210 festring ext
= ".bmp";
1212 festring fname
= dir
+"/deathshot_"+timestr
;
1213 if (inputfile::fileExists(fname
+ext
)) {
1214 for (int f
= 0; f
< 1000; f
++) {
1216 sprintf(buf
, "%03d", f
);
1217 festring fn
= fname
+buf
;
1218 if (!inputfile::fileExists(fn
+ext
)) {
1225 fprintf(stderr
, "deathshot: %s\n", fname
.CStr());
1226 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1227 DOUBLE_BUFFER
->SavePNG(fname
);
1229 DOUBLE_BUFFER
->SaveBMP(fname
);
1232 if (game::WizardModeIsActive()) {
1233 game::DrawEverything();
1234 if (!game::TruthQuestion(CONST_S("Do you want to do this, cheater?"), REQUIRES_ANSWER
)) {
1240 SetNP(SATIATED_LEVEL
);
1241 SendNewDrawRequest();
1245 } else if (CanBeSeenByPlayer() && !(DeathFlags
& DISALLOW_MSG
)) {
1246 ProcessAndAddMessage(GetDeathMessage());
1247 } else if (DeathFlags
& FORCE_MSG
) {
1248 ADD_MESSAGE("You sense the death of something.");
1251 if (!(DeathFlags
& FORBID_REINCARNATION
)) {
1252 if (StateIsActivated(LIFE_SAVED
) && CanMoveOn(!game::IsInWilderness() ? GetSquareUnder() : PLAYER
->GetSquareUnder())) {
1256 if (SpecialSaveLife()) return;
1257 } else if (StateIsActivated(LIFE_SAVED
)) {
1261 Flags
|= C_IN_NO_MSG_MODE
;
1262 character
*Ghost
= 0;
1264 game::RemoveSaves();
1265 if (!game::IsInWilderness()) {
1266 Ghost
= game::CreateGhost();
1271 square
*SquareUnder
[MAX_SQUARES_UNDER
];
1272 memset(SquareUnder
, 0, sizeof(SquareUnder
));
1274 if (IsPlayer() || !game::IsInWilderness()) {
1275 for (int c
= 0; c
< SquaresUnder
; ++c
) SquareUnder
[c
] = GetSquareUnder(c
);
1278 charactervector
& V
= game::GetWorldMap()->GetPlayerGroup();
1279 V
.erase(std::find(V
.begin(), V
.end(), this));
1281 //lsquare **LSquareUnder = reinterpret_cast<lsquare **>(SquareUnder); /* warning; wtf? */
1282 lsquare
*LSquareUnder
[MAX_SQUARES_UNDER
];
1283 memmove(LSquareUnder
, SquareUnder
, sizeof(SquareUnder
));
1285 if (!game::IsInWilderness()) {
1286 if (!StateIsActivated(POLYMORPHED
)) {
1287 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(this, Killer
, Msg
);
1288 if (!(DeathFlags
& DISALLOW_CORPSE
)) CreateCorpse(LSquareUnder
[0]); else SendToHell();
1290 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(GetPolymorphBackup(), Killer
, Msg
);
1291 GetPolymorphBackup()->CreateCorpse(LSquareUnder
[0]);
1292 GetPolymorphBackup()->Flags
&= ~C_POLYMORPHED
;
1293 SetPolymorphBackup(0);
1297 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(this, Killer
, Msg
);
1302 if (!game::IsInWilderness()) {
1303 for (int c
= 0; c
< GetSquaresUnder(); ++c
) LSquareUnder
[c
]->SetTemporaryEmitation(GetEmitation());
1305 ShowAdventureInfo();
1306 if (!game::IsInWilderness()) {
1307 for(int c
= 0; c
< GetSquaresUnder(); ++c
) LSquareUnder
[c
]->SetTemporaryEmitation(0);
1311 if (!game::IsInWilderness()) {
1312 if (GetSquaresUnder() == 1) {
1313 stack
*StackUnder
= LSquareUnder
[0]->GetStack();
1314 GetStack()->MoveItemsTo(StackUnder
);
1315 doforbodypartswithparam
<stack
*>()(this, &bodypart::DropEquipment
, StackUnder
);
1317 while (GetStack()->GetItems()) GetStack()->GetBottom()->MoveTo(LSquareUnder
[RAND_N(GetSquaresUnder())]->GetStack());
1318 for (int c
= 0; c
< BodyParts
; ++c
) {
1319 bodypart
*BodyPart
= GetBodyPart(c
);
1320 if (BodyPart
) BodyPart
->DropEquipment(LSquareUnder
[RAND_N(GetSquaresUnder())]->GetStack());
1325 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(0);
1327 Flags
&= ~C_IN_NO_MSG_MODE
;
1331 if (!game::IsInWilderness()) {
1332 Ghost
->PutTo(LSquareUnder
[0]->GetPos());
1336 game::TextScreen(CONST_S("Unfortunately you died."), ZERO_V2
, WHITE
, true, true, &game::ShowDeathSmiley
);
1342 void character::AddMissMessage (ccharacter
*Enemy
) const {
1344 if (Enemy
->IsPlayer()) Msg
= GetDescription(DEFINITE
)+" misses you!";
1345 else if (IsPlayer()) Msg
= CONST_S("You miss ")+Enemy
->GetDescription(DEFINITE
)+'!';
1346 else if (CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer()) Msg
= GetDescription(DEFINITE
)+" misses "+Enemy
->GetDescription(DEFINITE
)+'!';
1348 ADD_MESSAGE("%s", Msg
.CStr());
1352 void character::AddBlockMessage (ccharacter
*Enemy
, citem
*Blocker
, cfestring
&HitNoun
, truth Partial
) const {
1354 festring BlockVerb
= (Partial
? " to partially block the " : " to block the ")+HitNoun
;
1356 Msg
<< "You manage" << BlockVerb
<< " with your " << Blocker
->GetName(UNARTICLED
) << '!';
1357 } else if (Enemy
->IsPlayer() || Enemy
->CanBeSeenByPlayer()) {
1358 if (CanBeSeenByPlayer())
1359 Msg
<< GetName(DEFINITE
) << " manages" << BlockVerb
<< " with " << GetPossessivePronoun() << ' ' << Blocker
->GetName(UNARTICLED
) << '!';
1361 Msg
<< "Something manages" << BlockVerb
<< " with something!";
1365 ADD_MESSAGE("%s", Msg
.CStr());
1369 void character::AddPrimitiveHitMessage (ccharacter
*Enemy
, cfestring
&FirstPersonHitVerb
,
1370 cfestring
&ThirdPersonHitVerb
, int BodyPart
) const
1373 festring BodyPartDescription
;
1374 if (BodyPart
&& (Enemy
->CanBeSeenByPlayer() || Enemy
->IsPlayer()))
1375 BodyPartDescription
<< " in the " << Enemy
->GetBodyPartName(BodyPart
);
1376 if (Enemy
->IsPlayer())
1377 Msg
<< GetDescription(DEFINITE
) << ' ' << ThirdPersonHitVerb
<< " you" << BodyPartDescription
<< '!';
1378 else if (IsPlayer())
1379 Msg
<< "You " << FirstPersonHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
<< '!';
1380 else if (CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer())
1381 Msg
<< GetDescription(DEFINITE
) << ' ' << ThirdPersonHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) + BodyPartDescription
<< '!';
1384 ADD_MESSAGE("%s", Msg
.CStr());
1388 cchar
*const HitVerb
[] = { "strike", "slash", "stab" };
1389 cchar
*const HitVerb3rdPersonEnd
[] = { "s", "es", "s" };
1392 void character::AddWeaponHitMessage (ccharacter
*Enemy
, citem
*Weapon
, int BodyPart
, truth Critical
) const {
1394 festring BodyPartDescription
;
1396 if (BodyPart
&& (Enemy
->CanBeSeenByPlayer() || Enemy
->IsPlayer()))
1397 BodyPartDescription
<< " in the " << Enemy
->GetBodyPartName(BodyPart
);
1399 int FittingTypes
= 0;
1400 int DamageFlags
= Weapon
->GetDamageFlags();
1403 for (int c
= 0; c
< DAMAGE_TYPES
; ++c
) {
1404 if (1 << c
& DamageFlags
) {
1405 if (!FittingTypes
|| !RAND_N(FittingTypes
+1)) DamageType
= c
;
1410 if (!FittingTypes
) ABORT("No damage flags specified for %s!", Weapon
->CHAR_NAME(UNARTICLED
));
1412 festring NewHitVerb
= Critical
? " critically " : " ";
1413 NewHitVerb
<< HitVerb
[DamageType
];
1414 cchar
*const E
= HitVerb3rdPersonEnd
[DamageType
];
1416 if (Enemy
->IsPlayer()) {
1417 Msg
<< GetDescription(DEFINITE
) << NewHitVerb
<< E
<< " you" << BodyPartDescription
;
1418 if (CanBeSeenByPlayer()) Msg
<< " with " << GetPossessivePronoun() << ' ' << Weapon
->GetName(UNARTICLED
);
1420 } else if (IsPlayer()) {
1421 Msg
<< "You" << NewHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
<< '!';
1422 } else if(CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer()) {
1423 Msg
<< GetDescription(DEFINITE
) << NewHitVerb
<< E
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
;
1424 if (CanBeSeenByPlayer()) Msg
<< " with " << GetPossessivePronoun() << ' ' << Weapon
->GetName(UNARTICLED
);
1429 ADD_MESSAGE("%s", Msg
.CStr());
1433 item
*character::GeneralFindItem (ItemCheckerCB chk
) const {
1434 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1436 if (it
&& chk(it
)) return it
;
1442 static truth
isEncryptedScroll (item
*i
) { return i
->IsEncryptedScroll(); }
1443 truth
character::HasEncryptedScroll () const {
1444 if (GeneralFindItem(::isEncryptedScroll
)) return true;
1445 return combineequipmentpredicates()(this, &item::IsEncryptedScroll
, 1);
1449 static truth
isElpuriHead (item
*i
) { return i
->IsHeadOfElpuri(); }
1450 truth
character::HasHeadOfElpuri () const {
1451 if (GeneralFindItem(::isElpuriHead
)) return true;
1452 return combineequipmentpredicates()(this, &item::IsHeadOfElpuri
, 1);
1456 static truth
isPetrussNut (item
*i
) { return i
->IsPetrussNut(); }
1457 truth
character::HasPetrussNut () const {
1458 if (GeneralFindItem(::isPetrussNut
)) return true;
1459 return combineequipmentpredicates()(this, &item::IsPetrussNut
, 1);
1463 static truth
isGoldenEagleShirt (item
*i
) { return i
->IsGoldenEagleShirt(); }
1464 truth
character::HasGoldenEagleShirt () const {
1465 if (GeneralFindItem(::isGoldenEagleShirt
)) return true;
1466 return combineequipmentpredicates()(this, &item::IsGoldenEagleShirt
, 1);
1470 static truth
isShadowVeil (item
*i
) { return i
->IsShadowVeil(); }
1471 truth
character::HasShadowVeil () const {
1472 if (GeneralFindItem(::isShadowVeil
)) return true;
1473 return combineequipmentpredicates()(this, &item::IsShadowVeil
, 1);
1477 static truth
isLostRubyFlamingSword (item
*i
) { return i
->IsLostRubyFlamingSword(); }
1478 truth
character::HasLostRubyFlamingSword () const {
1479 if (GeneralFindItem(::isLostRubyFlamingSword
)) return true;
1480 return combineequipmentpredicates()(this, &item::IsLostRubyFlamingSword
, 1);
1484 truth
character::HasOmmelBlood () const {
1485 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
)
1486 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) return true;
1488 for (int c
= 0; c
< GetEquipments(); ++c
) {
1489 item
*Item
= GetEquipment(c
);
1491 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) return true;
1493 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1497 truth
character::HasCurdledBlood () const {
1498 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
)
1499 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) return true;
1501 for (int c
= 0; c
< GetEquipments(); ++c
) {
1502 item
*Item
= GetEquipment(c
);
1504 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) return true;
1506 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1510 truth
character::CurdleOmmelBlood () const {
1511 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1512 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) {
1513 i
->ChangeSecondaryMaterial(MAKE_MATERIAL(CURDLED_OMMEL_BLOOD
));
1518 for (int c
= 0; c
< GetEquipments(); ++c
) {
1519 item
*Item
= GetEquipment(c
);
1521 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) {
1522 Item
->ChangeSecondaryMaterial(MAKE_MATERIAL(CURDLED_OMMEL_BLOOD
));
1526 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1530 truth
character::RemoveCurdledOmmelBlood () {
1531 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1532 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) {
1533 (*i
)->RemoveFromSlot();
1539 for (int c
= 0; c
< GetEquipments(); ++c
) {
1540 item
*Item
= GetEquipment(c
);
1542 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) {
1543 Item
->RemoveFromSlot();
1552 int character::GeneralRemoveItem (ItemCheckerCB chk
, truth allItems
) {
1558 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1560 if (Item
&& chk(Item
)) {
1561 Item
->RemoveFromSlot();
1564 if (!allItems
) return cnt
;
1573 for (int c
= 0; c
< GetEquipments(); ++c
) {
1574 item
*Item
= GetEquipment(c
);
1575 if (Item
&& chk(Item
)) {
1576 Item
->RemoveFromSlot();
1579 if (!allItems
) return cnt
;
1589 //static truth isEncryptedScroll (item *i) { return i->IsEncryptedScroll(); }
1590 truth
character::RemoveEncryptedScroll () { return GeneralRemoveItem(::isEncryptedScroll
) != 0; }
1593 static truth
isMondedrPass (item
*i
) { return i
->IsMondedrPass(); }
1594 truth
character::RemoveMondedrPass () { return GeneralRemoveItem(::isMondedrPass
) != 0; }
1597 static truth
isRingOfThieves (item
*i
) { return i
->IsRingOfThieves(); }
1598 truth
character::RemoveRingOfThieves () { return GeneralRemoveItem(::isRingOfThieves
) != 0; }
1601 truth
character::RemoveShadowVeil () { return (GeneralRemoveItem(::isShadowVeil
) != 0); }
1604 truth
character::ReadItem (item
*ToBeRead
) {
1605 if (!ToBeRead
->CanBeRead(this)) {
1606 if (IsPlayer()) ADD_MESSAGE("You can't read this.");
1609 if (!GetLSquareUnder()->IsDark() || game::GetSeeWholeMapCheatMode()) {
1610 if (StateIsActivated(CONFUSED
) && !(RAND()&7)) {
1611 if (!ToBeRead
->IsDestroyable(this)) {
1612 ADD_MESSAGE("You read some words of %s and understand exactly nothing.", ToBeRead
->CHAR_NAME(DEFINITE
));
1614 ADD_MESSAGE("%s is very confusing. Or perhaps you are just too confused?", ToBeRead
->CHAR_NAME(DEFINITE
));
1615 ActivateRandomState(SRC_CONFUSE_READ
, 1000+RAND()%1500);
1616 ToBeRead
->RemoveFromSlot();
1617 ToBeRead
->SendToHell();
1622 if (ToBeRead
->Read(this)) {
1623 if (!game::WizardModeIsActive()) {
1624 /* This AP is used to take the stuff out of backpack */
1631 if (IsPlayer()) ADD_MESSAGE("It's too dark here to read.");
1636 void character::CalculateBurdenState () {
1637 int OldBurdenState
= BurdenState
;
1638 sLong SumOfMasses
= GetCarriedWeight();
1639 sLong CarryingStrengthUnits
= sLong(GetCarryingStrength())*2500;
1640 if (SumOfMasses
> (CarryingStrengthUnits
<< 1) + CarryingStrengthUnits
) BurdenState
= OVER_LOADED
;
1641 else if (SumOfMasses
> CarryingStrengthUnits
<< 1) BurdenState
= STRESSED
;
1642 else if (SumOfMasses
> CarryingStrengthUnits
) BurdenState
= BURDENED
;
1643 else BurdenState
= UNBURDENED
;
1644 if (!IsInitializing() && BurdenState
!= OldBurdenState
) CalculateBattleInfo();
1648 void character::Save (outputfile
&SaveFile
) const {
1649 SaveFile
<< (uShort
)GetType();
1650 Stack
->Save(SaveFile
);
1652 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) SaveFile
<< BaseExperience
[c
];
1654 SaveFile
<< ExpModifierMap
;
1655 SaveFile
<< NP
<< AP
<< Stamina
<< GenerationDanger
<< ScienceTalks
<< CounterToMindWormHatch
;
1656 SaveFile
<< TemporaryState
<< EquipmentState
<< Money
<< GoingTo
<< RegenerationCounter
<< Route
<< Illegal
;
1657 SaveFile
<< CurrentSweatMaterial
;
1658 SaveFile
.Put(!!IsEnabled());
1659 SaveFile
<< HomeData
<< BlocksSinceLastTurn
<< CommandFlags
;
1660 SaveFile
<< WarnFlags
<< (uShort
)Flags
;
1662 for (int c
= 0; c
< BodyParts
; ++c
) SaveFile
<< BodyPartSlot
[c
] << OriginalBodyPartID
[c
];
1664 SaveLinkedList(SaveFile
, TrapData
);
1667 for (int c
= 0; c
< STATES
; ++c
) SaveFile
<< TemporaryStateCounter
[c
];
1671 SaveFile
<< Team
->GetID(); // feuLong
1673 SaveFile
.Put(false);
1676 if (GetTeam() && GetTeam()->GetLeader() == this) SaveFile
.Put(true); else SaveFile
.Put(false);
1678 SaveFile
<< AssignedName
<< PolymorphBackup
;
1680 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) SaveFile
<< CWeaponSkill
[c
];
1682 SaveFile
<< (uShort
)GetConfig();
1686 void character::Load (inputfile
&SaveFile
) {
1688 Stack
->Load(SaveFile
);
1690 game::AddCharacterID(this, ID
);
1692 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) SaveFile
>> BaseExperience
[c
];
1694 SaveFile
>> ExpModifierMap
;
1695 SaveFile
>> NP
>> AP
>> Stamina
>> GenerationDanger
>> ScienceTalks
>> CounterToMindWormHatch
;
1696 SaveFile
>> TemporaryState
>> EquipmentState
>> Money
>> GoingTo
>> RegenerationCounter
>> Route
>> Illegal
;
1697 SaveFile
>> CurrentSweatMaterial
;
1699 if (!SaveFile
.Get()) Disable();
1701 SaveFile
>> HomeData
>> BlocksSinceLastTurn
>> CommandFlags
;
1702 SaveFile
>> WarnFlags
;
1703 WarnFlags
&= ~WARNED
;
1704 Flags
|= ReadType(uShort
, SaveFile
) & ~ENTITY_FLAGS
;
1706 for (int c
= 0; c
< BodyParts
; ++c
) {
1707 SaveFile
>> BodyPartSlot
[c
] >> OriginalBodyPartID
[c
];
1708 item
*BodyPart
= *BodyPartSlot
[c
];
1709 if (BodyPart
) BodyPart
->Disable();
1712 LoadLinkedList(SaveFile
, TrapData
);
1715 if (Action
) Action
->SetActor(this);
1717 for (int c
= 0; c
< STATES
; ++c
) SaveFile
>> TemporaryStateCounter
[c
];
1719 if (SaveFile
.Get()) SetTeam(game::GetTeam(ReadType(feuLong
, SaveFile
)));
1721 if (SaveFile
.Get()) GetTeam()->SetLeader(this);
1723 SaveFile
>> AssignedName
>> PolymorphBackup
;
1725 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) SaveFile
>> CWeaponSkill
[c
];
1727 databasecreator
<character
>::InstallDataBase(this, ReadType(uShort
, SaveFile
));
1729 if (IsEnabled() && !game::IsInWilderness()) {
1730 for (int c
= 1; c
< GetSquaresUnder(); ++c
) GetSquareUnder(c
)->SetCharacter(this);
1734 const fearray<festring> < = GetLevelTags();
1736 fprintf(stderr, "====\n");
1737 for (uInt f = 0; f < lt.Size; ++f) fprintf(stderr, " %u: [%s]\n", f, lt[f].CStr());
1743 truth
character::Engrave (cfestring
&What
) {
1744 GetLSquareUnder()->Engrave(What
);
1748 truth
character::MoveRandomly () {
1749 if (!IsEnabled()) return false;
1750 for (int c
= 0; c
< 10; ++c
) {
1751 v2 ToTry
= game::GetMoveVector(RAND()&7);
1752 if (GetLevel()->IsValidPos(GetPos()+ToTry
)) {
1753 lsquare
*Square
= GetNearLSquare(GetPos()+ToTry
);
1754 if (!Square
->IsDangerous(this) && !Square
->IsScary(this) && TryMove(ToTry
, false, false)) return true;
1761 truth
character::TestForPickup (item
*ToBeTested
) const {
1762 if (MakesBurdened(ToBeTested
->GetWeight()+GetCarriedWeight())) return false;
1767 void character::AddScoreEntry (cfestring
&Description
, double Multiplier
, truth AddEndLevel
) const {
1768 if (!game::WizardModeIsReallyActive()) {
1770 if (!HScore
.CheckVersion()) {
1771 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;
1774 festring Desc
= game::GetPlayerName();
1775 Desc
<< ", " << Description
;
1776 if (AddEndLevel
) Desc
<< " in "+(game::IsInWilderness() ? "the world map" : game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex()));
1777 HScore
.Add(sLong(game::GetScore()*Multiplier
), Desc
);
1783 truth
character::CheckDeath (cfestring
&Msg
, character
*Murderer
, feuLong DeathFlags
) {
1784 if (!IsEnabled()) return true;
1785 if (game::IsSumoWrestling() && IsDead()) {
1786 game::EndSumoWrestling(!!IsPlayer());
1789 if (DeathFlags
& FORCE_DEATH
|| IsDead()) {
1790 if (Murderer
&& Murderer
->IsPlayer() && GetTeam()->GetKillEvilness()) game::DoEvilDeed(GetTeam()->GetKillEvilness());
1791 festring SpecifierMsg
;
1792 int SpecifierParts
= 0;
1793 if (GetPolymorphBackup()) {
1794 SpecifierMsg
<< " polymorphed into ";
1795 id::AddName(SpecifierMsg
, INDEFINITE
);
1798 if (!(DeathFlags
& IGNORE_TRAPS
) && IsStuck()) {
1799 if (SpecifierParts
++) SpecifierMsg
<< " and";
1800 SpecifierMsg
<< " caught in " << GetTrapDescription();
1802 if (GetAction() && !(DeathFlags
& IGNORE_UNCONSCIOUSNESS
&& GetAction()->IsUnconsciousness())) {
1803 festring ActionMsg
= GetAction()->GetDeathExplanation();
1804 if (!ActionMsg
.IsEmpty()) {
1805 if (SpecifierParts
> 1) {
1806 SpecifierMsg
= ActionMsg
<< ',' << SpecifierMsg
;
1808 if (SpecifierParts
) SpecifierMsg
<< " and";
1809 SpecifierMsg
<< ActionMsg
;
1814 festring NewMsg
= Msg
;
1815 if (Murderer
== this) {
1816 SEARCH_N_REPLACE(NewMsg
, "@bkp", CONST_S("by ") + GetPossessivePronoun(false) + " own");
1817 SEARCH_N_REPLACE(NewMsg
, "@bk", CONST_S("by ") + GetObjectPronoun(false) + "self");
1818 SEARCH_N_REPLACE(NewMsg
, "@k", GetObjectPronoun(false) + "self");
1820 SEARCH_N_REPLACE(NewMsg
, "@bkp", CONST_S("by ") + Murderer
->GetName(INDEFINITE
) + "'s");
1821 SEARCH_N_REPLACE(NewMsg
, "@bk", CONST_S("by ") + Murderer
->GetName(INDEFINITE
));
1822 SEARCH_N_REPLACE(NewMsg
, "@k", CONST_S("by ") + Murderer
->GetName(INDEFINITE
));
1824 if (SpecifierParts
) NewMsg
<< " while" << SpecifierMsg
;
1825 if (IsPlayer() && game::WizardModeIsActive()) ADD_MESSAGE("Death message: %s. Score: %d.", NewMsg
.CStr(), game::GetScore());
1826 Die(Murderer
, NewMsg
, DeathFlags
);
1833 truth
character::CheckStarvationDeath (cfestring
&Msg
) {
1834 if (GetNP() < 1 && UsesNutrition()) return CheckDeath(Msg
, 0, FORCE_DEATH
);
1839 void character::ThrowItem (int Direction
, item
*ToBeThrown
) {
1840 if (Direction
> 7) ABORT("Throw in TOO odd direction...");
1841 ToBeThrown
->Fly(this, Direction
, GetAttribute(ARM_STRENGTH
));
1845 void character::HasBeenHitByItem (character
*Thrower
, item
*Thingy
, int Damage
, double ToHitValue
, int Direction
) {
1846 if (IsPlayer()) ADD_MESSAGE("%s hits you.", Thingy
->CHAR_NAME(DEFINITE
));
1847 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s.", Thingy
->CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
));
1848 int BodyPart
= ChooseBodyPartToReceiveHit(ToHitValue
, DodgeValue
);
1849 int WeaponSkillHits
= Thrower
? CalculateWeaponSkillHits(Thrower
) : 0;
1850 int DoneDamage
= ReceiveBodyPartDamage(Thrower
, Damage
, PHYSICAL_DAMAGE
, BodyPart
, Direction
);
1851 truth Succeeded
= (GetBodyPart(BodyPart
) && HitEffect(Thrower
, Thingy
, Thingy
->GetPos(), THROW_ATTACK
, BodyPart
, Direction
, !DoneDamage
, false, DoneDamage
)) || DoneDamage
;
1852 if (Succeeded
&& Thrower
) Thrower
->WeaponSkillHit(Thingy
, THROW_ATTACK
, WeaponSkillHits
);
1853 festring DeathMsg
= CONST_S("killed by a flying ")+Thingy
->GetName(UNARTICLED
);
1854 if (CheckDeath(DeathMsg
, Thrower
)) return;
1856 if (Thrower
->CanBeSeenByPlayer())
1857 DeActivateVoluntaryAction(CONST_S("The attack of ")+Thrower
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
1859 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
1861 DeActivateVoluntaryAction(CONST_S("The hit interrupts you."));
1866 truth
character::DodgesFlyingItem (item
*Item
, double ToHitValue
) {
1867 return !Item
->EffectIsGood() && RAND() % int(100+ToHitValue
/DodgeValue
*100) < 100;
1871 void character::GetPlayerCommand () {
1873 truth HasActed
= false;
1875 game::DrawEverything();
1876 if (game::GetDangerFound() && !StateIsActivated(FEARLESS
)) {
1877 if (game::GetDangerFound() > 500.) {
1878 if (game::GetCausePanicFlag()) {
1879 game::SetCausePanicFlag(false);
1880 BeginTemporaryState(PANIC
, 500+RAND_N(500));
1882 game::AskForEscPress(CONST_S("You are horrified by your situation!"));
1883 } else if (ivanconfig::GetWarnAboutDanger()) {
1884 if (game::GetDangerFound() > 50.) game::AskForEscPress(CONST_S("You sense great danger!"));
1885 else game::AskForEscPress(CONST_S("You sense danger!"));
1887 game::SetDangerFound(0);
1889 game::SetIsInGetCommand(true);
1890 int Key
= GET_KEY();
1891 game::SetIsInGetCommand(false);
1892 if (Key
!= '+' && Key
!= '-' && Key
!= 'M') msgsystem::ThyMessagesAreNowOld(); // gum
1893 truth ValidKeyPressed
= false;
1895 for (int c
= 0; c
< DIRECTION_COMMAND_KEYS
; ++c
) {
1896 if (Key
== game::GetMoveCommandKey(c
)) {
1897 HasActed
= TryMove(ApplyStateModification(game::GetMoveVector(c
)), true, game::PlayerIsRunning());
1898 ValidKeyPressed
= true;
1902 if (!ValidKeyPressed
) {
1903 for (int c
= 0; (cmd
= commandsystem::GetCommand(c
)); ++c
) {
1905 /* Numpad aliases for most commonly used commands */
1906 if (Key
== KEY_DEL
&& cmd
->GetName() == "Eat") Key
= cmd
->GetKey();
1907 if (Key
== KEY_INS
&& cmd
->GetName() == "PickUp") Key
= cmd
->GetKey();
1908 if (Key
== KEY_PLUS
&& cmd
->GetName() == "EquipmentScreen") Key
= cmd
->GetKey();
1910 if (Key
== cmd
->GetKey()) {
1911 if (game::IsInWilderness() && !commandsystem::GetCommand(c
)->IsUsableInWilderness()) {
1912 ADD_MESSAGE("This function cannot be used while in wilderness.");
1913 } else if (!game::WizardModeIsActive() && commandsystem::GetCommand(c
)->IsWizardModeFunction()) {
1914 ADD_MESSAGE("Activate wizardmode to use this function.");
1916 HasActed
= commandsystem::GetCommand(c
)->GetLinkedFunction()(this);
1918 ValidKeyPressed
= true;
1923 if (!ValidKeyPressed
) ADD_MESSAGE("Unknown key. Press '?' for a list of commands.");
1925 game::IncreaseTurn();
1929 void character::Vomit (v2 Pos
, int Amount
, truth ShowMsg
) {
1930 if (!CanVomit()) return;
1932 if (IsPlayer()) ADD_MESSAGE("You vomit.");
1933 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s vomits.", CHAR_NAME(DEFINITE
));
1935 if (VomittingIsUnhealthy()) {
1936 EditExperience(ARM_STRENGTH
, -75, 1 << 9);
1937 EditExperience(LEG_STRENGTH
, -75, 1 << 9);
1940 EditNP(-2500-RAND()%2501);
1941 CheckStarvationDeath(CONST_S("vomited himself to death"));
1943 if (StateIsActivated(PARASITIZED
) && !(RAND() & 7)) {
1944 if (IsPlayer()) ADD_MESSAGE("You notice a dead broad tapeworm among your former stomach contents.");
1945 DeActivateTemporaryState(PARASITIZED
);
1947 if (!game::IsInWilderness()) {
1948 GetNearLSquare(Pos
)->ReceiveVomit(this, liquid::Spawn(GetVomitMaterial(), sLong(sqrt(GetBodyVolume())*Amount
/1000)));
1953 truth
character::Polymorph (character
*NewForm
, int Counter
) {
1954 if (!IsPolymorphable() || (!IsPlayer() && game::IsInWilderness())) {
1959 if (GetAction()) GetAction()->Terminate(false);
1960 NewForm
->SetAssignedName("");
1962 ADD_MESSAGE("Your body glows in a crimson light. You transform into %s!", NewForm
->CHAR_NAME(INDEFINITE
));
1963 else if (CanBeSeenByPlayer())
1964 ADD_MESSAGE("%s glows in a crimson light and %s transforms into %s!", CHAR_NAME(DEFINITE
), GetPersonalPronoun().CStr(), NewForm
->CHAR_NAME(INDEFINITE
));
1966 Flags
|= C_IN_NO_MSG_MODE
;
1967 NewForm
->Flags
|= C_IN_NO_MSG_MODE
;
1968 NewForm
->ChangeTeam(GetTeam());
1969 NewForm
->GenerationDanger
= GenerationDanger
;
1970 NewForm
->mOnEvents
= this->mOnEvents
;
1972 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(NewForm
);
1976 NewForm
->PutToOrNear(Pos
);
1977 NewForm
->SetAssignedName(GetAssignedName());
1978 NewForm
->ActivateTemporaryState(POLYMORPHED
);
1979 NewForm
->SetTemporaryStateCounter(POLYMORPHED
, Counter
);
1981 if (TemporaryStateIsActivated(POLYMORPHED
)) {
1982 NewForm
->SetPolymorphBackup(GetPolymorphBackup());
1983 SetPolymorphBackup(0);
1986 NewForm
->SetPolymorphBackup(this);
1987 Flags
|= C_POLYMORPHED
;
1991 GetStack()->MoveItemsTo(NewForm
->GetStack());
1992 NewForm
->SetMoney(GetMoney());
1993 DonateEquipmentTo(NewForm
);
1994 Flags
&= ~C_IN_NO_MSG_MODE
;
1995 NewForm
->Flags
&= ~C_IN_NO_MSG_MODE
;
1996 NewForm
->CalculateAll();
2000 game::SetPlayer(NewForm
);
2001 game::SendLOSUpdateRequest();
2005 NewForm
->TestWalkability();
2010 void character::BeKicked (character
*Kicker
, item
*Boot
, bodypart
*Leg
, v2 HitPos
, double KickDamage
,
2011 double ToHitValue
, int Success
, int Direction
, truth Critical
, truth ForceHit
)
2014 if (!game::RunCharEvent("before_kicked_by", this, Kicker
, Boot
)) return;
2016 auto hitres
= (TakeHit(Kicker
, Boot
, Leg
, HitPos
, KickDamage
, ToHitValue
, Success
, KICK_ATTACK
, Direction
, Critical
, ForceHit
));
2017 if (hitres
== HAS_HIT
|| hitres
== HAS_BLOCKED
|| hitres
== DID_NO_DAMAGE
) {
2018 if (IsEnabled() && !CheckBalance(KickDamage
)) {
2019 if (IsPlayer()) ADD_MESSAGE("The kick throws you off balance.");
2020 else if (Kicker
->IsPlayer()) ADD_MESSAGE("The kick throws %s off balance.", CHAR_DESCRIPTION(DEFINITE
));
2021 v2 FallToPos
= GetPos()+game::GetMoveVector(Direction
);
2022 FallTo(Kicker
, FallToPos
);
2028 /* Return true if still in balance */
2029 truth
character::CheckBalance (double KickDamage
) {
2030 return !CanMove() || IsStuck() || !KickDamage
|| (!IsFlying() && KickDamage
*5 < RAND()%GetSize());
2034 void character::FallTo (character
*GuiltyGuy
, v2 Where
) {
2036 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
2037 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, Where
);
2039 truth NoRoom
= false;
2040 for (int c
= 0; c
< Squares
; ++c
) {
2041 olterrain
*Terrain
= MoveToSquare
[c
]->GetOLTerrain();
2042 if (Terrain
&& !CanMoveOn(Terrain
)) { NoRoom
= true; break; }
2046 if (IsPlayer()) ADD_MESSAGE("You hit your head on the wall.");
2047 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s head on the wall.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
2049 ReceiveDamage(GuiltyGuy
, 1+RAND()%5, PHYSICAL_DAMAGE
, HEAD
);
2050 CheckDeath(CONST_S("killed by hitting a wall due to being kicked @bk"), GuiltyGuy
);
2052 if (IsFreeForMe(MoveToSquare
[0])) Move(Where
, true);
2053 // Place code that handles characters bouncing to each other here
2059 truth
character::CheckCannibalism (cmaterial
*What
) const {
2060 return GetTorso()->GetMainMaterial()->IsSameAs(What
);
2064 void character::StandIdleAI () {
2065 SeekLeader(GetLeader());
2066 if (CheckForEnemies(true, true, true)) return;
2067 if (CheckForUsefulItemsOnGround()) return;
2068 if (FollowLeader(GetLeader())) return;
2069 if (CheckForDoors()) return;
2070 if (MoveTowardsHomePos()) return;
2071 if (CheckSadism()) return;
2076 truth
character::LoseConsciousness (int Counter
, truth HungerFaint
) {
2077 if (!AllowUnconsciousness()) return false;
2078 action
*Action
= GetAction();
2080 if (HungerFaint
&& !Action
->AllowUnconsciousness()) return false;
2081 if (Action
->IsUnconsciousness()) {
2082 static_cast<unconsciousness
*>(Action
)->RaiseCounterTo(Counter
);
2085 Action
->Terminate(false);
2087 if (IsPlayer()) ADD_MESSAGE("You lose consciousness.");
2088 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s loses consciousness.", CHAR_NAME(DEFINITE
));
2089 unconsciousness
*Unconsciousness
= unconsciousness::Spawn(this);
2090 Unconsciousness
->SetCounter(Counter
);
2091 SetAction(Unconsciousness
);
2096 void character::DeActivateVoluntaryAction (cfestring
&Reason
) {
2097 if (GetAction() && GetAction()->IsVoluntary()) {
2099 if (Reason
.GetSize()) ADD_MESSAGE("%s", Reason
.CStr());
2100 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"?")) GetAction()->ActivateInDNDMode();
2101 else GetAction()->Terminate(false);
2103 GetAction()->Terminate(false);
2109 void character::ActionAutoTermination () {
2110 if (!GetAction() || !GetAction()->IsVoluntary() || GetAction()->InDNDMode()) return;
2112 for (int c
= 0; c
< game::GetTeams(); ++c
) {
2113 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
2114 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
2116 if (ch
->IsEnabled() && ch
->CanBeSeenBy(this, false, true) && (ch
->CanMove() || ch
->GetPos().IsAdjacent(Pos
)) && ch
->CanAttack()) {
2118 ADD_MESSAGE("%s seems to be hostile.", ch
->CHAR_NAME(DEFINITE
));
2119 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"?")) GetAction()->ActivateInDNDMode();
2120 else GetAction()->Terminate(false);
2122 GetAction()->Terminate(false);
2132 truth
character::CheckForEnemies (truth CheckDoors
, truth CheckGround
, truth MayMoveRandomly
, truth RunTowardsTarget
) {
2133 if (!IsEnabled()) return false;
2134 truth HostileCharsNear
= false;
2135 character
*NearestChar
= 0;
2136 sLong NearestDistance
= 0x7FFFFFFF;
2138 for (int c
= 0; c
< game::GetTeams(); ++c
) {
2139 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
2140 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
2142 if (ch
->IsEnabled() && GetAttribute(WISDOM
) < ch
->GetAttackWisdomLimit()) {
2143 sLong ThisDistance
= Max
<sLong
>(abs(ch
->GetPos().X
- Pos
.X
), abs(ch
->GetPos().Y
- Pos
.Y
));
2144 if (ThisDistance
<= GetLOSRangeSquare()) HostileCharsNear
= true;
2145 if ((ThisDistance
< NearestDistance
|| (ThisDistance
== NearestDistance
&& !(RAND() % 3))) &&
2146 ch
->CanBeSeenBy(this, false, IsGoingSomeWhere()) &&
2147 (!IsGoingSomeWhere() || HasClearRouteTo(ch
->GetPos()))) {
2149 NearestDistance
= ThisDistance
;
2157 if (GetAttribute(INTELLIGENCE
) >= 10 || IsSpy()) game::CallForAttention(GetPos(), 100);
2158 if (SpecialEnemySightedReaction(NearestChar
)) return true;
2159 if (IsExtraCoward() && !StateIsActivated(PANIC
) && NearestChar
->GetRelativeDanger(this) >= 0.5 && !StateIsActivated(FEARLESS
)) {
2160 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s sees %s.", CHAR_NAME(DEFINITE
), NearestChar
->CHAR_DESCRIPTION(DEFINITE
));
2161 BeginTemporaryState(PANIC
, 500+RAND()%500);
2163 if (!IsRetreating()) {
2164 if (CheckGround
&& NearestDistance
> 2 && CheckForUsefulItemsOnGround(false)) return true;
2165 SetGoingTo(NearestChar
->GetPos());
2167 SetGoingTo(Pos
-((NearestChar
->GetPos()-Pos
)<<4));
2169 return MoveTowardsTarget(true);
2171 character
*Leader
= GetLeader();
2172 if (Leader
== this) Leader
= 0;
2173 if (!Leader
&& IsGoingSomeWhere()) {
2174 if (!MoveTowardsTarget(RunTowardsTarget
)) {
2178 if (!IsEnabled()) return true;
2179 if (GetPos() == GoingTo
) TerminateGoingTo();
2183 if ((!Leader
|| (Leader
&& !IsGoingSomeWhere())) && HostileCharsNear
) {
2184 if (CheckDoors
&& CheckForDoors()) return true;
2185 if (CheckGround
&& CheckForUsefulItemsOnGround()) return true;
2186 if (MayMoveRandomly
&& MoveRandomly()) return true; // one has heard that an enemy is near but doesn't know where
2194 truth
character::CheckForDoors () {
2195 if (!CanOpen() || !IsEnabled()) return false;
2196 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2197 lsquare
*Square
= GetNeighbourLSquare(d
);
2198 if (Square
&& Square
->GetOLTerrain() && Square
->GetOLTerrain()->Open(this)) return true;
2204 truth
character::CheckForUsefulItemsOnGround (truth CheckFood
) {
2205 if (StateIsActivated(PANIC
) || !IsEnabled()) return false;
2206 itemvector ItemVector
;
2207 GetStackUnder()->FillItemVector(ItemVector
);
2208 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
2209 if (ItemVector
[c
]->CanBeSeenBy(this) && ItemVector
[c
]->IsPickable(this)) {
2210 if (!(CommandFlags
& DONT_CHANGE_EQUIPMENT
) && TryToEquip(ItemVector
[c
])) return true;
2211 if (CheckFood
&& UsesNutrition() && !CheckIfSatiated() && TryToConsume(ItemVector
[c
])) return true;
2212 if (IsRangedAttacker() && (ItemVector
[c
])->GetThrowItemTypes() && TryToAddToInventory(ItemVector
[c
])) return true;
2219 truth
character::TryToAddToInventory (item
*Item
) {
2220 if (!(GetBurdenState() > STRESSED
) || !CanUseEquipment() || Item
->GetSquaresUnder() != 1) return false;
2221 room
*Room
= GetRoom();
2222 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
2223 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up %s from the ground.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(INDEFINITE
));
2224 Item
->MoveTo(GetStack());
2232 truth
character::CheckInventoryForItemToThrow (item
*ToBeChecked
) {
2233 return (ToBeChecked
->GetThrowItemTypes() & GetWhatThrowItemTypesToThrow()) ? true : false; //hehe
2237 truth
character::CheckThrowItemOpportunity () {
2238 if (!IsRangedAttacker() || !CanThrow() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2239 //fprintf(stderr, "character::CheckThrowItemOpportunity...\n");
2241 // (1) - Acquire target as nearest enemy
2242 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2243 // (3) - check inventory for throwing weapon, select this weapon
2244 // (4) - throw item in direction where the enemy is
2246 //Check the visible area for hostiles
2247 int ThrowDirection
= 0;
2248 int TargetFound
= 0;
2251 int RangeMax
= GetLOSRange();
2252 int CandidateDirections
[7] = {0, 0, 0, 0, 0, 0, 0};
2253 int HostileFound
= 0;
2254 item
*ToBeThrown
= 0;
2255 level
*Level
= GetLevel();
2257 for (int r
= 1; r
<= RangeMax
; ++r
) {
2258 for (int dir
= 0; dir
< MDIR_STAND
; ++dir
) {
2261 case 0: TestPos
= v2(Pos
.X
-r
, Pos
.Y
-r
); break;
2262 case 1: TestPos
= v2(Pos
.X
, Pos
.Y
-r
); break;
2263 case 2: TestPos
= v2(Pos
.X
+r
, Pos
.Y
-r
); break;
2264 case 3: TestPos
= v2(Pos
.X
-r
, Pos
.Y
); break;
2265 case 4: TestPos
= v2(Pos
.X
+r
, Pos
.Y
); break;
2266 case 5: TestPos
= v2(Pos
.X
-r
, Pos
.Y
+r
); break;
2267 case 6: TestPos
= v2(Pos
.X
, Pos
.Y
+r
); break;
2268 case 7: TestPos
= v2(Pos
.X
+r
, Pos
.Y
+r
); break;
2270 if (Level
->IsValidPos(TestPos
)) {
2271 square
*TestSquare
= GetNearSquare(TestPos
);
2272 character
*Dude
= TestSquare
->GetCharacter();
2274 if (Dude
&& Dude
->IsEnabled() && Dude
->CanBeSeenBy(this, false, true)) {
2275 if (GetRelation(Dude
) != HOSTILE
) CandidateDirections
[dir
] = BLOCKED
;
2276 else if (GetRelation(Dude
) == HOSTILE
&& CandidateDirections
[dir
] != BLOCKED
) {
2277 //then load this candidate position direction into the vector of possible throw directions
2278 CandidateDirections
[dir
] = SUCCESS
;
2287 for (int dir
= 0; dir
< MDIR_STAND
; ++dir
) {
2288 if (CandidateDirections
[dir
] == SUCCESS
&& !TargetFound
) {
2289 ThrowDirection
= dir
;
2294 if (!TargetFound
) return false;
2298 //fprintf(stderr, "throw: has target.\n");
2299 // check inventory for throwing weapon
2300 itemvector ItemVector
;
2301 GetStack()->FillItemVector(ItemVector
);
2302 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
2303 if (ItemVector
[c
]->IsThrowingWeapon()) {
2304 ToBeThrown
= ItemVector
[c
];
2308 if (!ToBeThrown
) return false;
2309 //fprintf(stderr, "throw: has throwing weapon.\n");
2310 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s throws %s.", CHAR_NAME(DEFINITE
), ToBeThrown
->CHAR_NAME(INDEFINITE
));
2311 ThrowItem(ThrowDirection
, ToBeThrown
);
2312 EditExperience(ARM_STRENGTH
, 75, 1<<8);
2313 EditExperience(DEXTERITY
, 75, 1<<8);
2314 EditExperience(PERCEPTION
, 75, 1<<8);
2322 truth
character::CheckAIZapOpportunity () {
2323 if (/*!IsRangedAttacker() || */ !CanZap() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2325 // (1) - Acquire target as nearest enemy
2326 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2327 // (3) - check inventory for zappable item
2328 // (4) - zap item in direction where the enemy is
2329 //Check the rest of the visible area for hostiles
2332 int SensibleRange
= 5;
2333 int RangeMax
= GetLOSRange();
2334 if (RangeMax
< SensibleRange
) SensibleRange
= RangeMax
;
2335 int CandidateDirections
[7] = {0, 0, 0, 0, 0, 0, 0};
2336 int HostileFound
= 0;
2337 int ZapDirection
= 0;
2338 int TargetFound
= 0;
2339 item
*ToBeZapped
= 0;
2340 level
*Level
= GetLevel();
2342 for (int r
= 2; r
<= SensibleRange
; ++r
) {
2343 for (int dir
= 0; dir
< MDIR_STAND
; ++dir
) {
2345 case 0: TestPos
= v2(Pos
.X
-r
, Pos
.Y
-r
); break;
2346 case 1: TestPos
= v2(Pos
.X
, Pos
.Y
-r
); break;
2347 case 2: TestPos
= v2(Pos
.X
+r
, Pos
.Y
-r
); break;
2348 case 3: TestPos
= v2(Pos
.X
-r
, Pos
.Y
); break;
2349 case 4: TestPos
= v2(Pos
.X
+r
, Pos
.Y
); break;
2350 case 5: TestPos
= v2(Pos
.X
-r
, Pos
.Y
+r
); break;
2351 case 6: TestPos
= v2(Pos
.X
, Pos
.Y
+r
); break;
2352 case 7: TestPos
= v2(Pos
.X
+r
, Pos
.Y
+r
); break;
2354 if (Level
->IsValidPos(TestPos
)) {
2355 square
*TestSquare
= GetNearSquare(TestPos
);
2356 character
*Dude
= TestSquare
->GetCharacter();
2358 if (Dude
&& Dude
->IsEnabled() && Dude
->CanBeSeenBy(this, false, true)) {
2359 if (GetRelation(Dude
) != HOSTILE
) CandidateDirections
[dir
] = BLOCKED
;
2360 else if (GetRelation(Dude
) == HOSTILE
&& CandidateDirections
[dir
] != BLOCKED
) {
2361 //then load this candidate position direction into the vector of possible zap directions
2362 CandidateDirections
[dir
] = SUCCESS
;
2371 for (int dir
= 0; dir
< MDIR_STAND
; ++dir
) {
2372 if (CandidateDirections
[dir
] == SUCCESS
&& !TargetFound
) {
2378 if (!TargetFound
) return false;
2382 // check inventory for zappable item
2383 itemvector ItemVector
;
2384 GetStack()->FillItemVector(ItemVector
);
2385 for (unsigned int c
= 0; c
< ItemVector
.size(); ++c
) {
2386 if (ItemVector
[c
]->GetMinCharges() > 0 && ItemVector
[c
]->GetPrice()) {
2387 // bald-faced gum solution for choosing zappables that have shots left.
2388 // MinCharges needs to be replaced. Empty wands have zero price!
2389 ToBeZapped
= ItemVector
[c
];
2393 if (!ToBeZapped
) return false;
2394 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s zaps %s.", CHAR_NAME(DEFINITE
), ToBeZapped
->CHAR_NAME(INDEFINITE
));
2395 if (ToBeZapped
->Zap(this, GetPos(), ZapDirection
)) {
2396 EditAP(-100000/APBonus(GetAttribute(PERCEPTION
)));
2406 truth
character::FollowLeader (character
*Leader
) {
2407 if (!Leader
|| Leader
== this || !IsEnabled()) return false;
2408 if ((CommandFlags
&FOLLOW_LEADER
) && Leader
->CanBeSeenBy(this) && Leader
->SquareUnderCanBeSeenBy(this, true)) {
2409 v2 Distance
= GetPos()-GoingTo
;
2410 if (abs(Distance
.X
) <= 2 && abs(Distance
.Y
) <= 2) return false;
2411 return MoveTowardsTarget(false);
2413 if (IsGoingSomeWhere()) {
2414 if (!MoveTowardsTarget(true)) {
2424 void character::SeekLeader (ccharacter
*Leader
) {
2425 if (Leader
&& Leader
!= this) {
2426 if (Leader
->CanBeSeenBy(this) && (Leader
->SquareUnderCanBeSeenBy(this, true) || !IsGoingSomeWhere())) {
2427 if (CommandFlags
&FOLLOW_LEADER
) SetGoingTo(Leader
->GetPos());
2428 } else if (!IsGoingSomeWhere()) {
2429 team
*Team
= GetTeam();
2430 for (std::list
<character
*>::const_iterator i
= Team
->GetMember().begin(); i
!= Team
->GetMember().end(); ++i
) {
2432 if (ch
->IsEnabled() && ch
->GetID() != GetID() &&
2433 (CommandFlags
& FOLLOW_LEADER
) == (ch
->CommandFlags
& FOLLOW_LEADER
) && ch
->CanBeSeenBy(this)) {
2434 v2 Pos
= ch
->GetPos();
2435 v2 Distance
= GetPos()-Pos
;
2436 if (abs(Distance
.X
) > 2 && abs(Distance
.Y
) > 2) {
2447 int character::GetMoveEase () const {
2448 if (BurdenState
== OVER_LOADED
|| BurdenState
== STRESSED
) return 50;
2449 if (BurdenState
== BURDENED
) return 75;
2450 if (BurdenState
== UNBURDENED
) return 100;
2455 int character::GetLOSRange () const {
2456 if (!game::IsInWilderness()) return GetAttribute(PERCEPTION
)*GetLevel()->GetLOSModifier()/48;
2461 truth
character::Displace (character
*Who
, truth Forced
) {
2462 if (GetBurdenState() == OVER_LOADED
) {
2464 cchar
*CrawlVerb
= StateIsActivated(LEVITATION
) ? "float" : "crawl";
2465 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb
);
2472 double Danger
= GetRelativeDanger(Who
);
2473 int PriorityDifference
= Limit(GetDisplacePriority()-Who
->GetDisplacePriority(), -31, 31);
2475 if (IsPlayer()) ++PriorityDifference
;
2476 else if (Who
->IsPlayer()) --PriorityDifference
;
2478 if (PriorityDifference
>= 0) Danger
*= 1 << PriorityDifference
;
2479 else Danger
/= 1 << -PriorityDifference
;
2481 if (IsSmall() && Who
->IsSmall() &&
2482 (Forced
|| Danger
> 1.0 || !(Who
->IsPlayer() || Who
->IsBadPath(GetPos()))) &&
2483 !IsStuck() && !Who
->IsStuck() && (!Who
->GetAction() || Who
->GetAction()->TryDisplace()) &&
2484 CanMove() && Who
->CanMove() && Who
->CanMoveOn(GetLSquareUnder())) {
2485 if (IsPlayer()) ADD_MESSAGE("You displace %s!", Who
->CHAR_DESCRIPTION(DEFINITE
));
2486 else if (Who
->IsPlayer()) ADD_MESSAGE("%s displaces you!", CHAR_DESCRIPTION(DEFINITE
));
2487 else if (CanBeSeenByPlayer() || Who
->CanBeSeenByPlayer()) ADD_MESSAGE("%s displaces %s!", CHAR_DESCRIPTION(DEFINITE
), Who
->CHAR_DESCRIPTION(DEFINITE
));
2488 lsquare
*OldSquareUnder1
[MAX_SQUARES_UNDER
];
2489 lsquare
*OldSquareUnder2
[MAX_SQUARES_UNDER
];
2490 for (int c
= 0; c
< GetSquaresUnder(); ++c
) OldSquareUnder1
[c
] = GetLSquareUnder(c
);
2491 for (int c
= 0; c
< Who
->GetSquaresUnder(); ++c
) OldSquareUnder2
[c
] = Who
->GetLSquareUnder(c
);
2493 v2 WhoPos
= Who
->GetPos();
2498 EditAP(-GetMoveAPRequirement(GetSquareUnder()->GetEntryDifficulty()) - 500);
2499 EditNP(-12*GetSquareUnder()->GetEntryDifficulty());
2500 EditExperience(AGILITY
, 75, GetSquareUnder()->GetEntryDifficulty() << 7);
2501 if (IsPlayer()) ShowNewPosInfo();
2502 if (Who
->IsPlayer()) Who
->ShowNewPosInfo();
2503 SignalStepFrom(OldSquareUnder1
);
2504 Who
->SignalStepFrom(OldSquareUnder2
);
2508 ADD_MESSAGE("%s resists!", Who
->CHAR_DESCRIPTION(DEFINITE
));
2517 void character::SetNP (sLong What
) {
2518 int OldState
= GetHungerState();
2521 int NewState
= GetHungerState();
2522 if (NewState
== STARVING
&& OldState
> STARVING
) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
2523 else if (NewState
== VERY_HUNGRY
&& OldState
> VERY_HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting very hungry."));
2524 else if (NewState
== HUNGRY
&& OldState
> HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting hungry."));
2529 void character::ShowNewPosInfo () const {
2530 msgsystem::EnterBigMessageMode();
2533 if (ivanconfig::GetAutoCenterMap()) {
2534 game::UpdateCameraX();
2535 game::UpdateCameraY();
2537 if (Pos
.X
< game::GetCamera().X
+3 || Pos
.X
>= game::GetCamera().X
+game::GetScreenXSize()-3) game::UpdateCameraX();
2538 if (Pos
.Y
< game::GetCamera().Y
+3 || Pos
.Y
>= game::GetCamera().Y
+game::GetScreenYSize()-3) game::UpdateCameraY();
2541 game::SendLOSUpdateRequest();
2542 game::DrawEverythingNoBlit();
2545 if (!game::IsInWilderness()) {
2546 if (GetLSquareUnder()->IsDark() && !game::GetSeeWholeMapCheatMode()) ADD_MESSAGE("It's dark in here!");
2548 GetLSquareUnder()->ShowSmokeMessage();
2549 itemvectorvector PileVector
;
2550 GetStackUnder()->Pile(PileVector
, this, CENTER
);
2552 if (PileVector
.size()) {
2553 truth Feel
= !GetLSquareUnder()->IsTransparent() || GetLSquareUnder()->IsDark();
2555 if (PileVector
.size() == 1) {
2557 ADD_MESSAGE("You feel %s lying here.", PileVector
[0][0]->GetName(INDEFINITE
, PileVector
[0].size()).CStr());
2559 if (ivanconfig::GetShowFullItemDesc() && PileVector
[0][0]->AllowDetailedDescription()) {
2562 PileVector
[0][0]->AddInventoryEntry(PLAYER
, text
, PileVector
[0].size(), true);
2563 //fprintf(stderr, "invdsc : [%s]\n", text.CStr());
2564 ADD_MESSAGE("%s %s lying here.", text
.CStr(), PileVector
[0].size() == 1 ? "is" : "are");
2566 ADD_MESSAGE("%s %s lying here.", PileVector
[0][0]->GetName(INDEFINITE
, PileVector
[0].size()).CStr(), PileVector
[0].size() == 1 ? "is" : "are");
2569 fprintf(stderr, "description: [%s]\n", PileVector[0][0]->GetDescription(INDEFINITE).CStr());
2570 fprintf(stderr, "strength : [%s]\n", PileVector[0][0]->GetStrengthValueDescription());
2571 fprintf(stderr, "basetohit : [%s]\n", PileVector[0][0]->GetBaseToHitValueDescription());
2572 fprintf(stderr, "baseblock : [%s]\n", PileVector[0][0]->GetBaseBlockValueDescription());
2573 fprintf(stderr, "extdsc : [%s]\n", PileVector[0][0]->GetExtendedDescription().CStr());
2578 for (uInt c
= 0; c
< PileVector
.size(); ++c
) {
2579 if ((Items
+= PileVector
[c
].size()) > 3) break;
2582 if (Feel
) ADD_MESSAGE("You feel several items lying here.");
2583 else ADD_MESSAGE("Several items are lying here.");
2585 if (Feel
) ADD_MESSAGE("You feel a few items lying here.");
2586 else ADD_MESSAGE("A few items are lying here.");
2592 GetLSquareUnder()->GetSideItemDescription(SideItems
);
2594 if (!SideItems
.IsEmpty()) ADD_MESSAGE("There is %s.", SideItems
.CStr());
2596 if (GetLSquareUnder()->HasEngravings()) {
2597 if (CanRead()) ADD_MESSAGE("Something has been engraved here: \"%s\"", GetLSquareUnder()->GetEngraved());
2598 else ADD_MESSAGE("Something has been engraved here.");
2602 msgsystem::LeaveBigMessageMode();
2606 void character::Hostility (character
*Enemy
) {
2607 if (Enemy
== this || !Enemy
|| !Team
|| !Enemy
->Team
) return;
2608 if (Enemy
->IsMasochist() && GetRelation(Enemy
) == FRIEND
) return;
2609 if (!IsAlly(Enemy
)) {
2610 GetTeam()->Hostility(Enemy
->GetTeam());
2611 } else if (IsPlayer() && !Enemy
->IsPlayer()) {
2612 // I believe both may be players due to polymorph feature...
2613 if (Enemy
->CanBeSeenByPlayer()) ADD_MESSAGE("%s becomes enraged.", Enemy
->CHAR_NAME(DEFINITE
));
2614 Enemy
->ChangeTeam(game::GetTeam(BETRAYED_TEAM
));
2619 stack
*character::GetGiftStack () const {
2620 if (GetLSquareUnder()->GetRoomIndex() && !GetLSquareUnder()->GetRoom()->AllowDropGifts()) return GetStack();
2621 return GetStackUnder();
2625 truth
character::MoveRandomlyInRoom () {
2626 for (int c
= 0; c
< 10; ++c
) {
2627 v2 ToTry
= game::GetMoveVector(RAND()&7);
2628 if (GetLevel()->IsValidPos(GetPos()+ToTry
)) {
2629 lsquare
*Square
= GetNearLSquare(GetPos()+ToTry
);
2630 if (!Square
->IsDangerous(this) && !Square
->IsScary(this) &&
2631 (!Square
->GetOLTerrain() || !Square
->GetOLTerrain()->IsDoor()) &&
2632 TryMove(ToTry
, false, false)) return true;
2639 //#define dirlogf(...) do { fprintf(stderr, __VA_ARGS__); } while (0)
2640 #define dirlogf(...) ((void)0)
2643 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
};
2644 static const bool orthoDir
[MDIR_STAND
] = { false, true, false, true, true, false, true, false };
2647 // only for ortho moveDir
2648 static inline truth
IsDirExcluded (int moveDir
, int dir
) {
2649 if (moveDir
== dir
) return true;
2651 case MDIR_UP
: return (dir
== MDIR_UP_LEFT
|| dir
== MDIR_UP_RIGHT
);
2652 case MDIR_LEFT
: return (dir
== MDIR_UP_LEFT
|| dir
== MDIR_DOWN_LEFT
);
2653 case MDIR_RIGHT
: return (dir
== MDIR_UP_RIGHT
|| dir
== MDIR_DOWN_RIGHT
);
2654 case MDIR_DOWN
: return (dir
== MDIR_DOWN_LEFT
|| dir
== MDIR_DOWN_RIGHT
);
2660 truth
character::IsPassableSquare (int x
, int y
) const {
2661 if (x
>= 0 && y
>= 0) {
2662 area
*ca
= GetSquareUnder()->GetArea();
2665 if (x
>= ca
->GetXSize() || y
>= ca
->GetYSize()) return false;
2666 sq
= static_cast<lsquare
*>(ca
->GetSquare(x
, y
));
2667 return sq
&& CanMoveOn(sq
);
2673 void character::CountPossibleMoveDirs (cv2 pos
, int *odirs
, int *ndirs
, int exclideDir
) const {
2674 if (odirs
) *odirs
= 0;
2675 if (ndirs
) *ndirs
= 0;
2676 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2677 if (!IsDirExcluded(exclideDir
, f
)) {
2678 if (IsPassableSquare(pos
+game::GetMoveVector(f
))) {
2680 if (odirs
) ++(*odirs
);
2682 if (ndirs
) ++(*ndirs
);
2691 * in corridor (for orto-dirs):
2692 * count dirs excluding ortho-dir we going:
2693 * if there is one or less ortho-dirs and one or less non-ortho-dirs, we are in corridor
2695 // only for ortho-dirs
2696 truth
character::IsInCorridor (int x
, int y
, int moveDir
) const {
2699 dirlogf("IsInCorridor(%d,%d,%d)\n", x
, y
, moveDir
);
2701 moveDir
= (moveDir
>= 0 && moveDir
< MDIR_STAND
? revDir
[moveDir
] : -1);
2702 dirlogf(" reversedDir: %d\n", moveDir
);
2703 CountPossibleMoveDirs(v2(x
, y
), &od
, &nd
, moveDir
);
2704 dirlogf(" possibleDirs: (%d:%d)\n", od
, nd
);
2705 dirlogf(" IsInCorridor: %s\n", ((od
<= 1 && nd
<= 1) ? "yes" : "no"));
2706 return (od
<= 1 && nd
<= 1);
2710 cv2
character::GetDiagonalForDirs (int moveDir
, int newDir
) const {
2714 case MDIR_LEFT
: return game::GetMoveVector(MDIR_UP_LEFT
);
2715 case MDIR_RIGHT
: return game::GetMoveVector(MDIR_UP_RIGHT
);
2720 case MDIR_LEFT
: return game::GetMoveVector(MDIR_DOWN_LEFT
);
2721 case MDIR_RIGHT
: return game::GetMoveVector(MDIR_DOWN_RIGHT
);
2726 case MDIR_UP
: return game::GetMoveVector(MDIR_UP_LEFT
);
2727 case MDIR_DOWN
: return game::GetMoveVector(MDIR_DOWN_LEFT
);
2732 case MDIR_UP
: return game::GetMoveVector(MDIR_UP_RIGHT
);
2733 case MDIR_DOWN
: return game::GetMoveVector(MDIR_DOWN_RIGHT
);
2737 ABORT("wtf in character::GetDiagonalForDirs()");
2741 truth
character::IsInTunnelDeadEnd () const {
2744 CountPossibleMoveDirs(GetPos(), &od
, &nd
, -1);
2745 return (od
<= 1 && nd
== 0);
2750 * try to walk in the given dir
2751 * can do two steps without a turn and still in corridor?
2755 * go in non-ortho dir, set prevdir to last ortho-dir from corridor tracing
2757 // only for ortho-dirs; assume that the char is in corridor
2758 int character::CheckCorridorMove (v2
&moveVector
, cv2 pos
, int moveDir
, truth
*markAsTurn
) const {
2759 v2
ps1(pos
+(moveVector
= game::GetMoveVector(moveDir
)));
2761 if (markAsTurn
) *markAsTurn
= true;
2763 if (IsPassableSquare(ps1
)) {
2764 // we can do first step in the given dir
2765 // check if we will be in corridor after it
2766 dirlogf("CheckCorridorMove: can do first step\n");
2767 if (IsInCorridor(ps1
, moveDir
)) {
2768 // check second step
2769 v2
ps2(ps1
+moveVector
);
2770 dirlogf("CheckCorridorMove: still in corridor after the first step\n");
2771 if (IsPassableSquare(ps2
)) {
2772 // can do second step
2773 dirlogf("CheckCorridorMove: can do second step\n");
2776 // can't do second step; but we still in corridor, so we should make a turn
2777 int newDir
= -1; // direction to turn
2778 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2779 if (f
!= moveDir
&& orthoDir
[f
] && f
!= revDir
[moveDir
] && IsPassableSquare(ps1
+game::GetMoveVector(f
))) {
2784 dirlogf("CheckCorridorMove: can't do second step; moveDir=%d; newDir=%d\n", moveDir
, newDir
);
2786 // dead end, will stop
2787 //ABORT("wtd in character::CheckCorridorMove()");
2790 // we should do diagonal move
2791 moveVector
= GetDiagonalForDirs(moveDir
, newDir
);
2792 // if this is 'one-tile-turn', we should not change the direction to newDir
2793 if (IsPassableSquare(ps1
+game::GetMoveVector(newDir
)+game::GetMoveVector(moveDir
))) {
2794 // yes, this is 'one-tile-turn'
2795 dirlogf("CheckCorridorMove: one-tile-turn, don't change dir\n");
2803 * 'g'o right: should stop at '*', but it just goes right
2805 if (markAsTurn
) *markAsTurn
= IsInCorridor(ps1
+game::GetMoveVector(newDir
), newDir
);
2811 dirlogf("CheckCorridorMove: can do one or two steps; move forward\n");
2812 // can do one or two steps: check for T-junction
2813 // we should stop if we have more than two open dirs, or one of open dirs is not moveDir
2815 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2816 if (f
== revDir
[moveDir
]) continue; // skip "reverse dir" check
2817 v2
ps2(pos
+game::GetMoveVector(f
));
2818 if (IsPassableSquare(ps2
)) {
2820 if (dcount
> 2) return -1; // more than two open dirs, stop
2821 if (f
!= moveDir
) return -1; // one of open dirs is not moveDir
2824 // just move forward
2827 dirlogf("CheckCorridorMove: dead end\n");
2828 // can't go, assume invalid direction
2833 truth
character::IsDangerousSquare (v2 pos
) const {
2834 if (!IsPassableSquare(pos
)) return false;
2835 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
2836 auto Squares
= CalculateNewSquaresUnder(MoveToSquare
, pos
);
2837 for (decltype(Squares
) c
= 0; c
< Squares
; ++c
) {
2838 lsquare
*Square
= MoveToSquare
[c
];
2840 if (!Square
->HasBeenSeen()) continue;
2842 if (!Square
->CanBeSeenBy(this)) continue;
2844 // check if someone is standing at the square
2845 if (Square
->GetCharacter() && GetTeam() != Square
->GetCharacter()->GetTeam() && Square
->GetCharacter()->CanBeSeenBy(this)) return true;
2846 if (Square
->IsDangerous(this)) {
2847 if (IsPlayer() && Square
->HasBeenSeen()) return true;
2848 if (Square
->CanBeSeenBy(this)) return true;
2855 void character::MarkAdjacentItemsAsSeen (v2 pos
) {
2856 lsquare
*sqlist
[MAX_SQUARES_UNDER
];
2857 for (int d
= 0; d
< MDIR_STAND
; ++d
) {
2858 auto np
= pos
+game::GetMoveVector(d
);
2859 if (!IsPassableSquare(np
)) continue;
2860 auto sqcount
= CalculateNewSquaresUnder(sqlist
, np
);
2861 for (int n
= 0; n
< sqcount
; ++n
) {
2862 lsquare
*sq
= sqlist
[n
];
2863 if ((IsPlayer() && sq
->HasBeenSeen()) || sq
->CanBeSeenBy(this)) {
2864 sq
->GetStack()->SetSteppedOn(true);
2871 void character::GoOn (go
*Go
, truth FirstStep
) {
2872 dirlogf("=== character::GoOn; dir=%d; pos=(%d,%d) ===\n", Go
->GetDirection(), GetPos().X
, GetPos().Y
);
2874 dirlogf("FirstStep\n");
2875 mPrevMoveDir
= Go
->GetDirection();
2876 Go
->SetIsWalkingInOpen(!IsInCorridor(Go
->GetDirection()));
2879 v2 MoveVector
= ApplyStateModification(game::GetMoveVector(Go
->GetDirection()));
2880 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
2881 lsquare
*MoveToSquare2
[MAX_SQUARES_UNDER
];
2882 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, GetPos()+MoveVector
);
2883 int moveDir
= game::MoveVectorToDirection(MoveVector
);
2885 if (Squares
== 0 || !CanMoveOn(MoveToSquare
[0])) {
2886 dirlogf("just can't move\n");
2887 Go
->Terminate(false);
2892 // first step: mark all adjacent items as seen
2893 MarkAdjacentItemsAsSeen(GetPos());
2897 // check for corridor<->open place
2898 if (!Go
->GetPrevWasTurn() && Go
->IsWalkingInOpen() != !IsInCorridor(GetPos(), moveDir
)) {
2899 dirlogf("moved to/from open place\n");
2900 Go
->Terminate(false);
2904 // check for room change
2905 uInt OldRoomIndex
= GetLSquareUnder()->GetRoomIndex();
2906 uInt CurrentRoomIndex
= MoveToSquare
[0]->GetRoomIndex();
2907 if (OldRoomIndex
&& (CurrentRoomIndex
!= OldRoomIndex
)) {
2908 // room about to be changed, stop here
2909 dirlogf("room about to be changed\n");
2910 Go
->Terminate(false);
2914 // stop near a dangerous square
2915 if (IsDangerousSquare(GetPos()+MoveVector
)) {
2916 dirlogf("sense the danger\n");
2917 Go
->Terminate(false);
2922 // if the state modified the direction, move and stop
2923 if (moveDir
!= Go
->GetDirection()) {
2924 dirlogf("move affected by state\n");
2925 if (TryMove(MoveVector
, true, game::PlayerIsRunning())) {
2926 game::DrawEverything();
2927 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2929 Go
->Terminate(false);
2933 truth doStop
= false, markAsTurn
= false;
2935 // continuous walking
2936 if (Go
->IsWalkingInOpen() || !orthoDir
[moveDir
]) {
2937 // walking in open space or diagonal walking
2938 v2
newPos(GetPos()+MoveVector
);
2939 int ood
, ond
, nod
, nnd
;
2941 * open: stop if # of possible dirs in next step != # of possible dirs in current step
2942 * (or next step is in corridor)
2944 dirlogf("open walking\n");
2945 if (IsInCorridor(newPos
, moveDir
)) {
2946 // trying to enter the corridor, stop right here
2947 dirlogf("entering the corridor\n");
2948 Go
->Terminate(false);
2951 CountPossibleMoveDirs(GetPos(), &ood
, &ond
);
2952 CountPossibleMoveDirs(newPos
, &nod
, &nnd
);
2953 if (ood
!= nod
|| ond
!= nnd
) {
2954 // # of directions to walk to changed, stop right here
2955 dirlogf("# of directions changed from (%d:%d) to (%d:%d)\n", ood
, ond
, nod
, nnd
);
2956 //Go->Terminate(false);
2960 // ok, we can do this move
2962 // ortho-walking thru the corridor
2963 int newDir
= CheckCorridorMove(MoveVector
, GetPos(), moveDir
, &markAsTurn
);
2965 // ah, something weird; stop right here
2966 Go
->Terminate(false);
2969 Go
->SetDirection(newDir
); // perform possible turn
2972 // stop near the dangerous square
2973 for (int mdv
= 0; mdv
< MDIR_STAND
; ++mdv
) {
2974 if (IsDangerousSquare(GetPos()+MoveVector
+game::GetMoveVector(mdv
))) {
2975 dirlogf(" danger!\n");
2976 Go
->Terminate(false);
2982 // now try to perform the move
2983 dirlogf("trying to make the move\n");
2985 square
*BeginSquare
= GetSquareUnder();
2986 uInt OldRoomIndex
= GetLSquareUnder()->GetRoomIndex();
2987 uInt CurrentRoomIndex
= MoveToSquare
[0]->GetRoomIndex();
2989 // stop on the square with something interesting
2992 area
*ca
= GetSquareUnder()->GetArea();
2993 v2 npos
= GetPos()+MoveVector
;
2994 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2995 v2 np
= npos
+game::GetMoveVector(f
);
2996 if (np
.X
>= 0 && np
.Y
>= 0 && np
.X
< ca
->GetXSize() && np
.Y
< ca
->GetYSize()) {
2997 lsquare
*sq
= static_cast<lsquare
*>(ca
->GetSquare(np
.X
, np
.Y
));
2998 if (IsPlayer() && !sq
->HasBeenSeen()) continue;
2999 //if (!sq->CanBeSeenBy(this)) continue;
3000 olterrain
*terra
= sq
->GetOLTerrain();
3002 dirlogf("** OK terra at %d; door: %s; seen: %s\n", f
, (terra
->IsDoor() ? "yes" : "no"), (sq
->IsGoSeen() ? "yes" : "no"));
3003 if (terra
->IsDoor()) {
3004 if (ivanconfig::GetStopOnSeenDoors() || !sq
->IsGoSeen()) {
3005 dirlogf(" *** stop near the door\n");
3016 for (int c
= 0; c
< Squares
; ++c
) {
3017 lsquare
*Square
= MoveToSquare
[c
];
3019 if (!Square
->HasBeenSeen()) continue;
3021 if (!Square
->CanBeSeenBy(this)) continue;
3023 if (Square
->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
3024 dirlogf(" stepped near something interesting\n");
3031 // check items in adjacent squares too, so diagonal move won't miss any
3033 for (int f
= 0; f
< MDIR_STAND
&& !doStop
; ++f
) {
3034 v2 np
= game::GetMoveVector(f
);
3035 if (np
== MoveVector
) continue; // this will be checked on the next move
3036 if (!IsPassableSquare(GetPos()+np
)) continue;
3037 int sq2
= CalculateNewSquaresUnder(MoveToSquare2
, GetPos()+np
);
3038 for (int c
= 0; c
< sq2
; ++c
) {
3039 lsquare
*Square
= MoveToSquare2
[c
];
3041 if (!Square
->HasBeenSeen()) continue;
3043 if (!Square
->CanBeSeenBy(this)) continue;
3045 if (Square
->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
3046 dirlogf(" stepped near something interesting\n");
3048 Go
->Terminate(false);
3056 Go
->SetPrevWasTurn(markAsTurn
&& MoveVector
.X
&& MoveVector
.Y
); // diagonal move?
3058 truth moveOk
= TryMove(MoveVector
, true, game::PlayerIsRunning());
3060 if (!moveOk
|| BeginSquare
== GetSquareUnder() || (CurrentRoomIndex
&& (OldRoomIndex
!= CurrentRoomIndex
))) {
3061 dirlogf(" stopped\n");
3063 game::DrawEverything();
3064 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
3066 Go
->Terminate(false);
3071 mPrevMoveDir
= Go
->GetDirection();
3072 Go
->SetIsWalkingInOpen(!IsInCorridor(moveDir
));
3075 game::DrawEverything();
3076 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
3077 if (doStop
) Go
->Terminate(false);
3081 void character::SetTeam (team
*What
) {
3087 void character::ChangeTeam (team
*What
) {
3088 if (Team
) Team
->Remove(this);
3090 SendNewDrawRequest();
3091 if (Team
) Team
->Add(this);
3095 truth
character::ChangeRandomAttribute (int HowMuch
) {
3096 for (int c
= 0; c
< 50; ++c
) {
3097 int AttribID
= RAND()%ATTRIBUTES
;
3098 if (EditAttribute(AttribID
, HowMuch
)) return true;
3104 int character::RandomizeReply (sLong
&Said
, int Replies
) {
3105 truth NotSaid
= false;
3106 for (int c
= 0; c
< Replies
; ++c
) {
3107 if (!(Said
& (1 << c
))) {
3112 if (!NotSaid
) Said
= 0;
3114 while (Said
& 1 << (ToSay
= RAND() % Replies
));
3120 void character::DisplayInfo (festring
&Msg
) {
3122 Msg
<< " You are " << GetStandVerb() << " here.";
3124 Msg
<< ' ' << GetName(INDEFINITE
).CapitalizeCopy() << " is " << GetStandVerb() << " here. " << GetPersonalPronoun().CapitalizeCopy();
3125 cchar
*Separator1
= GetAction() ? "," : " and";
3126 cchar
*Separator2
= " and";
3127 if (GetTeam() == PLAYER
->GetTeam()) {
3130 int Relation
= GetRelation(PLAYER
);
3131 if (Relation
== HOSTILE
) Msg
<< " is hostile";
3132 else if (Relation
== UNCARING
) {
3133 Msg
<< " does not care about you";
3134 Separator1
= Separator2
= " and is";
3136 Msg
<< " is friendly";
3139 if (StateIsActivated(PANIC
)) {
3140 Msg
<< Separator1
<< " panicked";
3141 Separator2
= " and";
3143 if (GetAction()) Msg
<< Separator2
<< ' ' << GetAction()->GetDescription();
3149 void character::TestWalkability () {
3150 if (!IsEnabled()) return;
3151 square
*SquareUnder
= !game::IsInWilderness() ? GetSquareUnder() : PLAYER
->GetSquareUnder();
3152 if (SquareUnder
->IsFatalToStay() && !CanMoveOn(SquareUnder
)) {
3153 truth Alive
= false;
3154 if (!game::IsInWilderness() || IsPlayer()) {
3155 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
3156 square
*Square
= GetNeighbourSquare(d
);
3157 if (Square
&& CanMoveOn(Square
) && IsFreeForMe(Square
)) {
3158 if (IsPlayer()) ADD_MESSAGE("%s.", SquareUnder
->SurviveMessage(this));
3159 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE
), SquareUnder
->MonsterSurviveMessage(this));
3160 Move(Square
->GetPos(), true); // actually, this shouldn't be a teleport move
3161 SquareUnder
->SurviveEffect(this);
3171 festring DeathMsg
= festring(SquareUnder
->DeathMessage(this));
3172 game::AskForEscPress(DeathMsg
+".");
3173 festring Msg
= SquareUnder
->ScoreEntry(this);
3174 PLAYER
->AddScoreEntry(Msg
);
3177 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE
), SquareUnder
->MonsterDeathVerb(this));
3178 Die(0, SquareUnder
->ScoreEntry(this), DISALLOW_MSG
);
3185 int character::GetSize () const {
3186 if (GetTorso()->GetSize() < 1) {
3187 fprintf(stderr
, "WARNING: character::GetSize() is %d for %s!\n", GetTorso()->GetSize(), GetNameSingular().CStr());
3190 return GetTorso()->GetSize();
3194 void character::SetMainMaterial (material
*NewMaterial
, int SpecialFlags
) {
3195 NewMaterial
->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
3196 GetBodyPart(0)->SetMainMaterial(NewMaterial
, SpecialFlags
);
3197 for (int c
= 1; c
< BodyParts
; ++c
) {
3198 NewMaterial
= NewMaterial
->SpawnMore(GetBodyPart(c
)->GetMainMaterial()->GetVolume());
3199 GetBodyPart(c
)->SetMainMaterial(NewMaterial
, SpecialFlags
);
3204 void character::ChangeMainMaterial (material
*NewMaterial
, int SpecialFlags
) {
3205 NewMaterial
->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
3206 GetBodyPart(0)->ChangeMainMaterial(NewMaterial
, SpecialFlags
);
3207 for (int c
= 1; c
< BodyParts
; ++c
) {
3208 NewMaterial
= NewMaterial
->SpawnMore(GetBodyPart(c
)->GetMainMaterial()->GetVolume());
3209 GetBodyPart(c
)->ChangeMainMaterial(NewMaterial
, SpecialFlags
);
3214 void character::SetSecondaryMaterial (material
*, int) {
3215 ABORT("Illegal character::SetSecondaryMaterial call!");
3219 void character::ChangeSecondaryMaterial (material
*, int) {
3220 ABORT("Illegal character::ChangeSecondaryMaterial call!");
3224 void character::TeleportRandomly (truth Intentional
) {
3225 v2 TelePos
= ERROR_V2
;
3226 if (StateIsActivated(TELEPORT_LOCK
)) { ADD_MESSAGE("You flicker for a second."); return; }
3227 if (StateIsActivated(TELEPORT_CONTROL
)) {
3229 v2 Input
= game::PositionQuestion(CONST_S("Where do you wish to teleport? [direction keys move cursor, space accepts]"), GetPos(), &game::TeleportHandler
, 0, false);
3230 if (Input
== ERROR_V2
) Input
= GetPos(); // esc pressed
3231 lsquare
*Square
= GetNearLSquare(Input
);
3232 if (CanMoveOn(Square
) || game::GoThroughWallsCheatIsActive()) {
3233 if (Square
->GetPos() == GetPos()) {
3234 ADD_MESSAGE("You disappear and reappear.");
3237 if (IsFreeForMe(Square
)) {
3238 if ((Input
-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
3239 EditExperience(INTELLIGENCE
, 100, 1 << 10);
3242 ADD_MESSAGE("You cannot concentrate yourself enough to control a teleport that far.");
3245 character
*C
= Square
->GetCharacter();
3246 if (C
) ADD_MESSAGE("For a moment you feel very much like %s.", C
->CHAR_NAME(INDEFINITE
));
3247 else ADD_MESSAGE("You feel that something weird has happened, but can't really tell what exactly.");
3250 ADD_MESSAGE("You feel like having been hit by something really hard from the inside.");
3252 } else if (!Intentional
) {
3253 if (IsGoingSomeWhere() && GetLevel()->IsValidPos(GoingTo
)) {
3254 v2 Where
= GetLevel()->GetNearestFreeSquare(this, GoingTo
);
3255 if (Where
!= ERROR_V2
&& (Where
-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
3256 EditExperience(INTELLIGENCE
, 100, 1 << 10);
3264 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.");
3267 //if (TelePos != ERROR_V2) Move(TelePos, true);
3268 //else Move(GetLevel()->GetRandomSquare(this), true);
3269 //if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE));
3270 //if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3272 if (TelePos
== ERROR_V2
) TelePos
= GetLevel()->GetRandomSquare(this);
3274 room
*PossibleRoom
= game::GetCurrentLevel()->GetLSquare(TelePos
)->GetRoom();
3276 if (!PossibleRoom
) {
3277 //if it's outside of a room
3278 if (TelePos
!= ERROR_V2
) Move(TelePos
, true); else Move(GetLevel()->GetRandomSquare(this), true);
3279 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE
));
3280 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3281 } else if (PossibleRoom
&& PossibleRoom
->IsOKToTeleportInto()) {
3282 // If it's inside of a room, check whether a ward is active that might impede the player
3283 if (TelePos
!= ERROR_V2
) Move(TelePos
, true); else Move(GetLevel()->GetRandomSquare(this), true);
3284 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE
));
3285 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3288 ADD_MESSAGE("A mighty force blasts you back to where you were standing. A ward prevents you from teleporting.");
3290 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);
3295 CONST_S("killed by an explosion triggered when attempting to teleport into room protected by a ward"),
3299 lsquare* Square = GetNearLSquare(GetPos());
3300 Square->DrawParticles(RED);
3301 Square->FireBall(Beam);*/
3306 void character::DoDetecting () {
3307 material
*TempMaterial
;
3310 festring Temp
= game::DefaultQuestion(CONST_S("What material do you want to detect?"), game::GetDefaultDetectMaterial());
3311 TempMaterial
= protosystem::CreateMaterial(Temp
);
3312 if (TempMaterial
) break;
3313 game::DrawEverythingNoBlit();
3316 level
*Level
= GetLevel();
3317 int Squares
= Level
->DetectMaterial(TempMaterial
);
3319 if (Squares
> GetAttribute(INTELLIGENCE
) * (25+RAND()%51)) {
3320 ADD_MESSAGE("An enormous burst of geographical information overwhelms your consciousness. Your mind cannot cope with it and your memories blur.");
3321 Level
->BlurMemory();
3322 BeginTemporaryState(CONFUSED
, 1000 + RAND() % 1000);
3323 EditExperience(INTELLIGENCE
, -100, 1 << 12);
3324 } else if (!Squares
) {
3325 ADD_MESSAGE("You feel a sudden urge to imagine the dark void of a starless night sky.");
3326 EditExperience(INTELLIGENCE
, 200, 1 << 12);
3328 ADD_MESSAGE("You feel attracted to all things made of %s.", TempMaterial
->GetName(false, false).CStr());
3329 game::PositionQuestion(CONST_S("Detecting material [direction keys move cursor, space exits]"), GetPos(), 0, 0, false);
3330 EditExperience(INTELLIGENCE
, 300, 1 << 12);
3333 delete TempMaterial
;
3334 Level
->CalculateLuminances();
3335 game::SendLOSUpdateRequest();
3339 void character::RestoreHP () {
3340 doforbodyparts()(this, &bodypart::FastRestoreHP
);
3345 void character::RestoreLivingHP () {
3347 for (int c
= 0; c
< BodyParts
; ++c
) {
3348 bodypart
*BodyPart
= GetBodyPart(c
);
3349 if (BodyPart
&& BodyPart
->CanRegenerate()) {
3350 BodyPart
->FastRestoreHP();
3351 HP
+= BodyPart
->GetHP();
3357 truth
character::AllowDamageTypeBloodSpill (int Type
) {
3358 if ((Type
&0xFFF) == PHYSICAL_DAMAGE
) return true;
3359 if ((Type
&0xFFF) == SOUND
) return true;
3360 if ((Type
&0xFFF) == ENERGY
) return true;
3362 if ((Type
&0xFFF) == ACID
) return false;
3363 if ((Type
&0xFFF) == FIRE
) return false;
3364 if ((Type
&0xFFF) == DRAIN
) return false;
3365 if ((Type
&0xFFF) == POISON
) return false;
3366 if ((Type
&0xFFF) == ELECTRICITY
) return false;
3367 if ((Type
&0xFFF) == MUSTARD_GAS_DAMAGE
) return false;
3368 if ((Type
&0xFFF) == PSI
) return false;
3370 ABORT("Unknown blood effect destroyed the dungeon!");
3375 /* Returns truly done damage */
3376 int character::ReceiveBodyPartDamage (character
*Damager
, int Damage
, int Type
, int BodyPartIndex
,
3377 int Direction
, truth PenetrateResistance
, truth Critical
, truth ShowNoDamageMsg
, truth CaptureBodyPart
)
3379 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
3380 if (!Damager
|| Damager
->AttackMayDamageArmor()) BodyPart
->DamageArmor(Damager
, Damage
, Type
);
3381 if (!PenetrateResistance
) {
3382 Damage
-= (BodyPart
->GetTotalResistance(Type
)>>1)+RAND()%((BodyPart
->GetTotalResistance(Type
)>>1)+1);
3384 if (int(Damage
) < 1) {
3388 if (ShowNoDamageMsg
) {
3389 if (IsPlayer()) ADD_MESSAGE("You are not hurt.");
3390 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is not hurt.", GetPersonalPronoun().CStr());
3396 if (Critical
&& AllowDamageTypeBloodSpill(Type
) && !game::IsInWilderness()) {
3397 BodyPart
->SpillBlood(2+(RAND()&1));
3398 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
3399 lsquare
*Square
= GetNeighbourLSquare(d
);
3400 if (Square
&& Square
->IsFlyable()) BodyPart
->SpillBlood(1, Square
->GetPos());
3404 if (BodyPart
->ReceiveDamage(Damager
, Damage
, Type
, Direction
) && BodyPartCanBeSevered(BodyPartIndex
)) {
3405 if (DamageTypeDestroysBodyPart(Type
)) {
3406 if (IsPlayer()) ADD_MESSAGE("Your %s is destroyed!", BodyPart
->GetBodyPartName().CStr());
3407 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is destroyed!", GetPossessivePronoun().CStr(), BodyPart
->GetBodyPartName().CStr());
3408 GetBodyPart(BodyPartIndex
)->DropEquipment();
3409 item
*Severed
= SevereBodyPart(BodyPartIndex
);
3410 if (Severed
) Severed
->DestroyBodyPart(!game::IsInWilderness() ? GetStackUnder() : GetStack());
3411 SendNewDrawRequest();
3412 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart destroyed!"));
3414 if (IsPlayer()) ADD_MESSAGE("Your %s is severed off!", BodyPart
->GetBodyPartName().CStr());
3415 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is severed off!", GetPossessivePronoun().CStr(), BodyPart
->GetBodyPartName().CStr());
3416 item
*Severed
= SevereBodyPart(BodyPartIndex
);
3417 SendNewDrawRequest();
3419 if (CaptureBodyPart
) {
3420 Damager
->GetLSquareUnder()->AddItem(Severed
);
3421 } else if (!game::IsInWilderness()) {
3422 /** No multi-tile humanoid support! */
3423 GetStackUnder()->AddItem(Severed
);
3424 if (Direction
!= YOURSELF
) Severed
->Fly(0, Direction
, Damage
);
3426 GetStack()->AddItem(Severed
);
3428 Severed
->DropEquipment();
3429 } else if (IsPlayer() || CanBeSeenByPlayer()) {
3430 ADD_MESSAGE("It vanishes.");
3432 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart severed!"));
3434 if (CanPanicFromSeveredBodyPart() && RAND()%100 < GetPanicLevel() && !StateIsActivated(PANIC
) && !IsDead() && !StateIsActivated(FEARLESS
)) {
3435 BeginTemporaryState(PANIC
, 1000+RAND()%1001);
3437 SpecialBodyPartSeverReaction();
3440 if (!IsDead()) CheckPanic(500);
3446 /* Returns 0 if bodypart disappears */
3447 item
*character::SevereBodyPart (int BodyPartIndex
, truth ForceDisappearance
, stack
*EquipmentDropStack
) {
3448 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
3449 if (StateIsActivated(LEPROSY
)) BodyPart
->GetMainMaterial()->SetIsInfectedByLeprosy(true);
3450 if (ForceDisappearance
|| BodyPartsDisappearWhenSevered() || StateIsActivated(POLYMORPHED
) || game::AllBodyPartsVanish()) {
3451 BodyPart
->DropEquipment(EquipmentDropStack
);
3452 BodyPart
->RemoveFromSlot();
3453 CalculateAttributeBonuses();
3454 CalculateBattleInfo();
3455 BodyPart
->SendToHell();
3456 SignalPossibleTransparencyChange();
3457 RemoveTraps(BodyPartIndex
);
3460 BodyPart
->SetOwnerDescription("of " + GetName(INDEFINITE
));
3461 BodyPart
->SetIsUnique(LeftOversAreUnique());
3462 UpdateBodyPartPicture(BodyPartIndex
, true);
3463 BodyPart
->RemoveFromSlot();
3464 BodyPart
->RandomizePosition();
3465 CalculateAttributeBonuses();
3466 CalculateBattleInfo();
3468 SignalPossibleTransparencyChange();
3469 RemoveTraps(BodyPartIndex
);
3474 /* The second int is actually TargetFlags, which is not used here, but seems to be used in humanoid::ReceiveDamage.
3475 * Returns true if the character really receives damage */
3476 truth
character::ReceiveDamage (character
*Damager
, int Damage
, int Type
, int, int Direction
,
3477 truth
, truth PenetrateArmor
, truth Critical
, truth ShowMsg
)
3479 truth Affected
= ReceiveBodyPartDamage(Damager
, Damage
, Type
, 0, Direction
, PenetrateArmor
, Critical
, ShowMsg
);
3480 if (DamageTypeAffectsInventory(Type
)) {
3481 for (int c
= 0; c
< GetEquipments(); ++c
) {
3482 item
*Equipment
= GetEquipment(c
);
3483 if (Equipment
) Equipment
->ReceiveDamage(Damager
, Damage
, Type
);
3485 GetStack()->ReceiveDamage(Damager
, Damage
, Type
);
3491 festring
character::GetDescription (int Case
) const {
3492 if (IsPlayer()) return CONST_S("you");
3493 if (CanBeSeenByPlayer()) return GetName(Case
);
3494 return CONST_S("something");
3498 festring
character::GetPersonalPronoun (truth PlayersView
) const {
3499 if (IsPlayer() && PlayersView
) return CONST_S("you");
3500 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3501 if (GetSex() == MALE
) return CONST_S("he");
3502 return CONST_S("she");
3506 festring
character::GetPossessivePronoun (truth PlayersView
) const {
3507 if (IsPlayer() && PlayersView
) return CONST_S("your");
3508 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("its");
3509 if (GetSex() == MALE
) return CONST_S("his");
3510 return CONST_S("her");
3514 festring
character::GetObjectPronoun (truth PlayersView
) const {
3515 if (IsPlayer() && PlayersView
) return CONST_S("you");
3516 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3517 if (GetSex() == MALE
) return CONST_S("him");
3518 return CONST_S("her");
3522 void character::AddName (festring
&String
, int Case
) const {
3523 if (AssignedName
.IsEmpty()) {
3524 id::AddName(String
, Case
);
3525 } else if (!(Case
& PLURAL
)) {
3526 if (!ShowClassDescription()) {
3527 String
<< AssignedName
;
3529 String
<< AssignedName
<< ' ';
3530 id::AddName(String
, (Case
|ARTICLE_BIT
)&~INDEFINE_BIT
);
3533 id::AddName(String
, Case
);
3534 String
<< " named " << AssignedName
;
3539 int character::GetHungerState () const {
3540 if (!UsesNutrition()) return NOT_HUNGRY
;
3541 if (GetNP() > OVER_FED_LEVEL
) return OVER_FED
;
3542 if (GetNP() > BLOATED_LEVEL
) return BLOATED
;
3543 if (GetNP() > SATIATED_LEVEL
) return SATIATED
;
3544 if (GetNP() > NOT_HUNGER_LEVEL
) return NOT_HUNGRY
;
3545 if (GetNP() > HUNGER_LEVEL
) return HUNGRY
;
3546 if (GetNP() > VERY_HUNGER_LEVEL
) return VERY_HUNGRY
;
3551 truth
character::CanConsume (material
*Material
) const {
3552 return GetConsumeFlags() & Material
->GetConsumeType();
3556 void character::SetTemporaryStateCounter (sLong State
, int What
) {
3557 for (int c
= 0; c
< STATES
; ++c
) {
3558 if ((1 << c
) & State
) TemporaryStateCounter
[c
] = What
;
3563 void character::EditTemporaryStateCounter (sLong State
, int What
) {
3564 for (int c
= 0; c
< STATES
; ++c
) {
3565 if ((1 << c
) & State
) TemporaryStateCounter
[c
] += What
;
3570 int character::GetTemporaryStateCounter (sLong State
) const {
3571 for (int c
= 0; c
< STATES
; ++c
) {
3572 if ((1 << c
) & State
) return TemporaryStateCounter
[c
];
3574 ABORT("Illegal GetTemporaryStateCounter request!");
3579 truth
character::CheckKick () const {
3581 if (IsPlayer()) ADD_MESSAGE("This race can't kick.");
3588 int character::GetResistance (int Type
) const {
3589 if ((Type
&0xFFF) == PHYSICAL_DAMAGE
) return 0;
3590 if ((Type
&0xFFF) == DRAIN
) return 0;
3591 if ((Type
&0xFFF) == MUSTARD_GAS_DAMAGE
) return 0;
3592 if ((Type
&0xFFF) == PSI
) return 0;
3594 if ((Type
&0xFFF) == ENERGY
) return GetEnergyResistance();
3595 if ((Type
&0xFFF) == FIRE
) return GetFireResistance();
3596 if ((Type
&0xFFF) == POISON
) return GetPoisonResistance();
3597 if ((Type
&0xFFF) == ELECTRICITY
) return GetElectricityResistance();
3598 if ((Type
&0xFFF) == ACID
) return GetAcidResistance();
3599 if ((Type
&0xFFF) == SOUND
) return GetSoundResistance();
3601 ABORT("Resistance lack detected!");
3606 void character::Regenerate () {
3608 if (StateIsActivated(REGENERATION
) && !(RAND()%3000)) {
3609 bodypart
*NewBodyPart
= GenerateRandomBodyPart();
3610 if (!NewBodyPart
) return;
3611 NewBodyPart
->SetHP(1);
3612 if (IsPlayer()) ADD_MESSAGE("You grow a new %s.", NewBodyPart
->GetBodyPartName().CStr());
3613 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), NewBodyPart
->GetBodyPartName().CStr());
3617 sLong RegenerationBonus
= 0;
3618 truth NoHealableBodyParts
= true;
3619 for (int c
= 0; c
< BodyParts
; ++c
) {
3620 bodypart
*BodyPart
= GetBodyPart(c
);
3621 if (BodyPart
&& BodyPart
->CanRegenerate()) {
3622 RegenerationBonus
+= BodyPart
->GetMaxHP();
3623 if (NoHealableBodyParts
&& BodyPart
->GetHP() < BodyPart
->GetMaxHP()) NoHealableBodyParts
= false;
3626 if (!RegenerationBonus
|| NoHealableBodyParts
) return;
3627 RegenerationBonus
*= (50+GetAttribute(ENDURANCE
));
3629 if (Action
&& Action
->IsRest()) {
3630 if (SquaresUnder
== 1) RegenerationBonus
*= GetSquareUnder()->GetRestModifier() << 1;
3632 int Lowest
= GetSquareUnder(0)->GetRestModifier();
3633 for (int c
= 1; c
< GetSquaresUnder(); ++c
) {
3634 int Mod
= GetSquareUnder(c
)->GetRestModifier();
3635 if (Mod
< Lowest
) Lowest
= Mod
;
3637 RegenerationBonus
*= Lowest
<< 1;
3641 RegenerationCounter
+= RegenerationBonus
;
3643 while (RegenerationCounter
> 1250000) {
3644 bodypart
*BodyPart
= HealHitPoint();
3645 if (!BodyPart
) break;
3646 EditNP(-Max(7500/MaxHP
, 1));
3647 RegenerationCounter
-= 1250000;
3648 int HP
= BodyPart
->GetHP();
3649 EditExperience(ENDURANCE
, Min(1000*BodyPart
->GetMaxHP()/(HP
*HP
), 300), 1000);
3654 void character::PrintInfo () const {
3655 felist
Info(CONST_S("Information about ")+GetName(DEFINITE
));
3656 for (int c
= 0; c
< GetEquipments(); ++c
) {
3657 item
*Equipment
= GetEquipment(c
);
3658 if ((EquipmentEasilyRecognized(c
) || game::WizardModeIsActive()) && Equipment
) {
3659 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
3660 Info
.AddEntry(festring(GetEquipmentName(c
))+": "+Equipment
->GetName(INDEFINITE
), LIGHT_GRAY
, 0, ImageKey
, true);
3663 if (Info
.IsEmpty()) {
3664 ADD_MESSAGE("There's nothing special to tell about %s.", CHAR_NAME(DEFINITE
));
3666 game::SetStandardListAttributes(Info
);
3667 Info
.SetEntryDrawer(game::ItemEntryDrawer
);
3670 game::ClearItemDrawVector();
3674 truth
character::TryToRiseFromTheDead () {
3675 for (int c
= 0; c
< BodyParts
; ++c
) {
3676 bodypart
*BodyPart
= GetBodyPart(c
);
3678 BodyPart
->ResetSpoiling();
3679 if (BodyPart
->CanRegenerate() || BodyPart
->GetHP() < 1) BodyPart
->SetHP(1);
3687 truth
character::RaiseTheDead (character
*) {
3688 truth Useful
= false;
3689 for (int c
= 0; c
< BodyParts
; ++c
) {
3690 bodypart
*BodyPart
= GetBodyPart(c
);
3691 if (!BodyPart
&& CanCreateBodyPart(c
)) {
3692 CreateBodyPart(c
)->SetHP(1);
3693 if (IsPlayer()) ADD_MESSAGE("Suddenly you grow a new %s.", GetBodyPartName(c
).CStr());
3694 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), GetBodyPartName(c
).CStr());
3696 } else if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < 1) {
3701 if (IsPlayer()) ADD_MESSAGE("You shudder.");
3702 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s shudders.", CHAR_NAME(DEFINITE
));
3708 void character::SetSize (int Size
) {
3709 for (int c
= 0; c
< BodyParts
; ++c
) {
3710 bodypart
*BodyPart
= GetBodyPart(c
);
3711 if (BodyPart
) BodyPart
->SetSize(GetBodyPartSize(c
, Size
));
3716 sLong
character::GetBodyPartSize (int I
, int TotalSize
) const {
3717 if (I
== TORSO_INDEX
) return TotalSize
;
3718 ABORT("Weird bodypart size request for a character!");
3723 sLong
character::GetBodyPartVolume (int I
) const {
3724 if (I
== TORSO_INDEX
) return GetTotalVolume();
3725 ABORT("Weird bodypart volume request for a character!");
3730 void character::CreateBodyParts (int SpecialFlags
) {
3731 for (int c
= 0; c
< BodyParts
; ++c
) if (CanCreateBodyPart(c
)) CreateBodyPart(c
, SpecialFlags
);
3735 void character::RestoreBodyParts () {
3736 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) CreateBodyPart(c
);
3740 void character::UpdatePictures () {
3741 if (!PictureUpdatesAreForbidden()) for (int c
= 0; c
< BodyParts
; ++c
) UpdateBodyPartPicture(c
, false);
3745 bodypart
*character::MakeBodyPart (int I
) const {
3746 if (I
== TORSO_INDEX
) return normaltorso::Spawn(0, NO_MATERIALS
);
3747 ABORT("Weird bodypart to make for a character!");
3752 bodypart
*character::CreateBodyPart (int I
, int SpecialFlags
) {
3753 bodypart
*BodyPart
= MakeBodyPart(I
);
3754 material
*Material
= CreateBodyPartMaterial(I
, GetBodyPartVolume(I
));
3755 BodyPart
->InitMaterials(Material
, false);
3756 BodyPart
->SetSize(GetBodyPartSize(I
, GetTotalSize()));
3757 BodyPart
->SetBloodMaterial(GetBloodMaterial());
3758 BodyPart
->SetNormalMaterial(Material
->GetConfig());
3760 SetBodyPart(I
, BodyPart
);
3761 BodyPart
->InitSpecialAttributes();
3762 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdateBodyPartPicture(I
, false);
3763 if (!IsInitializing()) {
3764 CalculateBattleInfo();
3765 SendNewDrawRequest();
3766 SignalPossibleTransparencyChange();
3772 v2
character::GetBodyPartBitmapPos (int I
, truth
) const {
3773 if (I
== TORSO_INDEX
) return GetTorsoBitmapPos();
3774 ABORT("Weird bodypart BitmapPos request for a character!");
3779 void character::UpdateBodyPartPicture (int I
, truth Severed
) {
3780 bodypart
*BP
= GetBodyPart(I
);
3782 BP
->SetBitmapPos(GetBodyPartBitmapPos(I
, Severed
));
3783 BP
->GetMainMaterial()->SetSkinColor(GetBodyPartColorA(I
, Severed
));
3784 BP
->GetMainMaterial()->SetSkinColorIsSparkling(GetBodyPartSparkleFlags(I
) & SPARKLING_A
);
3785 BP
->SetMaterialColorB(GetBodyPartColorB(I
, Severed
));
3786 BP
->SetMaterialColorC(GetBodyPartColorC(I
, Severed
));
3787 BP
->SetMaterialColorD(GetBodyPartColorD(I
, Severed
));
3788 BP
->SetSparkleFlags(GetBodyPartSparkleFlags(I
));
3789 BP
->SetSpecialFlags(GetSpecialBodyPartFlags(I
));
3790 BP
->SetWobbleData(GetBodyPartWobbleData(I
));
3791 BP
->UpdatePictures();
3796 void character::LoadDataBaseStats () {
3797 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) {
3798 BaseExperience
[c
] = DataBase
->NaturalExperience
[c
];
3799 if (BaseExperience
[c
]) LimitRef(BaseExperience
[c
], MIN_EXP
, MAX_EXP
);
3801 SetMoney(GetDefaultMoney());
3802 SetInitialSweatMaterial(GetSweatMaterial());
3803 const fearray
<sLong
> &Skills
= GetKnownCWeaponSkills();
3805 const fearray
<sLong
> &Hits
= GetCWeaponSkillHits();
3806 if (Hits
.Size
== 1) {
3807 for (uInt c
= 0; c
< Skills
.Size
; ++c
) {
3808 if (Skills
[c
] < AllowedWeaponSkillCategories
) CWeaponSkill
[Skills
[c
]].AddHit(Hits
[0]*100);
3810 } else if (Hits
.Size
== Skills
.Size
) {
3811 for (uInt c
= 0; c
< Skills
.Size
; ++c
) {
3812 if (Skills
[c
] < AllowedWeaponSkillCategories
) CWeaponSkill
[Skills
[c
]].AddHit(Hits
[c
]*100);
3815 ABORT("Illegal weapon skill hit array size detected!");
3821 character
*characterprototype::SpawnAndLoad (inputfile
&SaveFile
) const {
3822 character
*Char
= Spawner(0, LOAD
);
3823 Char
->Load(SaveFile
);
3824 Char
->CalculateAll();
3829 void character::Initialize (int NewConfig
, int SpecialFlags
) {
3830 Flags
|= C_INITIALIZING
|C_IN_NO_MSG_MODE
;
3831 CalculateBodyParts();
3832 CalculateAllowedWeaponSkillCategories();
3833 CalculateSquaresUnder();
3834 BodyPartSlot
= new bodypartslot
[BodyParts
];
3835 OriginalBodyPartID
= new std::list
<feuLong
>[BodyParts
];
3836 CWeaponSkill
= new cweaponskill
[AllowedWeaponSkillCategories
];
3837 SquareUnder
= new square
*[SquaresUnder
];
3839 if (SquaresUnder
== 1) *SquareUnder
= 0; else memset(SquareUnder
, 0, SquaresUnder
*sizeof(square
*));
3841 for (int c
= 0; c
< BodyParts
; ++c
) BodyPartSlot
[c
].SetMaster(this);
3843 if (!(SpecialFlags
& LOAD
)) {
3844 ID
= game::CreateNewCharacterID(this);
3845 databasecreator
<character
>::InstallDataBase(this, NewConfig
);
3846 LoadDataBaseStats();
3847 TemporaryState
|= GetClassStates();
3848 if (TemporaryState
) {
3849 for (int c
= 0; c
< STATES
; ++c
) if (TemporaryState
& (1 << c
)) TemporaryStateCounter
[c
] = PERMANENT
;
3852 CreateBodyParts(SpecialFlags
| NO_PIC_UPDATE
);
3853 InitSpecialAttributes();
3854 CommandFlags
= GetDefaultCommandFlags();
3856 if (GetAttribute(INTELLIGENCE
, false) < 8) CommandFlags
&= ~DONT_CONSUME_ANYTHING_VALUABLE
; // gum
3857 if (!GetDefaultName().IsEmpty()) SetAssignedName(GetDefaultName());
3860 if (!(SpecialFlags
& LOAD
)) PostConstruct();
3862 if (!(SpecialFlags
& LOAD
)) {
3863 if (!(SpecialFlags
& NO_EQUIPMENT
)) CreateInitialEquipment((SpecialFlags
& NO_EQUIPMENT_PIC_UPDATE
) >> 1);
3864 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdatePictures();
3870 Flags
&= ~(C_INITIALIZING
|C_IN_NO_MSG_MODE
);
3874 truth
character::TeleportNear (character
*Caller
) {
3875 v2 Where
= GetLevel()->GetNearestFreeSquare(this, Caller
->GetPos());
3876 if (Where
== ERROR_V2
) return false;
3882 void character::ReceiveHeal (sLong Amount
) {
3884 for (c
= 0; c
< Amount
/ 10; ++c
) if (!HealHitPoint()) break;
3886 if (RAND()%10 < Amount
) HealHitPoint();
3887 if (Amount
>= 250 || RAND()%250 < Amount
) {
3888 bodypart
*NewBodyPart
= GenerateRandomBodyPart();
3889 if (!NewBodyPart
) return;
3890 NewBodyPart
->SetHP(1);
3891 if (IsPlayer()) ADD_MESSAGE("You grow a new %s.", NewBodyPart
->GetBodyPartName().CStr());
3892 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), NewBodyPart
->GetBodyPartName().CStr());
3897 void character::AddHealingLiquidConsumeEndMessage () const {
3898 if (IsPlayer()) ADD_MESSAGE("You feel better.");
3899 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks healthier.", CHAR_NAME(DEFINITE
));
3903 void character::ReceiveSchoolFood (sLong SizeOfEffect
) {
3904 SizeOfEffect
+= RAND()%SizeOfEffect
;
3905 if (SizeOfEffect
>= 250) VomitAtRandomDirection(SizeOfEffect
);
3906 if (!(RAND() % 3) && SizeOfEffect
>= 500 && EditAttribute(ENDURANCE
, SizeOfEffect
/500)) {
3907 if (IsPlayer()) ADD_MESSAGE("You gain a little bit of toughness for surviving this stuff.");
3908 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks tougher.", CHAR_NAME(DEFINITE
));
3910 BeginTemporaryState(POISONED
, (SizeOfEffect
>>1));
3914 void character::AddSchoolFoodConsumeEndMessage () const {
3915 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff tasted like vomit and old mousepads.");
3919 void character::AddSchoolFoodHitMessage () const {
3920 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff feels like vomit and old mousepads.");
3924 void character::ReceiveNutrition (sLong SizeOfEffect
) {
3925 EditNP(SizeOfEffect
);
3929 void character::ReceiveOmmelUrine (sLong Amount
) {
3930 EditExperience(ARM_STRENGTH
, 500, Amount
<<4);
3931 EditExperience(LEG_STRENGTH
, 500, Amount
<<4);
3932 if (IsPlayer()) game::DoEvilDeed(Amount
/25);
3936 void character::ReceiveOmmelCerumen (sLong Amount
) {
3937 EditExperience(INTELLIGENCE
, 500, Amount
<< 5);
3938 EditExperience(WISDOM
, 500, Amount
<< 5);
3939 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3943 void character::ReceiveOmmelSweat (sLong Amount
) {
3944 EditExperience(AGILITY
, 500, Amount
<< 4);
3945 EditExperience(DEXTERITY
, 500, Amount
<< 4);
3947 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3951 void character::ReceiveOmmelTears (sLong Amount
) {
3952 EditExperience(PERCEPTION
, 500, Amount
<< 4);
3953 EditExperience(CHARISMA
, 500, Amount
<< 4);
3954 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3958 void character::ReceiveOmmelSnot (sLong Amount
) {
3959 EditExperience(ENDURANCE
, 500, Amount
<< 5);
3961 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3965 void character::ReceiveOmmelBone (sLong Amount
) {
3966 EditExperience(ARM_STRENGTH
, 500, Amount
<< 6);
3967 EditExperience(LEG_STRENGTH
, 500, Amount
<< 6);
3968 EditExperience(DEXTERITY
, 500, Amount
<< 6);
3969 EditExperience(AGILITY
, 500, Amount
<< 6);
3970 EditExperience(ENDURANCE
, 500, Amount
<< 6);
3971 EditExperience(PERCEPTION
, 500, Amount
<< 6);
3972 EditExperience(INTELLIGENCE
, 500, Amount
<< 6);
3973 EditExperience(WISDOM
, 500, Amount
<< 6);
3974 EditExperience(CHARISMA
, 500, Amount
<< 6);
3977 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3981 void character::AddOmmelConsumeEndMessage () const {
3982 if (IsPlayer()) ADD_MESSAGE("You feel a primitive force coursing through your veins.");
3983 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks more powerful.", CHAR_NAME(DEFINITE
));
3987 void character::ReceivePepsi (sLong Amount
) {
3988 ReceiveDamage(0, Amount
/ 100, POISON
, TORSO
);
3989 EditExperience(PERCEPTION
, Amount
, 1 << 14);
3990 if (CheckDeath(CONST_S("was poisoned by pepsi"), 0)) return;
3991 if (IsPlayer()) game::DoEvilDeed(Amount
/ 10);
3995 void character::AddPepsiConsumeEndMessage () const {
3996 if (IsPlayer()) ADD_MESSAGE("Urgh. You feel your guruism fading away.");
3997 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very lame.", CHAR_NAME(DEFINITE
));
4001 void character::ReceiveDarkness (sLong Amount
) {
4002 EditExperience(INTELLIGENCE
, -Amount
/ 5, 1 << 13);
4003 EditExperience(WISDOM
, -Amount
/ 5, 1 << 13);
4004 EditExperience(CHARISMA
, -Amount
/ 5, 1 << 13);
4005 if (IsPlayer()) game::DoEvilDeed(int(Amount
/ 50));
4009 void character::AddFrogFleshConsumeEndMessage () const {
4010 if (IsPlayer()) ADD_MESSAGE("Arg. You feel the fate of a navastater placed upon you...");
4011 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks like a navastater.", CHAR_NAME(DEFINITE
));
4015 void character::ReceiveKoboldFlesh (sLong
) {
4016 /* As it is commonly known, the possibility of fainting per 500 cubic
4017 centimeters of kobold flesh is exactly 5%. */
4018 if (!(RAND() % 20)) {
4019 if (IsPlayer()) ADD_MESSAGE("You lose control of your legs and fall down.");
4020 LoseConsciousness(250 + RAND_N(250));
4025 void character::AddKoboldFleshConsumeEndMessage () const {
4026 if (IsPlayer()) ADD_MESSAGE("This stuff tasted really funny.");
4030 void character::AddKoboldFleshHitMessage () const {
4031 if (IsPlayer()) ADD_MESSAGE("You feel very funny.");
4035 void character::AddBoneConsumeEndMessage () const {
4036 if (IsPlayer()) ADD_MESSAGE("You feel like a hippie.");
4037 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s barks happily.", CHAR_NAME(DEFINITE
)); // this suspects that nobody except dogs can eat bones
4040 truth
character::RawEditAttribute (double &Experience
, int Amount
) const {
4041 /* Check if the attribute is disabled for creature */
4042 if (!Experience
) return false;
4043 if ((Amount
< 0 && Experience
< 2 * EXP_MULTIPLIER
) || (Amount
> 0 && Experience
> 999 * EXP_MULTIPLIER
)) return false;
4044 Experience
+= Amount
* EXP_MULTIPLIER
;
4045 LimitRef
<double>(Experience
, MIN_EXP
, MAX_EXP
);
4050 void character::DrawPanel (truth AnimationDraw
) const {
4051 if (AnimationDraw
) { DrawStats(true); return; }
4052 igraph::BlitBackGround(v2(19 + (game::GetScreenXSize() << 4), 0), v2(RES
.X
- 19 - (game::GetScreenXSize() << 4), RES
.Y
));
4053 igraph::BlitBackGround(v2(16, 45 + (game::GetScreenYSize() << 4)), v2(game::GetScreenXSize() << 4, 9));
4054 FONT
->Printf(DOUBLE_BUFFER
, v2(16, 45 + (game::GetScreenYSize() << 4)), WHITE
, "%s", GetPanelName().CStr());
4055 game::UpdateAttributeMemory();
4056 int PanelPosX
= RES
.X
- 96;
4057 int PanelPosY
= DrawStats(false);
4058 PrintAttribute("End", ENDURANCE
, PanelPosX
, PanelPosY
++);
4059 PrintAttribute("Per", PERCEPTION
, PanelPosX
, PanelPosY
++);
4060 PrintAttribute("Int", INTELLIGENCE
, PanelPosX
, PanelPosY
++);
4061 PrintAttribute("Wis", WISDOM
, PanelPosX
, PanelPosY
++);
4062 PrintAttribute("Wil", WILL_POWER
, PanelPosX
, PanelPosY
++);
4063 PrintAttribute("Cha", CHARISMA
, PanelPosX
, PanelPosY
++);
4064 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Siz %d", GetSize());
4065 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), IsInBadCondition() ? RED
: WHITE
, "HP %d/%d", GetHP(), GetMaxHP());
4067 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Gold: %d", GetMoney());
4070 if (game::IsInWilderness())
4071 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Worldmap");
4073 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", game::GetCurrentDungeon()->GetShortLevelDescription(game::GetCurrentLevelIndex()).CapitalizeCopy().CStr());
4076 game::GetTime(Time
);
4077 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Day %d", Time
.Day
);
4078 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Time %d:%s%d", Time
.Hour
, Time
.Min
< 10 ? "0" : "", Time
.Min
);
4079 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Turn %d", game::GetTurn());
4084 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", festring(GetAction()->GetDescription()).CapitalizeCopy().CStr());
4087 //printf("========= STATES =========\n");
4088 for (int c
= 0; c
< STATES
; ++c
) {
4089 //printf(" %d: %s (%s)\n", c, StateData[c].Description, (StateIsActivated(1<<c) ? "TAN" : "ona"));
4090 if (!(StateData
[c
].Flags
& SECRET
) && StateIsActivated(1 << c
) && (1 << c
!= HASTE
|| !StateIsActivated(SLOW
)) && (1 << c
!= SLOW
|| !StateIsActivated(HASTE
))) {
4091 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), (1 << c
) & EquipmentState
|| TemporaryStateCounter
[c
] >= PERMANENT
? BLUE
: WHITE
, "%s", StateData
[c
].Description
);
4095 auto hst
= GetHungerState();
4096 if (hst
== STARVING
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Starving");
4097 else if (hst
== VERY_HUNGRY
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Very hungry");
4098 else if (hst
== HUNGRY
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), ORANGE
, "Hungry");
4099 else if (hst
== SATIATED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Satiated");
4100 else if (hst
== BLOATED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Bloated");
4101 else if (hst
== OVER_FED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Overfed!");
4103 auto bst
= GetBurdenState();
4104 if (bst
== OVER_LOADED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Overload!");
4105 else if (bst
== STRESSED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), ORANGE
, "Stressed");
4106 else if (bst
== BURDENED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Burdened");
4108 auto trst
= GetTirednessState();
4109 if (trst
== FAINTING
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Fainting");
4110 else if (trst
== EXHAUSTED
) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), ORANGE
, "Exhausted");
4112 if (game::PlayerIsRunning()) {
4113 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", GetRunDescriptionLine(0));
4114 cchar
*SecondLine
= GetRunDescriptionLine(1);
4115 if (strlen(SecondLine
)) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", SecondLine
);
4120 void character::CalculateDodgeValue () {
4121 DodgeValue
= 0.05 * GetMoveEase() * GetAttribute(AGILITY
) / sqrt(GetSize());
4122 if (IsFlying()) DodgeValue
*= 2;
4123 if (DodgeValue
< 1) DodgeValue
= 1;
4127 truth
character::DamageTypeAffectsInventory (int Type
) {
4128 if ((Type
&0xFFF) == SOUND
) return true;
4129 if ((Type
&0xFFF) == ENERGY
) return true;
4130 if ((Type
&0xFFF) == ACID
) return true;
4131 if ((Type
&0xFFF) == FIRE
) return true;
4132 if ((Type
&0xFFF) == ELECTRICITY
) return true;
4134 if ((Type
&0xFFF) == PHYSICAL_DAMAGE
) return false;
4135 if ((Type
&0xFFF) == POISON
) return false;
4136 if ((Type
&0xFFF) == DRAIN
) return false;
4137 if ((Type
&0xFFF) == MUSTARD_GAS_DAMAGE
) return false;
4138 if ((Type
&0xFFF) == PSI
) return false;
4140 ABORT("Unknown reaping effect destroyed dungeon!");
4145 int character::CheckForBlockWithArm (character
*Enemy
, item
*Weapon
, arm
*Arm
,
4146 double WeaponToHitValue
, int Damage
, int Success
, int Type
)
4148 int BlockStrength
= Arm
->GetBlockCapability();
4149 double BlockValue
= Arm
->GetBlockValue();
4150 if (BlockStrength
&& BlockValue
) {
4151 item
*Blocker
= Arm
->GetWielded();
4152 if (RAND() % int(100+WeaponToHitValue
/BlockValue
/(1<<BlocksSinceLastTurn
)*(100+Success
)) < 100) {
4153 int NewDamage
= BlockStrength
< Damage
? Damage
-BlockStrength
: 0;
4154 if (Type
== UNARMED_ATTACK
) AddBlockMessage(Enemy
, Blocker
, Enemy
->UnarmedHitNoun(), NewDamage
);
4155 else if (Type
== WEAPON_ATTACK
) AddBlockMessage(Enemy
, Blocker
, "attack", NewDamage
);
4156 else if (Type
== KICK_ATTACK
) AddBlockMessage(Enemy
, Blocker
, Enemy
->KickNoun(), NewDamage
);
4157 else if (Type
== BITE_ATTACK
) AddBlockMessage(Enemy
, Blocker
, Enemy
->BiteNoun(), NewDamage
);
4158 sLong Weight
= Blocker
->GetWeight();
4159 sLong StrExp
= Limit(15 * Weight
/ 200, 75, 300);
4160 sLong DexExp
= Weight
? Limit(75000 / Weight
, 75, 300) : 300;
4161 Arm
->EditExperience(ARM_STRENGTH
, StrExp
, 1 << 8);
4162 Arm
->EditExperience(DEXTERITY
, DexExp
, 1 << 8);
4163 EditStamina(-10000 / GetAttribute(ARM_STRENGTH
), false);
4164 if (Arm
->TwoHandWieldIsActive()) {
4165 arm
*PairArm
= Arm
->GetPairArm();
4166 PairArm
->EditExperience(ARM_STRENGTH
, StrExp
, 1 << 8);
4167 PairArm
->EditExperience(DEXTERITY
, DexExp
, 1 << 8);
4169 Blocker
->WeaponSkillHit(Enemy
->CalculateWeaponSkillHits(this));
4170 Blocker
->ReceiveDamage(this, Damage
, PHYSICAL_DAMAGE
);
4171 Blocker
->BlockEffect(this, Enemy
, Weapon
, Type
);
4172 if (Weapon
) Weapon
->ReceiveDamage(Enemy
, Damage
- NewDamage
, PHYSICAL_DAMAGE
);
4173 if (BlocksSinceLastTurn
< 16) ++BlocksSinceLastTurn
;
4181 sLong
character::GetStateAPGain (sLong BaseAPGain
) const {
4182 if (!StateIsActivated(HASTE
) == !StateIsActivated(SLOW
)) return BaseAPGain
;
4183 if (StateIsActivated(HASTE
)) return (BaseAPGain
* 5) >> 2;
4184 return (BaseAPGain
<< 2) / 5;
4188 void character::SignalEquipmentAdd (int EquipmentIndex
) {
4189 item
*Equipment
= GetEquipment(EquipmentIndex
);
4190 if (Equipment
->IsInCorrectSlot(EquipmentIndex
)) {
4191 sLong AddedStates
= Equipment
->GetGearStates();
4193 for (int c
= 0; c
< STATES
; ++c
) {
4194 if (AddedStates
& (1 << c
)) {
4195 if (!StateIsActivated(1 << c
)) {
4196 if (!IsInNoMsgMode()) (this->*StateData
[c
].PrintBeginMessage
)();
4197 EquipmentState
|= 1 << c
;
4198 if (StateData
[c
].BeginHandler
) (this->*StateData
[c
].BeginHandler
)();
4200 EquipmentState
|= 1 << c
;
4206 if (!IsInitializing() && Equipment
->IsInCorrectSlot(EquipmentIndex
)) ApplyEquipmentAttributeBonuses(Equipment
);
4210 void character::SignalEquipmentRemoval (int, citem
*Item
) {
4211 CalculateEquipmentState();
4212 if (CalculateAttributeBonuses()) CheckDeath(festring("lost ")+GetPossessivePronoun(false)+" vital "+Item
->GetName(INDEFINITE
));
4216 void character::CalculateEquipmentState () {
4217 sLong Back
= EquipmentState
;
4219 for (int c
= 0; c
< GetEquipments(); ++c
) {
4220 item
*Equipment
= GetEquipment(c
);
4221 if (Equipment
&& Equipment
->IsInCorrectSlot(c
)) EquipmentState
|= Equipment
->GetGearStates();
4223 for (int c
= 0; c
< STATES
; ++c
) {
4224 if (Back
& (1 << c
) && !StateIsActivated(1 << c
)) {
4225 if (StateData
[c
].EndHandler
) {
4226 (this->*StateData
[c
].EndHandler
)();
4227 if (!IsEnabled()) return;
4229 if (!IsInNoMsgMode()) (this->*StateData
[c
].PrintEndMessage
)();
4235 /* Counter = duration in ticks */
4236 void character::BeginTemporaryState (sLong State
, int Counter
) {
4237 if (!Counter
) return;
4239 if (State
== POLYMORPHED
) ABORT("No Polymorphing with BeginTemporaryState!");
4240 for (Index
= 0; Index
< STATES
; ++Index
) if (1 << Index
== State
) break;
4241 if (Index
== STATES
) ABORT("BeginTemporaryState works only when State == 2^n!");
4242 if (TemporaryStateIsActivated(State
)) {
4243 int OldCounter
= GetTemporaryStateCounter(State
);
4244 if (OldCounter
!= PERMANENT
) EditTemporaryStateCounter(State
, Max(Counter
, 50-OldCounter
));
4245 } else if (StateData
[Index
].IsAllowed
== 0 || (this->*StateData
[Index
].IsAllowed
)()) {
4246 SetTemporaryStateCounter(State
, Max(Counter
, 50));
4247 if (!EquipmentStateIsActivated(State
)) {
4248 if (!IsInNoMsgMode()) (this->*StateData
[Index
].PrintBeginMessage
)();
4249 ActivateTemporaryState(State
);
4250 if (StateData
[Index
].BeginHandler
) (this->*StateData
[Index
].BeginHandler
)();
4252 ActivateTemporaryState(State
);
4258 void character::HandleStates () {
4259 if (!TemporaryState
&& !EquipmentState
) return;
4260 for (int c
= 0; c
< STATES
; ++c
) {
4261 if (TemporaryState
& (1 << c
) && TemporaryStateCounter
[c
] != PERMANENT
) {
4262 if (!--TemporaryStateCounter
[c
]) {
4263 TemporaryState
&= ~(1 << c
);
4264 if (!(EquipmentState
& (1 << c
))) {
4265 if (StateData
[c
].EndHandler
) {
4266 (this->*StateData
[c
].EndHandler
)();
4267 if (!IsEnabled()) return;
4269 if (!TemporaryStateCounter
[c
]) (this->*StateData
[c
].PrintEndMessage
)();
4273 if (StateIsActivated(1 << c
)) {
4274 if (StateData
[c
].Handler
) (this->*StateData
[c
].Handler
)();
4276 if (!IsEnabled()) return;
4281 void character::PrintBeginPolymorphControlMessage () const {
4282 if (IsPlayer()) ADD_MESSAGE("You feel your mind has total control over your body.");
4286 void character::PrintEndPolymorphControlMessage () const {
4287 if (IsPlayer()) ADD_MESSAGE("You are somehow uncertain of your willpower.");
4291 void character::PrintBeginLifeSaveMessage () const {
4292 if (IsPlayer()) ADD_MESSAGE("You hear Hell's gates being locked just now.");
4296 void character::PrintEndLifeSaveMessage () const {
4297 if (IsPlayer()) ADD_MESSAGE("You feel the Afterlife is welcoming you once again.");
4301 void character::PrintBeginLycanthropyMessage () const {
4302 if (IsPlayer()) ADD_MESSAGE("You suddenly notice you've always loved full moons.");
4306 void character::PrintEndLycanthropyMessage () const {
4307 if (IsPlayer()) ADD_MESSAGE("You feel the wolf inside you has had enough of your bad habits.");
4311 void character::PrintBeginVampirismMessage () const {
4312 if (IsPlayer()) ADD_MESSAGE("You suddenly decide you have always hated garlic.");
4316 void character::PrintEndVampirismMessage () const {
4317 if (IsPlayer()) ADD_MESSAGE("You recall your delight of the morning sunshine back in New Attnam. You are a vampire no longer.");
4321 void character::PrintBeginInvisibilityMessage () const {
4322 if ((PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm()) || (PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5)) {
4323 if (IsPlayer()) ADD_MESSAGE("You seem somehow transparent.");
4324 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems somehow transparent.", CHAR_NAME(DEFINITE
));
4326 if (IsPlayer()) ADD_MESSAGE("You fade away.");
4327 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears!", CHAR_NAME(DEFINITE
));
4332 void character::PrintEndInvisibilityMessage () const {
4333 if ((PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm()) || (PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5)) {
4334 if (IsPlayer()) ADD_MESSAGE("Your notice your transparency has ended.");
4335 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The appearance of %s seems far more solid now.", CHAR_NAME(INDEFINITE
));
4337 if (IsPlayer()) ADD_MESSAGE("You reappear.");
4338 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s appears from nowhere!", CHAR_NAME(INDEFINITE
));
4343 void character::PrintBeginInfraVisionMessage () const {
4345 if (StateIsActivated(INVISIBLE
) && IsWarm() && !(StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5))
4346 ADD_MESSAGE("You reappear.");
4348 ADD_MESSAGE("You feel your perception being magically altered.");
4353 void character::PrintEndInfraVisionMessage () const {
4355 if (StateIsActivated(INVISIBLE
) && IsWarm() && !(StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5))
4356 ADD_MESSAGE("You disappear.");
4358 ADD_MESSAGE("You feel your perception returning to normal.");
4363 void character::PrintBeginESPMessage () const {
4364 if (IsPlayer()) ADD_MESSAGE("You suddenly feel like being only a tiny part of a great network of intelligent minds.");
4368 void character::PrintEndESPMessage () const {
4369 if (IsPlayer()) ADD_MESSAGE("You are filled with desire to be just yourself from now on.");
4373 void character::PrintBeginHasteMessage () const {
4374 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4375 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE
));
4379 void character::PrintEndHasteMessage () const {
4380 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4381 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE
));
4385 void character::PrintBeginSlowMessage () const {
4386 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4387 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE
));
4391 void character::PrintEndSlowMessage () const {
4392 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4393 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE
));
4397 void character::EndPolymorph () {
4398 ForceEndPolymorph();
4402 character
*character::ForceEndPolymorph () {
4404 ADD_MESSAGE("You return to your true form.");
4405 } else if (game::IsInWilderness()) {
4406 ActivateTemporaryState(POLYMORPHED
);
4407 SetTemporaryStateCounter(POLYMORPHED
, 10);
4408 return this; // fast gum solution, state ends when the player enters a dungeon
4410 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s returns to %s true form.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
4412 if (GetAction()) GetAction()->Terminate(false);
4416 character
*Char
= GetPolymorphBackup();
4417 Flags
|= C_IN_NO_MSG_MODE
;
4418 Char
->Flags
|= C_IN_NO_MSG_MODE
;
4419 Char
->PutToOrNear(Pos
);
4420 Char
->ChangeTeam(GetTeam());
4421 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(Char
);
4422 SetPolymorphBackup(0);
4424 Char
->Flags
&= ~C_POLYMORPHED
;
4425 GetStack()->MoveItemsTo(Char
->GetStack());
4426 DonateEquipmentTo(Char
);
4427 Char
->SetMoney(GetMoney());
4428 Flags
&= ~C_IN_NO_MSG_MODE
;
4429 Char
->Flags
&= ~C_IN_NO_MSG_MODE
;
4430 Char
->CalculateAll();
4431 Char
->SetAssignedName(GetAssignedName());
4434 game::SetPlayer(Char
);
4435 game::SendLOSUpdateRequest();
4438 Char
->TestWalkability();
4443 void character::LycanthropyHandler () {
4444 if (StateIsActivated(POLYMORPH_LOCK
)) return;
4445 if (GetType() == werewolfwolf::ProtoType
.GetIndex()) return;
4446 if (!(RAND() % 2000)) {
4447 if (StateIsActivated(POLYMORPH_CONTROL
) && (IsPlayer() ? !game::TruthQuestion(CONST_S("Do you wish to change into a werewolf?")) : false)) return;
4448 Polymorph(werewolfwolf::Spawn(), 1000 + RAND() % 2000);
4453 void character::SaveLife () {
4454 if (TemporaryStateIsActivated(LIFE_SAVED
)) {
4456 ADD_MESSAGE("But wait! You glow briefly red and seem to be in a better shape!");
4457 else if (CanBeSeenByPlayer())
4458 ADD_MESSAGE("But wait, suddenly %s glows briefly red and seems to be in a better shape!", GetPersonalPronoun().CStr());
4459 DeActivateTemporaryState(LIFE_SAVED
);
4461 item
*LifeSaver
= 0;
4462 for (int c
= 0; c
< GetEquipments(); ++c
) {
4463 item
*Equipment
= GetEquipment(c
);
4464 if (Equipment
&& Equipment
->IsInCorrectSlot(c
) && Equipment
->GetGearStates() & LIFE_SAVED
) LifeSaver
= Equipment
;
4466 if (!LifeSaver
) ABORT("The Universe can only kill you once!");
4468 ADD_MESSAGE("But wait! Your %s glows briefly red and disappears and you seem to be in a better shape!", LifeSaver
->CHAR_NAME(UNARTICLED
));
4469 else if (CanBeSeenByPlayer())
4470 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());
4471 LifeSaver
->RemoveFromSlot();
4472 LifeSaver
->SendToHell();
4475 if (IsPlayer()) game::AskForEscPress(CONST_S("Life saved!"));
4483 if (GetNP() < SATIATED_LEVEL
) SetNP(SATIATED_LEVEL
);
4485 SendNewDrawRequest();
4487 if (GetAction()) GetAction()->Terminate(false);
4491 character
*character::PolymorphRandomly (int MinDanger
, int MaxDanger
, int Time
) {
4492 character
*NewForm
= 0;
4493 if (StateIsActivated(POLYMORPH_LOCK
)) { ADD_MESSAGE("You feel uncertain about your body for a moment."); return NewForm
; }
4494 if (StateIsActivated(POLYMORPH_CONTROL
)) {
4496 if (!GetNewFormForPolymorphWithControl(NewForm
)) return NewForm
;
4498 NewForm
= protosystem::CreateMonster(MinDanger
*10, MaxDanger
*10, NO_EQUIPMENT
);
4501 NewForm
= protosystem::CreateMonster(MinDanger
, MaxDanger
, NO_EQUIPMENT
);
4503 Polymorph(NewForm
, Time
);
4508 /* In reality, the reading takes Time / (Intelligence * 10) turns */
4509 void character::StartReading (item
*Item
, sLong Time
) {
4510 study
*Read
= study::Spawn(this);
4511 Read
->SetLiteratureID(Item
->GetID());
4512 if (game::WizardModeIsActive()) Time
= 1;
4513 Read
->SetCounter(Time
);
4515 if (IsPlayer()) ADD_MESSAGE("You start reading %s.", Item
->CHAR_NAME(DEFINITE
));
4516 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s starts reading %s.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(DEFINITE
));
4520 /* Call when one makes something with his/her/its hands.
4521 * Difficulty of 5 takes about one turn, so it's the most common to use. */
4522 void character::DexterityAction (int Difficulty
) {
4523 EditAP(-20000 * Difficulty
/ APBonus(GetAttribute(DEXTERITY
)));
4524 EditExperience(DEXTERITY
, Difficulty
* 15, 1 << 7);
4528 /* If Theoretically != false, range is not a factor. */
4529 truth
character::CanBeSeenByPlayer (truth Theoretically
, truth IgnoreESP
) const {
4530 if (IsEnabled() && !game::IsGenerating() && (Theoretically
|| GetSquareUnder())) {
4531 truth MayBeESPSeen
= PLAYER
->IsEnabled() && !IgnoreESP
&& PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5;
4532 truth MayBeInfraSeen
= PLAYER
->IsEnabled() && PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm();
4533 truth Visible
= !StateIsActivated(INVISIBLE
) || MayBeESPSeen
|| MayBeInfraSeen
;
4534 if (game::IsInWilderness()) return Visible
;
4535 if (MayBeESPSeen
&& (Theoretically
|| GetDistanceSquareFrom(PLAYER
) <= PLAYER
->GetESPRangeSquare())) return true;
4536 if (!Visible
) return false;
4537 return (Theoretically
|| SquareUnderCanBeSeenByPlayer(MayBeInfraSeen
));
4543 truth
character::CanBeSeenBy (ccharacter
*Who
, truth Theoretically
, truth IgnoreESP
) const {
4544 if (Who
->IsPlayer()) return CanBeSeenByPlayer(Theoretically
, IgnoreESP
);
4545 if (IsEnabled() && !game::IsGenerating() && (Theoretically
|| GetSquareUnder())) {
4546 truth MayBeESPSeen
= Who
->IsEnabled() && !IgnoreESP
&& Who
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5;
4547 truth MayBeInfraSeen
= Who
->IsEnabled() && Who
->StateIsActivated(INFRA_VISION
) && IsWarm();
4548 truth Visible
= !StateIsActivated(INVISIBLE
) || MayBeESPSeen
|| MayBeInfraSeen
;
4549 if (game::IsInWilderness()) return Visible
;
4550 if (MayBeESPSeen
&& (Theoretically
|| GetDistanceSquareFrom(Who
) <= Who
->GetESPRangeSquare())) return true;
4551 if (!Visible
) return false;
4552 return (Theoretically
|| SquareUnderCanBeSeenBy(Who
, MayBeInfraSeen
));
4558 truth
character::SquareUnderCanBeSeenByPlayer (truth IgnoreDarkness
) const {
4559 if (!GetSquareUnder()) return false;
4560 int S1
= SquaresUnder
, S2
= PLAYER
->SquaresUnder
;
4561 if (S1
== 1 && S2
== 1) {
4562 if (GetSquareUnder()->CanBeSeenByPlayer(IgnoreDarkness
)) return true;
4563 if (IgnoreDarkness
) {
4564 int LOSRangeSquare
= PLAYER
->GetLOSRangeSquare();
4565 if ((GetPos() - PLAYER
->GetPos()).GetLengthSquare() <= LOSRangeSquare
) {
4566 eyecontroller::Map
= GetLevel()->GetMap();
4567 return mapmath
<eyecontroller
>::DoLine(PLAYER
->GetPos().X
, PLAYER
->GetPos().Y
, GetPos().X
, GetPos().Y
, SKIP_FIRST
);
4572 for (int c1
= 0; c1
< S1
; ++c1
) {
4573 lsquare
*Square
= GetLSquareUnder(c1
);
4574 if (Square
->CanBeSeenByPlayer(IgnoreDarkness
)) return true;
4575 else if (IgnoreDarkness
) {
4576 v2 Pos
= Square
->GetPos();
4577 int LOSRangeSquare
= PLAYER
->GetLOSRangeSquare();
4578 for (int c2
= 0; c2
< S2
; ++c2
) {
4579 v2 PlayerPos
= PLAYER
->GetPos(c2
);
4580 if ((Pos
-PlayerPos
).GetLengthSquare() <= LOSRangeSquare
) {
4581 eyecontroller::Map
= GetLevel()->GetMap();
4582 if (mapmath
<eyecontroller
>::DoLine(PlayerPos
.X
, PlayerPos
.Y
, Pos
.X
, Pos
.Y
, SKIP_FIRST
)) return true;
4592 truth
character::SquareUnderCanBeSeenBy (ccharacter
*Who
, truth IgnoreDarkness
) const {
4593 int S1
= SquaresUnder
, S2
= Who
->SquaresUnder
;
4594 int LOSRangeSquare
= Who
->GetLOSRangeSquare();
4595 if (S1
== 1 && S2
== 1) return GetSquareUnder()->CanBeSeenFrom(Who
->GetPos(), LOSRangeSquare
, IgnoreDarkness
);
4596 for (int c1
= 0; c1
< S1
; ++c1
) {
4597 lsquare
*Square
= GetLSquareUnder(c1
);
4598 for (int c2
= 0; c2
< S2
; ++c2
) if (Square
->CanBeSeenFrom(Who
->GetPos(c2
), LOSRangeSquare
, IgnoreDarkness
)) return true;
4604 int character::GetDistanceSquareFrom (ccharacter
*Who
) const {
4605 int S1
= SquaresUnder
, S2
= Who
->SquaresUnder
;
4606 if (S1
== 1 && S2
== 1) return (GetPos() - Who
->GetPos()).GetLengthSquare();
4607 v2
MinDist(0x7FFF, 0x7FFF);
4608 int MinLength
= 0xFFFF;
4609 for (int c1
= 0; c1
< S1
; ++c1
) {
4610 for (int c2
= 0; c2
< S2
; ++c2
) {
4611 v2 Dist
= GetPos(c1
)-Who
->GetPos(c2
);
4612 if (Dist
.X
< 0) Dist
.X
= -Dist
.X
;
4613 if (Dist
.Y
< 0) Dist
.Y
= -Dist
.Y
;
4614 if (Dist
.X
<= MinDist
.X
&& Dist
.Y
<= MinDist
.Y
) {
4616 MinLength
= Dist
.GetLengthSquare();
4617 } else if (Dist
.X
< MinDist
.X
|| Dist
.Y
< MinDist
.Y
) {
4618 int Length
= Dist
.GetLengthSquare();
4619 if (Length
< MinLength
) {
4630 void character::AttachBodyPart (bodypart
*BodyPart
) {
4631 SetBodyPart(BodyPart
->GetBodyPartIndex(), BodyPart
);
4632 if (!AllowSpoil()) BodyPart
->ResetSpoiling();
4633 BodyPart
->ResetPosition();
4634 BodyPart
->UpdatePictures();
4635 CalculateAttributeBonuses();
4636 CalculateBattleInfo();
4637 SendNewDrawRequest();
4638 SignalPossibleTransparencyChange();
4642 /* Returns true if the character has all bodyparts, false if not. */
4643 truth
character::HasAllBodyParts () const {
4644 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) return false;
4649 bodypart
*character::GenerateRandomBodyPart () {
4650 int NeededBodyPart
[MAX_BODYPARTS
];
4652 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) NeededBodyPart
[Index
++] = c
;
4653 return Index
? CreateBodyPart(NeededBodyPart
[RAND() % Index
]) : 0;
4657 /* Searches the character's Stack and if it find some bodyparts there that are the character's
4658 * old bodyparts returns a stackiterator to one of them (choosen in random).
4659 * If no fitting bodyparts are found the function returns 0 */
4660 bodypart
*character::FindRandomOwnBodyPart (truth AllowNonLiving
) const {
4661 itemvector LostAndFound
;
4662 for (int c
= 0; c
< BodyParts
; ++c
) {
4663 if (!GetBodyPart(c
)) {
4664 for (std::list
<feuLong
>::iterator i
= OriginalBodyPartID
[c
].begin(); i
!= OriginalBodyPartID
[c
].end(); ++i
) {
4665 bodypart
*Found
= static_cast<bodypart
*>(SearchForItem(*i
));
4666 if (Found
&& (AllowNonLiving
|| Found
->CanRegenerate())) LostAndFound
.push_back(Found
);
4670 if (LostAndFound
.empty()) return 0;
4671 return static_cast<bodypart
*>(LostAndFound
[RAND() % LostAndFound
.size()]);
4675 void character::PrintBeginPoisonedMessage () const {
4676 if (IsPlayer()) ADD_MESSAGE("You seem to be very ill.");
4677 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very ill.", CHAR_NAME(DEFINITE
));
4681 void character::PrintEndPoisonedMessage () const {
4682 if (IsPlayer()) ADD_MESSAGE("You feel better again.");
4683 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks better.", CHAR_NAME(DEFINITE
));
4687 void character::PoisonedHandler () {
4688 if (!(RAND() % 100)) VomitAtRandomDirection(500 + RAND_N(250));
4690 for (int Used
= 0; Used
< GetTemporaryStateCounter(POISONED
); Used
+= 100) if (!(RAND() % 100)) ++Damage
;
4692 ReceiveDamage(0, Damage
, POISON
, ALL
, 8, false, false, false, false);
4693 CheckDeath(CONST_S("died of acute poisoning"), 0);
4698 truth
character::IsWarm () const {
4699 return combinebodypartpredicates()(this, &bodypart::IsWarm
, 1);
4703 truth
character::IsWarmBlooded() const
4705 return combinebodypartpredicates()(this, &bodypart::IsWarmBlooded
, 1);
4709 void character::BeginInvisibility () {
4711 SendNewDrawRequest();
4712 SignalPossibleTransparencyChange();
4716 void character::BeginInfraVision () {
4717 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4721 void character::BeginESP () {
4722 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4726 void character::EndInvisibility () {
4728 SendNewDrawRequest();
4729 SignalPossibleTransparencyChange();
4733 void character::EndInfraVision () {
4734 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4738 void character::EndESP () {
4739 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4743 void character::Draw (blitdata
&BlitData
) const {
4744 col24 L
= BlitData
.Luminance
;
4745 if (PLAYER
->IsEnabled() &&
4746 ((PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5 &&
4747 (PLAYER
->GetPos() - GetPos()).GetLengthSquare() <= PLAYER
->GetESPRangeSquare()) ||
4748 (PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm())))
4749 BlitData
.Luminance
= ivanconfig::GetContrastLuminance();
4751 DrawBodyParts(BlitData
);
4752 BlitData
.Luminance
= ivanconfig::GetContrastLuminance();
4753 BlitData
.Src
.Y
= 16;
4754 cint SquareIndex
= BlitData
.CustomData
& SQUARE_INDEX_MASK
;
4756 if (GetTeam() == PLAYER
->GetTeam() && !IsPlayer() && SquareIndex
== GetTameSymbolSquareIndex()) {
4757 BlitData
.Src
.X
= 32;
4758 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4761 if (IsFlying() && SquareIndex
== GetFlySymbolSquareIndex()) {
4762 BlitData
.Src
.X
= 128;
4763 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4766 if (IsSwimming() && SquareIndex
== GetSwimmingSymbolSquareIndex()) {
4767 BlitData
.Src
.X
= 240;
4768 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4771 if (GetAction() && GetAction()->IsUnconsciousness() && SquareIndex
== GetUnconsciousSymbolSquareIndex()) {
4772 BlitData
.Src
.X
= 224;
4773 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4776 BlitData
.Src
.X
= BlitData
.Src
.Y
= 0;
4777 BlitData
.Luminance
= L
;
4781 void character::DrawBodyParts (blitdata
&BlitData
) const {
4782 GetTorso()->Draw(BlitData
);
4786 void character::PrintBeginTeleportMessage () const {
4787 if (IsPlayer()) ADD_MESSAGE("You feel jumpy.");
4791 void character::PrintEndTeleportMessage () const {
4792 if (IsPlayer()) ADD_MESSAGE("You suddenly realize you've always preferred walking to jumping.");
4796 void character::PrintBeginDetectMessage () const {
4797 if (IsPlayer()) ADD_MESSAGE("You feel curious about your surroundings.");
4801 void character::PrintEndDetectMessage () const {
4802 if (IsPlayer()) ADD_MESSAGE("You decide to rely on your intuition from now on.");
4806 void character::TeleportHandler () {
4807 if (!(RAND() % 1500) && !game::IsInWilderness()) {
4808 if (IsPlayer()) ADD_MESSAGE("You feel an urgent spatial relocation is now appropriate.");
4809 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears.", CHAR_NAME(DEFINITE
));
4815 void character::DetectHandler () {
4817 //the AI can't be asked position questions! So only the player can hav this state really :/ a bit daft of me
4818 if (!(RAND()%3000) && !game::IsInWilderness()) {
4819 ADD_MESSAGE("Your mind wanders in search of something.");
4820 DoDetecting(); //in fact, who knows what would happen if a dark frog had the detecting state?
4826 void character::PrintBeginPolymorphMessage () const {
4827 if (IsPlayer()) ADD_MESSAGE("An uncomfortable uncertainty of who you really are overwhelms you.");
4831 void character::PrintEndPolymorphMessage () const {
4832 if (IsPlayer()) ADD_MESSAGE("You feel you are you and no one else.");
4836 void character::PolymorphHandler () {
4837 if (!(RAND() % 1500)) PolymorphRandomly(1, 999999, 200 + RAND() % 800);
4840 void character::PrintBeginTeleportControlMessage () const {
4841 if (IsPlayer()) ADD_MESSAGE("You feel very controlled.");
4845 void character::PrintEndTeleportControlMessage () const {
4846 if (IsPlayer()) ADD_MESSAGE("You feel your control slipping.");
4850 void character::DisplayStethoscopeInfo (character
*) const {
4851 felist
Info(CONST_S("Information about ") + GetDescription(DEFINITE
));
4852 AddSpecialStethoscopeInfo(Info
);
4853 Info
.AddEntry(CONST_S("Endurance: ") + GetAttribute(ENDURANCE
), LIGHT_GRAY
);
4854 Info
.AddEntry(CONST_S("Perception: ") + GetAttribute(PERCEPTION
), LIGHT_GRAY
);
4855 Info
.AddEntry(CONST_S("Intelligence: ") + GetAttribute(INTELLIGENCE
), LIGHT_GRAY
);
4856 Info
.AddEntry(CONST_S("Wisdom: ") + GetAttribute(WISDOM
), LIGHT_GRAY
);
4857 //Info.AddEntry(CONST_S("Willpower: ") + GetAttribute(WILL_POWER), LIGHT_GRAY);
4858 Info
.AddEntry(CONST_S("Charisma: ") + GetAttribute(CHARISMA
), LIGHT_GRAY
);
4859 Info
.AddEntry(CONST_S("HP: ") + GetHP() + "/" + GetMaxHP(), IsInBadCondition() ? RED
: LIGHT_GRAY
);
4860 if (GetAction()) Info
.AddEntry(festring(GetAction()->GetDescription()).CapitalizeCopy(), LIGHT_GRAY
);
4861 for (int c
= 0; c
< STATES
; ++c
) {
4862 if (StateIsActivated(1 << c
) && (1 << c
!= HASTE
|| !StateIsActivated(SLOW
)) && (1 << c
!= SLOW
|| !StateIsActivated(HASTE
)))
4863 Info
.AddEntry(StateData
[c
].Description
, LIGHT_GRAY
);
4865 switch (GetTirednessState()) {
4866 case FAINTING
: Info
.AddEntry("Fainting", RED
); break;
4867 case EXHAUSTED
: Info
.AddEntry("Exhausted", LIGHT_GRAY
); break;
4869 game::SetStandardListAttributes(Info
);
4874 truth
character::CanUseStethoscope (truth PrintReason
) const {
4875 if (PrintReason
) ADD_MESSAGE("This type of monster can't use a stethoscope.");
4880 /* Effect used by at least Sophos.
4881 * NOTICE: Doesn't check for death! */
4882 void character::TeleportSomePartsAway (int NumberToTeleport
) {
4883 if (StateIsActivated(TELEPORT_LOCK
)) {
4884 if (IsPlayer()) ADD_MESSAGE("You feel very itchy for a moment.");
4887 for (int c
= 0; c
< NumberToTeleport
; ++c
) {
4888 int RandomBodyPart
= GetRandomNonVitalBodyPart();
4889 if (RandomBodyPart
== NONE_INDEX
) {
4890 for (; c
< NumberToTeleport
; ++c
) {
4891 GetTorso()->SetHP((GetTorso()->GetHP() << 2) / 5);
4892 sLong TorsosVolume
= GetTorso()->GetMainMaterial()->GetVolume() / 10;
4893 if (!TorsosVolume
) break;
4894 sLong Amount
= (RAND() % TorsosVolume
)+1;
4895 item
*Lump
= GetTorso()->GetMainMaterial()->CreateNaturalForm(Amount
);
4896 GetTorso()->GetMainMaterial()->EditVolume(-Amount
);
4897 Lump
->MoveTo(GetNearLSquare(GetLevel()->GetRandomSquare())->GetStack());
4898 if (IsPlayer()) ADD_MESSAGE("Parts of you teleport away.");
4899 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Parts of %s teleport away.", CHAR_NAME(DEFINITE
));
4902 item
*SeveredBodyPart
= SevereBodyPart(RandomBodyPart
);
4903 if (SeveredBodyPart
) {
4904 GetNearLSquare(GetLevel()->GetRandomSquare())->AddItem(SeveredBodyPart
);
4905 SeveredBodyPart
->DropEquipment();
4906 if (IsPlayer()) ADD_MESSAGE("Your %s teleports away.", GetBodyPartName(RandomBodyPart
).CStr());
4907 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s teleports away.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4909 if (IsPlayer()) ADD_MESSAGE("Your %s disappears.", GetBodyPartName(RandomBodyPart
).CStr());
4910 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s disappears.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4917 /* Returns an index of a random bodypart that is not vital. If no non-vital bodypart is found returns NONE_INDEX */
4918 int character::GetRandomNonVitalBodyPart () const {
4919 int OKBodyPart
[MAX_BODYPARTS
];
4920 int OKBodyParts
= 0;
4921 for (int c
= 0; c
< BodyParts
; ++c
) if (GetBodyPart(c
) && !BodyPartIsVital(c
)) OKBodyPart
[OKBodyParts
++] = c
;
4922 return OKBodyParts
? OKBodyPart
[RAND() % OKBodyParts
] : NONE_INDEX
;
4926 void character::CalculateVolumeAndWeight () {
4927 Volume
= Stack
->GetVolume();
4928 Weight
= Stack
->GetWeight();
4930 CarriedWeight
= Weight
;
4931 for (int c
= 0; c
< BodyParts
; ++c
) {
4932 bodypart
*BodyPart
= GetBodyPart(c
);
4934 BodyVolume
+= BodyPart
->GetBodyPartVolume();
4935 Volume
+= BodyPart
->GetVolume();
4936 CarriedWeight
+= BodyPart
->GetCarriedWeight();
4937 Weight
+= BodyPart
->GetWeight();
4943 void character::SignalVolumeAndWeightChange () {
4944 if (!IsInitializing()) {
4945 CalculateVolumeAndWeight();
4946 if (IsEnabled()) CalculateBurdenState();
4947 if (MotherEntity
) MotherEntity
->SignalVolumeAndWeightChange();
4952 void character::SignalEmitationIncrease (col24 EmitationUpdate
) {
4953 if (game::CompareLights(EmitationUpdate
, Emitation
) > 0) {
4954 game::CombineLights(Emitation
, EmitationUpdate
);
4955 if (MotherEntity
) MotherEntity
->SignalEmitationIncrease(EmitationUpdate
);
4956 else if (SquareUnder
[0] && !game::IsInWilderness()) {
4957 for(int c
= 0; c
< GetSquaresUnder(); ++c
) GetLSquareUnder()->SignalEmitationIncrease(EmitationUpdate
);
4963 void character::SignalEmitationDecrease (col24 EmitationUpdate
) {
4964 if (game::CompareLights(EmitationUpdate
, Emitation
) >= 0 && Emitation
) {
4965 col24 Backup
= Emitation
;
4966 CalculateEmitation();
4967 if (Backup
!= Emitation
) {
4968 if (MotherEntity
) MotherEntity
->SignalEmitationDecrease(EmitationUpdate
);
4969 else if (SquareUnder
[0] && !game::IsInWilderness()) {
4970 for (int c
= 0; c
< GetSquaresUnder(); ++c
) GetLSquareUnder(c
)->SignalEmitationDecrease(EmitationUpdate
);
4977 void character::CalculateEmitation () {
4978 Emitation
= GetBaseEmitation();
4979 for (int c
= 0; c
< BodyParts
; ++c
) {
4980 bodypart
*BodyPart
= GetBodyPart(c
);
4981 if (BodyPart
) game::CombineLights(Emitation
, BodyPart
->GetEmitation());
4983 game::CombineLights(Emitation
, Stack
->GetEmitation());
4987 void character::CalculateAll () {
4988 Flags
|= C_INITIALIZING
;
4989 CalculateAttributeBonuses();
4990 CalculateVolumeAndWeight();
4991 CalculateEmitation();
4992 CalculateBodyPartMaxHPs(0);
4993 CalculateMaxStamina();
4994 CalculateBurdenState();
4995 CalculateBattleInfo();
4996 Flags
&= ~C_INITIALIZING
;
5000 void character::CalculateHP () {
5001 HP
= sumbodypartproperties()(this, &bodypart::GetHP
);
5005 void character::CalculateMaxHP () {
5006 MaxHP
= sumbodypartproperties()(this, &bodypart::GetMaxHP
);
5010 void character::CalculateBodyPartMaxHPs (feuLong Flags
) {
5011 doforbodypartswithparam
<feuLong
>()(this, &bodypart::CalculateMaxHP
, Flags
);
5017 truth
character::EditAttribute (int Identifier
, int Value
) {
5018 if (Identifier
== ENDURANCE
&& UseMaterialAttributes()) return false;
5019 if (RawEditAttribute(BaseExperience
[Identifier
], Value
)) {
5020 if (!IsInitializing()) {
5021 if (Identifier
== LEG_STRENGTH
) CalculateBurdenState();
5022 else if (Identifier
== ENDURANCE
) CalculateBodyPartMaxHPs();
5023 else if (IsPlayer() && Identifier
== PERCEPTION
) game::SendLOSUpdateRequest();
5024 else if (IsPlayerKind() && (Identifier
== INTELLIGENCE
|| Identifier
== WISDOM
|| Identifier
== CHARISMA
)) UpdatePictures();
5025 CalculateBattleInfo();
5033 truth
character::ActivateRandomState (int Flags
, int Time
, sLong Seed
) {
5035 if (Seed
) femath::SetSeed(Seed
);
5036 sLong ToBeActivated
= GetRandomState(Flags
|DUR_TEMPORARY
);
5038 if (!ToBeActivated
) return false;
5039 BeginTemporaryState(ToBeActivated
, Time
);
5044 truth
character::GainRandomIntrinsic (int Flags
) {
5045 sLong ToBeActivated
= GetRandomState(Flags
|DUR_PERMANENT
);
5046 if (!ToBeActivated
) return false;
5047 GainIntrinsic(ToBeActivated
);
5052 /* Returns 0 if state not found */
5053 sLong
character::GetRandomState (int Flags
) const {
5054 sLong OKStates
[STATES
];
5055 int NumberOfOKStates
= 0;
5056 for (int c
= 0; c
< STATES
; ++c
) {
5057 if (StateData
[c
].Flags
& Flags
& DUR_FLAGS
&& StateData
[c
].Flags
& Flags
& SRC_FLAGS
) OKStates
[NumberOfOKStates
++] = 1 << c
;
5059 return NumberOfOKStates
? OKStates
[RAND() % NumberOfOKStates
] : 0;
5063 int characterprototype::CreateSpecialConfigurations (characterdatabase
**TempConfig
, int Configs
, int Level
) {
5064 if (Level
== 0 && TempConfig
[0]->CreateDivineConfigurations
) {
5065 Configs
= databasecreator
<character
>::CreateDivineConfigurations(this, TempConfig
, Configs
);
5067 if (Level
== 1 && TempConfig
[0]->CreateUndeadConfigurations
) {
5068 for (int c
= 1; c
< protocontainer
<character
>::GetSize(); ++c
) {
5069 const character::prototype
*Proto
= protocontainer
<character
>::GetProto(c
);
5070 if (!Proto
) continue; // missing character
5071 const character::database
*const *CharacterConfigData
= Proto
->GetConfigData();
5072 if (!CharacterConfigData
) ABORT("No database entry for character <%s>!", Proto
->GetClassID());
5073 const character::database
*const* End
= CharacterConfigData
+Proto
->GetConfigSize();
5074 for (++CharacterConfigData
; CharacterConfigData
!= End
; ++CharacterConfigData
) {
5075 const character::database
*CharacterDataBase
= *CharacterConfigData
;
5076 if (CharacterDataBase
->UndeadVersions
) {
5077 character::database
* ConfigDataBase
= new character::database(**TempConfig
);
5078 festring ucfgname
= "undead ";
5079 ucfgname
<< CharacterDataBase
->CfgStrName
;
5080 ConfigDataBase
->InitDefaults(this, (c
<< 8) | CharacterDataBase
->Config
, ucfgname
);
5081 ConfigDataBase
->PostFix
<< "of ";
5082 if (CharacterDataBase
->Adjective
.GetSize()) {
5083 if (CharacterDataBase
->UsesLongAdjectiveArticle
) ConfigDataBase
->PostFix
<< "an ";
5084 else ConfigDataBase
->PostFix
<< "a ";
5085 ConfigDataBase
->PostFix
<< CharacterDataBase
->Adjective
<< ' ';
5087 if (CharacterDataBase
->UsesLongArticle
) ConfigDataBase
->PostFix
<< "an ";
5088 else ConfigDataBase
->PostFix
<< "a ";
5090 ConfigDataBase
->PostFix
<< CharacterDataBase
->NameSingular
;
5091 if (CharacterDataBase
->PostFix
.GetSize()) ConfigDataBase
->PostFix
<< ' ' << CharacterDataBase
->PostFix
;
5092 int P1
= TempConfig
[0]->UndeadAttributeModifier
;
5093 int P2
= TempConfig
[0]->UndeadVolumeModifier
;
5095 for (c2
= 0; c2
< ATTRIBUTES
; ++c2
) ConfigDataBase
->*ExpPtr
[c2
] = CharacterDataBase
->*ExpPtr
[c2
] * P1
/ 100;
5096 for (c2
= 0; c2
< EQUIPMENT_DATAS
; ++c2
) ConfigDataBase
->*EquipmentDataPtr
[c2
] = contentscript
<item
>();
5097 ConfigDataBase
->DefaultIntelligence
= 5;
5098 ConfigDataBase
->DefaultWisdom
= 5;
5099 ConfigDataBase
->DefaultCharisma
= 5;
5100 ConfigDataBase
->TotalSize
= CharacterDataBase
->TotalSize
;
5101 ConfigDataBase
->Sex
= CharacterDataBase
->Sex
;
5102 ConfigDataBase
->AttributeBonus
= CharacterDataBase
->AttributeBonus
;
5103 ConfigDataBase
->TotalVolume
= CharacterDataBase
->TotalVolume
* P2
/ 100;
5104 if (TempConfig
[0]->UndeadCopyMaterials
) {
5105 ConfigDataBase
->HeadBitmapPos
= CharacterDataBase
->HeadBitmapPos
;
5106 ConfigDataBase
->HairColor
= CharacterDataBase
->HairColor
;
5107 ConfigDataBase
->EyeColor
= CharacterDataBase
->EyeColor
;
5108 ConfigDataBase
->CapColor
= CharacterDataBase
->CapColor
;
5109 ConfigDataBase
->FleshMaterial
= CharacterDataBase
->FleshMaterial
;
5110 ConfigDataBase
->BloodMaterial
= CharacterDataBase
->BloodMaterial
;
5111 ConfigDataBase
->VomitMaterial
= CharacterDataBase
->VomitMaterial
;
5112 ConfigDataBase
->SweatMaterial
= CharacterDataBase
->SweatMaterial
;
5114 ConfigDataBase
->KnownCWeaponSkills
= CharacterDataBase
->KnownCWeaponSkills
;
5115 ConfigDataBase
->CWeaponSkillHits
= CharacterDataBase
->CWeaponSkillHits
;
5116 ConfigDataBase
->PostProcess();
5117 TempConfig
[Configs
++] = ConfigDataBase
;
5122 if (Level
== 0 && TempConfig
[0]->CreateGolemMaterialConfigurations
) {
5123 for (int c
= 1; c
< protocontainer
<material
>::GetSize(); ++c
) {
5124 const material::prototype
* Proto
= protocontainer
<material
>::GetProto(c
);
5125 if (!Proto
) continue; // missing matherial
5126 const material::database
*const* MaterialConfigData
= Proto
->GetConfigData();
5127 const material::database
*const* End
= MaterialConfigData
+ Proto
->GetConfigSize();
5128 for (++MaterialConfigData
; MaterialConfigData
!= End
; ++MaterialConfigData
) {
5129 const material::database
* MaterialDataBase
= *MaterialConfigData
;
5130 if (MaterialDataBase
->CategoryFlags
& IS_GOLEM_MATERIAL
) {
5131 character::database
* ConfigDataBase
= new character::database(**TempConfig
);
5133 gcfgname
<< MaterialDataBase
->CfgStrName
;
5134 gcfgname
<< " golem";
5135 ConfigDataBase
->InitDefaults(this, MaterialDataBase
->Config
, gcfgname
);
5136 ConfigDataBase
->Adjective
= MaterialDataBase
->NameStem
;
5137 ConfigDataBase
->UsesLongAdjectiveArticle
= MaterialDataBase
->NameFlags
& USE_AN
;
5138 ConfigDataBase
->AttachedGod
= MaterialDataBase
->AttachedGod
;
5139 TempConfig
[Configs
++] = ConfigDataBase
;
5148 double character::GetTimeToDie (ccharacter
*Enemy
, int Damage
, double ToHitValue
, truth AttackIsBlockable
, truth UseMaxHP
) const {
5149 double DodgeValue
= GetDodgeValue();
5150 if (!Enemy
->CanBeSeenBy(this, true)) ToHitValue
*= 2;
5151 if (!CanBeSeenBy(Enemy
, true)) DodgeValue
*= 2;
5152 double MinHits
= 1000;
5154 for (int c
= 0; c
< BodyParts
; ++c
) {
5155 if (BodyPartIsVital(c
) && GetBodyPart(c
)) {
5156 double Hits
= GetBodyPart(c
)->GetTimeToDie(Damage
, ToHitValue
, DodgeValue
, AttackIsBlockable
, UseMaxHP
);
5157 if (First
) { MinHits
= Hits
; First
= false; } else MinHits
= 1/(1/MinHits
+1/Hits
);
5164 double character::GetRelativeDanger (ccharacter
*Enemy
, truth UseMaxHP
) const {
5165 double Danger
= Enemy
->GetTimeToKill(this, UseMaxHP
)/GetTimeToKill(Enemy
, UseMaxHP
);
5166 int EnemyAP
= Enemy
->GetMoveAPRequirement(1);
5167 int ThisAP
= GetMoveAPRequirement(1);
5168 if (EnemyAP
> ThisAP
) Danger
*= 1.25; else if (ThisAP
> EnemyAP
) Danger
*= 0.80;
5169 if (!Enemy
->CanBeSeenBy(this, true)) Danger
*= (Enemy
->IsPlayer() ? 0.2 : 0.5);
5170 if (!CanBeSeenBy(Enemy
, true)) Danger
*= (IsPlayer() ? 5.0 : 2.0);
5171 if (GetAttribute(INTELLIGENCE
) < 10 && !IsPlayer()) Danger
*= 0.80;
5172 if (Enemy
->GetAttribute(INTELLIGENCE
) < 10 && !Enemy
->IsPlayer()) Danger
*= 1.25;
5173 return Limit(Danger
, 0.001, 1000.0);
5177 festring
character::GetBodyPartName (int I
, truth Articled
) const {
5178 if (I
== TORSO_INDEX
) return Articled
? CONST_S("a torso") : CONST_S("torso");
5179 ABORT("Illegal character bodypart name request!");
5184 item
*character::SearchForItem (feuLong ID
) const {
5185 item
*Equipment
= findequipment
<feuLong
>()(this, &item::HasID
, ID
);
5186 if (Equipment
) return Equipment
;
5187 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (i
->GetID() == ID
) return *i
;
5192 truth
character::ContentsCanBeSeenBy (ccharacter
*Viewer
) const {
5193 return (Viewer
== this);
5197 truth
character::HitEffect (character
*Enemy
, item
* Weapon
, v2 HitPos
, int Type
, int BodyPartIndex
,
5198 int Direction
, truth BlockedByArmour
, truth Critical
, int DoneDamage
)
5200 if (Weapon
) return Weapon
->HitEffect(this, Enemy
, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5201 if (Type
== UNARMED_ATTACK
) return Enemy
->SpecialUnarmedEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5202 if (Type
== KICK_ATTACK
) return Enemy
->SpecialKickEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5203 if (Type
== BITE_ATTACK
) return Enemy
->SpecialBiteEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
, Critical
, DoneDamage
);
5208 void character::WeaponSkillHit (item
*Weapon
, int Type
, int Hits
) {
5210 if (Type
== UNARMED_ATTACK
) Category
= UNARMED
;
5211 else if (Type
== WEAPON_ATTACK
) { Weapon
->WeaponSkillHit(Hits
); return; }
5212 else if (Type
== KICK_ATTACK
) Category
= KICK
;
5213 else if (Type
== BITE_ATTACK
) Category
= BITE
;
5214 else if (Type
== THROW_ATTACK
) { if (!IsHumanoid()) return; Category
= Weapon
->GetWeaponCategory(); }
5215 else { ABORT("Illegal Type %d passed to character::WeaponSkillHit()!", Type
); return; }
5216 if (GetCWeaponSkill(Category
)->AddHit(Hits
)) {
5217 CalculateBattleInfo();
5218 if (IsPlayer()) GetCWeaponSkill(Category
)->AddLevelUpMessage(Category
);
5223 /* Returns 0 if character cannot be duplicated */
5224 character
*character::Duplicate (feuLong Flags
) {
5225 if (!(Flags
& IGNORE_PROHIBITIONS
) && !CanBeCloned()) return 0;
5226 character
*Char
= GetProtoType()->Clone(this);
5227 if (Flags
& MIRROR_IMAGE
) {
5228 DuplicateEquipment(Char
, Flags
& ~IGNORE_PROHIBITIONS
);
5229 Char
->SetLifeExpectancy(Flags
>> LE_BASE_SHIFT
& LE_BASE_RANGE
, Flags
>> LE_RAND_SHIFT
& LE_RAND_RANGE
);
5231 Char
->CalculateAll();
5232 Char
->CalculateEmitation();
5233 Char
->UpdatePictures();
5234 Char
->Flags
&= ~(C_INITIALIZING
|C_IN_NO_MSG_MODE
);
5239 truth
character::TryToEquip (item
*Item
) {
5240 if (!Item
->AllowEquip() || !CanUseEquipment() || GetAttribute(WISDOM
) >= Item
->GetWearWisdomLimit() || Item
->GetSquaresUnder() != 1) {
5244 for (int e
= 0; e
< GetEquipments(); ++e
) {
5245 if (GetBodyPartOfEquipment(e
) && EquipmentIsAllowed(e
)) {
5246 sorter Sorter
= EquipmentSorter(e
);
5247 if ((Sorter
== 0 || (Item
->*Sorter
)(this)) &&
5248 ((e
!= RIGHT_WIELDED_INDEX
&& e
!= LEFT_WIELDED_INDEX
) ||
5249 Item
->IsWeapon(this) || Item
->IsShield(this)) && AllowEquipment(Item
, e
))
5251 item
*OldEquipment
= GetEquipment(e
);
5252 if (BoundToUse(OldEquipment
, e
)) continue;
5253 lsquare
*LSquareUnder
= GetLSquareUnder();
5254 stack
*StackUnder
= LSquareUnder
->GetStack();
5255 msgsystem::DisableMessages();
5256 Flags
|= C_PICTURE_UPDATES_FORBIDDEN
;
5257 LSquareUnder
->Freeze();
5258 StackUnder
->Freeze();
5259 double Danger
= GetRelativeDanger(PLAYER
);
5260 if (OldEquipment
) OldEquipment
->RemoveFromSlot();
5261 Item
->RemoveFromSlot();
5262 SetEquipment(e
, Item
);
5263 double NewDanger
= GetRelativeDanger(PLAYER
);
5264 Item
->RemoveFromSlot();
5265 StackUnder
->AddItem(Item
);
5266 if (OldEquipment
) SetEquipment(e
, OldEquipment
);
5267 msgsystem::EnableMessages();
5268 Flags
&= ~C_PICTURE_UPDATES_FORBIDDEN
;
5269 LSquareUnder
->UnFreeze();
5270 StackUnder
->UnFreeze();
5272 if (NewDanger
> Danger
|| BoundToUse(Item
, e
)) {
5273 room
*Room
= GetRoom();
5274 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
5275 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
));
5276 if (Room
) Room
->DropItem(this, OldEquipment
, 1);
5277 OldEquipment
->MoveTo(StackUnder
);
5278 Item
->RemoveFromSlot();
5279 SetEquipment(e
, Item
);
5285 if (NewDanger
> Danger
|| (NewDanger
== Danger
&& e
!= RIGHT_WIELDED_INDEX
&& e
!= LEFT_WIELDED_INDEX
) || BoundToUse(Item
, e
)) {
5286 room
*Room
= GetRoom();
5287 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
5288 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up and equips %s.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(INDEFINITE
));
5289 Item
->RemoveFromSlot();
5290 SetEquipment(e
, Item
);
5303 truth
character::TryToConsume (item
*Item
) {
5304 return Item
->CanBeEatenByAI(this) && ConsumeItem(Item
, Item
->GetConsumeMaterial(this)->GetConsumeVerb());
5308 void character::UpdateESPLOS () const {
5309 if (StateIsActivated(ESP
) && !game::IsInWilderness()) {
5310 for (int c
= 0; c
< game::GetTeams(); ++c
) {
5311 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
5312 const character
*ch
= *i
;
5313 if (ch
->IsEnabled()) ch
->SendNewDrawRequest();
5320 int character::GetCWeaponSkillLevel (citem
*Item
) const {
5321 if (Item
->GetWeaponCategory() < GetAllowedWeaponSkillCategories()) return GetCWeaponSkill(Item
->GetWeaponCategory())->GetLevel();
5326 void character::PrintBeginPanicMessage () const {
5327 if (IsPlayer()) ADD_MESSAGE("You panic!");
5328 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s panics.", CHAR_NAME(DEFINITE
));
5332 void character::PrintEndPanicMessage () const {
5333 if (IsPlayer()) ADD_MESSAGE("You finally calm down.");
5334 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s calms down.", CHAR_NAME(DEFINITE
));
5338 void character::CheckPanic (int Ticks
) {
5339 if (GetPanicLevel() > 1 && !StateIsActivated(PANIC
) && GetHP()*100 < RAND()%(GetPanicLevel()*GetMaxHP()<<1) && !StateIsActivated(FEARLESS
)) {
5340 BeginTemporaryState(PANIC
, ((Ticks
* 3) >> 2) + RAND() % ((Ticks
>> 1) + 1)); // 25% randomness to ticks...
5345 /* returns 0 if fails else the newly created character */
5346 character
*character::DuplicateToNearestSquare (character
*Cloner
, feuLong Flags
) {
5347 character
*NewlyCreated
= Duplicate(Flags
);
5348 if (!NewlyCreated
) return 0;
5349 if (Flags
& CHANGE_TEAM
&& Cloner
) NewlyCreated
->ChangeTeam(Cloner
->GetTeam());
5350 NewlyCreated
->PutNear(GetPos());
5351 return NewlyCreated
;
5355 void character::SignalSpoil (material
*m
) {
5356 if (GetMotherEntity()) GetMotherEntity()->SignalSpoil(m
);
5357 else Disappear(0, "spoil", &item::IsVeryCloseToSpoiling
);
5361 truth
character::CanHeal () const {
5362 for (int c
= 0; c
< BodyParts
; ++c
) {
5363 bodypart
*BodyPart
= GetBodyPart(c
);
5364 if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < BodyPart
->GetMaxHP()) return true;
5370 int character::GetRelation (ccharacter
*Who
) const {
5371 return GetTeam()->GetRelation(Who
->GetTeam());
5375 truth (item::*AffectTest
[BASE_ATTRIBUTES
])() const = {
5376 &item::AffectsEndurance
,
5377 &item::AffectsPerception
,
5378 &item::AffectsIntelligence
,
5379 &item::AffectsWisdom
,
5380 &item::AffectsWillPower
,
5381 &item::AffectsCharisma
,
5386 /* Returns nonzero if endurance has decreased and death may occur */
5387 truth
character::CalculateAttributeBonuses () {
5388 doforbodyparts()(this, &bodypart::CalculateAttributeBonuses
);
5389 int BackupBonus
[BASE_ATTRIBUTES
];
5390 int BackupCarryingBonus
= CarryingBonus
;
5393 for (c1
= 0; c1
< BASE_ATTRIBUTES
; ++c1
) {
5394 BackupBonus
[c1
] = AttributeBonus
[c1
];
5395 AttributeBonus
[c1
] = 0;
5397 for (c1
= 0; c1
< GetEquipments(); ++c1
) {
5398 item
*Equipment
= GetEquipment(c1
);
5399 if (!Equipment
|| !Equipment
->IsInCorrectSlot(c1
)) continue;
5400 for (int c2
= 0; c2
< BASE_ATTRIBUTES
; ++c2
) {
5401 if ((Equipment
->*AffectTest
[c2
])()) AttributeBonus
[c2
] += Equipment
->GetEnchantment();
5403 if (Equipment
->AffectsCarryingCapacity()) CarryingBonus
+= Equipment
->GetCarryingBonus();
5406 ApplySpecialAttributeBonuses();
5408 if (IsPlayer() && !IsInitializing() && AttributeBonus
[PERCEPTION
] != BackupBonus
[PERCEPTION
]) game::SendLOSUpdateRequest();
5409 if (IsPlayer() && !IsInitializing() && AttributeBonus
[INTELLIGENCE
] != BackupBonus
[INTELLIGENCE
]) UpdateESPLOS();
5411 if (!IsInitializing() && CarryingBonus
!= BackupCarryingBonus
) CalculateBurdenState();
5413 if (!IsInitializing() && AttributeBonus
[ENDURANCE
] != BackupBonus
[ENDURANCE
]) {
5414 CalculateBodyPartMaxHPs();
5415 CalculateMaxStamina();
5416 return AttributeBonus
[ENDURANCE
] < BackupBonus
[ENDURANCE
];
5423 void character::ApplyEquipmentAttributeBonuses (item
*Equipment
) {
5424 if (Equipment
->AffectsEndurance()) {
5425 AttributeBonus
[ENDURANCE
] += Equipment
->GetEnchantment();
5426 CalculateBodyPartMaxHPs();
5427 CalculateMaxStamina();
5429 if (Equipment
->AffectsPerception()) {
5430 AttributeBonus
[PERCEPTION
] += Equipment
->GetEnchantment();
5431 if (IsPlayer()) game::SendLOSUpdateRequest();
5433 if (Equipment
->AffectsIntelligence()) {
5434 AttributeBonus
[INTELLIGENCE
] += Equipment
->GetEnchantment();
5435 if (IsPlayer()) UpdateESPLOS();
5437 if (Equipment
->AffectsWisdom()) AttributeBonus
[WISDOM
] += Equipment
->GetEnchantment();
5438 if (Equipment
->AffectsWillPower()) AttributeBonus
[WILL_POWER
] += Equipment
->GetEnchantment();
5439 if (Equipment
->AffectsCharisma()) AttributeBonus
[CHARISMA
] += Equipment
->GetEnchantment();
5440 if (Equipment
->AffectsMana()) AttributeBonus
[MANA
] += Equipment
->GetEnchantment();
5441 if (Equipment
->AffectsCarryingCapacity()) {
5442 CarryingBonus
+= Equipment
->GetCarryingBonus();
5443 CalculateBurdenState();
5448 void character::ReceiveAntidote (sLong Amount
) {
5449 if (StateIsActivated(POISONED
)) {
5450 if (GetTemporaryStateCounter(POISONED
) > Amount
) {
5451 EditTemporaryStateCounter(POISONED
, -Amount
);
5454 if (IsPlayer()) ADD_MESSAGE("Aaaah... You feel much better.");
5455 Amount
-= GetTemporaryStateCounter(POISONED
);
5456 DeActivateTemporaryState(POISONED
);
5459 if ((Amount
>= 100 || RAND_N(100) < Amount
) && StateIsActivated(PARASITIZED
)) {
5460 if (IsPlayer()) ADD_MESSAGE("Something in your belly didn't seem to like this stuff.");
5461 DeActivateTemporaryState(PARASITIZED
);
5462 Amount
-= Min(100, Amount
);
5464 if ((Amount
>= 100 || RAND_N(100) < Amount
) && StateIsActivated(LEPROSY
)) {
5465 if (IsPlayer()) ADD_MESSAGE("You are not falling to pieces anymore.");
5466 DeActivateTemporaryState(LEPROSY
);
5467 Amount
-= Min(100, Amount
);
5472 void character::AddAntidoteConsumeEndMessage () const {
5473 if (StateIsActivated(POISONED
)) {
5474 // true only if the antidote didn't cure the poison completely
5475 if (IsPlayer()) ADD_MESSAGE("Your body processes the poison in your veins with rapid speed.");
5480 truth
character::IsDead () const {
5481 for (int c
= 0; c
< BodyParts
; ++c
) {
5482 bodypart
*BodyPart
= GetBodyPart(c
);
5483 if (BodyPartIsVital(c
) && (!BodyPart
|| BodyPart
->GetHP() < 1)) return true;
5489 void character::SignalSpoilLevelChange (material
*m
) {
5490 if (GetMotherEntity()) GetMotherEntity()->SignalSpoilLevelChange(m
); else UpdatePictures();
5494 void character::AddOriginalBodyPartID (int I
, feuLong What
) {
5495 if (std::find(OriginalBodyPartID
[I
].begin(), OriginalBodyPartID
[I
].end(), What
) == OriginalBodyPartID
[I
].end()) {
5496 OriginalBodyPartID
[I
].push_back(What
);
5497 if (OriginalBodyPartID
[I
].size() > 100) OriginalBodyPartID
[I
].erase(OriginalBodyPartID
[I
].begin());
5502 void character::AddToInventory (const fearray
<contentscript
<item
> > &ItemArray
, int SpecialFlags
) {
5503 for (uInt c1
= 0; c1
< ItemArray
.Size
; ++c1
) {
5504 if (ItemArray
[c1
].IsValid()) {
5505 const interval
*TimesPtr
= ItemArray
[c1
].GetTimes();
5506 int Times
= TimesPtr
? TimesPtr
->Randomize() : 1;
5507 for (int c2
= 0; c2
< Times
; ++c2
) {
5508 item
*Item
= ItemArray
[c1
].Instantiate(SpecialFlags
);
5510 Stack
->AddItem(Item
);
5511 Item
->SpecialGenerationHandler();
5519 truth
character::HasHadBodyPart (citem
*Item
) const {
5520 for (int c
= 0; c
< BodyParts
; ++c
)
5521 if (std::find(OriginalBodyPartID
[c
].begin(), OriginalBodyPartID
[c
].end(), Item
->GetID()) != OriginalBodyPartID
[c
].end())
5523 return GetPolymorphBackup() && GetPolymorphBackup()->HasHadBodyPart(Item
);
5527 festring
&character::ProcessMessage (festring
&Msg
) const {
5528 SEARCH_N_REPLACE(Msg
, "@nu", GetName(UNARTICLED
));
5529 SEARCH_N_REPLACE(Msg
, "@ni", GetName(INDEFINITE
));
5530 SEARCH_N_REPLACE(Msg
, "@nd", GetName(DEFINITE
));
5531 SEARCH_N_REPLACE(Msg
, "@du", GetDescription(UNARTICLED
));
5532 SEARCH_N_REPLACE(Msg
, "@di", GetDescription(INDEFINITE
));
5533 SEARCH_N_REPLACE(Msg
, "@dd", GetDescription(DEFINITE
));
5534 SEARCH_N_REPLACE(Msg
, "@pp", GetPersonalPronoun());
5535 SEARCH_N_REPLACE(Msg
, "@sp", GetPossessivePronoun());
5536 SEARCH_N_REPLACE(Msg
, "@op", GetObjectPronoun());
5537 SEARCH_N_REPLACE(Msg
, "@Nu", GetName(UNARTICLED
).CapitalizeCopy());
5538 SEARCH_N_REPLACE(Msg
, "@Ni", GetName(INDEFINITE
).CapitalizeCopy());
5539 SEARCH_N_REPLACE(Msg
, "@Nd", GetName(DEFINITE
).CapitalizeCopy());
5540 SEARCH_N_REPLACE(Msg
, "@Du", GetDescription(UNARTICLED
).CapitalizeCopy());
5541 SEARCH_N_REPLACE(Msg
, "@Di", GetDescription(INDEFINITE
).CapitalizeCopy());
5542 SEARCH_N_REPLACE(Msg
, "@Dd", GetDescription(DEFINITE
).CapitalizeCopy());
5543 SEARCH_N_REPLACE(Msg
, "@Pp", GetPersonalPronoun().CapitalizeCopy());
5544 SEARCH_N_REPLACE(Msg
, "@Sp", GetPossessivePronoun().CapitalizeCopy());
5545 SEARCH_N_REPLACE(Msg
, "@Op", GetObjectPronoun().CapitalizeCopy());
5546 SEARCH_N_REPLACE(Msg
, "@Gd", GetMasterGod()->GetName());
5551 void character::ProcessAndAddMessage (festring Msg
) const {
5552 ADD_MESSAGE("%s", ProcessMessage(Msg
).CStr());
5556 void character::BeTalkedTo () {
5558 if (GetRelation(PLAYER
) == HOSTILE
)
5559 ProcessAndAddMessage(GetHostileReplies()[RandomizeReply(Said
, GetHostileReplies().Size
)]);
5561 ProcessAndAddMessage(GetFriendlyReplies()[RandomizeReply(Said
, GetFriendlyReplies().Size
)]);
5565 truth
character::CheckZap () {
5567 ADD_MESSAGE("This monster type can't zap.");
5574 void character::DamageAllItems (character
*Damager
, int Damage
, int Type
) {
5575 GetStack()->ReceiveDamage(Damager
, Damage
, Type
);
5576 for (int c
= 0; c
< GetEquipments(); ++c
) {
5577 item
*Equipment
= GetEquipment(c
);
5578 if (Equipment
) Equipment
->ReceiveDamage(Damager
, Damage
, Type
);
5583 truth
character::Equips (citem
*Item
) const {
5584 return combineequipmentpredicateswithparam
<feuLong
>()(this, &item::HasID
, Item
->GetID(), 1);
5588 void character::PrintBeginConfuseMessage () const {
5589 if (IsPlayer()) ADD_MESSAGE("You feel quite happy.");
5593 void character::PrintEndConfuseMessage () const {
5594 if (IsPlayer()) ADD_MESSAGE("The world is boring again.");
5598 v2
character::ApplyStateModification (v2 TryDirection
) const {
5599 if (!StateIsActivated(CONFUSED
) || RAND() & 15 || game::IsInWilderness()) return TryDirection
;
5600 v2 To
= GetLevel()->GetFreeAdjacentSquare(this, GetPos(), true);
5601 if (To
== ERROR_V2
) return TryDirection
;
5603 if (To
!= TryDirection
&& IsPlayer()) ADD_MESSAGE("Whoa! You somehow don't manage to walk straight.");
5608 void character::AddConfuseHitMessage () const {
5609 if (IsPlayer()) ADD_MESSAGE("This stuff is confusing.");
5613 item
*character::SelectFromPossessions (cfestring
&Topic
, sorter Sorter
) {
5614 itemvector ReturnVector
;
5615 SelectFromPossessions(ReturnVector
, Topic
, NO_MULTI_SELECT
, Sorter
);
5616 return !ReturnVector
.empty() ? ReturnVector
[0] : 0;
5620 truth
character::SelectFromPossessions (itemvector
&ReturnVector
, cfestring
&Topic
, int Flags
, sorter Sorter
) {
5622 truth InventoryPossible
= GetStack()->SortedItems(this, Sorter
);
5623 if (InventoryPossible
) List
.AddEntry(CONST_S("choose from inventory"), LIGHT_GRAY
, 20, game::AddToItemDrawVector(itemvector()));
5628 for (c
= 0; c
< BodyParts
; ++c
) {
5629 bodypart
*BodyPart
= GetBodyPart(c
);
5630 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) {
5631 Item
.push_back(BodyPart
);
5633 BodyPart
->AddName(Entry
, UNARTICLED
);
5634 int ImageKey
= game::AddToItemDrawVector(itemvector(1, BodyPart
));
5635 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
5639 for (c
= 0; c
< GetEquipments(); ++c
) {
5640 item
*Equipment
= GetEquipment(c
);
5641 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) {
5642 Item
.push_back(Equipment
);
5643 Entry
= GetEquipmentName(c
);
5646 Equipment
->AddInventoryEntry(this, Entry
, 1, true);
5647 AddSpecialEquipmentInfo(Entry
, c
);
5648 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
5649 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
5654 game::SetStandardListAttributes(List
);
5655 List
.SetFlags(SELECTABLE
|DRAW_BACKGROUND_AFTERWARDS
);
5656 List
.SetEntryDrawer(game::ItemEntryDrawer
);
5657 game::DrawEverythingNoBlit();
5658 int Chosen
= List
.Draw();
5659 game::ClearItemDrawVector();
5660 if (Chosen
!= ESCAPED
) {
5661 if ((InventoryPossible
&& !Chosen
) || Chosen
& FELIST_ERROR_BIT
) {
5662 GetStack()->DrawContents(ReturnVector
, this, Topic
, Flags
, Sorter
);
5664 ReturnVector
.push_back(Item
[InventoryPossible
? Chosen
- 1 : Chosen
]);
5665 if (Flags
& SELECT_PAIR
&& ReturnVector
[0]->HandleInPairs()) {
5666 item
*PairEquipment
= GetPairEquipment(ReturnVector
[0]->GetEquipmentIndex());
5667 if (PairEquipment
&& PairEquipment
->CanBePiledWith(ReturnVector
[0], this)) ReturnVector
.push_back(PairEquipment
);
5672 if (!GetStack()->SortedItems(this, Sorter
)) return false;
5673 game::ClearItemDrawVector();
5674 GetStack()->DrawContents(ReturnVector
, this, Topic
, Flags
, Sorter
);
5680 truth
character::EquipsSomething (sorter Sorter
) {
5681 for (int c
= 0; c
< GetEquipments(); ++c
) {
5682 item
*Equipment
= GetEquipment(c
);
5683 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) return true;
5689 material
*character::CreateBodyPartMaterial (int, sLong Volume
) const {
5690 return MAKE_MATERIAL(GetFleshMaterial(), Volume
);
5694 truth
character::CheckTalk () {
5696 ADD_MESSAGE("This monster does not know the art of talking.");
5703 truth
character::MoveTowardsHomePos () {
5704 if (HomeDataIsValid() && IsEnabled()) {
5705 SetGoingTo(HomeData
->Pos
);
5706 return MoveTowardsTarget(false) || (!GetPos().IsAdjacent(HomeData
->Pos
) && MoveRandomly());
5712 int character::TryToChangeEquipment (stack
*MainStack
, stack
*SecStack
, int Chosen
) {
5713 if (!GetBodyPartOfEquipment(Chosen
)) {
5714 ADD_MESSAGE("Bodypart missing!");
5718 item
*OldEquipment
= GetEquipment(Chosen
);
5719 if (!IsPlayer() && BoundToUse(OldEquipment
, Chosen
)) {
5720 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE
), OldEquipment
->CHAR_NAME(DEFINITE
));
5723 if (OldEquipment
) OldEquipment
->MoveTo(MainStack
);
5725 sorter Sorter
= EquipmentSorter(Chosen
);
5726 if (!MainStack
->SortedItems(this, Sorter
) && (!SecStack
|| !SecStack
->SortedItems(this, Sorter
))) {
5727 ADD_MESSAGE("You haven't got any item that could be used for this purpose.");
5731 game::DrawEverythingNoBlit();
5732 itemvector ItemVector
;
5733 int Return
= MainStack
->DrawContents(ItemVector
, SecStack
, this,
5734 CONST_S("Choose ")+GetEquipmentName(Chosen
)+':',
5735 (SecStack
? CONST_S("Items in your inventory") : CONST_S("")),
5736 (SecStack
? festring(CONST_S("Items in ")+GetPossessivePronoun()+" inventory") : CONST_S("")),
5737 (SecStack
? festring(GetDescription(DEFINITE
)+" is "+GetVerbalBurdenState()) : CONST_S("")),
5738 GetVerbalBurdenStateColor(),
5739 NONE_AS_CHOICE
|NO_MULTI_SELECT
|SELECT_PAIR
|SKIP_FIRST_IF_NO_OLD
|SELECT_MOST_RECENT
,
5740 Sorter
, OldEquipment
);
5741 if (Return
== ESCAPED
) {
5743 OldEquipment
->RemoveFromSlot();
5744 SetEquipment(Chosen
, OldEquipment
);
5749 item
*Item
= (ItemVector
.empty() ? 0 : ItemVector
[0]);
5750 int otherChosen
= -1;
5753 if (!IsPlayer() && !AllowEquipment(Item
, Chosen
)) {
5754 ADD_MESSAGE("%s refuses to equip %s.", CHAR_DESCRIPTION(DEFINITE
), Item
->CHAR_NAME(DEFINITE
));
5757 if (ItemVector
[0]->HandleInPairs() && ItemVector
.size() > 1) {
5759 case RIGHT_GAUNTLET_INDEX
: otherChosen
= LEFT_GAUNTLET_INDEX
; break;
5760 case LEFT_GAUNTLET_INDEX
: otherChosen
= RIGHT_GAUNTLET_INDEX
; break;
5761 case RIGHT_BOOT_INDEX
: otherChosen
= LEFT_BOOT_INDEX
; break;
5762 case LEFT_BOOT_INDEX
: otherChosen
= RIGHT_BOOT_INDEX
; break;
5765 if (otherChosen
!= -1) {
5766 if (GetBodyPartOfEquipment(otherChosen
)) {
5767 if (!game::TruthQuestion("Do you want to wear both items?", NO
)) otherChosen
= -1;
5773 // wear/wield first item
5774 Item
->RemoveFromSlot();
5775 SetEquipment(Chosen
, Item
);
5776 if (CheckIfEquipmentIsNotUsable(Chosen
)) { Item
->MoveTo(MainStack
); Item
= 0; otherChosen
= -1; } // small bug?
5777 // wear/wield possible second item
5778 if (Item
&& otherChosen
!= -1 && ItemVector
[0]->HandleInPairs() && ItemVector
.size() > 1 && GetBodyPartOfEquipment(otherChosen
)) {
5779 item
*otherOld
= GetEquipment(otherChosen
);
5780 if (otherOld
&& !IsPlayer() && BoundToUse(otherOld
, otherChosen
)) {
5781 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE
), otherOld
->CHAR_NAME(DEFINITE
));
5783 } else if (otherOld
) {
5784 otherOld
->MoveTo(MainStack
);
5786 if (otherChosen
!= -1) {
5787 ItemVector
[1]->RemoveFromSlot();
5788 SetEquipment(otherChosen
, ItemVector
[1]);
5789 if (CheckIfEquipmentIsNotUsable(otherChosen
)) { ItemVector
[1]->MoveTo(MainStack
); otherChosen
= -1; } // small bug?
5794 return (Item
!= OldEquipment
? (otherChosen
!= -1 ? 2 : 1) : 0);
5798 void character::PrintBeginParasitizedMessage () const {
5799 if (IsPlayer()) ADD_MESSAGE("You feel you are no longer alone.");
5803 void character::PrintEndParasitizedMessage () const {
5804 if (IsPlayer()) ADD_MESSAGE("A feeling of long welcome emptiness overwhelms you.");
5808 void character::ParasitizedHandler () {
5810 if (!(RAND() % 250)) {
5811 if (IsPlayer()) ADD_MESSAGE("Ugh. You feel something violently carving its way through your intestines.");
5812 ReceiveDamage(0, 1, POISON
, TORSO
, 8, false, false, false, false);
5813 CheckDeath(CONST_S("killed by a vile parasite"), 0);
5818 truth
character::CanFollow () const {
5819 return CanMove() && !StateIsActivated(PANIC
) && !IsStuck();
5823 festring
character::GetKillName () const {
5824 if (!GetPolymorphBackup()) return GetName(INDEFINITE
);
5826 GetPolymorphBackup()->AddName(KillName
, INDEFINITE
);
5827 KillName
<< " polymorphed into ";
5828 id::AddName(KillName
, INDEFINITE
);
5833 festring
character::GetPanelName () const {
5835 Name
<< AssignedName
<< " the " << game::GetVerbalPlayerAlignment() << ' ';
5836 id::AddName(Name
, UNARTICLED
);
5841 sLong
character::GetMoveAPRequirement (int Difficulty
) const {
5842 return (!StateIsActivated(PANIC
) ? 10000000 : 8000000) * Difficulty
/ (APBonus(GetAttribute(AGILITY
)) * GetMoveEase());
5846 bodypart
*character::HealHitPoint() {
5847 int NeedHeal
= 0, NeedHealIndex
[MAX_BODYPARTS
];
5848 for (int c
= 0; c
< BodyParts
; ++c
) {
5849 bodypart
*BodyPart
= GetBodyPart(c
);
5850 if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < BodyPart
->GetMaxHP()) NeedHealIndex
[NeedHeal
++] = c
;
5853 bodypart
*BodyPart
= GetBodyPart(NeedHealIndex
[RAND() % NeedHeal
]);
5854 BodyPart
->IncreaseHP();
5862 void character::CreateHomeData () {
5863 HomeData
= new homedata
;
5864 lsquare
*Square
= GetLSquareUnder();
5865 HomeData
->Pos
= Square
->GetPos();
5866 HomeData
->Dungeon
= Square
->GetDungeonIndex();
5867 HomeData
->Level
= Square
->GetLevelIndex();
5868 HomeData
->Room
= Square
->GetRoomIndex();
5872 room
*character::GetHomeRoom() const {
5873 if (HomeDataIsValid() && HomeData
->Room
) return GetLevel()->GetRoom(HomeData
->Room
);
5878 void character::RemoveHomeData () {
5884 void character::AddESPConsumeMessage () const {
5885 if (IsPlayer()) ADD_MESSAGE("You feel a strange mental activity.");
5889 void character::SetBodyPart (int I
, bodypart
*What
) {
5890 BodyPartSlot
[I
].PutInItem(What
);
5892 What
->SignalPossibleUsabilityChange();
5894 AddOriginalBodyPartID(I
, What
->GetID());
5895 if (What
->GetMainMaterial()->IsInfectedByLeprosy()) GainIntrinsic(LEPROSY
);
5896 else if (StateIsActivated(LEPROSY
)) What
->GetMainMaterial()->SetIsInfectedByLeprosy(true);
5901 truth
character::ConsumeItem (item
*Item
, cfestring
&ConsumeVerb
) {
5902 if (IsPlayer() && HasHadBodyPart(Item
) && !game::TruthQuestion(CONST_S("Are you sure? You may be able to put it back..."))) {
5905 if (Item
->IsOnGround() && GetRoom() && !GetRoom()->ConsumeItem(this, Item
, 1)) {
5908 if (IsPlayer()) ADD_MESSAGE("You begin %s %s.", ConsumeVerb
.CStr(), Item
->CHAR_NAME(DEFINITE
));
5909 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins %s %s.", CHAR_NAME(DEFINITE
), ConsumeVerb
.CStr(), Item
->CHAR_NAME(DEFINITE
));
5910 consume
*Consume
= consume::Spawn(this);
5911 Consume
->SetDescription(ConsumeVerb
);
5912 Consume
->SetConsumingID(Item
->GetID());
5919 truth
character::CheckThrow () const {
5921 ADD_MESSAGE("This monster type cannot throw.");
5928 void character::GetHitByExplosion (const explosion
*Explosion
, int Damage
) {
5929 int DamageDirection
= GetPos() == Explosion
->Pos
? RANDOM_DIR
: game::CalculateRoughDirection(GetPos() - Explosion
->Pos
);
5930 if (!IsPet() && Explosion
->Terrorist
&& Explosion
->Terrorist
->IsPet()) Explosion
->Terrorist
->Hostility(this);
5931 GetTorso()->SpillBlood((8 - Explosion
->Size
+ RAND() % (8 - Explosion
->Size
)) >> 1);
5932 if (DamageDirection
== RANDOM_DIR
) DamageDirection
= RAND()&7;
5933 v2 SpillPos
= GetPos() + game::GetMoveVector(DamageDirection
);
5934 if (SquareUnder
[0] && GetArea()->IsValidPos(SpillPos
)) GetTorso()->SpillBlood((8-Explosion
->Size
+RAND()%(8-Explosion
->Size
))>>1, SpillPos
);
5935 if (IsPlayer()) ADD_MESSAGE("You are hit by the explosion!");
5936 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is hit by the explosion.", CHAR_NAME(DEFINITE
));
5937 truth WasUnconscious
= GetAction() && GetAction()->IsUnconsciousness();
5938 ReceiveDamage(Explosion
->Terrorist
, Damage
>> 1, FIRE
, ALL
, DamageDirection
, true, false, false, false);
5940 ReceiveDamage(Explosion
->Terrorist
, Damage
>> 1, PHYSICAL_DAMAGE
, ALL
, DamageDirection
, true, false, false, false);
5941 CheckDeath(Explosion
->DeathMsg
, Explosion
->Terrorist
, !WasUnconscious
? IGNORE_UNCONSCIOUSNESS
: 0);
5946 void character::SortAllItems (const sortdata
&SortData
) {
5947 GetStack()->SortAllItems(SortData
);
5948 doforequipmentswithparam
<const sortdata
&>()(this, &item::SortAllItems
, SortData
);
5952 void character::PrintBeginSearchingMessage () const {
5953 if (IsPlayer()) ADD_MESSAGE("You feel you can now notice even the very smallest details around you.");
5957 void character::PrintEndSearchingMessage () const {
5958 if (IsPlayer()) ADD_MESSAGE("You feel less perceptive.");
5962 void character::SearchingHandler () {
5963 if (!game::IsInWilderness()) Search(15);
5967 void character::Search (int Perception
) {
5968 for (int d
= 0; d
< GetExtendedNeighbourSquares(); ++d
) {
5969 lsquare
*LSquare
= GetNeighbourLSquare(d
);
5970 if (LSquare
) LSquare
->GetStack()->Search(this, Min(Perception
, 200));
5975 // surprisingly returns 0 if fails
5976 character
*character::GetRandomNeighbour (int RelationFlags
) const {
5977 character
*Chars
[MAX_NEIGHBOUR_SQUARES
];
5979 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
5980 lsquare
*LSquare
= GetNeighbourLSquare(d
);
5982 character
*Char
= LSquare
->GetCharacter();
5983 if (Char
&& (GetRelation(Char
) & RelationFlags
)) Chars
[Index
++] = Char
;
5986 return Index
? Chars
[RAND() % Index
] : 0;
5990 void character::ResetStates () {
5991 for (int c
= 0; c
< STATES
; ++c
) {
5992 if (1 << c
!= POLYMORPHED
&& TemporaryStateIsActivated(1 << c
) && TemporaryStateCounter
[c
] != PERMANENT
) {
5993 TemporaryState
&= ~(1 << c
);
5994 if (StateData
[c
].EndHandler
) {
5995 (this->*StateData
[c
].EndHandler
)();
5996 if (!IsEnabled())return;
6003 void characterdatabase::InitDefaults (const characterprototype
*NewProtoType
, int NewConfig
, cfestring
&acfgstrname
) {
6005 ProtoType
= NewProtoType
;
6007 CfgStrName
= acfgstrname
;
6012 void character::PrintBeginGasImmunityMessage () const {
6013 if (IsPlayer()) ADD_MESSAGE("All smells fade away.");
6017 void character::PrintEndGasImmunityMessage () const {
6018 if (IsPlayer()) ADD_MESSAGE("Yuck! The world smells bad again.");
6022 void character::ShowAdventureInfo () const {
6023 static const char *lists
[4][4] = {
6024 { "Show massacre history",
6026 "Show message history",
6029 "Show message history",
6032 { "Show message history",
6036 { "Show massacre history",
6037 "Show message history",
6041 // massacre, inventory, messages
6042 static const int nums
[4][3] = {
6049 if (GetStack()->GetItems()) {
6050 idx
= game::MassacreListsEmpty() ? 1 : 0;
6052 idx
= game::MassacreListsEmpty() ? 2 : 3;
6056 sel
= game::ListSelectorArray(sel
, CONST_S("Do you want to see some funny history?"), lists
[idx
]);
6058 if (sel
== nums
[idx
][0] && !game::MassacreListsEmpty()) {
6059 game::DisplayMassacreLists();
6061 if (sel
== nums
[idx
][1] && GetStack()->GetItems()) {
6062 GetStack()->DrawContents(this, CONST_S("Your inventory"), NO_SELECT
);
6063 for(stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) i
->DrawContents(this);
6064 doforequipmentswithparam
<ccharacter
*>()(this, &item::DrawContents
, this);
6066 if (sel
== nums
[idx
][2]) {
6067 msgsystem::DrawMessageHistory();
6073 truth
character::EditAllAttributes (int Amount
) {
6074 if (!Amount
) return true;
6076 truth MayEditMore
= false;
6077 for (c
= 0; c
< BodyParts
; ++c
) {
6078 bodypart
*BodyPart
= GetBodyPart(c
);
6079 if (BodyPart
&& BodyPart
->EditAllAttributes(Amount
)) MayEditMore
= true;
6081 for (c
= 0; c
< BASE_ATTRIBUTES
; ++c
) {
6082 if (BaseExperience
[c
]) {
6083 BaseExperience
[c
] += Amount
* EXP_MULTIPLIER
;
6084 LimitRef(BaseExperience
[c
], MIN_EXP
, MAX_EXP
);
6085 if ((Amount
< 0 && BaseExperience
[c
] != MIN_EXP
) || (Amount
> 0 && BaseExperience
[c
] != MAX_EXP
)) MayEditMore
= true;
6092 game::SendLOSUpdateRequest();
6095 if (IsPlayerKind()) UpdatePictures();
6101 void character::AddAttributeInfo (festring
&Entry
) const {
6103 Entry
<< GetAttribute(ENDURANCE
);
6105 Entry
<< GetAttribute(PERCEPTION
);
6107 Entry
<< GetAttribute(INTELLIGENCE
);
6109 Entry
<< GetAttribute(WISDOM
);
6111 Entry
<< GetAttribute(CHARISMA
);
6113 Entry
<< GetAttribute(MANA
);
6117 void character::AddDefenceInfo (felist
&List
) const {
6119 for (int c
= 0; c
< BodyParts
; ++c
) {
6120 bodypart
*BodyPart
= GetBodyPart(c
);
6122 Entry
= CONST_S(" ");
6123 BodyPart
->AddName(Entry
, UNARTICLED
);
6125 Entry
<< BodyPart
->GetMaxHP();
6127 Entry
<< BodyPart
->GetTotalResistance(PHYSICAL_DAMAGE
);
6128 List
.AddEntry(Entry
, LIGHT_GRAY
);
6134 void character::DetachBodyPart () {
6135 ADD_MESSAGE("You haven't got any extra bodyparts.");
6140 void character::ReceiveHolyBanana (sLong Amount
) {
6142 EditExperience(ARM_STRENGTH
, Amount
, 1 << 13);
6143 EditExperience(LEG_STRENGTH
, Amount
, 1 << 13);
6144 EditExperience(DEXTERITY
, Amount
, 1 << 13);
6145 EditExperience(AGILITY
, Amount
, 1 << 13);
6146 EditExperience(ENDURANCE
, Amount
, 1 << 13);
6147 EditExperience(PERCEPTION
, Amount
, 1 << 13);
6148 EditExperience(INTELLIGENCE
, Amount
, 1 << 13);
6149 EditExperience(WISDOM
, Amount
, 1 << 13);
6150 EditExperience(CHARISMA
, Amount
, 1 << 13);
6155 void character::AddHolyBananaConsumeEndMessage () const {
6156 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
6157 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE
));
6161 void character::ReceiveHolyMango (sLong Amount
) {
6163 EditExperience(ARM_STRENGTH
, Amount
, 1 << 13);
6164 EditExperience(LEG_STRENGTH
, Amount
, 1 << 13);
6165 EditExperience(DEXTERITY
, Amount
, 1 << 13);
6166 EditExperience(AGILITY
, Amount
, 1 << 13);
6167 EditExperience(ENDURANCE
, Amount
, 1 << 13);
6168 EditExperience(PERCEPTION
, Amount
, 1 << 13);
6169 EditExperience(INTELLIGENCE
, Amount
, 1 << 13);
6170 EditExperience(WISDOM
, Amount
, 1 << 13);
6171 EditExperience(CHARISMA
, Amount
, 1 << 13);
6176 void character::AddHolyMangoConsumeEndMessage () const {
6177 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
6178 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE
));
6182 truth
character::PreProcessForBone () {
6183 if (IsPet() && IsEnabled()) {
6184 Die(0, CONST_S(""), FORBID_REINCARNATION
);
6187 if (GetAction()) GetAction()->Terminate(false);
6188 if (TemporaryStateIsActivated(POLYMORPHED
)) {
6189 character
*PolymorphBackup
= GetPolymorphBackup();
6191 PolymorphBackup
->PreProcessForBone();
6194 if (MustBeRemovedFromBone()) return false;
6195 if (IsUnique() && !CanBeGenerated()) game::SignalQuestMonsterFound();
6199 GetStack()->PreProcessForBone();
6200 doforequipments()(this, &item::PreProcessForBone
);
6201 doforbodyparts()(this, &bodypart::PreProcessForBone
);
6202 game::RemoveCharacterID(ID
);
6204 game::AddCharacterID(this, ID
);
6209 truth
character::PostProcessForBone (double &DangerSum
, int& Enemies
) {
6210 if (PostProcessForBone()) {
6211 if (GetRelation(PLAYER
) == HOSTILE
) {
6212 double Danger
= GetRelativeDanger(PLAYER
, true);
6213 if (Danger
> 99.0) game::SetTooGreatDangerFound(true);
6214 else if (!IsUnique() && !IgnoreDanger()) {
6215 DangerSum
+= Danger
;
6225 truth
character::PostProcessForBone () {
6226 feuLong NewID
= game::CreateNewCharacterID(this);
6227 game::GetBoneCharacterIDMap().insert(std::make_pair(-ID
, NewID
));
6228 game::RemoveCharacterID(ID
);
6230 if (IsUnique() && CanBeGenerated()) {
6231 if (DataBase
->Flags
& HAS_BEEN_GENERATED
) return false;
6234 GetStack()->PostProcessForBone();
6235 doforequipments()(this, &item::PostProcessForBone
);
6236 doforbodyparts()(this, &bodypart::PostProcessForBone
);
6241 void character::FinalProcessForBone () {
6243 GetStack()->FinalProcessForBone();
6244 doforequipments()(this, &item::FinalProcessForBone
);
6246 for (c
= 0; c
< BodyParts
; ++c
) {
6247 for (std::list
<feuLong
>::iterator i
= OriginalBodyPartID
[c
].begin(); i
!= OriginalBodyPartID
[c
].end();) {
6248 boneidmap::iterator BI
= game::GetBoneItemIDMap().find(*i
);
6249 if (BI
== game::GetBoneItemIDMap().end()) {
6250 std::list
<feuLong
>::iterator Dirt
= i
++;
6251 OriginalBodyPartID
[c
].erase(Dirt
);
6261 void character::SetSoulID (feuLong What
) {
6262 if (GetPolymorphBackup()) GetPolymorphBackup()->SetSoulID(What
);
6266 truth
character::SearchForItem (citem
*Item
) const {
6267 if (combineequipmentpredicateswithparam
<feuLong
>()(this, &item::HasID
, Item
->GetID(), 1)) return true;
6268 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (*i
== Item
) return true;
6273 item
*character::SearchForItem (const sweaponskill
*SWeaponSkill
) const {
6274 for (int c
= 0; c
< GetEquipments(); ++c
) {
6275 item
*Equipment
= GetEquipment(c
);
6276 if (Equipment
&& SWeaponSkill
->IsSkillOf(Equipment
)) return Equipment
;
6278 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (SWeaponSkill
->IsSkillOf(*i
)) return *i
;
6283 void character::PutNear (v2 Pos
) {
6284 v2 NewPos
= game::GetCurrentLevel()->GetNearestFreeSquare(this, Pos
, false);
6285 if (NewPos
== ERROR_V2
) {
6286 do { NewPos
= game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos
== Pos
);
6292 void character::PutToOrNear (v2 Pos
) {
6293 if (game::IsInWilderness() || (CanMoveOn(game::GetCurrentLevel()->GetLSquare(Pos
)) && IsFreeForMe(game::GetCurrentLevel()->GetLSquare(Pos
))))
6300 void character::PutTo (v2 Pos
) {
6301 SquareUnder
[0] = game::GetCurrentArea()->GetSquare(Pos
);
6302 SquareUnder
[0]->AddCharacter(this);
6306 void character::Remove () {
6307 SquareUnder
[0]->RemoveCharacter();
6312 void character::SendNewDrawRequest () const {
6313 for (int c
= 0; c
< SquaresUnder
; ++c
) {
6314 square
*Square
= GetSquareUnder(c
);
6315 if (Square
) Square
->SendNewDrawRequest();
6320 truth
character::IsOver (v2 Pos
) const {
6321 for (int c
= 0; c
< SquaresUnder
; ++c
) {
6322 square
*Square
= GetSquareUnder(c
);
6323 if (Square
&& Square
->GetPos() == Pos
) return true;
6329 truth
character::CanTheoreticallyMoveOn (const lsquare
*LSquare
) const { return GetMoveType() & LSquare
->GetTheoreticalWalkability(); }
6330 truth
character::CanMoveOn (const lsquare
*LSquare
) const { return GetMoveType() & LSquare
->GetWalkability(); }
6331 truth
character::CanMoveOn (const square
*Square
) const { return GetMoveType() & Square
->GetSquareWalkability(); }
6332 truth
character::CanMoveOn (const olterrain
*OLTerrain
) const { return GetMoveType() & OLTerrain
->GetWalkability(); }
6333 truth
character::CanMoveOn (const oterrain
*OTerrain
) const { return GetMoveType() & OTerrain
->GetWalkability(); }
6334 truth
character::IsFreeForMe(square
*Square
) const { return !Square
->GetCharacter() || Square
->GetCharacter() == this; }
6335 void character::LoadSquaresUnder () { SquareUnder
[0] = game::GetSquareInLoad(); }
6337 truth
character::AttackAdjacentEnemyAI () {
6338 if (!IsEnabled()) return false;
6339 character
*Char
[MAX_NEIGHBOUR_SQUARES
];
6340 v2 Pos
[MAX_NEIGHBOUR_SQUARES
];
6341 int Dir
[MAX_NEIGHBOUR_SQUARES
];
6343 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
6344 square
*Square
= GetNeighbourSquare(d
);
6346 character
*Enemy
= Square
->GetCharacter();
6347 if (Enemy
&& (GetRelation(Enemy
) == HOSTILE
|| StateIsActivated(CONFUSED
))) {
6349 Pos
[Index
] = Square
->GetPos();
6350 Char
[Index
++] = Enemy
;
6355 int ChosenIndex
= RAND() % Index
;
6356 Hit(Char
[ChosenIndex
], Pos
[ChosenIndex
], Dir
[ChosenIndex
]);
6363 void character::SignalStepFrom (lsquare
**OldSquareUnder
) {
6365 lsquare
*NewSquareUnder
[MAX_SQUARES_UNDER
];
6366 for (c
= 0; c
< GetSquaresUnder(); ++c
) NewSquareUnder
[c
] = GetLSquareUnder(c
);
6367 for (c
= 0; c
< GetSquaresUnder(); ++c
) {
6368 if (IsEnabled() && GetLSquareUnder(c
) == NewSquareUnder
[c
]) NewSquareUnder
[c
]->StepOn(this, OldSquareUnder
);
6373 int character::GetSumOfAttributes () const {
6374 return GetAttribute(ENDURANCE
) + GetAttribute(PERCEPTION
) + GetAttribute(INTELLIGENCE
) + GetAttribute(WISDOM
) + GetAttribute(CHARISMA
) + GetAttribute(ARM_STRENGTH
) + GetAttribute(AGILITY
);
6378 void character::IntelligenceAction (int Difficulty
) {
6379 EditAP(-20000 * Difficulty
/ APBonus(GetAttribute(INTELLIGENCE
)));
6380 EditExperience(INTELLIGENCE
, Difficulty
* 15, 1 << 7);
6384 struct walkabilitycontroller
{
6385 static truth
Handler (int x
, int y
) {
6386 return x
>= 0 && y
>= 0 && x
< LevelXSize
&& y
< LevelYSize
&& Map
[x
][y
]->GetTheoreticalWalkability() & MoveType
;
6388 static lsquare
***Map
;
6389 static int LevelXSize
, LevelYSize
;
6390 static int MoveType
;
6394 lsquare
***walkabilitycontroller::Map
;
6395 int walkabilitycontroller::LevelXSize
, walkabilitycontroller::LevelYSize
;
6396 int walkabilitycontroller::MoveType
;
6399 truth
character::CreateRoute () {
6401 if (GetAttribute(INTELLIGENCE
) >= 10 && !StateIsActivated(CONFUSED
)) {
6403 walkabilitycontroller::Map
= GetLevel()->GetMap();
6404 walkabilitycontroller::LevelXSize
= GetLevel()->GetXSize();
6405 walkabilitycontroller::LevelYSize
= GetLevel()->GetYSize();
6406 walkabilitycontroller::MoveType
= GetMoveType();
6408 for (int c
= 0; c
< game::GetTeams(); ++c
)
6409 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
6410 character
*Char
= *i
;
6411 if (Char
->IsEnabled() && !Char
->Route
.empty() && (Char
->GetMoveType()&GetMoveType()) == Char
->GetMoveType()) {
6412 v2 CharGoingTo
= Char
->Route
[0];
6413 v2 iPos
= Char
->Route
.back();
6414 if ((GoingTo
-CharGoingTo
).GetLengthSquare() <= 100 && (Pos
- iPos
).GetLengthSquare() <= 100 &&
6415 mapmath
<walkabilitycontroller
>::DoLine(CharGoingTo
.X
, CharGoingTo
.Y
, GoingTo
.X
, GoingTo
.Y
, SKIP_FIRST
) &&
6416 mapmath
<walkabilitycontroller
>::DoLine(Pos
.X
, Pos
.Y
, iPos
.X
, iPos
.Y
, SKIP_FIRST
)) {
6417 if (!Illegal
.empty() && Illegal
.find(Char
->Route
.back()) != Illegal
.end()) continue;
6418 Node
= GetLevel()->FindRoute(CharGoingTo
, GoingTo
, Illegal
, GetMoveType());
6419 if (Node
) { while(Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
6420 else { Route
.clear(); continue; }
6421 Route
.insert(Route
.end(), Char
->Route
.begin(), Char
->Route
.end());
6422 Node
= GetLevel()->FindRoute(Pos
, iPos
, Illegal
, GetMoveType());
6423 if (Node
) { while (Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
6424 else { Route
.clear(); continue; }
6425 IntelligenceAction(1);
6430 Node
= GetLevel()->FindRoute(Pos
, GoingTo
, Illegal
, GetMoveType());
6431 if (Node
) { while(Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
6432 else TerminateGoingTo();
6433 IntelligenceAction(5);
6440 void character::SetGoingTo (v2 What
) {
6441 if (GoingTo
!= What
) {
6449 void character::TerminateGoingTo () {
6456 truth
character::CheckForFood (int Radius
) {
6457 if (StateIsActivated(PANIC
) || !UsesNutrition() || !IsEnabled()) return false;
6460 for (int r
= 1; r
<= Radius
; ++r
) {
6463 for (y
= Pos
.Y
-r
; y
<= Pos
.Y
+r
; ++y
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6466 if (x
< GetLevel()->GetXSize()) {
6467 for (y
= Pos
.Y
-r
; y
<= Pos
.Y
+r
; ++y
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6471 for (x
= Pos
.X
-r
; x
<= Pos
.X
+r
; ++x
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6474 if (y
< GetLevel()->GetYSize()) {
6475 for (x
= Pos
.X
-r
; x
<= Pos
.X
+r
; ++x
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6482 truth
character::CheckForFoodInSquare (v2 Pos
) {
6483 level
*Level
= GetLevel();
6484 if (Level
->IsValidPos(Pos
)) {
6485 lsquare
*Square
= Level
->GetLSquare(Pos
);
6486 stack
*Stack
= Square
->GetStack();
6487 if (Stack
->GetItems()) {
6488 for (stackiterator i
= Stack
->GetBottom(); i
.HasItem(); ++i
) {
6489 if (i
->IsPickable(this) && i
->CanBeSeenBy(this) && i
->CanBeEatenByAI(this) && (!Square
->GetRoomIndex() || Square
->GetRoom()->AllowFoodSearch())) {
6491 return MoveTowardsTarget(false);
6500 void character::SetConfig (int NewConfig
, int SpecialFlags
) {
6501 databasecreator
<character
>::InstallDataBase(this, NewConfig
);
6504 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdatePictures();
6508 truth
character::IsOver (citem
*Item
) const {
6509 for (int c1
= 0; c1
< Item
->GetSquaresUnder(); ++c1
)
6510 for (int c2
= 0; c2
< SquaresUnder
; ++c2
)
6511 if (Item
->GetPos(c1
) == GetPos(c2
)) return true;
6516 truth
character::CheckConsume (cfestring
&Verb
) const {
6517 if (!UsesNutrition()) {
6518 if (IsPlayer()) ADD_MESSAGE("In this form you can't and don't need to %s.", Verb
.CStr());
6525 void character::PutTo (lsquare
*To
) {
6526 PutTo(To
->GetPos());
6530 double character::RandomizeBabyExperience (double SumE
) {
6531 if (!SumE
) return 0;
6532 double E
= (SumE
/ 4) - (SumE
/ 32) + (double(RAND()) / MAX_RAND
) * (SumE
/ 16 + 1);
6533 return Limit(E
, MIN_EXP
, MAX_EXP
);
6537 liquid
*character::CreateBlood (sLong Volume
) const {
6538 return liquid::Spawn(GetBloodMaterial(), Volume
);
6542 void character::SpillFluid (character
*Spiller
, liquid
*Liquid
, int SquareIndex
) {
6543 sLong ReserveVolume
= Liquid
->GetVolume() >> 1;
6544 Liquid
->EditVolume(-ReserveVolume
);
6545 GetStack()->SpillFluid(Spiller
, Liquid
, sLong(Liquid
->GetVolume() * sqrt(double(GetStack()->GetVolume()) / GetVolume())));
6546 Liquid
->EditVolume(ReserveVolume
);
6548 sLong Modifier
[MAX_BODYPARTS
], ModifierSum
= 0;
6549 for (c
= 0; c
< BodyParts
; ++c
) {
6550 if (GetBodyPart(c
)) {
6551 Modifier
[c
] = sLong(sqrt(GetBodyPart(c
)->GetVolume()));
6552 if (Modifier
[c
]) Modifier
[c
] *= 1 + (RAND() & 3);
6553 ModifierSum
+= Modifier
[c
];
6558 for (c
= 1; c
< GetBodyParts(); ++c
) {
6559 if (GetBodyPart(c
) && IsEnabled())
6560 GetBodyPart(c
)->SpillFluid(Spiller
, Liquid
->SpawnMoreLiquid(Liquid
->GetVolume() * Modifier
[c
] / ModifierSum
), SquareIndex
);
6563 Liquid
->SetVolume(Liquid
->GetVolume() * Modifier
[TORSO_INDEX
] / ModifierSum
);
6564 GetTorso()->SpillFluid(Spiller
, Liquid
, SquareIndex
);
6569 void character::StayOn (liquid
*Liquid
) {
6570 Liquid
->TouchEffect(this, TORSO_INDEX
);
6574 truth
character::IsAlly (ccharacter
*Char
) const {
6575 return Char
->GetTeam()->GetID() == GetTeam()->GetID();
6579 void character::ResetSpoiling () {
6580 doforbodyparts()(this, &bodypart::ResetSpoiling
);
6584 item
*character::SearchForItem (ccharacter
*Char
, sorter Sorter
) const {
6585 item
*Equipment
= findequipment
<ccharacter
*>()(this, Sorter
, Char
);
6586 if (Equipment
) return Equipment
;
6587 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (((*i
)->*Sorter
)(Char
)) return *i
;
6592 truth
character::DetectMaterial (cmaterial
*Material
) const {
6593 return GetStack()->DetectMaterial(Material
) ||
6594 combinebodypartpredicateswithparam
<cmaterial
*>()(this, &bodypart::DetectMaterial
, Material
, 1) ||
6595 combineequipmentpredicateswithparam
<cmaterial
*>()(this, &item::DetectMaterial
, Material
, 1);
6599 truth
character::DamageTypeDestroysBodyPart (int Type
) {
6600 return (Type
&0xFFF) != PHYSICAL_DAMAGE
;
6604 truth
character::CheckIfTooScaredToHit (ccharacter
*Enemy
) const {
6605 if (IsPlayer() && StateIsActivated(PANIC
)) {
6606 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
6607 square
*Square
= GetNeighbourSquare(d
);
6609 if(CanMoveOn(Square
) && (!Square
->GetCharacter() || Square
->GetCharacter()->IsPet())) {
6610 ADD_MESSAGE("You are too scared to attack %s.", Enemy
->CHAR_DESCRIPTION(DEFINITE
));
6620 void character::PrintBeginLevitationMessage () const {
6622 if (IsPlayer()) ADD_MESSAGE("You rise into the air like a small hot-air balloon.");
6623 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins to float.", CHAR_NAME(DEFINITE
));
6628 void character::PrintEndLevitationMessage () const {
6630 if (IsPlayer()) ADD_MESSAGE("You descend gently onto the ground.");
6631 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s drops onto the ground.", CHAR_NAME(DEFINITE
));
6636 truth
character::IsLimbIndex (int I
) {
6638 case RIGHT_ARM_INDEX
:
6639 case LEFT_ARM_INDEX
:
6640 case RIGHT_LEG_INDEX
:
6641 case LEFT_LEG_INDEX
:
6648 void character::EditExperience (int Identifier
, double Value
, double Speed
) {
6649 if (!AllowExperience() || (Identifier
== ENDURANCE
&& UseMaterialAttributes())) return;
6650 int Change
= RawEditExperience(BaseExperience
[Identifier
], GetNaturalExperience(Identifier
), Value
, Speed
);
6651 if (!Change
) return;
6652 cchar
*PlayerMsg
= 0, *NPCMsg
= 0;
6653 switch (Identifier
) {
6656 PlayerMsg
= "You feel tougher than anything!";
6657 if (IsPet()) NPCMsg
= "Suddenly %s looks tougher.";
6659 PlayerMsg
= "You feel less healthy.";
6660 if (IsPet()) NPCMsg
= "Suddenly %s looks less healthy.";
6662 CalculateBodyPartMaxHPs();
6663 CalculateMaxStamina();
6668 PlayerMsg
= "You now see the world in much better detail than before.";
6670 PlayerMsg
= "You feel very guru.";
6671 game::GetGod(VALPURUS
)->AdjustRelation(100);
6673 game::SendLOSUpdateRequest();
6678 if (Change
> 0) PlayerMsg
= "Suddenly the inner structure of the Multiverse around you looks quite simple.";
6679 else PlayerMsg
= "It surely is hard to think today.";
6682 if (IsPlayerKind()) UpdatePictures();
6686 if (Change
> 0) PlayerMsg
= "You feel your life experience increasing all the time.";
6687 else PlayerMsg
= "You feel like having done something unwise.";
6689 if (IsPlayerKind()) UpdatePictures();
6693 PlayerMsg
= "You feel very confident of your social skills.";
6695 if (GetAttribute(CHARISMA
) <= 15) NPCMsg
= "%s looks less ugly.";
6696 else NPCMsg
= "%s looks more attractive.";
6699 PlayerMsg
= "You feel somehow disliked.";
6701 if (GetAttribute(CHARISMA
) < 15) NPCMsg
= "%s looks more ugly.";
6702 else NPCMsg
= "%s looks less attractive.";
6705 if (IsPlayerKind()) UpdatePictures();
6709 PlayerMsg
= "You feel magical forces coursing through your body!";
6710 NPCMsg
= "You notice an odd glow around %s.";
6712 PlayerMsg
= "You feel your magical abilities withering slowly.";
6713 NPCMsg
= "You notice strange vibrations in the air around %s. But they disappear rapidly.";
6718 if (IsPlayer()) ADD_MESSAGE("%s", PlayerMsg
);
6719 else if (NPCMsg
&& CanBeSeenByPlayer()) ADD_MESSAGE(NPCMsg
, CHAR_NAME(DEFINITE
));
6721 CalculateBattleInfo();
6725 int character::RawEditExperience (double &Exp
, double NaturalExp
, double Value
, double Speed
) const {
6726 double OldExp
= Exp
;
6731 if(!OldExp
|| !Value
|| (Value
> 0 && OldExp
>= NaturalExp
* (100 + Value
) / 100) ||
6732 (Value
< 0 && OldExp
<= NaturalExp
* (100 + Value
) / 100)) return 0;
6733 if (!IsPlayer()) Speed
*= 1.5;
6734 Exp
+= (NaturalExp
* (100 + Value
) - 100 * OldExp
) * Speed
* EXP_DIVISOR
;
6735 LimitRef(Exp
, MIN_EXP
, MAX_EXP
);
6736 int NewA
= int(Exp
* EXP_DIVISOR
);
6737 int OldA
= int(OldExp
* EXP_DIVISOR
);
6738 int Delta
= NewA
- OldA
;
6739 if (Delta
> 0) Exp
= Max(Exp
, (NewA
+ 0.05) * EXP_MULTIPLIER
);
6740 else if (Delta
< 0) Exp
= Min(Exp
, (NewA
+ 0.95) * EXP_MULTIPLIER
);
6741 LimitRef(Exp
, MIN_EXP
, MAX_EXP
);
6746 int character::GetAttribute (int Identifier
, truth AllowBonus
) const {
6747 int A
= int(BaseExperience
[Identifier
] * EXP_DIVISOR
);
6748 if (AllowBonus
&& Identifier
== INTELLIGENCE
&& BrainsHurt()) return Max((A
+ AttributeBonus
[INTELLIGENCE
]) / 3, 1);
6749 return A
&& AllowBonus
? Max(A
+ AttributeBonus
[Identifier
], 1) : A
;
6753 void characterdatabase::PostProcess () {
6754 double AM
= (100 + AttributeBonus
) * EXP_MULTIPLIER
/ 100;
6755 for (int c
= 0; c
< ATTRIBUTES
; ++c
) NaturalExperience
[c
] = this->*ExpPtr
[c
] * AM
;
6759 void character::EditDealExperience (sLong Price
) {
6760 EditExperience(CHARISMA
, sqrt(Price
) / 5, 1 << 9);
6764 void character::PrintBeginLeprosyMessage () const {
6765 if (IsPlayer()) ADD_MESSAGE("You feel you're falling in pieces.");
6769 void character::PrintEndLeprosyMessage () const {
6770 if (IsPlayer()) ADD_MESSAGE("You feel your limbs are stuck in place tightly."); // CHANGE OR DIE
6774 void character::TryToInfectWithLeprosy (ccharacter
*Infector
) {
6775 if (!IsImmuneToLeprosy() &&
6776 ((GetRelation(Infector
) == HOSTILE
&& !RAND_N(50 * GetAttribute(ENDURANCE
))) ||
6777 !RAND_N(500 * GetAttribute(ENDURANCE
)))) GainIntrinsic(LEPROSY
);
6781 void character::SignalGeneration () {
6782 const_cast<database
*>(DataBase
)->Flags
|= HAS_BEEN_GENERATED
;
6786 void character::CheckIfSeen () {
6787 if (IsPlayer() || CanBeSeenByPlayer()) SignalSeen();
6791 void character::SignalSeen () {
6792 if (!(WarnFlags
& WARNED
) && GetRelation(PLAYER
) == HOSTILE
&& !StateIsActivated(FEARLESS
)) {
6793 double Danger
= GetRelativeDanger(PLAYER
);
6795 game::SetDangerFound(Max(game::GetDangerFound(), Danger
));
6796 if (Danger
> 500.0 && !(WarnFlags
& HAS_CAUSED_PANIC
)) {
6797 WarnFlags
|= HAS_CAUSED_PANIC
;
6798 game::SetCausePanicFlag(true);
6800 WarnFlags
|= WARNED
;
6803 const_cast<database
*>(DataBase
)->Flags
|= HAS_BEEN_SEEN
;
6807 int character::GetPolymorphIntelligenceRequirement () const {
6808 if (DataBase
->PolymorphIntelligenceRequirement
== DEPENDS_ON_ATTRIBUTES
) return Max(GetAttributeAverage() - 5, 0);
6809 return DataBase
->PolymorphIntelligenceRequirement
;
6813 void character::RemoveAllItems () {
6814 GetStack()->Clean();
6815 for (int c
= 0; c
< GetEquipments(); ++c
) {
6816 item
*Equipment
= GetEquipment(c
);
6818 Equipment
->RemoveFromSlot();
6819 Equipment
->SendToHell();
6825 int character::CalculateWeaponSkillHits (ccharacter
*Enemy
) const {
6826 if (Enemy
->IsPlayer()) {
6827 configid
ConfigID(GetType(), GetConfig());
6828 const dangerid
& DangerID
= game::GetDangerMap().find(ConfigID
)->second
;
6829 return Min(int(DangerID
.EquippedDanger
* 2000), 1000);
6831 return Min(int(GetRelativeDanger(Enemy
, true) * 2000), 1000);
6835 truth
character::CanUseEquipment (int I
) const {
6836 return CanUseEquipment() && I
< GetEquipments() && GetBodyPartOfEquipment(I
) && EquipmentIsAllowed(I
);
6840 /* Target mustn't have any equipment */
6841 void character::DonateEquipmentTo (character
*Character
) {
6843 feuLong
*EquipmentMemory
= game::GetEquipmentMemory();
6844 for (int c
= 0; c
< MAX_EQUIPMENT_SLOTS
; ++c
) {
6845 item
*Item
= GetEquipment(c
);
6847 if (Character
->CanUseEquipment(c
)) {
6848 Item
->RemoveFromSlot();
6849 Character
->SetEquipment(c
, Item
);
6851 EquipmentMemory
[c
] = Item
->GetID();
6852 Item
->MoveTo(Character
->GetStack());
6854 } else if (CanUseEquipment(c
)) {
6855 EquipmentMemory
[c
] = 0;
6856 } else if (EquipmentMemory
[c
] && Character
->CanUseEquipment(c
)) {
6857 for (stackiterator i
= Character
->GetStack()->GetBottom(); i
.HasItem(); ++i
) {
6858 if (i
->GetID() == EquipmentMemory
[c
]) {
6860 Item
->RemoveFromSlot();
6861 Character
->SetEquipment(c
, Item
);
6865 EquipmentMemory
[c
] = 0;
6869 for (int c
= 0; c
< GetEquipments(); ++c
) {
6870 item
*Item
= GetEquipment(c
);
6872 if (Character
->CanUseEquipment(c
)) {
6873 Item
->RemoveFromSlot();
6874 Character
->SetEquipment(c
, Item
);
6876 Item
->MoveTo(Character
->GetStackUnder());
6884 void character::ReceivePeaSoup (sLong
) {
6885 if (!game::IsInWilderness()) {
6886 lsquare
*Square
= GetLSquareUnder();
6887 if (Square
->IsFlyable()) Square
->AddSmoke(gas::Spawn(FART
, 250));
6892 void character::AddPeaSoupConsumeEndMessage () const {
6894 if (CanHear()) ADD_MESSAGE("Mmmh! The soup is very tasty. You hear a small puff.");
6895 else ADD_MESSAGE("Mmmh! The soup is very tasty.");
6896 } else if (CanBeSeenByPlayer() && PLAYER
->CanHear()) {
6898 ADD_MESSAGE("You hear a small puff.");
6903 void character::CalculateMaxStamina () {
6904 MaxStamina
= TorsoIsAlive() ? GetAttribute(ENDURANCE
) * 10000 : 0;
6908 void character::EditStamina (int Amount
, truth CanCauseUnconsciousness
) {
6909 if (!TorsoIsAlive()) return;
6910 int UnconsciousnessStamina
= MaxStamina
>> 3;
6911 if (!CanCauseUnconsciousness
&& Amount
< 0) {
6912 if (Stamina
> UnconsciousnessStamina
) {
6914 if (Stamina
< UnconsciousnessStamina
) Stamina
= UnconsciousnessStamina
;
6918 int OldStamina
= Stamina
;
6920 if (Stamina
> MaxStamina
) {
6921 Stamina
= MaxStamina
;
6922 } else if (Stamina
< 0) {
6924 LoseConsciousness(250 + RAND_N(250));
6925 } else if (IsPlayer()) {
6926 if (OldStamina
>= MaxStamina
>> 2 && Stamina
< MaxStamina
>> 2) {
6927 ADD_MESSAGE("You are getting a little tired.");
6928 } else if(OldStamina
>= UnconsciousnessStamina
&& Stamina
< UnconsciousnessStamina
) {
6929 ADD_MESSAGE("You are seriously out of breath!");
6930 game::SetPlayerIsRunning(false);
6933 if (IsPlayer() && StateIsActivated(PANIC
) && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6937 void character::RegenerateStamina () {
6938 if (GetTirednessState() != UNTIRED
) {
6939 EditExperience(ENDURANCE
, 50, 1);
6940 if (Sweats() && TorsoIsAlive() && !RAND_N(30) && !game::IsInWilderness()) {
6941 // Sweat amount proportional to endurance also
6942 //sLong Volume = sLong(0.05 * sqrt(GetBodyVolume()));
6943 sLong Volume
= long(0.05*sqrt(GetBodyVolume()*GetAttribute(ENDURANCE
)/10));
6944 if (GetTirednessState() == FAINTING
) Volume
<<= 1;
6945 for (int c
= 0; c
< SquaresUnder
; ++c
) GetLSquareUnder(c
)->SpillFluid(0, CreateSweat(Volume
), false, false);
6950 if (Action
->IsRest()) {
6951 if (SquaresUnder
== 1) {
6952 Bonus
= GetSquareUnder()->GetRestModifier() << 1;
6954 int Lowest
= GetSquareUnder(0)->GetRestModifier();
6955 for (int c
= 1; c
< GetSquaresUnder(); ++c
) {
6956 int Mod
= GetSquareUnder(c
)->GetRestModifier();
6957 if (Mod
< Lowest
) Lowest
= Mod
;
6959 Bonus
= Lowest
<< 1;
6961 } else if (Action
->IsUnconsciousness()) Bonus
= 2;
6964 auto bst
= GetBurdenState();
6965 if (bst
== OVER_LOADED
) Plus1
= 25;
6966 else if (bst
== STRESSED
) Plus1
= 50;
6967 else if (bst
== BURDENED
) Plus1
= 75;
6970 auto hst
= GetHungerState();
6971 if (hst
== STARVING
) Plus2
= 25;
6972 else if (hst
== VERY_HUNGRY
) Plus2
= 50;
6973 else if (hst
== HUNGRY
) Plus2
= 75;
6975 Stamina
+= Plus1
* Plus2
* Bonus
/ 1000;
6976 if (Stamina
> MaxStamina
) Stamina
= MaxStamina
;
6977 if (IsPlayer() && StateIsActivated(PANIC
) && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6981 void character::BeginPanic () {
6982 if (IsPlayer() && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6983 DeActivateVoluntaryAction();
6987 void character::EndPanic () {
6988 if (IsPlayer()) game::SetPlayerIsRunning(false);
6992 int character::GetTirednessState () const {
6993 if (Stamina
>= MaxStamina
>> 2) return UNTIRED
;
6994 if (Stamina
>= MaxStamina
>> 3) return EXHAUSTED
;
6999 void character::ReceiveBlackUnicorn (sLong Amount
) {
7000 if (!(RAND() % 160)) game::DoEvilDeed(Amount
/ 50);
7001 BeginTemporaryState(TELEPORT
, Amount
/ 100);
7002 for (int c
= 0; c
< STATES
; ++c
) {
7003 if (StateData
[c
].Flags
& DUR_TEMPORARY
) {
7004 BeginTemporaryState(1 << c
, Amount
/ 100);
7005 if (!IsEnabled()) return;
7006 } else if (StateData
[c
].Flags
& DUR_PERMANENT
) {
7007 GainIntrinsic(1 << c
);
7008 if (!IsEnabled()) return;
7014 void character::ReceiveGrayUnicorn (sLong Amount
) {
7015 if (!(RAND() % 80)) game::DoEvilDeed(Amount
/ 50);
7016 BeginTemporaryState(TELEPORT
, Amount
/ 100);
7017 for (int c
= 0; c
< STATES
; ++c
) {
7018 if (1 << c
!= TELEPORT
) {
7019 DecreaseStateCounter(1 << c
, -Amount
/ 100);
7020 if (!IsEnabled()) return;
7026 void character::ReceiveWhiteUnicorn (sLong Amount
) {
7027 if (!(RAND() % 40)) game::DoEvilDeed(Amount
/ 50);
7028 BeginTemporaryState(TELEPORT
, Amount
/ 100);
7029 DecreaseStateCounter(LYCANTHROPY
, -Amount
/ 100);
7030 DecreaseStateCounter(POISONED
, -Amount
/ 100);
7031 DecreaseStateCounter(PARASITIZED
, -Amount
/ 100);
7032 DecreaseStateCounter(LEPROSY
, -Amount
/ 100);
7033 DecreaseStateCounter(VAMPIRISM
, -Amount
/ 100);
7037 /* Counter should be negative. Removes intrinsics. */
7038 void character::DecreaseStateCounter (sLong State
, int Counter
) {
7040 for (Index
= 0; Index
< STATES
; ++Index
) if (1 << Index
== State
) break;
7041 if (Index
== STATES
) ABORT("DecreaseTemporaryStateCounter works only when State == 2^n!");
7042 if (TemporaryState
& State
) {
7043 if (TemporaryStateCounter
[Index
] == PERMANENT
|| (TemporaryStateCounter
[Index
] += Counter
) <= 0) {
7044 TemporaryState
&= ~State
;
7045 if (!(EquipmentState
& State
)) {
7046 if (StateData
[Index
].EndHandler
) {
7047 (this->*StateData
[Index
].EndHandler
)();
7048 if (!IsEnabled()) return;
7050 (this->*StateData
[Index
].PrintEndMessage
)();
7057 truth
character::IsImmuneToLeprosy () const {
7058 return (DataBase
->IsImmuneToLeprosy
|| UseMaterialAttributes() || StateIsActivated(DISEASE_IMMUNITY
));
7062 void character::LeprosyHandler () {
7063 EditExperience(ARM_STRENGTH
, -25, 1 << 1);
7064 EditExperience(LEG_STRENGTH
, -25, 1 << 1);
7065 EditExperience(DEXTERITY
, -25, 1 << 1);
7066 EditExperience(AGILITY
, -25, 1 << 1);
7067 EditExperience(ENDURANCE
, -25, 1 << 1);
7068 EditExperience(CHARISMA
, -25, 1 << 1);
7069 CheckDeath(CONST_S("killed by leprosy"));
7073 bodypart
*character::SearchForOriginalBodyPart (int I
) const {
7074 for (stackiterator i1
= GetStackUnder()->GetBottom(); i1
.HasItem(); ++i1
) {
7075 for (std::list
<feuLong
>::iterator i2
= OriginalBodyPartID
[I
].begin(); i2
!= OriginalBodyPartID
[I
].end(); ++i2
)
7076 if (i1
->GetID() == *i2
) return static_cast<bodypart
*>(*i1
);
7082 void character::SetLifeExpectancy (int Base
, int RandPlus
) {
7084 for (c
= 0; c
< BodyParts
; ++c
) {
7085 bodypart
*BodyPart
= GetBodyPart(c
);
7086 if (BodyPart
) BodyPart
->SetLifeExpectancy(Base
, RandPlus
);
7088 for (c
= 0; c
< GetEquipments(); ++c
) {
7089 item
*Equipment
= GetEquipment(c
);
7090 if (Equipment
) Equipment
->SetLifeExpectancy(Base
, RandPlus
);
7095 /* Receiver should be a fresh duplicate of this */
7096 void character::DuplicateEquipment (character
*Receiver
, feuLong Flags
) {
7097 for (int c
= 0; c
< GetEquipments(); ++c
) {
7098 item
*Equipment
= GetEquipment(c
);
7100 item
*Duplicate
= Equipment
->Duplicate(Flags
);
7101 Receiver
->SetEquipment(c
, Duplicate
);
7107 void character::Disappear (corpse
*Corpse
, cchar
*Verb
, truth (item::*ClosePredicate
)() const) {
7108 truth TorsoDisappeared
= false;
7109 truth CanBeSeen
= Corpse
? Corpse
->CanBeSeenByPlayer() : IsPlayer() || CanBeSeenByPlayer();
7111 if ((GetTorso()->*ClosePredicate
)()) {
7113 if (Corpse
) ADD_MESSAGE("%s %ss.", Corpse
->CHAR_NAME(DEFINITE
), Verb
);
7114 else if (IsPlayer()) ADD_MESSAGE("You %s.", Verb
);
7115 else ADD_MESSAGE("%s %ss.", CHAR_NAME(DEFINITE
), Verb
);
7117 TorsoDisappeared
= true;
7118 for (c
= 0; c
< GetEquipments(); ++c
) {
7119 item
*Equipment
= GetEquipment(c
);
7120 if (Equipment
&& (Equipment
->*ClosePredicate
)()) {
7121 Equipment
->RemoveFromSlot();
7122 Equipment
->SendToHell();
7125 itemvector ItemVector
;
7126 GetStack()->FillItemVector(ItemVector
);
7127 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
7128 if (ItemVector
[c
] && (ItemVector
[c
]->*ClosePredicate
)()) {
7129 ItemVector
[c
]->RemoveFromSlot();
7130 ItemVector
[c
]->SendToHell();
7134 for (c
= 1; c
< GetBodyParts(); ++c
) {
7135 bodypart
*BodyPart
= GetBodyPart(c
);
7137 if ((BodyPart
->*ClosePredicate
)()) {
7138 if (!TorsoDisappeared
&& CanBeSeen
) {
7139 if(IsPlayer()) ADD_MESSAGE("Your %s %ss.", GetBodyPartName(c
).CStr(), Verb
);
7140 else ADD_MESSAGE("The %s of %s %ss.", GetBodyPartName(c
).CStr(), CHAR_NAME(DEFINITE
), Verb
);
7142 BodyPart
->DropEquipment();
7143 item
*BodyPart
= SevereBodyPart(c
);
7144 if (BodyPart
) BodyPart
->SendToHell();
7145 } else if (TorsoDisappeared
) {
7146 BodyPart
->DropEquipment();
7147 item
*BodyPart
= SevereBodyPart(c
);
7149 if (Corpse
) Corpse
->GetSlot()->AddFriendItem(BodyPart
);
7150 else if (!game::IsInWilderness()) GetStackUnder()->AddItem(BodyPart
);
7151 else BodyPart
->SendToHell();
7156 if (TorsoDisappeared
) {
7158 Corpse
->RemoveFromSlot();
7159 Corpse
->SendToHell();
7161 CheckDeath(festring(Verb
) + "ed", 0, FORCE_DEATH
|DISALLOW_CORPSE
|DISALLOW_MSG
);
7164 CheckDeath(festring(Verb
) + "ed", 0, DISALLOW_MSG
);
7169 void character::SignalDisappearance () {
7170 if (GetMotherEntity()) GetMotherEntity()->SignalDisappearance();
7171 else Disappear(0, "disappear", &item::IsVeryCloseToDisappearance
);
7175 truth
character::HornOfFearWorks () const {
7176 return CanHear() && GetPanicLevel() > RAND()%33 && !StateIsActivated(FEARLESS
);
7180 void character::BeginLeprosy () {
7181 doforbodypartswithparam
<truth
>()(this, &bodypart::SetIsInfectedByLeprosy
, true);
7185 void character::EndLeprosy () {
7186 doforbodypartswithparam
<truth
>()(this, &bodypart::SetIsInfectedByLeprosy
, false);
7190 truth
character::IsSameAs (ccharacter
*What
) const {
7191 return What
->GetType() == GetType() && What
->GetConfig() == GetConfig();
7195 feuLong
character::GetCommandFlags () const {
7196 return !StateIsActivated(PANIC
) ? CommandFlags
: CommandFlags
|FLEE_FROM_ENEMIES
;
7200 feuLong
character::GetConstantCommandFlags () const {
7201 return !StateIsActivated(PANIC
) ? DataBase
->ConstantCommandFlags
: DataBase
->ConstantCommandFlags
|FLEE_FROM_ENEMIES
;
7205 feuLong
character::GetPossibleCommandFlags () const {
7206 int Int
= GetAttribute(INTELLIGENCE
);
7207 feuLong Flags
= ALL_COMMAND_FLAGS
;
7208 if (!CanMove() || Int
< 4) Flags
&= ~FOLLOW_LEADER
;
7209 if (!CanMove() || Int
< 6) Flags
&= ~FLEE_FROM_ENEMIES
;
7210 if (!CanUseEquipment() || Int
< 8) Flags
&= ~DONT_CHANGE_EQUIPMENT
;
7211 if (!UsesNutrition() || Int
< 8) Flags
&= ~DONT_CONSUME_ANYTHING_VALUABLE
;
7216 truth
character::IsRetreating () const {
7217 return StateIsActivated(PANIC
) || (CommandFlags
& FLEE_FROM_ENEMIES
&& IsPet());
7221 truth
character::ChatMenu () {
7222 if (GetAction() && !GetAction()->CanBeTalkedTo()) {
7223 ADD_MESSAGE("%s is silent.", CHAR_DESCRIPTION(DEFINITE
));
7224 PLAYER
->EditAP(-200);
7227 feuLong ManagementFlags
= GetManagementFlags();
7228 if (ManagementFlags
== CHAT_IDLY
|| !IsPet()) return ChatIdly();
7229 static cchar
*const ChatMenuEntry
[CHAT_MENU_ENTRIES
] = {
7236 static const petmanagementfunction PMF
[CHAT_MENU_ENTRIES
] = {
7237 &character::ChangePetEquipment
,
7238 &character::TakePetItems
,
7239 &character::GivePetItems
,
7240 &character::IssuePetCommands
,
7241 &character::ChatIdly
7243 felist
List(CONST_S("Choose action:"));
7244 game::SetStandardListAttributes(List
);
7245 List
.AddFlags(SELECTABLE
);
7247 for (c
= 0; c
< CHAT_MENU_ENTRIES
; ++c
) if (1 << c
& ManagementFlags
) List
.AddEntry(ChatMenuEntry
[c
], LIGHT_GRAY
);
7248 int Chosen
= List
.Draw();
7249 if (Chosen
& FELIST_ERROR_BIT
) return false;
7250 for (c
= 0, i
= 0; c
< CHAT_MENU_ENTRIES
; ++c
) {
7251 if (1 << c
& ManagementFlags
&& i
++ == Chosen
) return (this->*PMF
[c
])();
7253 return false; // dummy
7257 truth
character::ChangePetEquipment () {
7258 if (EquipmentScreen(PLAYER
->GetStack(), GetStack())) {
7266 truth
character::TakePetItems () {
7267 truth Success
= false;
7268 stack::SetSelected(0);
7271 game::DrawEverythingNoBlit();
7272 GetStack()->DrawContents(
7276 CONST_S("What do you want to take from ") + CHAR_DESCRIPTION(DEFINITE
) + '?',
7279 GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState(),
7280 GetVerbalBurdenStateColor(),
7282 if (ToTake
.empty()) break;
7283 for (uInt c
= 0; c
< ToTake
.size(); ++c
) ToTake
[c
]->MoveTo(PLAYER
->GetStack());
7284 ADD_MESSAGE("You take %s.", ToTake
[0]->GetName(DEFINITE
, ToTake
.size()).CStr());
7289 PLAYER
->DexterityAction(2);
7295 truth
character::GivePetItems () {
7296 truth Success
= false;
7297 stack::SetSelected(0);
7300 game::DrawEverythingNoBlit();
7301 PLAYER
->GetStack()->DrawContents(
7305 CONST_S("What do you want to give to ") + CHAR_DESCRIPTION(DEFINITE
) + '?',
7308 GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState(),
7309 GetVerbalBurdenStateColor(),
7311 if (ToGive
.empty()) break;
7312 for (uInt c
= 0; c
< ToGive
.size(); ++c
) ToGive
[c
]->MoveTo(GetStack());
7313 ADD_MESSAGE("You give %s to %s.", ToGive
[0]->GetName(DEFINITE
, ToGive
.size()).CStr(), CHAR_DESCRIPTION(DEFINITE
));
7318 PLAYER
->DexterityAction(2);
7324 truth
character::IssuePetCommands () {
7325 if (!IsConscious()) {
7326 ADD_MESSAGE("%s is unconscious.", CHAR_DESCRIPTION(DEFINITE
));
7329 feuLong PossibleC
= GetPossibleCommandFlags();
7331 ADD_MESSAGE("%s cannot be commanded.", CHAR_DESCRIPTION(DEFINITE
));
7334 feuLong OldC
= GetCommandFlags();
7335 feuLong NewC
= OldC
, VaryFlags
= 0;
7336 game::CommandScreen(CONST_S("Issue commands to ")+GetDescription(DEFINITE
), PossibleC
, GetConstantCommandFlags(), VaryFlags
, NewC
);
7337 if (NewC
== OldC
) return false;
7338 SetCommandFlags(NewC
);
7339 PLAYER
->EditAP(-500);
7340 PLAYER
->EditExperience(CHARISMA
, 25, 1 << 7);
7345 truth
character::ChatIdly () {
7346 if (!TryToTalkAboutScience()) {
7348 PLAYER
->EditExperience(CHARISMA
, 75, 1 << 7);
7350 PLAYER
->EditAP(-1000);
7355 int character::HasSomethingToEquipAt (int chosen
, truth equippedIsTrue
) {
7356 if (!GetBodyPartOfEquipment(chosen
)) return 0;
7358 item
*oldEquipment
= GetEquipment(chosen
);
7359 if (!IsPlayer() && oldEquipment
&& BoundToUse(oldEquipment
, chosen
)) return 0;
7361 stack
*mainStack
= GetStack();
7362 sorter Sorter
= EquipmentSorter(chosen
);
7363 auto count
= mainStack
->SortedItemsCount(this, Sorter
);
7365 if (equippedIsTrue
&& oldEquipment
) ++count
;
7371 // returns 0, 1 or pickup time
7372 feuLong
character::HasSomethingToEquipAtRecentTime (int chosen
, truth equippedIsTrue
) {
7373 if (!GetBodyPartOfEquipment(chosen
)) return 0;
7375 item
*oldEquipment
= GetEquipment(chosen
);
7376 if (!IsPlayer() && oldEquipment
&& BoundToUse(oldEquipment
, chosen
)) return 0;
7378 stack
*mainStack
= GetStack();
7379 sorter Sorter
= EquipmentSorter(chosen
);
7380 feuLong highestTime
= mainStack
->SortedItemsRecentTime(this, Sorter
);
7382 if (equippedIsTrue
&& oldEquipment
&& oldEquipment
->pickupTime
> highestTime
) highestTime
= oldEquipment
->pickupTime
;
7388 truth
character::EquipmentScreen (stack
*MainStack
, stack
*SecStack
) {
7389 if (!CanUseEquipment()) {
7390 ADD_MESSAGE("%s cannot use equipment.", CHAR_DESCRIPTION(DEFINITE
));
7394 truth EquipmentChanged
= false;
7395 felist
List(CONST_S("Equipment menu [ESC exits]"));
7399 List
.EmptyDescription();
7401 List
.AddDescription(CONST_S(""));
7402 List
.AddDescription(festring(GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState()).CapitalizeCopy(), GetVerbalBurdenStateColor());
7404 int totalWeight
= 0;
7405 for (int c
= 0; c
< GetEquipments(); ++c
) {
7406 item
*Equipment
= GetEquipment(c
);
7407 totalWeight
+= (Equipment
? Equipment
->GetWeight() : 0);
7409 festring
Total("Total weight: ");
7410 Total
<< totalWeight
;
7412 List
.AddDescription(CONST_S(""));
7413 List
.AddDescription(Total
);
7415 int firstEmpty
= -1, firstNonEmpty
= -1, selected
= -1;
7416 feuLong selPickTime
= 1;
7417 feuLong armPickTime
= 0;
7419 //truth selectedIsEmpty = false;
7420 for (int c
= 0; c
< GetEquipments(); ++c
) {
7421 truth isArm
= (c
== 5 || c
== 6);
7422 //int bpidx = (GetBodyPartOfEquipment(c) ? GetBodyPartOfEquipment(c)->GetBodyPartIndex() : -1);
7423 truth equippable
= !!GetBodyPartOfEquipment(c
);
7424 Entry
= GetEquipmentName(c
);
7427 item
*Equipment
= GetEquipment(c
);
7428 feuLong pickTm
= (equippable
? HasSomethingToEquipAtRecentTime(c
, false) : 0);
7429 int availEquipCount
= (equippable
? HasSomethingToEquipAt(c
, false) : 0);
7430 if (pickTm
> 1 && game::GetTick()-pickTm
> game::PickTimeout
) pickTm
= 0;
7431 //fprintf(stderr, "c=%d; equippable=%d; availcount=%d; pickTm=%u; tick=%u\n", c, (int)equippable, availEquipCount, pickTm, game::GetTick());
7433 Equipment
->AddInventoryEntry(this, Entry
, 1, true);
7434 AddSpecialEquipmentInfo(Entry
, c
);
7435 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
7436 if (firstNonEmpty
< 0 && equippable
&& !isArm
&& availEquipCount
> 0) firstNonEmpty
= c
;
7438 if (availEquipCount
> 0 && isArm
&& armPickTime
< pickTm
) { armFirst
= c
; armPickTime
= pickTm
; }
7439 if (selPickTime
< pickTm
&& equippable
&& !isArm
) { selected
= c
; selPickTime
= pickTm
; }
7441 List
.AddEntry(Entry
, (availEquipCount
? ORANGE
: LIGHT_GRAY
), 20, ImageKey
, true);
7443 truth canUse
= !!GetBodyPartOfEquipment(c
);
7444 Entry
<< (canUse
? "-" : "can't use");
7446 if (canUse
&& availEquipCount
> 0) {
7447 if (firstEmpty
< 0 && equippable
&& !isArm
&& availEquipCount
> 0) firstEmpty
= c
;
7448 if (equippable
&& isArm
&& armFirst
< 0) armFirst
= c
;
7449 switch (availEquipCount
) {
7450 case 0: color
= RED
; break;
7451 case 1: color
= LIGHT_GRAY
; break;
7452 default: color
= ORANGE
; break;
7455 if (color
!= RED
&& equippable
) {
7456 if (pickTm
> selPickTime
&& !isArm
) { selected
= c
; selPickTime
= pickTm
; }
7458 List
.AddEntry(Entry
, color
, 20, game::AddToItemDrawVector(itemvector()));
7461 game::DrawEverythingNoBlit();
7462 game::SetStandardListAttributes(List
);
7464 //fprintf(stderr, " selected=%d; firstEmpty=%d; firstNonEmpty=%d; armFirst=%d; armPickTime=%u\n", selected, firstEmpty, firstNonEmpty, armFirst, armPickTime);
7466 if (armPickTime
> 0) selected
= armFirst
;
7467 else if (firstEmpty
>= 0) selected
= firstEmpty
;
7468 else if (firstNonEmpty
>= 0) selected
= firstNonEmpty
;
7470 // 7: right ring; switch to left ring if that slot has nothing in it
7471 if (selected
== 7 && GetBodyPartOfEquipment(8) && !GetEquipment(8)) selected
= 8;
7472 if (selected
>= 0) List
.SetSelected(selected
);
7474 List
.SetFlags(SELECTABLE
|DRAW_BACKGROUND_AFTERWARDS
);
7475 List
.SetEntryDrawer(game::ItemEntryDrawer
);
7476 Chosen
= List
.Draw();
7477 game::ClearItemDrawVector();
7478 if (Chosen
>= GetEquipments()) break;
7479 EquipmentChanged
= TryToChangeEquipment(MainStack
, SecStack
, Chosen
);
7481 if (EquipmentChanged
) DexterityAction(5);
7482 return EquipmentChanged
;
7486 feuLong
character::GetManagementFlags () const {
7487 feuLong Flags
= ALL_MANAGEMENT_FLAGS
;
7488 if (!CanUseEquipment() || !AllowPlayerToChangeEquipment()) Flags
&= ~CHANGE_EQUIPMENT
;
7489 if (!GetStack()->GetItems()) Flags
&= ~TAKE_ITEMS
;
7490 if (!WillCarryItems()) Flags
&= ~GIVE_ITEMS
;
7491 if (!GetPossibleCommandFlags()) Flags
&= ~ISSUE_COMMANDS
;
7496 cchar
*VerbalBurdenState
[] = { "overloaded", "stressed", "burdened", "unburdened" };
7497 col16 VerbalBurdenStateColor
[] = { RED
, BLUE
, BLUE
, WHITE
};
7499 cchar
*character::GetVerbalBurdenState () const { return VerbalBurdenState
[BurdenState
]; }
7500 col16
character::GetVerbalBurdenStateColor () const { return VerbalBurdenStateColor
[BurdenState
]; }
7501 int character::GetAttributeAverage () const { return GetSumOfAttributes()/7; }
7503 cfestring
&character::GetStandVerb() const {
7504 if (ForceCustomStandVerb()) return DataBase
->StandVerb
;
7505 static festring Hovering
= "hovering";
7506 static festring Swimming
= "swimming";
7507 if (StateIsActivated(LEVITATION
)) return Hovering
;
7508 if (IsSwimming()) return Swimming
;
7509 return DataBase
->StandVerb
;
7513 truth
character::CheckApply () const {
7515 ADD_MESSAGE("This monster type cannot apply.");
7522 void character::EndLevitation () {
7523 if (!IsFlying() && GetSquareUnder()) {
7524 if (!game::IsInWilderness()) SignalStepFrom(0);
7525 if (game::IsInWilderness() || !GetLSquareUnder()->IsFreezed()) TestWalkability();
7530 truth
character::CanMove () const {
7531 return !IsRooted() || StateIsActivated(LEVITATION
);
7535 void character::CalculateEnchantments () {
7536 doforequipments()(this, &item::CalculateEnchantment
);
7537 GetStack()->CalculateEnchantments();
7541 truth
character::GetNewFormForPolymorphWithControl (character
*&NewForm
) {
7542 if (StateIsActivated(POLYMORPH_LOCK
)) { ADD_MESSAGE("You feel uncertain about your body for a moment."); return false; }
7543 festring Topic
, Temp
;
7546 festring Temp
= game::DefaultQuestion(CONST_S("What do you want to become? [press '?' for a list]"), game::GetDefaultPolymorphTo(), &game::PolymorphControlKeyHandler
);
7547 NewForm
= protosystem::CreateMonster(Temp
);
7549 if (NewForm
->IsSameAs(this)) {
7551 ADD_MESSAGE("You choose not to polymorph.");
7555 if (PolymorphBackup
&& NewForm
->IsSameAs(PolymorphBackup
)) {
7557 NewForm
= ForceEndPolymorph();
7560 if (NewForm
->GetPolymorphIntelligenceRequirement() > GetAttribute(INTELLIGENCE
) && !game::WizardModeIsActive()) {
7561 ADD_MESSAGE("You feel your mind isn't yet powerful enough to call forth the form of %s.", NewForm
->CHAR_NAME(INDEFINITE
));
7565 NewForm
->RemoveAllItems();
7573 liquid
*character::CreateSweat(sLong Volume
) const {
7574 //return liquid::Spawn(GetSweatMaterial(), Volume);
7575 return liquid::Spawn(GetCurrentSweatMaterial(), Volume
);
7579 truth
character::TeleportRandomItem (truth TryToHinderVisibility
) {
7580 if (IsImmuneToItemTeleport() || StateIsActivated(TELEPORT_LOCK
)) return false;
7581 itemvector ItemVector
;
7582 std::vector
<sLong
> PossibilityVector
;
7583 int TotalPossibility
= 0;
7584 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
7585 ItemVector
.push_back(*i
);
7586 int Possibility
= i
->GetTeleportPriority();
7587 if (TryToHinderVisibility
) Possibility
+= i
->GetHinderVisibilityBonus(this);
7588 PossibilityVector
.push_back(Possibility
);
7589 TotalPossibility
+= Possibility
;
7591 for (int c
= 0; c
< GetEquipments(); ++c
) {
7592 item
*Equipment
= GetEquipment(c
);
7594 ItemVector
.push_back(Equipment
);
7595 int Possibility
= Equipment
->GetTeleportPriority();
7596 if (TryToHinderVisibility
) Possibility
+= Equipment
->GetHinderVisibilityBonus(this);
7597 PossibilityVector
.push_back(Possibility
<<= 1);
7598 TotalPossibility
+= Possibility
;
7601 if (!TotalPossibility
) return false;
7602 int Chosen
= femath::WeightedRand(PossibilityVector
, TotalPossibility
);
7603 item
*Item
= ItemVector
[Chosen
];
7604 truth Equipped
= PLAYER
->Equips(Item
);
7605 truth Seen
= Item
->CanBeSeenByPlayer();
7606 Item
->RemoveFromSlot();
7607 if (Seen
) ADD_MESSAGE("%s disappears.", Item
->CHAR_NAME(DEFINITE
));
7608 if (Equipped
) game::AskForEscPress(CONST_S("Equipment lost!"));
7610 int Range
= Item
->GetEmitation() && TryToHinderVisibility
? 25 : 5;
7611 rect
Border(Pos
+ v2(-Range
, -Range
), Pos
+ v2(Range
, Range
));
7612 Pos
= GetLevel()->GetRandomSquare(this, 0, &Border
);
7613 if (Pos
== ERROR_V2
) Pos
= GetLevel()->GetRandomSquare();
7614 GetNearLSquare(Pos
)->GetStack()->AddItem(Item
);
7615 if (Item
->CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", Item
->CHAR_NAME(INDEFINITE
));
7620 truth
character::HasClearRouteTo (v2 Pos
) const {
7621 pathcontroller::Map
= GetLevel()->GetMap();
7622 pathcontroller::Character
= this;
7623 v2 ThisPos
= GetPos();
7624 return mapmath
<pathcontroller
>::DoLine(ThisPos
.X
, ThisPos
.Y
, Pos
.X
, Pos
.Y
, SKIP_FIRST
);
7628 truth
character::IsTransparent () const {
7629 return !IsEnormous() || GetTorso()->GetMainMaterial()->IsTransparent() || StateIsActivated(INVISIBLE
);
7633 void character::SignalPossibleTransparencyChange () {
7634 if (!game::IsInWilderness()) {
7635 for (int c
= 0; c
< SquaresUnder
; ++c
) {
7636 lsquare
*Square
= GetLSquareUnder(c
);
7637 if (Square
) Square
->SignalPossibleTransparencyChange();
7643 int character::GetCursorData () const {
7645 int Color
= (game::PlayerIsRunning() ? BLUE_CURSOR
: DARK_CURSOR
);
7646 for (int c
= 0; c
< BodyParts
; ++c
) {
7647 bodypart
*BodyPart
= GetBodyPart(c
);
7648 if (BodyPart
&& BodyPart
->IsUsable()) {
7649 int ConditionColorIndex
= BodyPart
->GetConditionColorIndex();
7650 if ((BodyPartIsVital(c
) && !ConditionColorIndex
) || (ConditionColorIndex
<= 1 && ++Bad
== 2)) return Color
|CURSOR_FLASH
;
7651 } else if (++Bad
== 2) {
7652 return Color
|CURSOR_FLASH
;
7655 Color
= (game::PlayerIsRunning() ? YELLOW_CURSOR
: RED_CURSOR
);
7656 return (Bad
? Color
|CURSOR_FLASH
: Color
);
7660 void character::TryToName () {
7661 if (!IsPet()) ADD_MESSAGE("%s refuses to let YOU decide what %s's called.", CHAR_NAME(DEFINITE
), CHAR_PERSONAL_PRONOUN
);
7662 else if (IsPlayer()) ADD_MESSAGE("You can't rename yourself.");
7663 else if (!IsNameable()) ADD_MESSAGE("%s refuses to be called anything else but %s.", CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
));
7665 festring Topic
= CONST_S("What name will you give to ")+GetName(DEFINITE
)+'?';
7666 festring Name
= game::StringQuestion(Topic
, WHITE
, 0, 80, true);
7667 if (Name
.GetSize()) SetAssignedName(Name
);
7672 double character::GetSituationDanger (ccharacter
*Enemy
, v2 ThisPos
, v2 EnemyPos
, truth SeesEnemy
) const {
7674 if (IgnoreDanger() && !IsPlayer()) {
7675 if (Enemy
->IgnoreDanger() && !Enemy
->IsPlayer()) {
7676 Danger
= double(GetHP())*GetHPRequirementForGeneration()/(Enemy
->GetHP()*Enemy
->GetHPRequirementForGeneration());
7679 Danger
= 0.25*GetHPRequirementForGeneration()/Enemy
->GetHP();
7681 } else if (Enemy
->IgnoreDanger() && !Enemy
->IsPlayer()) {
7682 Danger
= 4.0*GetHP()/Enemy
->GetHPRequirementForGeneration();
7684 Danger
= GetRelativeDanger(Enemy
);
7686 Danger
*= 3.0/((EnemyPos
-ThisPos
).GetManhattanLength()+2);
7687 if (!SeesEnemy
) Danger
*= 0.2;
7688 if (StateIsActivated(PANIC
)) Danger
*= 0.2;
7689 Danger
*= double(GetHP())*Enemy
->GetMaxHP()/(Enemy
->GetHP()*GetMaxHP());
7694 void character::ModifySituationDanger (double &Danger
) const {
7695 switch (GetTirednessState()) {
7696 case FAINTING
: Danger
*= 1.5;
7697 case EXHAUSTED
: Danger
*= 1.25;
7699 for (int c
= 0; c
< STATES
; ++c
) {
7700 if (StateIsActivated(1 << c
) && StateData
[c
].SituationDangerModifier
!= 0) (this->*StateData
[c
].SituationDangerModifier
)(Danger
);
7705 void character::LycanthropySituationDangerModifier (double &Danger
) const {
7706 character
*Wolf
= werewolfwolf::Spawn();
7707 double DangerToWolf
= GetRelativeDanger(Wolf
);
7708 Danger
*= pow(DangerToWolf
, 0.1);
7713 void character::PoisonedSituationDangerModifier (double &Danger
) const {
7714 int C
= GetTemporaryStateCounter(POISONED
);
7715 Danger
*= (1+(C
*C
)/(GetHP()*10000.0*(GetGlobalResistance(POISON
)+1)));
7719 void character::PolymorphingSituationDangerModifier (double &Danger
) const {
7720 if ((!StateIsActivated(POLYMORPH_CONTROL
)) && (!StateIsActivated(POLYMORPH_LOCK
))) Danger
*= 1.5;
7724 void character::PanicSituationDangerModifier (double &Danger
) const {
7729 void character::ConfusedSituationDangerModifier (double &Danger
) const {
7734 void character::ParasitizedSituationDangerModifier (double &Danger
) const {
7739 void character::LeprosySituationDangerModifier (double &Danger
) const {
7744 void character::AddRandomScienceName (festring
&String
) const {
7745 festring Science
= GetScienceTalkName().GetRandomElement().CStr();
7746 if (Science
[0] == '!') {
7747 String
<< Science
.CStr()+1;
7750 festring Attribute
= GetScienceTalkAdjectiveAttribute().GetRandomElement();
7752 truth NoAttrib
= Attribute
.IsEmpty(), NoSecondAdjective
= false;
7753 if (!Attribute
.IsEmpty() && Attribute
[0] == '!') {
7754 NoSecondAdjective
= true;
7755 Attribute
.Erase(0, 1);
7757 if (!Science
.Find("the ")) {
7758 Science
.Erase(0, 4);
7759 if (!Attribute
.Find("the ", 0, 4)) Attribute
<< " the"; else Attribute
.Insert(0, "the ", 4);
7761 if (islower(Science
[0]) && Science
.Find(' ') == festring::NPos
&& Science
.Find('-') == festring::NPos
&&
7762 Science
.Find("phobia") == festring::NPos
) {
7763 Prefix
= GetScienceTalkPrefix().GetRandomElement();
7764 if (!Prefix
.IsEmpty() && Science
.Find(Prefix
) != festring::NPos
) Prefix
.Empty();
7766 int L
= Prefix
.GetSize();
7767 if (L
&& Prefix
[L
-1] == Science
[0]) Science
.Erase(0, 1);
7768 if (!NoAttrib
&& !NoSecondAdjective
== !RAND_GOOD(3)) {
7769 int S1
= NoSecondAdjective
? 0 : GetScienceTalkAdjectiveAttribute().Size
;
7770 int S2
= GetScienceTalkSubstantiveAttribute().Size
;
7771 festring OtherAttribute
;
7772 int Chosen
= RAND_GOOD(S1
+S2
);
7773 if (Chosen
< S1
) OtherAttribute
= GetScienceTalkAdjectiveAttribute()[Chosen
];
7774 else OtherAttribute
= GetScienceTalkSubstantiveAttribute()[Chosen
- S1
];
7775 if (!OtherAttribute
.IsEmpty() && OtherAttribute
.Find("the ", 0, 4) && Attribute
.Find(OtherAttribute
) == festring::NPos
) {
7776 String
<< Attribute
<< ' ' << OtherAttribute
<< ' ' << Prefix
<< Science
;
7780 String
<< Attribute
;
7781 if (!NoAttrib
) String
<< ' ';
7782 String
<< Prefix
<< Science
;
7786 truth
character::TryToTalkAboutScience () {
7787 if (GetRelation(PLAYER
) == HOSTILE
||
7788 GetScienceTalkPossibility() <= RAND_GOOD(100) ||
7789 PLAYER
->GetAttribute(INTELLIGENCE
) < GetScienceTalkIntelligenceRequirement() ||
7790 PLAYER
->GetAttribute(WISDOM
) < GetScienceTalkWisdomRequirement() ||
7791 PLAYER
->GetAttribute(CHARISMA
) < GetScienceTalkCharismaRequirement())
7795 AddRandomScienceName(Science
);
7798 AddRandomScienceName(S1
);
7799 AddRandomScienceName(S2
);
7800 if (S1
.Find(S2
) == festring::NPos
&& S2
.Find(S1
) == festring::NPos
) {
7801 switch (RAND_GOOD(3)) {
7802 case 0: Science
= "the relation of "; break;
7803 case 1: Science
= "the differences of "; break;
7804 case 2: Science
= "the similarities of "; break;
7806 Science
<< S1
<< " and " << S2
;
7809 AddRandomScienceName(Science
);
7812 switch ((RAND() + GET_TICK()) % 10) {
7814 ADD_MESSAGE("You have a rather pleasant chat about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7817 ADD_MESSAGE("%s explains a few of %s opinions regarding %s to you.", CHAR_DESCRIPTION(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, Science
.CStr());
7820 ADD_MESSAGE("%s reveals a number of %s insightful views of %s to you.", CHAR_DESCRIPTION(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, Science
.CStr());
7823 ADD_MESSAGE("You exchange some information pertaining to %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7826 ADD_MESSAGE("You engage in a pretty intriguing conversation about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7829 ADD_MESSAGE("You discuss at length about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7832 ADD_MESSAGE("You have a somewhat boring talk concerning %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7835 ADD_MESSAGE("You are drawn into a heated argument regarding %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7838 ADD_MESSAGE("%s delivers a long monologue concerning eg. %s.", CHAR_DESCRIPTION(DEFINITE
), Science
.CStr());
7841 ADD_MESSAGE("You dive into a brief but thought-provoking debate over %s with %s", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7844 PLAYER
->EditExperience(INTELLIGENCE
, 1000, 50. * GetScienceTalkIntelligenceModifier() / ++ScienceTalks
);
7845 PLAYER
->EditExperience(WISDOM
, 1000, 50. * GetScienceTalkWisdomModifier() / ++ScienceTalks
);
7846 PLAYER
->EditExperience(CHARISMA
, 1000, 50. * GetScienceTalkCharismaModifier() / ++ScienceTalks
);
7851 truth
character::IsUsingWeaponOfCategory (int Category
) const {
7853 ((GetMainWielded() && GetMainWielded()->GetWeaponCategory() == Category
) ||
7854 (GetSecondaryWielded() && GetSecondaryWielded()->GetWeaponCategory() == Category
));
7858 truth
character::TryToUnStickTraps (v2 Dir
) {
7859 if (!TrapData
) return true;
7860 std::vector
<trapdata
> TrapVector
;
7861 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) TrapVector
.push_back(*TrapData
);
7862 for (uInt c
= 0; c
< TrapVector
.size(); ++c
) {
7864 entity
*Trap
= game::SearchTrap(TrapVector
[c
].TrapID
);
7865 /*k8:??? if(!Trap->Exists()) int esko = esko = 2; */
7866 if (!Trap
->Exists()) continue; /*k8: ??? added by me; what this means? */
7867 if (Trap
->GetVictimID() == GetID() && Trap
->TryToUnStick(this, Dir
)) break;
7870 return !TrapData
&& IsEnabled();
7874 struct trapidcomparer
{
7875 trapidcomparer (feuLong ID
) : ID(ID
) {}
7876 truth
operator () (const trapdata
*T
) const { return T
->TrapID
== ID
; }
7881 void character::RemoveTrap (feuLong ID
) {
7882 trapdata
*&T
= ListFind(TrapData
, trapidcomparer(ID
));
7884 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7888 void character::AddTrap (feuLong ID
, feuLong BodyParts
) {
7889 trapdata
*&T
= ListFind(TrapData
, trapidcomparer(ID
));
7890 if (T
) T
->BodyParts
|= BodyParts
;
7891 else T
= new trapdata(ID
, GetID(), BodyParts
);
7892 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7896 truth
character::IsStuckToTrap (feuLong ID
) const {
7897 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) if (T
->TrapID
== ID
) return true;
7902 void character::RemoveTraps () {
7903 for (trapdata
*T
= TrapData
; T
; T
= T
->Next
) {
7904 entity
*Trap
= game::SearchTrap(T
->TrapID
);
7905 if (Trap
) Trap
->UnStick();
7907 deleteList(TrapData
);
7908 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7912 void character::RemoveTraps (int BodyPartIndex
) {
7913 feuLong Flag
= 1 << BodyPartIndex
;
7914 for (trapdata
**T
= &TrapData
; *T
;) {
7915 if ((*T
)->BodyParts
& Flag
) {
7916 entity
*Trap
= game::SearchTrap((*T
)->TrapID
);
7917 if (!((*T
)->BodyParts
&= ~Flag
)) {
7918 if (Trap
) Trap
->UnStick();
7919 trapdata
*ToDel
= *T
;
7923 if (Trap
) Trap
->UnStick(BodyPartIndex
);
7931 if (GetBodyPart(BodyPartIndex
)) GetBodyPart(BodyPartIndex
)->SignalPossibleUsabilityChange();
7935 festring
character::GetTrapDescription () const {
7937 std::pair
<entity
*, int> TrapStack
[3];
7939 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) {
7941 entity
*Trap
= game::SearchTrap(T
->TrapID
);
7944 for (c
= 0; c
< Index
; ++c
) if (TrapStack
[c
].first
->GetTrapType() == Trap
->GetTrapType()) ++TrapStack
[c
].second
;
7945 if (c
== Index
) TrapStack
[Index
++] = std::make_pair(Trap
, 1);
7953 TrapStack
[0].first
->AddTrapName(Desc
, TrapStack
[0].second
);
7956 TrapStack
[1].first
->AddTrapName(Desc
, TrapStack
[1].second
);
7957 } else if (Index
== 3) {
7959 TrapStack
[1].first
->AddTrapName(Desc
, TrapStack
[1].second
);
7961 TrapStack
[2].first
->AddTrapName(Desc
, TrapStack
[2].second
);
7964 Desc
<< "lots of traps";
7970 int character::RandomizeHurtBodyPart (feuLong BodyParts
) const {
7971 int BodyPartIndex
[MAX_BODYPARTS
];
7973 for (int c
= 0; c
< GetBodyParts(); ++c
) {
7974 if (1 << c
& BodyParts
) {
7975 /*k8: ??? if(!GetBodyPart(c)) int esko = esko = 2; */
7976 if (!GetBodyPart(c
)) continue;
7977 BodyPartIndex
[Index
++] = c
;
7979 /*k8: ??? if(!Index) int esko = esko = 2;*/
7982 fprintf(stderr
, "FATAL: RandomizeHurtBodyPart -- Index==0\n");
7985 return BodyPartIndex
[RAND_N(Index
)];
7989 truth
character::BodyPartIsStuck (int I
) const {
7990 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) if (1 << I
& T
->BodyParts
) return true;
7995 void character::PrintAttribute (cchar
*Desc
, int I
, int PanelPosX
, int PanelPosY
) const {
7996 int Attribute
= GetAttribute(I
);
7997 int NoBonusAttribute
= GetAttribute(I
, false);
7998 col16 C
= game::GetAttributeColor(I
);
7999 festring String
= Desc
;
8001 String
<< Attribute
;
8003 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
* 10), C
, "%s", String
.CStr());
8004 if (Attribute
!= NoBonusAttribute
) {
8005 int Where
= PanelPosX
+ ((String
.GetSize() + 1) << 3);
8006 FONT
->Printf(DOUBLE_BUFFER
, v2(Where
, PanelPosY
* 10), LIGHT_GRAY
, "%d", NoBonusAttribute
);
8011 truth
character::AllowUnconsciousness () const {
8012 return DataBase
->AllowUnconsciousness
&& TorsoIsAlive();
8016 truth
character::CanPanic () const {
8017 return !Action
|| !Action
->IsUnconsciousness() || !StateIsActivated(FEARLESS
);
8021 int character::GetRandomBodyPart (feuLong Possible
) const {
8022 int OKBodyPart
[MAX_BODYPARTS
];
8023 int OKBodyParts
= 0;
8024 for (int c
= 0; c
< BodyParts
; ++c
) if (1 << c
& Possible
&& GetBodyPart(c
)) OKBodyPart
[OKBodyParts
++] = c
;
8025 return OKBodyParts
? OKBodyPart
[RAND_N(OKBodyParts
)] : NONE_INDEX
;
8029 void character::EditNP (sLong What
) {
8030 int OldState
= GetHungerState();
8032 int NewState
= GetHungerState();
8033 if (OldState
> VERY_HUNGRY
&& NewState
== VERY_HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
8034 if (OldState
> STARVING
&& NewState
== STARVING
) DeActivateVoluntaryAction(CONST_S("You are getting extremely hungry."));
8038 truth
character::IsSwimming () const {
8039 return !IsFlying() && GetSquareUnder() && GetSquareUnder()->GetSquareWalkability() & SWIM
;
8043 void character::AddBlackUnicornConsumeEndMessage () const {
8044 if (IsPlayer()) ADD_MESSAGE("You feel dirty and loathsome.");
8048 void character::AddGrayUnicornConsumeEndMessage () const {
8049 if (IsPlayer()) ADD_MESSAGE("You feel neutralized.");
8053 void character::AddWhiteUnicornConsumeEndMessage () const {
8054 if (IsPlayer()) ADD_MESSAGE("You feel purified.");
8058 void character::AddOmmelBoneConsumeEndMessage () const {
8059 if (IsPlayer()) ADD_MESSAGE("You feel the power of all your canine ancestors combining in your body.");
8060 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s looks extremely ferocious. You shudder.", CHAR_NAME(DEFINITE
));
8064 void character::AddLiquidHorrorConsumeEndMessage () const {
8065 if (IsPlayer()) ADD_MESSAGE("Untold horrors flash before your eyes. The melancholy of the world is on your shoulders!");
8066 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks as if the melancholy of the world is on %s shoulders!.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
8070 void character::AddAlienFleshConsumeEndMessage() const
8072 if (IsPlayer()) ADD_MESSAGE("You feel somehow sick by eating such acidic corpse...");
8073 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks like he eat something bad.", CHAR_NAME(DEFINITE
));
8077 int character::GetBodyPartSparkleFlags (int) const {
8079 ((GetNaturalSparkleFlags() & SKIN_COLOR
? SPARKLING_A
: 0) |
8080 (GetNaturalSparkleFlags() & TORSO_MAIN_COLOR
? SPARKLING_B
: 0) |
8081 (GetNaturalSparkleFlags() & TORSO_SPECIAL_COLOR
? SPARKLING_D
: 0));
8085 truth
character::IsAnimated () const {
8086 return combinebodypartpredicates()(this, &bodypart::IsAnimated
, 1);
8090 double character::GetNaturalExperience (int Identifier
) const {
8091 return DataBase
->NaturalExperience
[Identifier
];
8095 truth
character::HasBodyPart (sorter Sorter
) const {
8096 if (Sorter
== 0) return true;
8097 return combinebodypartpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1);
8101 truth
character::PossessesItem (sorter Sorter
) const {
8102 if (Sorter
== 0) return true;
8104 (GetStack()->SortedItems(this, Sorter
) ||
8105 combinebodypartpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1) ||
8106 combineequipmentpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1));
8110 truth
character::MoreThanOnePossessesItem (sorter Sorter
) const {
8114 for (int c
= 0; c
< BodyParts
; ++c
) {
8115 bodypart
*BodyPart
= GetBodyPart(c
);
8117 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) {
8118 if (++count
> 1) return true;
8121 for (int c
= 0; c
< GetEquipments(); ++c
) {
8122 item
*Equipment
= GetEquipment(c
);
8124 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) {
8125 if (++count
> 1) return true;
8128 for (int c
= 0; c
< GetStack()->GetItems(); ++c
) {
8129 item
*Stk
= GetStack()->GetItem(c
);
8131 if (Stk
&& (Sorter
== 0 || (Stk
->*Sorter
)(this))) {
8132 if (++count
> 1) return true;
8141 item
*character::FirstPossessesItem (sorter Sorter
) const {
8143 for (int c
= 0; c
< BodyParts
; ++c
) {
8144 bodypart
*BodyPart
= GetBodyPart(c
);
8146 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) return BodyPart
;
8148 for (int c
= 0; c
< GetEquipments(); ++c
) {
8149 item
*Equipment
= GetEquipment(c
);
8151 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) return Equipment
;
8153 for (int c
= 0; c
< GetStack()->GetItems(); ++c
) {
8154 item
*Stk
= GetStack()->GetItem(c
);
8156 if (Stk
&& (Sorter
== 0 || (Stk
->*Sorter
)(this))) return Stk
;
8164 cchar
*character::GetRunDescriptionLine (int I
) const {
8165 if (!GetRunDescriptionLineOne().IsEmpty()) return !I
? GetRunDescriptionLineOne().CStr() : GetRunDescriptionLineTwo().CStr();
8166 if (IsFlying()) return !I
? "Flying" : "very fast";
8167 if (IsSwimming()) return !I
? "Swimming" : "very fast";
8168 return !I
? "Running" : "";
8172 void character::VomitAtRandomDirection (int Amount
) {
8173 if (game::IsInWilderness()) return;
8174 /* Lacks support of multitile monsters */
8177 for (int d
= 0; d
< 9; ++d
) {
8178 lsquare
*Square
= GetLSquareUnder()->GetNeighbourLSquare(d
);
8179 if (Square
&& !Square
->VomitingIsDangerous(this)) Possible
[Index
++] = Square
->GetPos();
8181 if (Index
) Vomit(Possible
[RAND_N(Index
)], Amount
);
8182 else Vomit(GetPos(), Amount
);
8186 void character::RemoveLifeSavers () {
8187 for (int c
= 0; c
< GetEquipments(); ++c
) {
8188 item
*Equipment
= GetEquipment(c
);
8189 if (Equipment
&& Equipment
->IsInCorrectSlot(c
) && Equipment
->GetGearStates() & LIFE_SAVED
) {
8190 Equipment
->SendToHell();
8191 Equipment
->RemoveFromSlot();
8197 ccharacter
*character::FindCarrier () const {
8198 return this; //check
8202 void character::PrintBeginHiccupsMessage () const {
8203 if (IsPlayer()) ADD_MESSAGE("Your diaphragm is spasming vehemently.");
8207 void character::PrintEndHiccupsMessage () const {
8208 if (IsPlayer()) ADD_MESSAGE("You feel your annoying hiccoughs have finally subsided.");
8212 void character::HiccupsHandler () {
8214 if (!(RAND() % 2000)) {
8215 if (IsPlayer()) ADD_MESSAGE("");
8216 else if (CanBeSeenByPlayer()) ADD_MESSAGE("");
8217 else if ((PLAYER->GetPos()-GetPos()).GetLengthSquare() <= 400) ADD_MESSAGE("");
8218 game::CallForAttention(GetPos(), 400);
8224 void character::VampirismHandler () {
8225 //EditExperience(ARM_STRENGTH, -25, 1 << 1);
8226 //EditExperience(LEG_STRENGTH, -25, 1 << 1);
8227 //EditExperience(DEXTERITY, -25, 1 << 1);
8228 //EditExperience(AGILITY, -25, 1 << 1);
8229 //EditExperience(ENDURANCE, -25, 1 << 1);
8230 EditExperience(CHARISMA
, -25, 1 << 1);
8231 EditExperience(WISDOM
, -25, 1 << 1);
8232 EditExperience(INTELLIGENCE
, -25, 1 << 1);
8233 CheckDeath(CONST_S("killed by vampirism"));
8237 void character::HiccupsSituationDangerModifier (double &Danger
) const {
8242 void character::VampirismSituationDangerModifier (double &Danger
) const {
8243 character
*Vampire
= vampire::Spawn();
8244 double DangerToVampire
= GetRelativeDanger(Vampire
);
8245 Danger
*= pow(DangerToVampire
, 0.1);
8250 bool character::IsConscious () const {
8251 return !Action
|| !Action
->IsUnconsciousness();
8255 wsquare
*character::GetNearWSquare (v2 Pos
) const {
8256 return static_cast<wsquare
*>(GetSquareUnder()->GetArea()->GetSquare(Pos
));
8260 wsquare
*character::GetNearWSquare (int x
, int y
) const {
8261 return static_cast<wsquare
*>(GetSquareUnder()->GetArea()->GetSquare(x
, y
));
8265 void character::ForcePutNear (v2 Pos
) {
8266 /* GUM SOLUTION!!! */
8267 v2 NewPos
= game::GetCurrentLevel()->GetNearestFreeSquare(PLAYER
, Pos
, false);
8268 if (NewPos
== ERROR_V2
) do { NewPos
= game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos
== Pos
);
8273 void character::ReceiveMustardGas (int BodyPart
, sLong Volume
) {
8274 if (Volume
) GetBodyPart(BodyPart
)->AddFluid(liquid::Spawn(MUSTARD_GAS_LIQUID
, Volume
), CONST_S("skin"), 0, true);
8278 void character::ReceiveMustardGasLiquid (int BodyPartIndex
, sLong Modifier
) {
8279 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
8280 if (BodyPart
->GetMainMaterial()->GetInteractionFlags() & IS_AFFECTED_BY_MUSTARD_GAS
) {
8281 sLong Tries
= Modifier
;
8282 Modifier
-= Tries
; //opt%?
8284 for (sLong c
= 0; c
< Tries
; ++c
) if (!(RAND() % 100)) ++Damage
;
8285 if (Modifier
&& !(RAND() % 1000 / Modifier
)) ++Damage
;
8287 feuLong Minute
= game::GetTotalMinutes();
8288 if (GetLastAcidMsgMin() != Minute
&& (CanBeSeenByPlayer() || IsPlayer())) {
8289 SetLastAcidMsgMin(Minute
);
8290 if (IsPlayer()) ADD_MESSAGE("Mustard gas dissolves the skin of your %s.", BodyPart
->GetBodyPartName().CStr());
8291 else ADD_MESSAGE("Mustard gas dissolves %s.", CHAR_NAME(DEFINITE
));
8293 ReceiveBodyPartDamage(0, Damage
, MUSTARD_GAS_DAMAGE
, BodyPartIndex
, YOURSELF
, false, false, false);
8294 CheckDeath(CONST_S("killed by a fatal exposure to mustard gas"));
8300 truth
character::IsBadPath (v2 Pos
) const {
8301 if (!IsGoingSomeWhere()) return false;
8302 v2 TPos
= !Route
.empty() ? Route
.back() : GoingTo
;
8303 return ((TPos
- Pos
).GetManhattanLength() > (TPos
- GetPos()).GetManhattanLength());
8307 double &character::GetExpModifierRef (expid E
) {
8308 return ExpModifierMap
.insert(std::make_pair(E
, 1.)).first
->second
;
8312 /* Should probably do more. Now only makes Player forget gods */
8313 truth
character::ForgetRandomThing () {
8315 /* hopefully this code isn't some where else */
8316 std::vector
<god
*> Known
;
8317 for (int c
= 1; c
<= GODS
; ++c
) if (game::GetGod(c
)->IsKnown()) Known
.push_back(game::GetGod(c
));
8318 if (Known
.empty()) return false;
8319 int RandomGod
= RAND_N(Known
.size());
8320 Known
.at(RAND_N(Known
.size()))->SetIsKnown(false);
8321 ADD_MESSAGE("You forget how to pray to %s.", Known
.at(RandomGod
)->GetName());
8328 int character::CheckForBlock (character
*Enemy
, item
*Weapon
, double ToHitValue
, int Damage
, int Success
, int Type
) {
8333 void character::ApplyAllGodsKnownBonus () {
8334 stack
*AddPlace
= GetStackUnder();
8335 if (game::IsInWilderness()) AddPlace
= GetStack(); else AddPlace
= GetStackUnder();
8336 pantheonbook
*NewBook
= pantheonbook::Spawn();
8337 AddPlace
->AddItem(NewBook
);
8338 ADD_MESSAGE("\"MORTAL! BEHOLD THE HOLY SAGA\"");
8339 ADD_MESSAGE("%s materializes near your feet.", NewBook
->CHAR_NAME(INDEFINITE
));
8343 void character::ReceiveSirenSong (character
*Siren
) {
8344 if (Siren
->GetTeam() == GetTeam()) return;
8346 if (IsPlayer()) ADD_MESSAGE("The beautiful melody of %s makes you feel sleepy.", Siren
->CHAR_NAME(DEFINITE
));
8347 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The beautiful melody of %s makes %s look sleepy.", Siren
->CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
)); /*k8*/
8348 Stamina
-= (1 + RAND_N(4)) * 10000;
8351 if (!IsPlayer() && IsCharmable() && !RAND_N(5)) {
8352 ChangeTeam(Siren
->GetTeam());
8353 ADD_MESSAGE("%s seems to be totally brainwashed by %s melodies.", CHAR_NAME(DEFINITE
), Siren
->CHAR_NAME(DEFINITE
));
8357 item
*What
= GiveMostExpensiveItem(Siren
);
8360 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
);
8362 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
);
8365 if (IsPlayer()) ADD_MESSAGE("You would like to give something to %s.", Siren
->CHAR_NAME(DEFINITE
));
8372 item
*character::findFirstEquippedItem (cfestring
&aclassname
, int aconfig
) const {
8373 for (int f
= 0; f
< GetEquipments(); ++f
) {
8374 item
*it
= GetEquipment(f
);
8376 if (aclassname
.CompareIgnoreCase(it
->GetClassID()) == 0) {
8377 if (aconfig
== -1 || it
->GetConfig() == aconfig
) return it
;
8380 for (uInt c = 0; c < it->GetDataBase()->Alias.Size; ++c) {
8381 if (s.CompareIgnoreCase(it->GetDataBase()->Alias[c]) == 0) return it;
8389 item
*character::findFirstInventoryItem (cfestring
&aclassname
, int aconfig
) const {
8391 GetStack()->FillItemVector(items
);
8392 for (int f
= 0; f
< (int)items
.size(); ++f
) {
8393 item
*it
= items
[f
];
8395 if (aclassname
.CompareIgnoreCase(it
->GetClassID()) == 0) {
8396 if (aconfig
== -1 || it
->GetConfig() == aconfig
) return it
;
8403 item
*character::findFirstItem (cfestring
&aclassname
, int aconfig
) const {
8404 item
*it
= findFirstInventoryItem(aclassname
, aconfig
);
8405 if (!it
) it
= findFirstEquippedItem(aclassname
, aconfig
);
8410 // return 0, if no item found
8411 item
*character::FindMostExpensiveItem () const {
8413 item
*MostExpensive
= 0;
8414 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
8415 if ((*i
)->GetPrice() > MaxPrice
) {
8416 MaxPrice
= (*i
)->GetPrice();
8417 MostExpensive
= (*i
);
8420 for (int c
= 0; c
< GetEquipments(); ++c
) {
8421 item
*Equipment
= GetEquipment(c
);
8422 if (Equipment
&& Equipment
->GetPrice() > MaxPrice
) {
8423 MaxPrice
= Equipment
->GetPrice();
8424 MostExpensive
= Equipment
;
8427 return MostExpensive
;
8431 // returns 0 if no items available
8432 item
*character::GiveMostExpensiveItem(character
*ToWhom
) {
8433 item
*ToGive
= FindMostExpensiveItem();
8434 if (!ToGive
) return 0;
8435 truth Equipped
= PLAYER
->Equips(ToGive
);
8436 ToGive
->RemoveFromSlot();
8437 if (Equipped
) game::AskForEscPress(CONST_S("Equipment lost!"));
8438 ToWhom
->ReceiveItemAsPresent(ToGive
);
8444 void character::ReceiveItemAsPresent (item
*Present
) {
8445 if (TestForPickup(Present
)) GetStack()->AddItem(Present
); else GetStackUnder()->AddItem(Present
);
8449 /* returns 0 if no enemies in sight */
8450 character
*character::GetNearestEnemy () const {
8451 character
*NearestEnemy
= 0;
8452 sLong NearestEnemyDistance
= 0x7FFFFFFF;
8454 for (int c
= 0; c
< game::GetTeams(); ++c
) {
8455 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
8456 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
8457 if ((*i
)->IsEnabled()) {
8458 sLong ThisDistance
= Max
<sLong
>(abs((*i
)->GetPos().X
- Pos
.X
), abs((*i
)->GetPos().Y
- Pos
.Y
));
8459 if ((ThisDistance
< NearestEnemyDistance
|| (ThisDistance
== NearestEnemyDistance
&& !(RAND() % 3))) && (*i
)->CanBeSeenBy(this)) {
8461 NearestEnemyDistance
= ThisDistance
;
8467 return NearestEnemy
;
8471 truth
character::MindWormCanPenetrateSkull (mindworm
*) const {
8476 truth
character::CanTameWithDulcis (const character
*Tamer
) const {
8477 int TamingDifficulty
= GetTamingDifficulty();
8478 if (TamingDifficulty
== NO_TAMING
) return false;
8479 if (GetAttachedGod() == DULCIS
) return true;
8480 int Modifier
= Tamer
->GetAttribute(WISDOM
) + Tamer
->GetAttribute(CHARISMA
);
8481 if (Tamer
->IsPlayer()) Modifier
+= game::GetGod(DULCIS
)->GetRelation() / 20;
8482 else if (Tamer
->GetAttachedGod() == DULCIS
) Modifier
+= 50;
8483 if (TamingDifficulty
== 0) {
8484 if (!IgnoreDanger()) TamingDifficulty
= int(10 * GetRelativeDanger(Tamer
));
8485 else TamingDifficulty
= 10 * GetHPRequirementForGeneration()/Max(Tamer
->GetHP(), 1);
8487 return Modifier
>= TamingDifficulty
* 3;
8491 truth
character::CanTameWithLyre (const character
*Tamer
) const {
8492 int TamingDifficulty
= GetTamingDifficulty();
8493 if (TamingDifficulty
== NO_TAMING
) return false;
8494 if (TamingDifficulty
== 0) {
8495 if (!IgnoreDanger()) TamingDifficulty
= int(10 * GetRelativeDanger(Tamer
));
8496 else TamingDifficulty
= 10*GetHPRequirementForGeneration()/Max(Tamer
->GetHP(), 1);
8498 return Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
;
8502 truth
character::CanTameWithScroll (const character
*Tamer
) const {
8503 int TamingDifficulty
= GetTamingDifficulty();
8505 (TamingDifficulty
!= NO_TAMING
&&
8506 (TamingDifficulty
== 0 ||
8507 Tamer
->GetAttribute(INTELLIGENCE
) * 4 + Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
* 5));
8511 truth
character::CanTameWithResurrection (const character
*Tamer
) const {
8512 int TamingDifficulty
= GetTamingDifficulty();
8513 if (TamingDifficulty
== NO_TAMING
) return false;
8514 if (TamingDifficulty
== 0) return true;
8515 return (Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
/2);
8519 truth
character::CheckSadism () {
8520 if (!IsSadist() || !HasSadistAttackMode() || !IsSmall()) return false; // gum
8522 for (int d
= 0; d
< MDIR_STAND
; ++d
) {
8523 square
*Square
= GetNeighbourSquare(d
);
8525 character
*Char
= Square
->GetCharacter();
8526 if (Char
&& Char
->IsMasochist() && GetRelation(Char
) == FRIEND
&&
8527 Char
->GetHP() * 3 >= Char
->GetMaxHP() * 2 && Hit(Char
, Square
->GetPos(), d
, SADIST_HIT
)) {
8538 truth
character::CheckForBeverage () {
8539 if (StateIsActivated(PANIC
) || !IsEnabled() || !UsesNutrition() || CheckIfSatiated()) return false;
8540 itemvector ItemVector
;
8541 GetStack()->FillItemVector(ItemVector
);
8542 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) if (ItemVector
[c
]->IsBeverage(this) && TryToConsume(ItemVector
[c
])) return true;
8547 void character::Haste () {
8548 doforbodyparts()(this, &bodypart::Haste
);
8549 doforequipments()(this, &item::Haste
);
8550 BeginTemporaryState(HASTE
, 500 + RAND() % 1000);
8554 void character::Slow () {
8555 doforbodyparts()(this, &bodypart::Slow
);
8556 doforequipments()(this, &item::Slow
);
8557 //BeginTemporaryState(HASTE, 500 + RAND() % 1000); // this seems to be a bug
8558 BeginTemporaryState(SLOW
, 500 + RAND() % 1000);
8562 void character::SurgicallyDetachBodyPart () {
8563 ADD_MESSAGE("You haven't got any extra bodyparts.");
8567 truth
character::CanHear() const
8569 return DataBase
->CanHear
&& HasHead();
8573 truth
character::IsAllowedInDungeon (int dunIndex
) {
8574 const fearray
<int> &dlist
= GetAllowedDungeons();
8576 for (uInt f
= 0; f
< dlist
.Size
; ++f
) {
8577 if (dlist
[f
] == ALL_DUNGEONS
|| dlist
[f
] == dunIndex
) {
8578 fprintf(stderr
, "OK!\n");
8582 fprintf(stderr
, "NO!\n");
8587 truth
character::IsESPBlockedByEquipment () const {
8588 for (int c
= 0; c
< GetEquipments(); ++c
) {
8589 item
*Item
= GetEquipment(c
);
8590 if (Item
&& Item
->IsHelmet(this) &&
8591 ((Item
->GetMainMaterial() && Item
->GetMainMaterial()->BlockESP()) ||
8592 (Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->BlockESP()))) return true;
8598 truth
character::TemporaryStateIsActivated (sLong What
) const {
8599 if ((What
&ESP
) && (TemporaryState
&ESP
) && IsESPBlockedByEquipment()) {
8600 return ((TemporaryState
&What
)&(~ESP
));
8602 return (TemporaryState
& What
);
8606 truth
character::StateIsActivated (sLong What
) const {
8607 if ((What
&ESP
) && ((TemporaryState
|EquipmentState
)&ESP
) && IsESPBlockedByEquipment()) {
8608 return ((TemporaryState
&What
)&(~ESP
)) || ((EquipmentState
&What
)&(~ESP
));
8610 return (TemporaryState
& What
) || (EquipmentState
& What
);
8614 void character::PrintBeginFearlessMessage () const {
8615 if (!StateIsActivated(FEARLESS
)) {
8616 if (IsPlayer()) ADD_MESSAGE("You feel very comfortable.");
8617 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems very comfortable.", CHAR_NAME(DEFINITE
));
8621 void character::PrintEndFearlessMessage () const {
8622 if (!StateIsActivated(FEARLESS
)) {
8623 if (IsPlayer()) ADD_MESSAGE("Everything looks more dangerous now.");
8624 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems to have lost his confidence.", CHAR_NAME(DEFINITE
));
8628 void character::BeginFearless () {
8629 DeActivateTemporaryState(PANIC
);
8632 void character::EndFearless () {
8637 void character::PrintBeginEtherealityMessage () const {
8638 if (IsPlayer()) ADD_MESSAGE("You feel like many miscible droplets of ether.");
8639 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s melds into the surroundings.", CHAR_NAME(DEFINITE
));
8642 void character::PrintEndEtherealityMessage () const {
8643 if (IsPlayer()) ADD_MESSAGE("You drop out of the firmament, feeling suddenly quite dense.");
8644 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s displaces the air with a puff.", CHAR_NAME(INDEFINITE
));
8647 void character::BeginEthereality () {}
8649 void character::EndEthereality () {}
8652 void character::PrintBeginPolymorphLockMessage () const {
8653 if (IsPlayer()) ADD_MESSAGE("You feel incredibly stubborn about who you are.");
8656 void character::PrintEndPolymorphLockMessage () const {
8657 if (IsPlayer()) ADD_MESSAGE("You feel more open to new ideas.");
8660 void character::PolymorphLockHandler () {
8661 if (TemporaryStateIsActivated(POLYMORPHED
)) {
8662 EditTemporaryStateCounter(POLYMORPHED
, 1);
8663 if (GetTemporaryStateCounter(POLYMORPHED
) < 1000) EditTemporaryStateCounter(POLYMORPHED
, 1);
8667 void character::PrintBeginRegenerationMessage () const {
8668 if (IsPlayer()) ADD_MESSAGE("Your heart races.");
8671 void character::PrintEndRegenerationMessage () const {
8672 if (IsPlayer()) ADD_MESSAGE("Your rapid heartbeat calms down.");
8675 void character::PrintBeginDiseaseImmunityMessage () const {
8676 if (IsPlayer()) ADD_MESSAGE("You feel especially healthy.");
8679 void character::PrintEndDiseaseImmunityMessage () const {
8680 if (IsPlayer()) ADD_MESSAGE("You develop a sudden fear of germs.");
8683 void character::PrintBeginTeleportLockMessage () const {
8684 if (IsPlayer()) ADD_MESSAGE("You feel firmly planted in reality.");
8687 void character::PrintEndTeleportLockMessage () const {
8688 if (IsPlayer()) ADD_MESSAGE("Your mind soars far and wide.");
8691 void character::TeleportLockHandler () {
8692 if (StateIsActivated(TELEPORT_LOCK
)) {
8693 EditTemporaryStateCounter(TELEPORT_LOCK
, 1);
8694 if (GetTemporaryStateCounter(TELEPORT_LOCK
) < 1000) EditTemporaryStateCounter(TELEPORT_LOCK
, 1);
8699 void character::PrintBeginSwimmingMessage () const {
8700 if (IsPlayer()) ADD_MESSAGE("You fondly remember the sound of ocean waves.");
8701 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks wet.", CHAR_NAME(DEFINITE
));
8704 void character::PrintEndSwimmingMessage () const {
8705 if (IsPlayer()) ADD_MESSAGE("You suddenly remember how you nearly drowned as a child.");
8706 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks less wet.", CHAR_NAME(INDEFINITE
));
8709 void character::BeginSwimming () {}
8710 void character::EndSwimming () {}