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>
19 /* These statedata structs contain functions and values used for handling
20 * states. Remember to update them. All normal states must have
21 * PrintBeginMessage and PrintEndMessage functions and a Description string.
22 * BeginHandler, EndHandler, Handler (called each tick) and IsAllowed are
23 * optional, enter zero if the state doesn't need one. If the SECRET flag
24 * is set, Description is not shown in the panel without magical means.
25 * You can also set some source (SRC_*) and duration (DUR_*) flags, which
26 * control whether the state can be randomly activated in certain situations.
27 * These flags can be found in ivandef.h. RANDOMIZABLE sets all source
28 * & duration flags at once. */
30 const char *Description
;
32 void (character::*PrintBeginMessage
) () const;
33 void (character::*PrintEndMessage
) () const;
34 void (character::*BeginHandler
) ();
35 void (character::*EndHandler
) ();
36 void (character::*Handler
) ();
37 truth (character::*IsAllowed
) () const;
38 void (character::*SituationDangerModifier
) (double &) const;
42 const statedata StateData
[STATES
] =
50 &character::EndPolymorph
,
56 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
57 &character::PrintBeginHasteMessage
,
58 &character::PrintEndHasteMessage
,
66 RANDOMIZABLE
&~SRC_GOOD
,
67 &character::PrintBeginSlowMessage
,
68 &character::PrintEndSlowMessage
,
76 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
|SRC_GOOD
),
77 &character::PrintBeginPolymorphControlMessage
,
78 &character::PrintEndPolymorphControlMessage
,
87 &character::PrintBeginLifeSaveMessage
,
88 &character::PrintEndLifeSaveMessage
,
96 SECRET
|SRC_FOUNTAIN
|SRC_CONFUSE_READ
|DUR_FLAGS
,
97 &character::PrintBeginLycanthropyMessage
,
98 &character::PrintEndLycanthropyMessage
,
101 &character::LycanthropyHandler
,
103 &character::LycanthropySituationDangerModifier
106 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
107 &character::PrintBeginInvisibilityMessage
,
108 &character::PrintEndInvisibilityMessage
,
109 &character::BeginInvisibility
, &character::EndInvisibility
,
115 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
116 &character::PrintBeginInfraVisionMessage
,
117 &character::PrintEndInfraVisionMessage
,
118 &character::BeginInfraVision
,
119 &character::EndInfraVision
,
125 RANDOMIZABLE
&~SRC_EVIL
,
126 &character::PrintBeginESPMessage
,
127 &character::PrintEndESPMessage
,
128 &character::BeginESP
,
136 &character::PrintBeginPoisonedMessage
,
137 &character::PrintEndPoisonedMessage
,
140 &character::PoisonedHandler
,
141 &character::CanBePoisoned
,
142 &character::PoisonedSituationDangerModifier
145 SECRET
|(RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_GOOD
)),
146 &character::PrintBeginTeleportMessage
,
147 &character::PrintEndTeleportMessage
,
150 &character::TeleportHandler
,
155 SECRET
|(RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_GOOD
)),
156 &character::PrintBeginPolymorphMessage
,
157 &character::PrintEndPolymorphMessage
,
160 &character::PolymorphHandler
,
162 &character::PolymorphingSituationDangerModifier
165 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
166 &character::PrintBeginTeleportControlMessage
,
167 &character::PrintEndTeleportControlMessage
,
176 &character::PrintBeginPanicMessage
,
177 &character::PrintEndPanicMessage
,
178 &character::BeginPanic
,
179 &character::EndPanic
,
181 &character::CanPanic
,
182 &character::PanicSituationDangerModifier
185 SECRET
|(RANDOMIZABLE
&~(DUR_PERMANENT
|SRC_GOOD
)),
186 &character::PrintBeginConfuseMessage
,
187 &character::PrintEndConfuseMessage
,
191 &character::CanBeConfused
,
192 &character::ConfusedSituationDangerModifier
195 SECRET
|(RANDOMIZABLE
&~DUR_TEMPORARY
),
196 &character::PrintBeginParasitizedMessage
,
197 &character::PrintEndParasitizedMessage
,
200 &character::ParasitizedHandler
,
201 &character::CanBeParasitized
,
202 &character::ParasitizedSituationDangerModifier
206 &character::PrintBeginSearchingMessage
,
207 &character::PrintEndSearchingMessage
,
210 &character::SearchingHandler
,
215 SECRET
|(RANDOMIZABLE
&~(SRC_GOOD
|SRC_EVIL
)),
216 &character::PrintBeginGasImmunityMessage
,
217 &character::PrintEndGasImmunityMessage
,
225 RANDOMIZABLE
&~SRC_EVIL
,
226 &character::PrintBeginLevitationMessage
,
227 &character::PrintEndLevitationMessage
,
229 &character::EndLevitation
,
235 SECRET
|(RANDOMIZABLE
&~DUR_TEMPORARY
),
236 &character::PrintBeginLeprosyMessage
,
237 &character::PrintEndLeprosyMessage
,
238 &character::BeginLeprosy
,
239 &character::EndLeprosy
,
240 &character::LeprosyHandler
,
242 &character::LeprosySituationDangerModifier
245 SRC_FOUNTAIN
|SRC_CONFUSE_READ
|DUR_FLAGS
,
246 &character::PrintBeginHiccupsMessage
,
247 &character::PrintEndHiccupsMessage
,
250 &character::HiccupsHandler
,
252 &character::HiccupsSituationDangerModifier
255 DUR_FLAGS
, //perhaps no fountain, no secret and no confuse read either: SECRET|SRC_FOUNTAIN|SRC_CONFUSE_READ|
256 &character::PrintBeginVampirismMessage
,
257 &character::PrintEndVampirismMessage
,
260 &character::VampirismHandler
,
262 &character::VampirismSituationDangerModifier
265 SECRET
|(RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
)),
266 &character::PrintBeginDetectMessage
,
267 &character::PrintEndDetectMessage
,
270 &character::DetectHandler
,
277 characterprototype::characterprototype (const characterprototype
*Base
, characterspawner Spawner
,
278 charactercloner Cloner
, cchar
*ClassID
)
284 Index
= protocontainer
<character
>::Add(this);
288 std::list
<character
*>::iterator
character::GetTeamIterator () { return TeamIterator
; }
289 void character::SetTeamIterator (std::list
<character
*>::iterator What
) { TeamIterator
= What
; }
290 void character::CreateInitialEquipment (int SpecialFlags
) { AddToInventory(DataBase
->Inventory
, SpecialFlags
); }
291 void character::EditAP (sLong What
) { AP
= Limit
<sLong
>(AP
+What
, -12000, 1200); }
292 int character::GetRandomStepperBodyPart () const { return TORSO_INDEX
; }
293 void character::GainIntrinsic (sLong What
) { BeginTemporaryState(What
, PERMANENT
); }
294 truth
character::IsUsingArms () const { return GetAttackStyle() & USE_ARMS
; }
295 truth
character::IsUsingLegs () const { return GetAttackStyle() & USE_LEGS
; }
296 truth
character::IsUsingHead () const { return GetAttackStyle() & USE_HEAD
; }
297 void character::CalculateAllowedWeaponSkillCategories () { AllowedWeaponSkillCategories
= MARTIAL_SKILL_CATEGORIES
; }
298 festring
character::GetBeVerb () const { return IsPlayer() ? CONST_S("are") : CONST_S("is"); }
299 void character::SetEndurance (int What
) { BaseExperience
[ENDURANCE
] = What
* EXP_MULTIPLIER
; }
300 void character::SetPerception (int What
) { BaseExperience
[PERCEPTION
] = What
* EXP_MULTIPLIER
; }
301 void character::SetIntelligence (int What
) { BaseExperience
[INTELLIGENCE
] = What
* EXP_MULTIPLIER
; }
302 void character::SetWisdom (int What
) { BaseExperience
[WISDOM
] = What
* EXP_MULTIPLIER
; }
303 void character::SetWillPower (int What
) { BaseExperience
[WILL_POWER
] = What
* EXP_MULTIPLIER
; }
304 void character::SetCharisma (int What
) { BaseExperience
[CHARISMA
] = What
* EXP_MULTIPLIER
; }
305 void character::SetMana (int What
) { BaseExperience
[MANA
] = What
* EXP_MULTIPLIER
; }
306 truth
character::IsOnGround () const { return MotherEntity
&& MotherEntity
->IsOnGround(); }
307 truth
character::LeftOversAreUnique () const { return GetArticleMode() || AssignedName
.GetSize(); }
308 truth
character::HomeDataIsValid () const { return (HomeData
&& HomeData
->Level
== GetLSquareUnder()->GetLevelIndex() && HomeData
->Dungeon
== GetLSquareUnder()->GetDungeonIndex()); }
309 void character::SetHomePos (v2 Pos
) { HomeData
->Pos
= Pos
; }
310 cchar
*character::FirstPersonUnarmedHitVerb () const { return "hit"; }
311 cchar
*character::FirstPersonCriticalUnarmedHitVerb () const { return "critically hit"; }
312 cchar
*character::ThirdPersonUnarmedHitVerb () const { return "hits"; }
313 cchar
*character::ThirdPersonCriticalUnarmedHitVerb () const { return "critically hits"; }
314 cchar
*character::FirstPersonKickVerb () const { return "kick"; }
315 cchar
*character::FirstPersonCriticalKickVerb () const { return "critically kick"; }
316 cchar
*character::ThirdPersonKickVerb () const { return "kicks"; }
317 cchar
*character::ThirdPersonCriticalKickVerb () const { return "critically kicks"; }
318 cchar
*character::FirstPersonBiteVerb () const { return "bite"; }
319 cchar
*character::FirstPersonCriticalBiteVerb () const { return "critically bite"; }
320 cchar
*character::ThirdPersonBiteVerb () const { return "bites"; }
321 cchar
*character::ThirdPersonCriticalBiteVerb () const { return "critically bites"; }
322 cchar
*character::UnarmedHitNoun () const { return "attack"; }
323 cchar
*character::KickNoun () const { return "kick"; }
324 cchar
*character::BiteNoun () const { return "attack"; }
325 cchar
*character::GetEquipmentName (int) const { return ""; }
326 const std::list
<feuLong
> &character::GetOriginalBodyPartID (int I
) const { return OriginalBodyPartID
[I
]; }
327 square
*character::GetNeighbourSquare (int I
) const { return GetSquareUnder()->GetNeighbourSquare(I
); }
328 lsquare
*character::GetNeighbourLSquare (int I
) const { return static_cast<lsquare
*>(GetSquareUnder())->GetNeighbourLSquare(I
); }
329 wsquare
*character::GetNeighbourWSquare (int I
) const { return static_cast<wsquare
*>(GetSquareUnder())->GetNeighbourWSquare(I
); }
330 god
*character::GetMasterGod () const { return game::GetGod(GetConfig()); }
331 col16
character::GetBodyPartColorA (int, truth
) const { return GetSkinColor(); }
332 col16
character::GetBodyPartColorB (int, truth
) const { return GetTorsoMainColor(); }
333 col16
character::GetBodyPartColorC (int, truth
) const { return GetBeltColor(); } // sorry...
334 col16
character::GetBodyPartColorD (int, truth
) const { return GetTorsoSpecialColor(); }
335 int character::GetRandomApplyBodyPart () const { return TORSO_INDEX
; }
336 truth
character::MustBeRemovedFromBone () const { return IsUnique() && !CanBeGenerated(); }
337 truth
character::IsPet () const { return GetTeam()->GetID() == PLAYER_TEAM
; }
338 character
* character::GetLeader () const { return GetTeam()->GetLeader(); }
339 int character::GetMoveType () const { return (!StateIsActivated(LEVITATION
) ? DataBase
->MoveType
: DataBase
->MoveType
| FLY
); }
340 festring
character::GetZombieDescription () const { return " of "+GetName(INDEFINITE
); }
341 truth
character::BodyPartCanBeSevered (int I
) const { return I
; }
342 truth
character::HasBeenSeen () const { return DataBase
->Flags
& HAS_BEEN_SEEN
; }
343 truth
character::IsTemporary () const { return GetTorso()->GetLifeExpectancy(); }
344 cchar
*character::GetNormalDeathMessage () const { return "killed @k"; }
347 int characterdatabase::*ExpPtr
[ATTRIBUTES
] = {
348 &characterdatabase::DefaultEndurance
,
349 &characterdatabase::DefaultPerception
,
350 &characterdatabase::DefaultIntelligence
,
351 &characterdatabase::DefaultWisdom
,
352 &characterdatabase::DefaultWillPower
,
353 &characterdatabase::DefaultCharisma
,
354 &characterdatabase::DefaultMana
,
355 &characterdatabase::DefaultArmStrength
,
356 &characterdatabase::DefaultLegStrength
,
357 &characterdatabase::DefaultDexterity
,
358 &characterdatabase::DefaultAgility
362 contentscript
<item
> characterdatabase::*EquipmentDataPtr
[EQUIPMENT_DATAS
] = {
363 &characterdatabase::Helmet
,
364 &characterdatabase::Amulet
,
365 &characterdatabase::Cloak
,
366 &characterdatabase::BodyArmor
,
367 &characterdatabase::Belt
,
368 &characterdatabase::RightWielded
,
369 &characterdatabase::LeftWielded
,
370 &characterdatabase::RightRing
,
371 &characterdatabase::LeftRing
,
372 &characterdatabase::RightGauntlet
,
373 &characterdatabase::LeftGauntlet
,
374 &characterdatabase::RightBoot
,
375 &characterdatabase::LeftBoot
379 character::character (ccharacter
&Char
) :
380 entity(Char
), id(Char
), NP(Char
.NP
), AP(Char
.AP
),
381 TemporaryState(Char
.TemporaryState
&~POLYMORPHED
),
382 Team(Char
.Team
), GoingTo(ERROR_V2
), Money(0),
383 AssignedName(Char
.AssignedName
), Action(0),
384 DataBase(Char
.DataBase
), MotherEntity(0),
385 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
386 AllowedWeaponSkillCategories(Char
.AllowedWeaponSkillCategories
),
387 BodyParts(Char
.BodyParts
),
388 RegenerationCounter(Char
.RegenerationCounter
),
389 SquaresUnder(Char
.SquaresUnder
), LastAcidMsgMin(0),
390 Stamina(Char
.Stamina
), MaxStamina(Char
.MaxStamina
),
391 BlocksSinceLastTurn(0), GenerationDanger(Char
.GenerationDanger
),
392 CommandFlags(Char
.CommandFlags
), WarnFlags(0),
393 ScienceTalks(Char
.ScienceTalks
), TrapData(0), CounterToMindWormHatch(0)
397 Flags
|= C_INITIALIZING
|C_IN_NO_MSG_MODE
;
398 Stack
= new stack(0, this, HIDDEN
);
399 for (c
= 0; c
< STATES
; ++c
) TemporaryStateCounter
[c
] = Char
.TemporaryStateCounter
[c
];
400 if (Team
) TeamIterator
= Team
->Add(this);
401 for (c
= 0; c
< BASE_ATTRIBUTES
; ++c
) BaseExperience
[c
] = Char
.BaseExperience
[c
];
402 BodyPartSlot
= new bodypartslot
[BodyParts
];
403 OriginalBodyPartID
= new std::list
<feuLong
>[BodyParts
];
404 CWeaponSkill
= new cweaponskill
[AllowedWeaponSkillCategories
];
405 SquareUnder
= new square
*[SquaresUnder
];
406 if (SquaresUnder
== 1) *SquareUnder
= 0; else memset(SquareUnder
, 0, SquaresUnder
*sizeof(square
*));
407 for (c
= 0; c
< BodyParts
; ++c
) {
408 BodyPartSlot
[c
].SetMaster(this);
409 bodypart
*CharBodyPart
= Char
.GetBodyPart(c
);
410 OriginalBodyPartID
[c
] = Char
.OriginalBodyPartID
[c
];
412 bodypart
*BodyPart
= static_cast<bodypart
*>(CharBodyPart
->Duplicate());
413 SetBodyPart(c
, BodyPart
);
414 BodyPart
->CalculateEmitation();
417 for (c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) CWeaponSkill
[c
] = Char
.CWeaponSkill
[c
];
418 HomeData
= Char
.HomeData
? new homedata(*Char
.HomeData
) : 0;
419 ID
= game::CreateNewCharacterID(this);
423 character::character () :
424 entity(HAS_BE
), NP(50000), AP(0), TemporaryState(0), Team(0),
425 GoingTo(ERROR_V2
), Money(0), Action(0), MotherEntity(0),
426 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
427 RegenerationCounter(0), HomeData(0), LastAcidMsgMin(0),
428 BlocksSinceLastTurn(0), GenerationDanger(DEFAULT_GENERATION_DANGER
),
429 WarnFlags(0), ScienceTalks(0), TrapData(0), CounterToMindWormHatch(0)
431 Stack
= new stack(0, this, HIDDEN
);
435 character::~character () {
436 if (Action
) delete Action
;
437 if (Team
) Team
->Remove(GetTeamIterator());
439 for (int c
= 0; c
< BodyParts
; ++c
) delete GetBodyPart(c
);
440 delete [] BodyPartSlot
;
441 delete [] OriginalBodyPartID
;
442 delete PolymorphBackup
;
443 delete [] SquareUnder
;
444 delete [] CWeaponSkill
;
446 for (trapdata
*T
= TrapData
; T
;) {
451 game::RemoveCharacterID(ID
);
455 void character::Hunger () {
456 switch (GetBurdenState()) {
460 EditExperience(LEG_STRENGTH
, 150, 1 << 2);
461 EditExperience(AGILITY
, -50, 1 << 2);
465 EditExperience(LEG_STRENGTH
, 75, 1 << 1);
466 EditExperience(AGILITY
, -25, 1 << 1);
473 switch (GetHungerState()) {
475 EditExperience(ARM_STRENGTH
, -75, 1 << 3);
476 EditExperience(LEG_STRENGTH
, -75, 1 << 3);
479 EditExperience(ARM_STRENGTH
, -50, 1 << 2);
480 EditExperience(LEG_STRENGTH
, -50, 1 << 2);
483 EditExperience(ARM_STRENGTH
, -25, 1 << 1);
484 EditExperience(LEG_STRENGTH
, -25, 1 << 1);
487 EditExperience(AGILITY
, -25, 1 << 1);
490 EditExperience(AGILITY
, -50, 1 << 2);
493 EditExperience(AGILITY
, -75, 1 << 3);
496 CheckStarvationDeath(CONST_S("starved to death"));
500 int character::TakeHit (character
*Enemy
, item
*Weapon
, bodypart
*EnemyBodyPart
, v2 HitPos
, double Damage
,
501 double ToHitValue
, int Success
, int Type
, int GivenDir
, truth Critical
, truth ForceHit
)
504 game::ClearEventData();
505 game::mActor
= Enemy
;
506 game::mResult
= DID_NO_DAMAGE
;
507 if (game::RunOnCharEvent(this, CONST_S("take_hit"))) { game::ClearEventData(); return game::mResult
; }
508 game::ClearEventData();
509 int Dir
= Type
== BITE_ATTACK
? YOURSELF
: GivenDir
;
510 double DodgeValue
= GetDodgeValue();
511 if (!Enemy
->IsPlayer() && GetAttackWisdomLimit() != NO_LIMIT
) Enemy
->EditExperience(WISDOM
, 75, 1 << 13);
512 if (!Enemy
->CanBeSeenBy(this)) ToHitValue
*= 2;
513 if (!CanBeSeenBy(Enemy
)) DodgeValue
*= 2;
514 if (Enemy
->StateIsActivated(CONFUSED
)) ToHitValue
*= 0.75;
516 switch (Enemy
->GetTirednessState()) {
522 switch (GetTirednessState()) {
530 if (!IsRetreating()) SetGoingTo(Enemy
->GetPos());
531 else SetGoingTo(GetPos()-((Enemy
->GetPos()-GetPos())<<4));
532 if (!Enemy
->IsRetreating()) Enemy
->SetGoingTo(GetPos());
533 else Enemy
->SetGoingTo(Enemy
->GetPos()-((GetPos()-Enemy
->GetPos())<<4));
536 /* Effectively, the average chance to hit is 100% / (DV/THV + 1). */
537 if (RAND() % int(100+ToHitValue
/DodgeValue
*(100+Success
)) < 100 && !Critical
&& !ForceHit
) {
538 Enemy
->AddMissMessage(this);
539 EditExperience(AGILITY
, 150, 1 << 7);
540 EditExperience(PERCEPTION
, 75, 1 << 7);
541 if (Enemy
->CanBeSeenByPlayer())
542 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
544 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
548 int TrueDamage
= int(Damage
*(100+Success
)/100)+(RAND()%3 ? 1 : 0);
550 TrueDamage
+= TrueDamage
>> 1;
554 int BodyPart
= ChooseBodyPartToReceiveHit(ToHitValue
, DodgeValue
);
558 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalUnarmedHitVerb(), Enemy
->ThirdPersonCriticalUnarmedHitVerb(), BodyPart
);
561 Enemy
->AddWeaponHitMessage(this, Weapon
, BodyPart
, true);
564 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalKickVerb(), Enemy
->ThirdPersonCriticalKickVerb(), BodyPart
);
567 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalBiteVerb(), Enemy
->ThirdPersonCriticalBiteVerb(), BodyPart
);
573 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonUnarmedHitVerb(), Enemy
->ThirdPersonUnarmedHitVerb(), BodyPart
);
576 Enemy
->AddWeaponHitMessage(this, Weapon
, BodyPart
, false);
579 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonKickVerb(), Enemy
->ThirdPersonKickVerb(), BodyPart
);
582 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonBiteVerb(), Enemy
->ThirdPersonBiteVerb(), BodyPart
);
587 if (!Critical
&& TrueDamage
&& Enemy
->AttackIsBlockable(Type
)) {
588 TrueDamage
= CheckForBlock(Enemy
, Weapon
, ToHitValue
, TrueDamage
, Success
, Type
);
589 if (!TrueDamage
|| (Weapon
&& !Weapon
->Exists())) {
590 if (Enemy
->CanBeSeenByPlayer())
591 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
593 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
598 int WeaponSkillHits
= CalculateWeaponSkillHits(Enemy
);
599 int DoneDamage
= ReceiveBodyPartDamage(Enemy
, TrueDamage
, PHYSICAL_DAMAGE
, BodyPart
, Dir
, false, Critical
, true, Type
== BITE_ATTACK
&& Enemy
->BiteCapturesBodyPart());
600 truth Succeeded
= (GetBodyPart(BodyPart
) && HitEffect(Enemy
, Weapon
, HitPos
, Type
, BodyPart
, Dir
, !DoneDamage
)) || DoneDamage
;
601 if (Succeeded
) Enemy
->WeaponSkillHit(Weapon
, Type
, WeaponSkillHits
);
604 if (Weapon
->Exists() && DoneDamage
< TrueDamage
) Weapon
->ReceiveDamage(Enemy
, TrueDamage
-DoneDamage
, PHYSICAL_DAMAGE
);
605 if (Weapon
->Exists() && DoneDamage
&& SpillsBlood() && GetBodyPart(BodyPart
) &&
606 (GetBodyPart(BodyPart
)->IsAlive() || GetBodyPart(BodyPart
)->GetMainMaterial()->IsLiquid()))
607 Weapon
->SpillFluid(0, CreateBlood(15+RAND()%15));
610 if (Enemy
->AttackIsBlockable(Type
)) SpecialBodyDefenceEffect(Enemy
, EnemyBodyPart
, Type
);
613 if (Enemy
->CanBeSeenByPlayer())
614 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
616 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
618 return DID_NO_DAMAGE
;
621 if (CheckDeath(GetNormalDeathMessage(), Enemy
, Enemy
->IsPlayer() ? FORCE_MSG
: 0)) return HAS_DIED
;
623 if (Enemy
->CanBeSeenByPlayer())
624 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
626 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
632 struct svpriorityelement
{
633 svpriorityelement (int BodyPart
, int StrengthValue
) : BodyPart(BodyPart
), StrengthValue(StrengthValue
) {}
634 bool operator < (const svpriorityelement
&AnotherPair
) const { return StrengthValue
> AnotherPair
.StrengthValue
; }
640 int character::ChooseBodyPartToReceiveHit (double ToHitValue
, double DodgeValue
) {
641 if (BodyParts
== 1) return 0;
642 std::priority_queue
<svpriorityelement
> SVQueue
;
643 for (int c
= 0; c
< BodyParts
; ++c
) {
644 bodypart
*BodyPart
= GetBodyPart(c
);
645 if (BodyPart
&& (BodyPart
->GetHP() != 1 || BodyPart
->CanBeSevered(PHYSICAL_DAMAGE
)))
646 SVQueue
.push(svpriorityelement(c
, ModifyBodyPartHitPreference(c
, BodyPart
->GetStrengthValue()+BodyPart
->GetHP())));
648 while (SVQueue
.size()) {
649 svpriorityelement E
= SVQueue
.top();
650 int ToHitPercentage
= int(GLOBAL_WEAK_BODYPART_HIT_MODIFIER
*ToHitValue
*GetBodyPart(E
.BodyPart
)->GetBodyPartVolume()/(DodgeValue
*GetBodyVolume()));
651 ToHitPercentage
= ModifyBodyPartToHitChance(E
.BodyPart
, ToHitPercentage
);
652 if (ToHitPercentage
< 1) ToHitPercentage
= 1;
653 else if (ToHitPercentage
> 95) ToHitPercentage
= 95;
654 if (ToHitPercentage
> RAND()%100) return E
.BodyPart
;
661 void character::Be () {
662 if (game::ForceJumpToPlayerBe()) {
663 if (!IsPlayer()) return;
664 game::SetForceJumpToPlayerBe(false);
666 truth ForceBe
= HP
!= MaxHP
|| AllowSpoil();
667 for (int c
= 0; c
< BodyParts
; ++c
) {
668 bodypart
*BodyPart
= GetBodyPart(c
);
669 if (BodyPart
&& (ForceBe
|| BodyPart
->NeedsBe())) BodyPart
->Be();
672 if (!IsEnabled()) return;
673 if (GetTeam() == PLAYER
->GetTeam()) {
674 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) {
675 if (CWeaponSkill
[c
].Tick() && IsPlayer()) CWeaponSkill
[c
].AddLevelDownMessage(c
);
680 if (GetHungerState() == STARVING
&& !(RAND()%50)) LoseConsciousness(250+RAND_N(250), true);
681 if (!Action
|| Action
->AllowFoodConsumption()) Hunger();
683 if (Stamina
!= MaxStamina
) RegenerateStamina();
684 if (HP
!= MaxHP
) Regenerate();
685 if (Action
&& AP
>= 1000) ActionAutoTermination();
686 if (Action
&& AP
>= 1000) {
688 if (!IsEnabled()) return;
690 EditAP(GetStateAPGain(100));
694 SpecialTurnHandler();
695 BlocksSinceLastTurn
= 0;
697 static int Timer
= 0;
699 if (ivanconfig::GetAutoSaveInterval() && !GetAction() && ++Timer
>= ivanconfig::GetAutoSaveInterval()) {
700 //fprintf(stderr, "autosaving..."); fflush(stderr);
701 game::Save(game::GetAutoSaveFileName());
702 //fprintf(stderr, "done\n"); fflush(stderr);
705 game::CalculateNextDanger();
706 if (!StateIsActivated(POLYMORPHED
)) game::UpdatePlayerAttributeAverage();
707 if (!game::IsInWilderness()) Search(GetAttribute(PERCEPTION
));
711 if (Action
->ShowEnvironment()) {
712 static int Counter
= 0;
713 if (++Counter
== 10) {
714 game::DrawEverything();
718 msgsystem::ThyMessagesAreNowOld();
719 if (Action
->IsVoluntary() && READ_KEY()) Action
->Terminate(false);
722 if (!Action
&& !game::IsInWilderness()) GetAICommand();
728 void character::Move (v2 MoveTo
, truth TeleportMove
, truth Run
) {
729 if (!IsEnabled()) return;
730 /* Test whether the player is stuck to something */
731 if (!TeleportMove
&& !TryToUnStickTraps(MoveTo
-GetPos())) return;
732 if (Run
&& !IsPlayer() && TorsoIsAlive() && (Stamina
<= 10000 / Max(GetAttribute(LEG_STRENGTH
), 1) ||
733 (!StateIsActivated(PANIC
) && Stamina
< MaxStamina
>> 2)))
736 if (GetBurdenState() != OVER_LOADED
|| TeleportMove
) {
737 lsquare
*OldSquareUnder
[MAX_SQUARES_UNDER
];
738 if (!game::IsInWilderness()) {
739 for (int c
= 0; c
< GetSquaresUnder(); ++c
) OldSquareUnder
[c
] = GetLSquareUnder(c
);
744 /* Multitiled creatures should behave differently, maybe? */
746 int ED
= GetSquareUnder()->GetEntryDifficulty();
747 EditAP(-GetMoveAPRequirement(ED
) >> 1);
749 EditExperience(AGILITY
, 125, ED
<< 7);
752 switch (GetHungerState()) {
753 case SATIATED
: Base
= 11000; break;
754 case BLOATED
: Base
= 12500; break;
755 case OVER_FED
: Base
= 15000; break;
758 EditStamina(-Base
/ Max(GetAttribute(LEG_STRENGTH
), 1), true);
760 int ED
= GetSquareUnder()->GetEntryDifficulty();
761 EditAP(-GetMoveAPRequirement(ED
));
763 EditExperience(AGILITY
, 75, ED
<< 7);
766 if (IsPlayer()) ShowNewPosInfo();
767 if (IsPlayer() && !game::IsInWilderness()) GetStackUnder()->SetSteppedOn(true);
768 if (!game::IsInWilderness()) SignalStepFrom(OldSquareUnder
);
771 cchar
*CrawlVerb
= StateIsActivated(LEVITATION
) ? "float" : "crawl";
772 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb
);
779 void character::GetAICommand () {
780 SeekLeader(GetLeader());
781 if (FollowLeader(GetLeader())) return;
782 if (CheckForEnemies(true, true, true)) return;
783 if (CheckForUsefulItemsOnGround()) return;
784 if (CheckForDoors()) return;
785 if (CheckSadism()) return;
786 if (MoveRandomly()) return;
791 truth
character::MoveTowardsTarget (truth Run
) {
796 if (!Route
.empty()) {
799 } else TPos
= GoingTo
;
801 MoveTo
[0] = v2(0, 0);
802 MoveTo
[1] = v2(0, 0);
803 MoveTo
[2] = v2(0, 0);
805 if (TPos
.X
< Pos
.X
) {
806 if (TPos
.Y
< Pos
.Y
) {
807 MoveTo
[0] = v2(-1, -1);
808 MoveTo
[1] = v2(-1, 0);
809 MoveTo
[2] = v2( 0, -1);
810 } else if (TPos
.Y
== Pos
.Y
) {
811 MoveTo
[0] = v2(-1, 0);
812 MoveTo
[1] = v2(-1, -1);
813 MoveTo
[2] = v2(-1, 1);
814 } else if (TPos
.Y
> Pos
.Y
) {
815 MoveTo
[0] = v2(-1, 1);
816 MoveTo
[1] = v2(-1, 0);
817 MoveTo
[2] = v2( 0, 1);
819 } else if (TPos
.X
== Pos
.X
) {
820 if (TPos
.Y
< Pos
.Y
) {
821 MoveTo
[0] = v2( 0, -1);
822 MoveTo
[1] = v2(-1, -1);
823 MoveTo
[2] = v2( 1, -1);
824 } else if (TPos
.Y
== Pos
.Y
) {
827 } else if (TPos
.Y
> Pos
.Y
) {
828 MoveTo
[0] = v2( 0, 1);
829 MoveTo
[1] = v2(-1, 1);
830 MoveTo
[2] = v2( 1, 1);
832 } else if (TPos
.X
> Pos
.X
) {
833 if (TPos
.Y
< Pos
.Y
) {
834 MoveTo
[0] = v2(1, -1);
835 MoveTo
[1] = v2(1, 0);
836 MoveTo
[2] = v2(0, -1);
837 } else if (TPos
.Y
== Pos
.Y
) {
838 MoveTo
[0] = v2(1, 0);
839 MoveTo
[1] = v2(1, -1);
840 MoveTo
[2] = v2(1, 1);
841 } else if (TPos
.Y
> Pos
.Y
) {
842 MoveTo
[0] = v2(1, 1);
843 MoveTo
[1] = v2(1, 0);
844 MoveTo
[2] = v2(0, 1);
848 v2 ModifiedMoveTo
= ApplyStateModification(MoveTo
[0]);
850 if (TryMove(ModifiedMoveTo
, true, Run
)) return true;
852 int L
= (Pos
-TPos
).GetManhattanLength();
854 if (RAND()&1) Swap(MoveTo
[1], MoveTo
[2]);
856 if (Pos
.IsAdjacent(TPos
)) {
861 if ((Pos
+MoveTo
[1]-TPos
).GetManhattanLength() <= L
&& TryMove(ApplyStateModification(MoveTo
[1]), true, Run
)) return true;
862 if ((Pos
+MoveTo
[2]-TPos
).GetManhattanLength() <= L
&& TryMove(ApplyStateModification(MoveTo
[2]), true, Run
)) return true;
863 Illegal
.insert(Pos
+ModifiedMoveTo
);
864 if (CreateRoute()) return true;
869 int character::CalculateNewSquaresUnder (lsquare
**NewSquare
, v2 Pos
) const {
870 if (GetLevel()->IsValidPos(Pos
)) {
871 *NewSquare
= GetNearLSquare(Pos
);
878 truth
character::TryMove (v2 MoveVector
, truth Important
, truth Run
) {
879 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
880 character
*Pet
[MAX_SQUARES_UNDER
];
881 character
*Neutral
[MAX_SQUARES_UNDER
];
882 character
*Hostile
[MAX_SQUARES_UNDER
];
883 v2 PetPos
[MAX_SQUARES_UNDER
];
884 v2 NeutralPos
[MAX_SQUARES_UNDER
];
885 v2 HostilePos
[MAX_SQUARES_UNDER
];
886 v2 MoveTo
= GetPos()+MoveVector
;
887 int Direction
= game::GetDirectionForVector(MoveVector
);
888 if (Direction
== DIR_ERROR
) ABORT("Direction fault.");
889 if (!game::IsInWilderness()) {
890 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, MoveTo
);
895 for (int c
= 0; c
< Squares
; ++c
) {
896 character
* Char
= MoveToSquare
[c
]->GetCharacter();
897 if (Char
&& Char
!= this) {
898 v2 Pos
= MoveToSquare
[c
]->GetPos();
901 PetPos
[Pets
++] = Pos
;
902 } else if (Char
->GetRelation(this) != HOSTILE
) {
903 Neutral
[Neutrals
] = Char
;
904 NeutralPos
[Neutrals
++] = Pos
;
906 Hostile
[Hostiles
] = Char
;
907 HostilePos
[Hostiles
++] = Pos
;
912 if (Hostiles
== 1) return Hit(Hostile
[0], HostilePos
[0], Direction
);
914 int Index
= RAND() % Hostiles
;
915 return Hit(Hostile
[Index
], HostilePos
[Index
], Direction
);
919 if (!IsPlayer() && !Pets
&& Important
&& CanMoveOn(MoveToSquare
[0]))
920 return HandleCharacterBlockingTheWay(Neutral
[0], NeutralPos
[0], Direction
);
922 return IsPlayer() && Hit(Neutral
[0], NeutralPos
[0], Direction
);
923 } else if (Neutrals
) {
925 int Index
= RAND() % Neutrals
;
926 return Hit(Neutral
[Index
], NeutralPos
[Index
], Direction
);
932 for (int c
= 0; c
< Squares
; ++c
) if (MoveToSquare
[c
]->IsScary(this)) return false;
936 if (IsPlayer() && !ivanconfig::GetBeNice() && Pet
[0]->IsMasochist() && HasSadistAttackMode() &&
937 game::TruthQuestion("Do you want to punish " + Pet
[0]->GetObjectPronoun() + "? [y/N]"))
938 return Hit(Pet
[0], PetPos
[0], Direction
, SADIST_HIT
);
940 return (Important
&& (CanMoveOn(MoveToSquare
[0]) ||
941 (IsPlayer() && game::GoThroughWallsCheatIsActive())) && Displace(Pet
[0]));
942 } else if (Pets
) return false;
944 if ((CanMove() && CanMoveOn(MoveToSquare
[0])) || ((game::GoThroughWallsCheatIsActive() && IsPlayer()))) {
945 Move(MoveTo
, false, Run
);
946 if (IsEnabled() && GetPos() == GoingTo
) TerminateGoingTo();
949 for (int c
= 0; c
< Squares
; ++c
) {
950 olterrain
*Terrain
= MoveToSquare
[c
]->GetOLTerrain();
951 if (Terrain
&& Terrain
->CanBeOpened()) {
953 if (Terrain
->IsLocked()) {
956 if (ivanconfig::GetKickDownDoors()) {
957 if (game::TruthQuestion(CONST_S("Locked! Do you want to kick ")+Terrain
->GetName(DEFINITE
)+"? [Y/n]", true, game::GetMoveCommandKeyBetweenPoints(PLAYER
->GetPos(), MoveToSquare
[0]->GetPos()))) {
958 Kick(MoveToSquare
[c
], Direction
);
965 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 */
967 } else if (Important
&& CheckKick()) {
968 room
*Room
= MoveToSquare
[c
]->GetRoom();
969 if (!Room
|| Room
->AllowKick(this, MoveToSquare
[c
])) {
970 int HP
= Terrain
->GetHP();
971 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s kicks %s.", CHAR_NAME(DEFINITE
), Terrain
->CHAR_NAME(DEFINITE
));
972 Kick(MoveToSquare
[c
], Direction
);
973 olterrain
*NewTerrain
= MoveToSquare
[c
]->GetOLTerrain();
974 if (NewTerrain
== Terrain
&& Terrain
->GetHP() == HP
) { // BUG!
975 Illegal
.insert(MoveTo
);
981 } else { /* if (Terrain->IsLocked()) */
982 /*if(!IsPlayer() || game::TruthQuestion(CONST_S("Do you want to open ") + Terrain->GetName(DEFINITE) + "? [y/N]", false, game::GetMoveCommandKeyBetweenPoints(PLAYER->GetPos(), MoveToSquare[0]->GetPos()))) return MoveToSquare[c]->Open(this);*/
983 /* Non-players always try to open it */
984 if (!IsPlayer()) return MoveToSquare
[c
]->Open(this);
985 if (game::TruthQuestion(CONST_S("Do you want to ")+(ivanconfig::GetKickDownDoors()?"kick ":"open ")+
986 Terrain
->GetName(DEFINITE
)+"? [y/N]", false, game::GetMoveCommandKeyBetweenPoints(PLAYER
->GetPos(),
987 MoveToSquare
[0]->GetPos()))) {
988 if (ivanconfig::GetKickDownDoors()) {
989 Kick(MoveToSquare
[c
], Direction
);
992 return MoveToSquare
[c
]->Open(this);
995 } /* if (Terrain->IsLocked()) */
996 } else { /* if (CanOpen()) */
998 ADD_MESSAGE("This monster type cannot open doors.");
1002 Illegal
.insert(MoveTo
);
1003 return CreateRoute();
1005 } /* if (CanOpen()) */
1006 } /* if (Terrain && Terrain->CanBeOpened()) */
1011 if (IsPlayer() && !IsStuck() && GetLevel()->IsOnGround() && game::TruthQuestion(CONST_S("Do you want to leave ")+game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex())+"? [y/N]")) {
1012 if (HasPetrussNut() && !HasGoldenEagleShirt()) {
1013 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!"));
1014 game::GetCurrentArea()->SendNewDrawRequest();
1015 game::DrawEverything();
1016 ShowAdventureInfo();
1017 festring Msg
= CONST_S("killed Petrus and became the Avatar of Chaos");
1018 PLAYER
->AddScoreEntry(Msg
, 3, false);
1022 if (game::TryTravel(WORLD_MAP
, WORLD_MAP
, game::GetCurrentDungeonIndex())) return true;
1027 /** No multitile support */
1028 if (CanMove() && GetArea()->IsValidPos(MoveTo
) && (CanMoveOn(GetNearWSquare(MoveTo
)) || game::GoThroughWallsCheatIsActive())) {
1029 if (!game::GoThroughWallsCheatIsActive()) {
1030 charactervector
&V
= game::GetWorldMap()->GetPlayerGroup();
1031 truth Discard
= false;
1032 for (uInt c
= 0; c
< V
.size(); ++c
) {
1033 if (!V
[c
]->CanMoveOn(GetNearWSquare(MoveTo
))) {
1035 ADD_MESSAGE("One or more of your team members cannot cross this terrain.");
1036 if (!game::TruthQuestion("Discard them? [y/N]")) return false;
1039 if (Discard
) delete V
[c
];
1040 V
.erase(V
.begin() + c
--);
1044 Move(MoveTo
, false);
1053 void character::CreateCorpse (lsquare
*Square
) {
1054 if (!BodyPartsDisappearWhenSevered() && !game::AllBodyPartsVanish()) {
1055 corpse
*Corpse
= corpse::Spawn(0, NO_MATERIALS
);
1056 Corpse
->SetDeceased(this);
1057 Square
->AddItem(Corpse
);
1065 void character::Die (ccharacter
*Killer
, cfestring
&Msg
, feuLong DeathFlags
) {
1066 /* Note: This function musn't delete any objects, since one of these may be
1067 the one currently processed by pool::Be()! */
1068 if (!IsEnabled()) return;
1069 game::ClearEventData();
1070 game::mActor
= Killer
;
1071 if (game::RunOnCharEvent(this, CONST_S("die"))) { game::ClearEventData(); RemoveTraps(); return; }
1072 game::ClearEventData();
1075 ADD_MESSAGE("You die.");
1076 game::DrawEverything();
1077 if (game::TruthQuestion(CONST_S("Do you want to save screenshot? [y/n]"), REQUIRES_ANSWER
)) {
1078 festring dir
= inputfile::GetMyDir()+"/DeathShots";
1080 mkdir(dir
.CStr(), 0755);
1085 time_t t
= time(NULL
);
1086 struct tm
*ts
= localtime(&t
);
1088 timestr
<< (int)(ts
->tm_year
%100);
1089 int t
= ts
->tm_mon
+1;
1090 if (t
< 10) timestr
<< '0'; timestr
<< t
;
1091 t
= ts
->tm_mday
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1093 t
= ts
->tm_hour
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1094 t
= ts
->tm_min
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1095 t
= ts
->tm_sec
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1099 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1100 festring ext
= ".png";
1102 festring ext
= ".bmp";
1104 festring fname
= dir
+"/deathshot_"+timestr
;
1105 if (inputfile::fileExists(fname
+ext
)) {
1106 for (int f
= 0; f
< 1000; f
++) {
1108 sprintf(buf
, "%03d", f
);
1109 festring fn
= fname
+buf
;
1110 if (!inputfile::fileExists(fn
+ext
)) {
1117 fprintf(stderr
, "deathshot: %s\n", fname
.CStr());
1118 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1119 DOUBLE_BUFFER
->SavePNG(fname
);
1121 DOUBLE_BUFFER
->SaveBMP(fname
);
1124 if (game::WizardModeIsActive()) {
1125 game::DrawEverything();
1126 if (!game::TruthQuestion(CONST_S("Do you want to do this, cheater? [y/n]"), REQUIRES_ANSWER
)) {
1132 SetNP(SATIATED_LEVEL
);
1133 SendNewDrawRequest();
1137 } else if (CanBeSeenByPlayer() && !(DeathFlags
& DISALLOW_MSG
)) {
1138 ProcessAndAddMessage(GetDeathMessage());
1139 } else if (DeathFlags
& FORCE_MSG
) {
1140 ADD_MESSAGE("You sense the death of something.");
1143 if (!(DeathFlags
& FORBID_REINCARNATION
)) {
1144 if (StateIsActivated(LIFE_SAVED
) && CanMoveOn(!game::IsInWilderness() ? GetSquareUnder() : PLAYER
->GetSquareUnder())) {
1148 if (SpecialSaveLife()) return;
1149 } else if (StateIsActivated(LIFE_SAVED
)) {
1153 Flags
|= C_IN_NO_MSG_MODE
;
1154 character
*Ghost
= 0;
1156 game::RemoveSaves();
1157 if (!game::IsInWilderness()) {
1158 Ghost
= game::CreateGhost();
1163 square
*SquareUnder
[MAX_SQUARES_UNDER
];
1164 memset(SquareUnder
, 0, sizeof(SquareUnder
));
1166 if (IsPlayer() || !game::IsInWilderness()) {
1167 for (int c
= 0; c
< SquaresUnder
; ++c
) SquareUnder
[c
] = GetSquareUnder(c
);
1170 charactervector
& V
= game::GetWorldMap()->GetPlayerGroup();
1171 V
.erase(std::find(V
.begin(), V
.end(), this));
1173 //lsquare **LSquareUnder = reinterpret_cast<lsquare **>(SquareUnder); /* warning; wtf? */
1174 lsquare
*LSquareUnder
[MAX_SQUARES_UNDER
];
1175 memmove(LSquareUnder
, SquareUnder
, sizeof(SquareUnder
));
1177 if (!game::IsInWilderness()) {
1178 if (!StateIsActivated(POLYMORPHED
)) {
1179 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(this, Killer
, Msg
);
1180 if (!(DeathFlags
& DISALLOW_CORPSE
)) CreateCorpse(LSquareUnder
[0]); else SendToHell();
1182 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(GetPolymorphBackup(), Killer
, Msg
);
1183 GetPolymorphBackup()->CreateCorpse(LSquareUnder
[0]);
1184 GetPolymorphBackup()->Flags
&= ~C_POLYMORPHED
;
1185 SetPolymorphBackup(0);
1189 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(this, Killer
, Msg
);
1194 if (!game::IsInWilderness()) {
1195 for (int c
= 0; c
< GetSquaresUnder(); ++c
) LSquareUnder
[c
]->SetTemporaryEmitation(GetEmitation());
1197 ShowAdventureInfo();
1198 if (!game::IsInWilderness()) {
1199 for(int c
= 0; c
< GetSquaresUnder(); ++c
) LSquareUnder
[c
]->SetTemporaryEmitation(0);
1203 if (!game::IsInWilderness()) {
1204 if (GetSquaresUnder() == 1) {
1205 stack
*StackUnder
= LSquareUnder
[0]->GetStack();
1206 GetStack()->MoveItemsTo(StackUnder
);
1207 doforbodypartswithparam
<stack
*>()(this, &bodypart::DropEquipment
, StackUnder
);
1209 while (GetStack()->GetItems()) GetStack()->GetBottom()->MoveTo(LSquareUnder
[RAND_N(GetSquaresUnder())]->GetStack());
1210 for (int c
= 0; c
< BodyParts
; ++c
) {
1211 bodypart
*BodyPart
= GetBodyPart(c
);
1212 if (BodyPart
) BodyPart
->DropEquipment(LSquareUnder
[RAND_N(GetSquaresUnder())]->GetStack());
1217 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(0);
1219 Flags
&= ~C_IN_NO_MSG_MODE
;
1223 if (!game::IsInWilderness()) {
1224 Ghost
->PutTo(LSquareUnder
[0]->GetPos());
1228 game::TextScreen(CONST_S("Unfortunately you died."), ZERO_V2
, WHITE
, true, true, &game::ShowDeathSmiley
);
1234 void character::AddMissMessage (ccharacter
*Enemy
) const {
1236 if (Enemy
->IsPlayer()) Msg
= GetDescription(DEFINITE
)+" misses you!";
1237 else if (IsPlayer()) Msg
= CONST_S("You miss ")+Enemy
->GetDescription(DEFINITE
)+'!';
1238 else if (CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer()) Msg
= GetDescription(DEFINITE
)+" misses "+Enemy
->GetDescription(DEFINITE
)+'!';
1240 ADD_MESSAGE("%s", Msg
.CStr());
1244 void character::AddBlockMessage (ccharacter
*Enemy
, citem
*Blocker
, cfestring
&HitNoun
, truth Partial
) const {
1246 festring BlockVerb
= (Partial
? " to partially block the " : " to block the ")+HitNoun
;
1248 Msg
<< "You manage" << BlockVerb
<< " with your " << Blocker
->GetName(UNARTICLED
) << '!';
1249 } else if (Enemy
->IsPlayer() || Enemy
->CanBeSeenByPlayer()) {
1250 if (CanBeSeenByPlayer())
1251 Msg
<< GetName(DEFINITE
) << " manages" << BlockVerb
<< " with " << GetPossessivePronoun() << ' ' << Blocker
->GetName(UNARTICLED
) << '!';
1253 Msg
<< "Something manages" << BlockVerb
<< " with something!";
1257 ADD_MESSAGE("%s", Msg
.CStr());
1261 void character::AddPrimitiveHitMessage (ccharacter
*Enemy
, cfestring
&FirstPersonHitVerb
,
1262 cfestring
&ThirdPersonHitVerb
, int BodyPart
) const
1265 festring BodyPartDescription
;
1266 if (BodyPart
&& (Enemy
->CanBeSeenByPlayer() || Enemy
->IsPlayer()))
1267 BodyPartDescription
<< " in the " << Enemy
->GetBodyPartName(BodyPart
);
1268 if (Enemy
->IsPlayer())
1269 Msg
<< GetDescription(DEFINITE
) << ' ' << ThirdPersonHitVerb
<< " you" << BodyPartDescription
<< '!';
1270 else if (IsPlayer())
1271 Msg
<< "You " << FirstPersonHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
<< '!';
1272 else if (CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer())
1273 Msg
<< GetDescription(DEFINITE
) << ' ' << ThirdPersonHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) + BodyPartDescription
<< '!';
1276 ADD_MESSAGE("%s", Msg
.CStr());
1280 cchar
*const HitVerb
[] = { "strike", "slash", "stab" };
1281 cchar
*const HitVerb3rdPersonEnd
[] = { "s", "es", "s" };
1284 void character::AddWeaponHitMessage (ccharacter
*Enemy
, citem
*Weapon
, int BodyPart
, truth Critical
) const {
1286 festring BodyPartDescription
;
1288 if (BodyPart
&& (Enemy
->CanBeSeenByPlayer() || Enemy
->IsPlayer()))
1289 BodyPartDescription
<< " in the " << Enemy
->GetBodyPartName(BodyPart
);
1291 int FittingTypes
= 0;
1292 int DamageFlags
= Weapon
->GetDamageFlags();
1295 for (int c
= 0; c
< DAMAGE_TYPES
; ++c
) {
1296 if (1 << c
& DamageFlags
) {
1297 if (!FittingTypes
|| !RAND_N(FittingTypes
+1)) DamageType
= c
;
1302 if (!FittingTypes
) ABORT("No damage flags specified for %s!", Weapon
->CHAR_NAME(UNARTICLED
));
1304 festring NewHitVerb
= Critical
? " critically " : " ";
1305 NewHitVerb
<< HitVerb
[DamageType
];
1306 cchar
*const E
= HitVerb3rdPersonEnd
[DamageType
];
1308 if (Enemy
->IsPlayer()) {
1309 Msg
<< GetDescription(DEFINITE
) << NewHitVerb
<< E
<< " you" << BodyPartDescription
;
1310 if (CanBeSeenByPlayer()) Msg
<< " with " << GetPossessivePronoun() << ' ' << Weapon
->GetName(UNARTICLED
);
1312 } else if (IsPlayer()) {
1313 Msg
<< "You" << NewHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
<< '!';
1314 } else if(CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer()) {
1315 Msg
<< GetDescription(DEFINITE
) << NewHitVerb
<< E
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
;
1316 if (CanBeSeenByPlayer()) Msg
<< " with " << GetPossessivePronoun() << ' ' << Weapon
->GetName(UNARTICLED
);
1321 ADD_MESSAGE("%s", Msg
.CStr());
1325 item
*character::GeneralFindItem (ItemCheckerCB chk
) const {
1326 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1328 if (it
&& chk(it
)) return it
;
1334 static truth
isElpuriHead (item
*i
) { return i
->IsHeadOfElpuri(); }
1335 truth
character::HasHeadOfElpuri () const {
1336 if (GeneralFindItem(::isElpuriHead
)) return true;
1337 return combineequipmentpredicates()(this, &item::IsHeadOfElpuri
, 1);
1341 static truth
isPetrussNut (item
*i
) { return i
->IsPetrussNut(); }
1342 truth
character::HasPetrussNut () const {
1343 if (GeneralFindItem(::isPetrussNut
)) return true;
1344 return combineequipmentpredicates()(this, &item::IsPetrussNut
, 1);
1348 static truth
isGoldenEagleShirt (item
*i
) { return i
->IsGoldenEagleShirt(); }
1349 truth
character::HasGoldenEagleShirt () const {
1350 if (GeneralFindItem(::isGoldenEagleShirt
)) return true;
1351 return combineequipmentpredicates()(this, &item::IsGoldenEagleShirt
, 1);
1355 truth
character::HasOmmelBlood () const {
1356 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
)
1357 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) return true;
1359 for (int c
= 0; c
< GetEquipments(); ++c
) {
1360 item
*Item
= GetEquipment(c
);
1362 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) return true;
1364 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1368 truth
character::CurdleOmmelBlood () const {
1369 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1370 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) {
1371 i
->ChangeSecondaryMaterial(MAKE_MATERIAL(CURDLED_OMMEL_BLOOD
));
1376 for (int c
= 0; c
< GetEquipments(); ++c
) {
1377 item
*Item
= GetEquipment(c
);
1379 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) {
1380 Item
->ChangeSecondaryMaterial(MAKE_MATERIAL(CURDLED_OMMEL_BLOOD
));
1384 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1388 truth
character::RemoveCurdledOmmelBlood () {
1389 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1390 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) {
1391 (*i
)->RemoveFromSlot();
1397 for (int c
= 0; c
< GetEquipments(); ++c
) {
1398 item
*Item
= GetEquipment(c
);
1400 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) {
1401 Item
->RemoveFromSlot();
1410 int character::GeneralRemoveItem (ItemCheckerCB chk
, truth allItems
) {
1416 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1418 if (Item
&& chk(Item
)) {
1419 Item
->RemoveFromSlot();
1422 if (!allItems
) return cnt
;
1431 for (int c
= 0; c
< GetEquipments(); ++c
) {
1432 item
*Item
= GetEquipment(c
);
1433 if (Item
&& chk(Item
)) {
1434 Item
->RemoveFromSlot();
1437 if (!allItems
) return cnt
;
1447 static truth
isEncryptedScroll (item
*i
) { return i
->IsEncryptedScroll(); }
1448 truth
character::RemoveEncryptedScroll () { return GeneralRemoveItem(::isEncryptedScroll
) != 0; }
1451 static truth
isMondedrPass (item
*i
) { return i
->IsMondedrPass(); }
1452 truth
character::RemoveMondedrPass () { return GeneralRemoveItem(::isMondedrPass
) != 0; }
1455 static truth
isRingOfThieves (item
*i
) { return i
->IsRingOfThieves(); }
1456 truth
character::RemoveRingOfThieves () { return GeneralRemoveItem(::isRingOfThieves
) != 0; }
1459 truth
character::ReadItem (item
*ToBeRead
) {
1460 if (!ToBeRead
->CanBeRead(this)) {
1461 if (IsPlayer()) ADD_MESSAGE("You can't read this.");
1464 if (!GetLSquareUnder()->IsDark() || game::GetSeeWholeMapCheatMode()) {
1465 if (StateIsActivated(CONFUSED
) && !(RAND()&7)) {
1466 if (!ToBeRead
->IsDestroyable(this)) {
1467 ADD_MESSAGE("You read some words of %s and understand exactly nothing.", ToBeRead
->CHAR_NAME(DEFINITE
));
1469 ADD_MESSAGE("%s is very confusing. Or perhaps you are just too confused?", ToBeRead
->CHAR_NAME(DEFINITE
));
1470 ActivateRandomState(SRC_CONFUSE_READ
, 1000+RAND()%1500);
1471 ToBeRead
->RemoveFromSlot();
1472 ToBeRead
->SendToHell();
1477 if (ToBeRead
->Read(this)) {
1478 if (!game::WizardModeIsActive()) {
1479 /* This AP is used to take the stuff out of backpack */
1486 if (IsPlayer()) ADD_MESSAGE("It's too dark here to read.");
1491 void character::CalculateBurdenState () {
1492 int OldBurdenState
= BurdenState
;
1493 sLong SumOfMasses
= GetCarriedWeight();
1494 sLong CarryingStrengthUnits
= sLong(GetCarryingStrength())*2500;
1495 if (SumOfMasses
> (CarryingStrengthUnits
<< 1) + CarryingStrengthUnits
) BurdenState
= OVER_LOADED
;
1496 else if (SumOfMasses
> CarryingStrengthUnits
<< 1) BurdenState
= STRESSED
;
1497 else if (SumOfMasses
> CarryingStrengthUnits
) BurdenState
= BURDENED
;
1498 else BurdenState
= UNBURDENED
;
1499 if (!IsInitializing() && BurdenState
!= OldBurdenState
) CalculateBattleInfo();
1503 void character::Save (outputfile
&SaveFile
) const {
1504 SaveFile
<< (uShort
)GetType();
1505 Stack
->Save(SaveFile
);
1507 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) SaveFile
<< BaseExperience
[c
];
1509 SaveFile
<< ExpModifierMap
;
1510 SaveFile
<< NP
<< AP
<< Stamina
<< GenerationDanger
<< ScienceTalks
<< CounterToMindWormHatch
;
1511 SaveFile
<< TemporaryState
<< EquipmentState
<< Money
<< GoingTo
<< RegenerationCounter
<< Route
<< Illegal
;
1512 SaveFile
<< CurrentSweatMaterial
;
1513 SaveFile
.Put(!!IsEnabled());
1514 SaveFile
<< HomeData
<< BlocksSinceLastTurn
<< CommandFlags
;
1515 SaveFile
<< WarnFlags
<< (uShort
)Flags
;
1517 for (int c
= 0; c
< BodyParts
; ++c
) SaveFile
<< BodyPartSlot
[c
] << OriginalBodyPartID
[c
];
1519 SaveLinkedList(SaveFile
, TrapData
);
1522 for (int c
= 0; c
< STATES
; ++c
) SaveFile
<< TemporaryStateCounter
[c
];
1526 SaveFile
<< Team
->GetID(); // feuLong
1528 SaveFile
.Put(false);
1531 if (GetTeam() && GetTeam()->GetLeader() == this) SaveFile
.Put(true); else SaveFile
.Put(false);
1533 SaveFile
<< AssignedName
<< PolymorphBackup
;
1535 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) SaveFile
<< CWeaponSkill
[c
];
1537 SaveFile
<< (uShort
)GetConfig();
1541 void character::Load (inputfile
&SaveFile
) {
1543 Stack
->Load(SaveFile
);
1545 game::AddCharacterID(this, ID
);
1547 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) SaveFile
>> BaseExperience
[c
];
1549 SaveFile
>> ExpModifierMap
;
1550 SaveFile
>> NP
>> AP
>> Stamina
>> GenerationDanger
>> ScienceTalks
>> CounterToMindWormHatch
;
1551 SaveFile
>> TemporaryState
>> EquipmentState
>> Money
>> GoingTo
>> RegenerationCounter
>> Route
>> Illegal
;
1552 SaveFile
>> CurrentSweatMaterial
;
1554 if (!SaveFile
.Get()) Disable();
1556 SaveFile
>> HomeData
>> BlocksSinceLastTurn
>> CommandFlags
;
1557 SaveFile
>> WarnFlags
;
1558 WarnFlags
&= ~WARNED
;
1559 Flags
|= ReadType
<uShort
>(SaveFile
) & ~ENTITY_FLAGS
;
1561 for (int c
= 0; c
< BodyParts
; ++c
) {
1562 SaveFile
>> BodyPartSlot
[c
] >> OriginalBodyPartID
[c
];
1563 item
*BodyPart
= *BodyPartSlot
[c
];
1564 if (BodyPart
) BodyPart
->Disable();
1567 LoadLinkedList(SaveFile
, TrapData
);
1570 if (Action
) Action
->SetActor(this);
1572 for (int c
= 0; c
< STATES
; ++c
) SaveFile
>> TemporaryStateCounter
[c
];
1574 if (SaveFile
.Get()) SetTeam(game::GetTeam(ReadType
<feuLong
>(SaveFile
)));
1576 if (SaveFile
.Get()) GetTeam()->SetLeader(this);
1578 SaveFile
>> AssignedName
>> PolymorphBackup
;
1580 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) SaveFile
>> CWeaponSkill
[c
];
1582 databasecreator
<character
>::InstallDataBase(this, ReadType
<uShort
>(SaveFile
));
1584 if (IsEnabled() && !game::IsInWilderness()) {
1585 for (int c
= 1; c
< GetSquaresUnder(); ++c
) GetSquareUnder(c
)->SetCharacter(this);
1590 truth
character::Engrave (cfestring
&What
) {
1591 GetLSquareUnder()->Engrave(What
);
1595 truth
character::MoveRandomly () {
1596 if (!IsEnabled()) return false;
1597 for (int c
= 0; c
< 10; ++c
) {
1598 v2 ToTry
= game::GetMoveVector(RAND()&7);
1599 if (GetLevel()->IsValidPos(GetPos()+ToTry
)) {
1600 lsquare
*Square
= GetNearLSquare(GetPos()+ToTry
);
1601 if (!Square
->IsDangerous(this) && !Square
->IsScary(this) && TryMove(ToTry
, false, false)) return true;
1608 truth
character::TestForPickup (item
*ToBeTested
) const {
1609 if (MakesBurdened(ToBeTested
->GetWeight()+GetCarriedWeight())) return false;
1614 void character::AddScoreEntry (cfestring
&Description
, double Multiplier
, truth AddEndLevel
) const {
1615 if (!game::WizardModeIsReallyActive()) {
1617 if (!HScore
.CheckVersion()) {
1618 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;
1621 festring Desc
= game::GetPlayerName();
1622 Desc
<< ", " << Description
;
1623 if (AddEndLevel
) Desc
<< " in "+(game::IsInWilderness() ? "the world map" : game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex()));
1624 HScore
.Add(sLong(game::GetScore()*Multiplier
), Desc
);
1630 truth
character::CheckDeath (cfestring
&Msg
, ccharacter
*Murderer
, feuLong DeathFlags
) {
1631 if (!IsEnabled()) return true;
1632 if (game::IsSumoWrestling() && IsDead()) {
1633 game::EndSumoWrestling(!!IsPlayer());
1636 if (DeathFlags
& FORCE_DEATH
|| IsDead()) {
1637 if (Murderer
&& Murderer
->IsPlayer() && GetTeam()->GetKillEvilness()) game::DoEvilDeed(GetTeam()->GetKillEvilness());
1638 festring SpecifierMsg
;
1639 int SpecifierParts
= 0;
1640 if (GetPolymorphBackup()) {
1641 SpecifierMsg
<< " polymorphed into ";
1642 id::AddName(SpecifierMsg
, INDEFINITE
);
1645 if (!(DeathFlags
& IGNORE_TRAPS
) && IsStuck()) {
1646 if (SpecifierParts
++) SpecifierMsg
<< " and";
1647 SpecifierMsg
<< " caught in " << GetTrapDescription();
1649 if (GetAction() && !(DeathFlags
& IGNORE_UNCONSCIOUSNESS
&& GetAction()->IsUnconsciousness())) {
1650 festring ActionMsg
= GetAction()->GetDeathExplanation();
1651 if (!ActionMsg
.IsEmpty()) {
1652 if (SpecifierParts
> 1) {
1653 SpecifierMsg
= ActionMsg
<< ',' << SpecifierMsg
;
1655 if (SpecifierParts
) SpecifierMsg
<< " and";
1656 SpecifierMsg
<< ActionMsg
;
1661 festring NewMsg
= Msg
;
1662 if (Murderer
== this) {
1663 SEARCH_N_REPLACE(NewMsg
, "@bkp", CONST_S("by ") + GetPossessivePronoun(false) + " own");
1664 SEARCH_N_REPLACE(NewMsg
, "@bk", CONST_S("by ") + GetObjectPronoun(false) + "self");
1665 SEARCH_N_REPLACE(NewMsg
, "@k", GetObjectPronoun(false) + "self");
1667 SEARCH_N_REPLACE(NewMsg
, "@bkp", CONST_S("by ") + Murderer
->GetName(INDEFINITE
) + "'s");
1668 SEARCH_N_REPLACE(NewMsg
, "@bk", CONST_S("by ") + Murderer
->GetName(INDEFINITE
));
1669 SEARCH_N_REPLACE(NewMsg
, "@k", CONST_S("by ") + Murderer
->GetName(INDEFINITE
));
1671 if (SpecifierParts
) NewMsg
<< " while" << SpecifierMsg
;
1672 if (IsPlayer() && game::WizardModeIsActive()) ADD_MESSAGE("Death message: %s. Score: %d.", NewMsg
.CStr(), game::GetScore());
1673 Die(Murderer
, NewMsg
, DeathFlags
);
1680 truth
character::CheckStarvationDeath (cfestring
&Msg
) {
1681 if (GetNP() < 1 && UsesNutrition()) return CheckDeath(Msg
, 0, FORCE_DEATH
);
1686 void character::ThrowItem (int Direction
, item
*ToBeThrown
) {
1687 if (Direction
> 7) ABORT("Throw in TOO odd direction...");
1688 ToBeThrown
->Fly(this, Direction
, GetAttribute(ARM_STRENGTH
));
1692 void character::HasBeenHitByItem (character
*Thrower
, item
*Thingy
, int Damage
, double ToHitValue
, int Direction
) {
1693 if (IsPlayer()) ADD_MESSAGE("%s hits you.", Thingy
->CHAR_NAME(DEFINITE
));
1694 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s.", Thingy
->CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
));
1695 int BodyPart
= ChooseBodyPartToReceiveHit(ToHitValue
, DodgeValue
);
1696 int WeaponSkillHits
= Thrower
? CalculateWeaponSkillHits(Thrower
) : 0;
1697 int DoneDamage
= ReceiveBodyPartDamage(Thrower
, Damage
, PHYSICAL_DAMAGE
, BodyPart
, Direction
);
1698 truth Succeeded
= (GetBodyPart(BodyPart
) && HitEffect(Thrower
, Thingy
, Thingy
->GetPos(), THROW_ATTACK
, BodyPart
, Direction
, !DoneDamage
)) || DoneDamage
;
1699 if (Succeeded
&& Thrower
) Thrower
->WeaponSkillHit(Thingy
, THROW_ATTACK
, WeaponSkillHits
);
1700 festring DeathMsg
= CONST_S("killed by a flying ")+Thingy
->GetName(UNARTICLED
);
1701 if (CheckDeath(DeathMsg
, Thrower
)) return;
1703 if (Thrower
->CanBeSeenByPlayer())
1704 DeActivateVoluntaryAction(CONST_S("The attack of ")+Thrower
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
1706 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
1708 DeActivateVoluntaryAction(CONST_S("The hit interrupts you."));
1713 truth
character::DodgesFlyingItem (item
*Item
, double ToHitValue
) {
1714 return !Item
->EffectIsGood() && RAND() % int(100+ToHitValue
/DodgeValue
*100) < 100;
1718 void character::GetPlayerCommand () {
1720 truth HasActed
= false;
1722 game::DrawEverything();
1723 if (game::GetDangerFound()) {
1724 if (game::GetDangerFound() > 500.) {
1725 if (game::GetCausePanicFlag()) {
1726 game::SetCausePanicFlag(false);
1727 BeginTemporaryState(PANIC
, 500+RAND_N(500));
1729 game::AskForEscPress(CONST_S("You are horrified by your situation!"));
1730 } else if (ivanconfig::GetWarnAboutDanger()) {
1731 if (game::GetDangerFound() > 50.) game::AskForEscPress(CONST_S("You sense great danger!"));
1732 else game::AskForEscPress(CONST_S("You sense danger!"));
1734 game::SetDangerFound(0);
1736 game::SetIsInGetCommand(true);
1737 int Key
= GET_KEY();
1738 game::SetIsInGetCommand(false);
1739 if (Key
!= '+' && Key
!= '-' && Key
!= 'M') msgsystem::ThyMessagesAreNowOld(); // gum
1740 truth ValidKeyPressed
= false;
1742 for (int c
= 0; c
< DIRECTION_COMMAND_KEYS
; ++c
) {
1743 if (Key
== game::GetMoveCommandKey(c
)) {
1744 HasActed
= TryMove(ApplyStateModification(game::GetMoveVector(c
)), true, game::PlayerIsRunning());
1745 ValidKeyPressed
= true;
1749 if (!ValidKeyPressed
) {
1750 for (int c
= 0; (cmd
= commandsystem::GetCommand(c
)); ++c
) {
1752 /* Numpad aliases for most commonly used commands */
1753 if (Key
== KEY_DEL
&& cmd
->GetName() == "Eat") Key
= cmd
->GetKey();
1754 if (Key
== KEY_INS
&& cmd
->GetName() == "PickUp") Key
= cmd
->GetKey();
1755 if (Key
== KEY_PLUS
&& cmd
->GetName() == "EquipmentScreen") Key
= cmd
->GetKey();
1757 if (Key
== cmd
->GetKey()) {
1758 if (game::IsInWilderness() && !commandsystem::GetCommand(c
)->IsUsableInWilderness()) {
1759 ADD_MESSAGE("This function cannot be used while in wilderness.");
1760 } else if (!game::WizardModeIsActive() && commandsystem::GetCommand(c
)->IsWizardModeFunction()) {
1761 ADD_MESSAGE("Activate wizardmode to use this function.");
1763 HasActed
= commandsystem::GetCommand(c
)->GetLinkedFunction()(this);
1765 ValidKeyPressed
= true;
1770 if (!ValidKeyPressed
) ADD_MESSAGE("Unknown key. Press '?' for a list of commands.");
1772 game::IncreaseTurn();
1776 void character::Vomit (v2 Pos
, int Amount
, truth ShowMsg
) {
1777 if (!CanVomit()) return;
1779 if (IsPlayer()) ADD_MESSAGE("You vomit.");
1780 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s vomits.", CHAR_NAME(DEFINITE
));
1782 if (VomittingIsUnhealthy()) {
1783 EditExperience(ARM_STRENGTH
, -75, 1 << 9);
1784 EditExperience(LEG_STRENGTH
, -75, 1 << 9);
1787 EditNP(-2500-RAND()%2501);
1788 CheckStarvationDeath(CONST_S("vomited himself to death"));
1790 if (StateIsActivated(PARASITIZED
) && !(RAND() & 7)) {
1791 if (IsPlayer()) ADD_MESSAGE("You notice a dead broad tapeworm among your former stomach contents.");
1792 DeActivateTemporaryState(PARASITIZED
);
1794 if (!game::IsInWilderness()) {
1795 GetNearLSquare(Pos
)->ReceiveVomit(this, liquid::Spawn(GetVomitMaterial(), sLong(sqrt(GetBodyVolume())*Amount
/1000)));
1800 truth
character::Polymorph (character
*NewForm
, int Counter
) {
1801 if (!IsPolymorphable() || (!IsPlayer() && game::IsInWilderness())) {
1806 if (GetAction()) GetAction()->Terminate(false);
1807 NewForm
->SetAssignedName("");
1809 ADD_MESSAGE("Your body glows in a crimson light. You transform into %s!", NewForm
->CHAR_NAME(INDEFINITE
));
1810 else if (CanBeSeenByPlayer())
1811 ADD_MESSAGE("%s glows in a crimson light and %s transforms into %s!", CHAR_NAME(DEFINITE
), GetPersonalPronoun().CStr(), NewForm
->CHAR_NAME(INDEFINITE
));
1813 Flags
|= C_IN_NO_MSG_MODE
;
1814 NewForm
->Flags
|= C_IN_NO_MSG_MODE
;
1815 NewForm
->ChangeTeam(GetTeam());
1816 NewForm
->GenerationDanger
= GenerationDanger
;
1817 NewForm
->mOnEvents
= this->mOnEvents
;
1819 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(NewForm
);
1823 NewForm
->PutToOrNear(Pos
);
1824 NewForm
->SetAssignedName(GetAssignedName());
1825 NewForm
->ActivateTemporaryState(POLYMORPHED
);
1826 NewForm
->SetTemporaryStateCounter(POLYMORPHED
, Counter
);
1828 if (TemporaryStateIsActivated(POLYMORPHED
)) {
1829 NewForm
->SetPolymorphBackup(GetPolymorphBackup());
1830 SetPolymorphBackup(0);
1833 NewForm
->SetPolymorphBackup(this);
1834 Flags
|= C_POLYMORPHED
;
1838 GetStack()->MoveItemsTo(NewForm
->GetStack());
1839 NewForm
->SetMoney(GetMoney());
1840 DonateEquipmentTo(NewForm
);
1841 Flags
&= ~C_IN_NO_MSG_MODE
;
1842 NewForm
->Flags
&= ~C_IN_NO_MSG_MODE
;
1843 NewForm
->CalculateAll();
1847 game::SetPlayer(NewForm
);
1848 game::SendLOSUpdateRequest();
1852 NewForm
->TestWalkability();
1857 void character::BeKicked (character
*Kicker
, item
*Boot
, bodypart
*Leg
, v2 HitPos
, double KickDamage
,
1858 double ToHitValue
, int Success
, int Direction
, truth Critical
, truth ForceHit
)
1861 game::ClearEventData();
1862 game::mActor
= Kicker
;
1863 if (game::RunOnCharEvent(this, CONST_S("before_be_kicked"))) { game::ClearEventData(); return; }
1864 game::ClearEventData();
1866 switch (TakeHit(Kicker
, Boot
, Leg
, HitPos
, KickDamage
, ToHitValue
, Success
, KICK_ATTACK
, Direction
, Critical
, ForceHit
)) {
1870 if (IsEnabled() && !CheckBalance(KickDamage
)) {
1871 if (IsPlayer()) ADD_MESSAGE("The kick throws you off balance.");
1872 else if (Kicker
->IsPlayer()) ADD_MESSAGE("The kick throws %s off balance.", CHAR_DESCRIPTION(DEFINITE
));
1873 v2 FallToPos
= GetPos()+game::GetMoveVector(Direction
);
1874 FallTo(Kicker
, FallToPos
);
1880 /* Return true if still in balance */
1881 truth
character::CheckBalance (double KickDamage
) {
1882 return !CanMove() || IsStuck() || !KickDamage
|| (!IsFlying() && KickDamage
*5 < RAND()%GetSize());
1886 void character::FallTo (character
*GuiltyGuy
, v2 Where
) {
1888 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
1889 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, Where
);
1891 truth NoRoom
= false;
1892 for (int c
= 0; c
< Squares
; ++c
) {
1893 olterrain
*Terrain
= MoveToSquare
[c
]->GetOLTerrain();
1894 if (Terrain
&& !CanMoveOn(Terrain
)) { NoRoom
= true; break; }
1898 if (IsPlayer()) ADD_MESSAGE("You hit your head on the wall.");
1899 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s head on the wall.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
1901 ReceiveDamage(GuiltyGuy
, 1+RAND()%5, PHYSICAL_DAMAGE
, HEAD
);
1902 CheckDeath(CONST_S("killed by hitting a wall due to being kicked @bk"), GuiltyGuy
);
1904 if (IsFreeForMe(MoveToSquare
[0])) Move(Where
, true);
1905 // Place code that handles characters bouncing to each other here
1911 truth
character::CheckCannibalism (cmaterial
*What
) const {
1912 return GetTorso()->GetMainMaterial()->IsSameAs(What
);
1916 void character::StandIdleAI () {
1917 SeekLeader(GetLeader());
1918 if (CheckForEnemies(true, true, true)) return;
1919 if (CheckForUsefulItemsOnGround()) return;
1920 if (FollowLeader(GetLeader())) return;
1921 if (CheckForDoors()) return;
1922 if (MoveTowardsHomePos()) return;
1923 if (CheckSadism()) return;
1928 truth
character::LoseConsciousness (int Counter
, truth HungerFaint
) {
1929 if (!AllowUnconsciousness()) return false;
1930 action
*Action
= GetAction();
1932 if (HungerFaint
&& !Action
->AllowUnconsciousness()) return false;
1933 if (Action
->IsUnconsciousness()) {
1934 static_cast<unconsciousness
*>(Action
)->RaiseCounterTo(Counter
);
1937 Action
->Terminate(false);
1939 if (IsPlayer()) ADD_MESSAGE("You lose consciousness.");
1940 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s loses consciousness.", CHAR_NAME(DEFINITE
));
1941 unconsciousness
*Unconsciousness
= unconsciousness::Spawn(this);
1942 Unconsciousness
->SetCounter(Counter
);
1943 SetAction(Unconsciousness
);
1948 void character::DeActivateVoluntaryAction (cfestring
&Reason
) {
1949 if (GetAction() && GetAction()->IsVoluntary()) {
1951 if (Reason
.GetSize()) ADD_MESSAGE("%s", Reason
.CStr());
1952 if (game::TruthQuestion(CONST_S("Continue ") + GetAction()->GetDescription()+"? [y/N]")) GetAction()->ActivateInDNDMode();
1953 else GetAction()->Terminate(false);
1956 GetAction()->Terminate(false);
1962 void character::ActionAutoTermination () {
1963 if (!GetAction() || !GetAction()->IsVoluntary() || GetAction()->InDNDMode()) return;
1965 for (int c
= 0; c
< game::GetTeams(); ++c
) {
1966 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
1967 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
1969 if (ch
->IsEnabled() && ch
->CanBeSeenBy(this, false, true) && (ch
->CanMove() || ch
->GetPos().IsAdjacent(Pos
)) && ch
->CanAttack()) {
1971 ADD_MESSAGE("%s seems to be hostile.", ch
->CHAR_NAME(DEFINITE
));
1972 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"? [y/N]")) GetAction()->ActivateInDNDMode();
1973 else GetAction()->Terminate(false);
1975 GetAction()->Terminate(false);
1985 truth
character::CheckForEnemies (truth CheckDoors
, truth CheckGround
, truth MayMoveRandomly
, truth RunTowardsTarget
) {
1986 if (!IsEnabled()) return false;
1987 truth HostileCharsNear
= false;
1988 character
*NearestChar
= 0;
1989 sLong NearestDistance
= 0x7FFFFFFF;
1991 for (int c
= 0; c
< game::GetTeams(); ++c
) {
1992 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
1993 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
1995 if (ch
->IsEnabled() && GetAttribute(WISDOM
) < ch
->GetAttackWisdomLimit()) {
1996 sLong ThisDistance
= Max
<sLong
>(abs(ch
->GetPos().X
- Pos
.X
), abs(ch
->GetPos().Y
- Pos
.Y
));
1997 if (ThisDistance
<= GetLOSRangeSquare()) HostileCharsNear
= true;
1998 if ((ThisDistance
< NearestDistance
|| (ThisDistance
== NearestDistance
&& !(RAND() % 3))) &&
1999 ch
->CanBeSeenBy(this, false, IsGoingSomeWhere()) &&
2000 (!IsGoingSomeWhere() || HasClearRouteTo(ch
->GetPos()))) {
2002 NearestDistance
= ThisDistance
;
2010 if (GetAttribute(INTELLIGENCE
) >= 10 || IsSpy()) game::CallForAttention(GetPos(), 100);
2011 if (SpecialEnemySightedReaction(NearestChar
)) return true;
2012 if (IsExtraCoward() && !StateIsActivated(PANIC
) && NearestChar
->GetRelativeDanger(this) >= 0.5) {
2013 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s sees %s.", CHAR_NAME(DEFINITE
), NearestChar
->CHAR_DESCRIPTION(DEFINITE
));
2014 BeginTemporaryState(PANIC
, 500+RAND()%500);
2016 if (!IsRetreating()) {
2017 if (CheckGround
&& NearestDistance
> 2 && CheckForUsefulItemsOnGround(false)) return true;
2018 SetGoingTo(NearestChar
->GetPos());
2020 SetGoingTo(Pos
-((NearestChar
->GetPos()-Pos
)<<4));
2022 return MoveTowardsTarget(true);
2024 character
*Leader
= GetLeader();
2025 if (Leader
== this) Leader
= 0;
2026 if (!Leader
&& IsGoingSomeWhere()) {
2027 if (!MoveTowardsTarget(RunTowardsTarget
)) {
2031 if (!IsEnabled()) return true;
2032 if (GetPos() == GoingTo
) TerminateGoingTo();
2036 if ((!Leader
|| (Leader
&& !IsGoingSomeWhere())) && HostileCharsNear
) {
2037 if (CheckDoors
&& CheckForDoors()) return true;
2038 if (CheckGround
&& CheckForUsefulItemsOnGround()) return true;
2039 if (MayMoveRandomly
&& MoveRandomly()) return true; // one has heard that an enemy is near but doesn't know where
2047 truth
character::CheckForDoors () {
2048 if (!CanOpen() || !IsEnabled()) return false;
2049 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2050 lsquare
*Square
= GetNeighbourLSquare(d
);
2051 if (Square
&& Square
->GetOLTerrain() && Square
->GetOLTerrain()->Open(this)) return true;
2057 truth
character::CheckForUsefulItemsOnGround (truth CheckFood
) {
2058 if (StateIsActivated(PANIC
) || !IsEnabled()) return false;
2059 itemvector ItemVector
;
2060 GetStackUnder()->FillItemVector(ItemVector
);
2061 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
2062 if (ItemVector
[c
]->CanBeSeenBy(this) && ItemVector
[c
]->IsPickable(this)) {
2063 if (!(CommandFlags
& DONT_CHANGE_EQUIPMENT
) && TryToEquip(ItemVector
[c
])) return true;
2064 if (CheckFood
&& UsesNutrition() && !CheckIfSatiated() && TryToConsume(ItemVector
[c
])) return true;
2065 if (IsRangedAttacker() && (ItemVector
[c
])->GetThrowItemTypes() && TryToAddToInventory(ItemVector
[c
])) return true;
2072 truth
character::TryToAddToInventory (item
*Item
) {
2073 if (!(GetBurdenState() > STRESSED
) || !CanUseEquipment() || Item
->GetSquaresUnder() != 1) return false;
2074 room
*Room
= GetRoom();
2075 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
2076 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up %s from the ground.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(INDEFINITE
));
2077 Item
->MoveTo(GetStack());
2085 truth
character::CheckInventoryForItemToThrow (item
*ToBeChecked
) {
2086 return (ToBeChecked
->GetThrowItemTypes() & GetWhatThrowItemTypesToThrow()) ? true : false; //hehe
2090 truth
character::CheckThrowItemOpportunity () {
2091 if (!IsRangedAttacker() || !CanThrow() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2092 //fprintf(stderr, "character::CheckThrowItemOpportunity...\n");
2094 // (1) - Acquire target as nearest enemy
2095 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2096 // (3) - check inventory for throwing weapon, select this weapon
2097 // (4) - throw item in direction where the enemy is
2099 //Check the visible area for hostiles
2100 int ThrowDirection
= 0;
2101 int TargetFound
= 0;
2104 int RangeMax
= GetLOSRange();
2105 int CandidateDirections
[7] = {0, 0, 0, 0, 0, 0, 0};
2106 int HostileFound
= 0;
2107 item
*ToBeThrown
= 0;
2108 level
*Level
= GetLevel();
2110 for (int r
= 1; r
<= RangeMax
; ++r
) {
2111 for (int dir
= 0; dir
< 8; ++dir
) {
2114 case 0: TestPos
= v2(Pos
.X
-r
, Pos
.Y
-r
); break;
2115 case 1: TestPos
= v2(Pos
.X
, Pos
.Y
-r
); break;
2116 case 2: TestPos
= v2(Pos
.X
+r
, Pos
.Y
-r
); break;
2117 case 3: TestPos
= v2(Pos
.X
-r
, Pos
.Y
); break;
2118 case 4: TestPos
= v2(Pos
.X
+r
, Pos
.Y
); break;
2119 case 5: TestPos
= v2(Pos
.X
-r
, Pos
.Y
+r
); break;
2120 case 6: TestPos
= v2(Pos
.X
, Pos
.Y
+r
); break;
2121 case 7: TestPos
= v2(Pos
.X
+r
, Pos
.Y
+r
); break;
2123 if (Level
->IsValidPos(TestPos
)) {
2124 square
*TestSquare
= GetNearSquare(TestPos
);
2125 character
*Dude
= TestSquare
->GetCharacter();
2127 if (Dude
&& Dude
->IsEnabled() && Dude
->CanBeSeenBy(this, false, true)) {
2128 if (GetRelation(Dude
) != HOSTILE
) CandidateDirections
[dir
] = BLOCKED
;
2129 else if (GetRelation(Dude
) == HOSTILE
&& CandidateDirections
[dir
] != BLOCKED
) {
2130 //then load this candidate position direction into the vector of possible throw directions
2131 CandidateDirections
[dir
] = SUCCESS
;
2140 for (int dir
= 0; dir
< 8; ++dir
) {
2141 if (CandidateDirections
[dir
] == SUCCESS
&& !TargetFound
) {
2142 ThrowDirection
= dir
;
2147 if (!TargetFound
) return false;
2151 //fprintf(stderr, "throw: has target.\n");
2152 // check inventory for throwing weapon
2153 itemvector ItemVector
;
2154 GetStack()->FillItemVector(ItemVector
);
2155 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
2156 if (ItemVector
[c
]->IsThrowingWeapon()) {
2157 ToBeThrown
= ItemVector
[c
];
2161 if (!ToBeThrown
) return false;
2162 //fprintf(stderr, "throw: has throwing weapon.\n");
2163 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s throws %s.", CHAR_NAME(DEFINITE
), ToBeThrown
->CHAR_NAME(INDEFINITE
));
2164 ThrowItem(ThrowDirection
, ToBeThrown
);
2165 EditExperience(ARM_STRENGTH
, 75, 1<<8);
2166 EditExperience(DEXTERITY
, 75, 1<<8);
2167 EditExperience(PERCEPTION
, 75, 1<<8);
2175 truth
character::CheckAIZapOpportunity () {
2176 if (/*!IsRangedAttacker() || */ !CanZap() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2178 // (1) - Acquire target as nearest enemy
2179 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2180 // (3) - check inventory for zappable item
2181 // (4) - zap item in direction where the enemy is
2182 //Check the rest of the visible area for hostiles
2185 int SensibleRange
= 5;
2186 int RangeMax
= GetLOSRange();
2187 if (RangeMax
< SensibleRange
) SensibleRange
= RangeMax
;
2188 int CandidateDirections
[7] = {0, 0, 0, 0, 0, 0, 0};
2189 int HostileFound
= 0;
2190 int ZapDirection
= 0;
2191 int TargetFound
= 0;
2192 item
*ToBeZapped
= 0;
2193 level
*Level
= GetLevel();
2195 for (int r
= 2; r
<= SensibleRange
; ++r
) {
2196 for (int dir
= 0; dir
< 8; ++dir
) {
2198 case 0: TestPos
= v2(Pos
.X
-r
, Pos
.Y
-r
); break;
2199 case 1: TestPos
= v2(Pos
.X
, Pos
.Y
-r
); break;
2200 case 2: TestPos
= v2(Pos
.X
+r
, Pos
.Y
-r
); break;
2201 case 3: TestPos
= v2(Pos
.X
-r
, Pos
.Y
); break;
2202 case 4: TestPos
= v2(Pos
.X
+r
, Pos
.Y
); break;
2203 case 5: TestPos
= v2(Pos
.X
-r
, Pos
.Y
+r
); break;
2204 case 6: TestPos
= v2(Pos
.X
, Pos
.Y
+r
); break;
2205 case 7: TestPos
= v2(Pos
.X
+r
, Pos
.Y
+r
); break;
2207 if (Level
->IsValidPos(TestPos
)) {
2208 square
*TestSquare
= GetNearSquare(TestPos
);
2209 character
*Dude
= TestSquare
->GetCharacter();
2211 if (Dude
&& Dude
->IsEnabled() && Dude
->CanBeSeenBy(this, false, true)) {
2212 if (GetRelation(Dude
) != HOSTILE
) CandidateDirections
[dir
] = BLOCKED
;
2213 else if (GetRelation(Dude
) == HOSTILE
&& CandidateDirections
[dir
] != BLOCKED
) {
2214 //then load this candidate position direction into the vector of possible zap directions
2215 CandidateDirections
[dir
] = SUCCESS
;
2224 for (int dir
= 0; dir
< 8; ++dir
) {
2225 if (CandidateDirections
[dir
] == SUCCESS
&& !TargetFound
) {
2231 if (!TargetFound
) return false;
2235 // check inventory for zappable item
2236 itemvector ItemVector
;
2237 GetStack()->FillItemVector(ItemVector
);
2238 for (unsigned int c
= 0; c
< ItemVector
.size(); ++c
) {
2239 if (ItemVector
[c
]->GetMinCharges() > 0 && ItemVector
[c
]->GetPrice()) {
2240 // bald-faced gum solution for choosing zappables that have shots left.
2241 // MinCharges needs to be replaced. Empty wands have zero price!
2242 ToBeZapped
= ItemVector
[c
];
2246 if (!ToBeZapped
) return false;
2247 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s zaps %s.", CHAR_NAME(DEFINITE
), ToBeZapped
->CHAR_NAME(INDEFINITE
));
2248 if (ToBeZapped
->Zap(this, GetPos(), ZapDirection
)) {
2249 EditAP(-100000/APBonus(GetAttribute(PERCEPTION
)));
2259 truth
character::FollowLeader (character
*Leader
) {
2260 if (!Leader
|| Leader
== this || !IsEnabled()) return false;
2261 if (CommandFlags
& FOLLOW_LEADER
&& Leader
->CanBeSeenBy(this) && Leader
->SquareUnderCanBeSeenBy(this, true)) {
2262 v2 Distance
= GetPos()-GoingTo
;
2263 if (abs(Distance
.X
) <= 2 && abs(Distance
.Y
) <= 2) return false;
2264 return MoveTowardsTarget(false);
2266 if (IsGoingSomeWhere()) {
2267 if (!MoveTowardsTarget(true)) {
2277 void character::SeekLeader (ccharacter
*Leader
) {
2278 if (Leader
&& Leader
!= this) {
2279 if (Leader
->CanBeSeenBy(this) && (Leader
->SquareUnderCanBeSeenBy(this, true) || !IsGoingSomeWhere())) {
2280 if (CommandFlags
& FOLLOW_LEADER
) SetGoingTo(Leader
->GetPos());
2281 } else if (!IsGoingSomeWhere()) {
2282 team
*Team
= GetTeam();
2283 for (std::list
<character
*>::const_iterator i
= Team
->GetMember().begin(); i
!= Team
->GetMember().end(); ++i
) {
2285 if (ch
->IsEnabled() && ch
->GetID() != GetID() &&
2286 (CommandFlags
& FOLLOW_LEADER
) == (ch
->CommandFlags
& FOLLOW_LEADER
) && ch
->CanBeSeenBy(this)) {
2287 v2 Pos
= ch
->GetPos();
2288 v2 Distance
= GetPos()-Pos
;
2289 if (abs(Distance
.X
) > 2 && abs(Distance
.Y
) > 2) {
2300 int character::GetMoveEase () const {
2301 switch (BurdenState
) {
2303 case STRESSED
: return 50;
2304 case BURDENED
: return 75;
2305 case UNBURDENED
: return 100;
2311 int character::GetLOSRange () const {
2312 if (!game::IsInWilderness()) return GetAttribute(PERCEPTION
)*GetLevel()->GetLOSModifier()/48;
2317 truth
character::Displace (character
*Who
, truth Forced
) {
2318 if (GetBurdenState() == OVER_LOADED
) {
2320 cchar
*CrawlVerb
= StateIsActivated(LEVITATION
) ? "float" : "crawl";
2321 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb
);
2328 double Danger
= GetRelativeDanger(Who
);
2329 int PriorityDifference
= Limit(GetDisplacePriority()-Who
->GetDisplacePriority(), -31, 31);
2331 if (IsPlayer()) ++PriorityDifference
;
2332 else if (Who
->IsPlayer()) --PriorityDifference
;
2334 if (PriorityDifference
>= 0) Danger
*= 1 << PriorityDifference
;
2335 else Danger
/= 1 << -PriorityDifference
;
2337 if (IsSmall() && Who
->IsSmall() &&
2338 (Forced
|| Danger
> 1.0 || !(Who
->IsPlayer() || Who
->IsBadPath(GetPos()))) &&
2339 !IsStuck() && !Who
->IsStuck() && (!Who
->GetAction() || Who
->GetAction()->TryDisplace()) &&
2340 CanMove() && Who
->CanMove() && Who
->CanMoveOn(GetLSquareUnder())) {
2341 if (IsPlayer()) ADD_MESSAGE("You displace %s!", Who
->CHAR_DESCRIPTION(DEFINITE
));
2342 else if (Who
->IsPlayer()) ADD_MESSAGE("%s displaces you!", CHAR_DESCRIPTION(DEFINITE
));
2343 else if (CanBeSeenByPlayer() || Who
->CanBeSeenByPlayer()) ADD_MESSAGE("%s displaces %s!", CHAR_DESCRIPTION(DEFINITE
), Who
->CHAR_DESCRIPTION(DEFINITE
));
2344 lsquare
*OldSquareUnder1
[MAX_SQUARES_UNDER
];
2345 lsquare
*OldSquareUnder2
[MAX_SQUARES_UNDER
];
2346 for (int c
= 0; c
< GetSquaresUnder(); ++c
) OldSquareUnder1
[c
] = GetLSquareUnder(c
);
2347 for (int c
= 0; c
< Who
->GetSquaresUnder(); ++c
) OldSquareUnder2
[c
] = Who
->GetLSquareUnder(c
);
2349 v2 WhoPos
= Who
->GetPos();
2354 EditAP(-GetMoveAPRequirement(GetSquareUnder()->GetEntryDifficulty()) - 500);
2355 EditNP(-12*GetSquareUnder()->GetEntryDifficulty());
2356 EditExperience(AGILITY
, 75, GetSquareUnder()->GetEntryDifficulty() << 7);
2357 if (IsPlayer()) ShowNewPosInfo();
2358 if (Who
->IsPlayer()) Who
->ShowNewPosInfo();
2359 SignalStepFrom(OldSquareUnder1
);
2360 Who
->SignalStepFrom(OldSquareUnder2
);
2364 ADD_MESSAGE("%s resists!", Who
->CHAR_DESCRIPTION(DEFINITE
));
2373 void character::SetNP (sLong What
) {
2374 int OldState
= GetHungerState();
2377 int NewState
= GetHungerState();
2378 if (NewState
== STARVING
&& OldState
> STARVING
) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
2379 else if (NewState
== VERY_HUNGRY
&& OldState
> VERY_HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting very hungry."));
2380 else if (NewState
== HUNGRY
&& OldState
> HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting hungry."));
2385 void character::ShowNewPosInfo () const {
2386 msgsystem::EnterBigMessageMode();
2389 if (ivanconfig::GetAutoCenterMap()) {
2390 game::UpdateCameraX();
2391 game::UpdateCameraY();
2393 if (Pos
.X
< game::GetCamera().X
+3 || Pos
.X
>= game::GetCamera().X
+game::GetScreenXSize()-3) game::UpdateCameraX();
2394 if (Pos
.Y
< game::GetCamera().Y
+3 || Pos
.Y
>= game::GetCamera().Y
+game::GetScreenYSize()-3) game::UpdateCameraY();
2397 game::SendLOSUpdateRequest();
2398 game::DrawEverythingNoBlit();
2401 if (!game::IsInWilderness()) {
2402 if (GetLSquareUnder()->IsDark() && !game::GetSeeWholeMapCheatMode()) ADD_MESSAGE("It's dark in here!");
2404 GetLSquareUnder()->ShowSmokeMessage();
2405 itemvectorvector PileVector
;
2406 GetStackUnder()->Pile(PileVector
, this, CENTER
);
2408 if (PileVector
.size()) {
2409 truth Feel
= !GetLSquareUnder()->IsTransparent() || GetLSquareUnder()->IsDark();
2410 if (PileVector
.size() == 1) {
2411 if (Feel
) ADD_MESSAGE("You feel %s lying here.", PileVector
[0][0]->GetName(INDEFINITE
, PileVector
[0].size()).CStr());
2412 else ADD_MESSAGE("%s %s lying here.", PileVector
[0][0]->GetName(INDEFINITE
, PileVector
[0].size()).CStr(), PileVector
[0].size() == 1 ? "is" : "are");
2415 for (uInt c
= 0; c
< PileVector
.size(); ++c
) {
2416 if ((Items
+= PileVector
[c
].size()) > 3) break;
2419 if (Feel
) ADD_MESSAGE("You feel several items lying here.");
2420 else ADD_MESSAGE("Several items are lying here.");
2422 if (Feel
) ADD_MESSAGE("You feel a few items lying here.");
2423 else ADD_MESSAGE("A few items are lying here.");
2429 GetLSquareUnder()->GetSideItemDescription(SideItems
);
2431 if (!SideItems
.IsEmpty()) ADD_MESSAGE("There is %s.", SideItems
.CStr());
2433 if (GetLSquareUnder()->HasEngravings()) {
2434 if (CanRead()) ADD_MESSAGE("Something has been engraved here: \"%s\"", GetLSquareUnder()->GetEngraved());
2435 else ADD_MESSAGE("Something has been engraved here.");
2439 msgsystem::LeaveBigMessageMode();
2443 void character::Hostility (character
*Enemy
) {
2444 if (Enemy
== this || !Enemy
|| !Team
|| !Enemy
->Team
) return;
2445 if (Enemy
->IsMasochist() && GetRelation(Enemy
) == FRIEND
) return;
2446 if (!IsAlly(Enemy
)) {
2447 GetTeam()->Hostility(Enemy
->GetTeam());
2448 } else if (IsPlayer() && !Enemy
->IsPlayer()) {
2449 // I believe both may be players due to polymorph feature...
2450 if (Enemy
->CanBeSeenByPlayer()) ADD_MESSAGE("%s becomes enraged.", Enemy
->CHAR_NAME(DEFINITE
));
2451 Enemy
->ChangeTeam(game::GetTeam(BETRAYED_TEAM
));
2456 stack
*character::GetGiftStack () const {
2457 if (GetLSquareUnder()->GetRoomIndex() && !GetLSquareUnder()->GetRoom()->AllowDropGifts()) return GetStack();
2458 return GetStackUnder();
2462 truth
character::MoveRandomlyInRoom () {
2463 for (int c
= 0; c
< 10; ++c
) {
2464 v2 ToTry
= game::GetMoveVector(RAND()&7);
2465 if (GetLevel()->IsValidPos(GetPos()+ToTry
)) {
2466 lsquare
*Square
= GetNearLSquare(GetPos()+ToTry
);
2467 if (!Square
->IsDangerous(this) && !Square
->IsScary(this) &&
2468 (!Square
->GetOLTerrain() || !Square
->GetOLTerrain()->IsDoor()) &&
2469 TryMove(ToTry
, false, false)) return true;
2476 truth
character::IsPassableSquare (int x
, int y
) const {
2477 area
*ca
= GetSquareUnder()->GetArea();
2478 if (x
< 0 || y
< 0 || x
>= ca
->GetXSize() || y
>= ca
->GetYSize()) return false;
2479 lsquare
*sq
= static_cast<lsquare
*>(ca
->GetSquare(x
, y
));
2480 return sq
&& CanMoveOn(sq
);
2484 truth
character::IsInCorridor () const { return IsInCorridor(GetPos().X
, GetPos().Y
); }
2486 truth
character::IsInCorridor (int x
, int y
) const {
2487 if (!IsPassableSquare(x
, y
-1) && !IsPassableSquare(x
, y
+1)) return true;
2488 if (!IsPassableSquare(x
-1, y
) && !IsPassableSquare(x
+1, y
)) return true;
2489 if (IsPassableSquare(x
, y
-1) && IsPassableSquare(x
, y
+1) &&
2490 (IsPassableSquare(x
-1, y
) || IsPassableSquare(x
+1, y
))) return false;
2491 if (IsPassableSquare(x
-1, y
) && IsPassableSquare(x
+1, y
) &&
2492 (IsPassableSquare(x
, y
-1) || IsPassableSquare(x
, y
+1))) return false;
2493 if (!IsPassableSquare(x
-1, y
-1) && !IsPassableSquare(x
+1, y
-1) &&
2494 !IsPassableSquare(x
-1, y
+1) && !IsPassableSquare(x
+1, y
+1)) return true;
2499 truth
character::IsInCorridor (const v2 dir
) const {
2500 v2 nxy
= GetPos()+dir
;
2501 return IsInCorridor(nxy
.X
, nxy
.Y
);
2505 void character::GoOn (go
*Go
, truth FirstStep
) {
2506 //fprintf(stderr, "corridor: %s\n", IsInCorridor() ? "tan" : "ona");
2507 if (FirstStep
) Go
->SetIsWalkingInOpen(!IsInCorridor());
2509 v2 MoveVector
= ApplyStateModification(game::GetMoveVector(Go
->GetDirection()));
2510 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
2512 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, GetPos()+MoveVector
);
2513 if (!Squares
|| !CanMoveOn(MoveToSquare
[0])) {
2514 Go
->Terminate(false);
2518 if (!FirstStep
&& !Go
->IsWalkingInOpen() && !IsInCorridor(MoveVector
)) {
2519 Go
->Terminate(false);
2523 uInt OldRoomIndex
= GetLSquareUnder()->GetRoomIndex();
2524 uInt CurrentRoomIndex
= MoveToSquare
[0]->GetRoomIndex();
2526 if ((OldRoomIndex
&& (CurrentRoomIndex
!= OldRoomIndex
)) && !FirstStep
) {
2527 Go
->Terminate(false);
2531 for (int c
= 0; c
< Squares
; ++c
) {
2532 if ((MoveToSquare
[c
]->GetCharacter() && GetTeam() != MoveToSquare
[c
]->GetCharacter()->GetTeam()) || MoveToSquare
[c
]->IsDangerous(this)) {
2533 Go
->Terminate(false);
2549 static const int revDir
[8] = { 7, 6, 5, 4, 3, 3, 1, 0 };
2550 static const bool orthoDir
[8] = { false, true, false, true, true, false, true, false };
2552 int OKDirectionsCounter
= 0, gd
= Go
->GetDirection(), odc
= 0;
2553 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2554 lsquare
*Square
= GetNeighbourLSquare(d
);
2555 if (Square
&& CanMoveOn(Square
)) ++OKDirectionsCounter
;
2559 if (orthoDir
[gd
] && !Go
->IsWalkingInOpen()) {
2560 // check if there is dead end forward and we have only one turn
2561 v2 nxy
= GetPos()+MoveVector
;
2562 v2 nxyff
= nxy
+MoveVector
;
2564 lsquare
*sqf
= static_cast<lsquare
*>(GetSquareUnder()->GetArea()->GetSquare(nxy
));
2565 lsquare
*sqff
= static_cast<lsquare
*>(GetSquareUnder()->GetArea()->GetSquare(nxyff
));
2566 if (sqf
&& CanMoveOn(sqf
) && (!sqff
|| !CanMoveOn(sqff
))) {
2568 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2569 if (d
== gd
|| d
== revDir
[gd
] || !orthoDir
[d
]) continue;
2570 v2 sqxy
= nxy
+game::GetMoveVector(d
);
2571 lsquare
*sq
= static_cast<lsquare
*>(GetSquareUnder()->GetArea()->GetSquare(sqxy
));
2572 if (sq
&& CanMoveOn(sq
)) {
2578 //fprintf(stderr, "*: %d; nDir: %d\n", odc, nDir);
2581 bool doStop
= false;
2582 if (!Go
->IsWalkingInOpen()) {
2584 //fprintf(stderr, "dc: %d\n", OKDirectionsCounter);
2585 if (Go
->prevWasTurn()) {
2586 Go
->SetPrevWasTurn(false);
2587 } else if (odc
== 1) {
2588 // if we will step on something, do it
2589 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2590 lsquare
*Square
= GetNeighbourLSquare(d
);
2591 if (Square
&& Square
->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
2597 // follow the turn; 3: back, forward and turn
2602 if (gd
== 3) newDir
= 0;
2603 else if (gd
== 4) newDir
= 2;
2606 if (gd
== 1) newDir
= 0;
2607 else if (gd
== 6) newDir
= 5;
2610 if (gd
== 1) newDir
= 2;
2611 else if (gd
== 6) newDir
= 7;
2614 if (gd
== 3) newDir
= 5;
2615 else if (gd
== 4) newDir
= 7;
2618 //if (newDir < 0) ABORT("go error");
2619 if (newDir
< 0) { Go
->Terminate(false); return; }
2620 lsquare
*Square
= GetNeighbourLSquare(newDir
);
2621 if (Square
&& CanMoveOn(Square
)) {
2622 // fuckin' copypasta
2623 MoveVector
= ApplyStateModification(game::GetMoveVector(newDir
));
2624 int squares
= CalculateNewSquaresUnder(MoveToSquare
, GetPos()+MoveVector
);
2626 for (int c
= 0; c
< squares
; ++c
) {
2627 if ((MoveToSquare
[c
]->GetCharacter() && GetTeam() != MoveToSquare
[c
]->GetCharacter()->GetTeam()) || MoveToSquare
[c
]->IsDangerous(this)) {
2638 if (newDir
< 0) { Go
->Terminate(false); return; }
2640 //fprintf(stderr, "newDir: %d\n", newDir);
2641 Go
->SetDirection(newDir
);
2642 Go
->SetPrevWasTurn(true);
2643 v2 nxyf
= GetPos()+MoveVector
+game::GetMoveVector(newDir
);
2644 v2 nxyff
= nxyf
+game::GetMoveVector(newDir
);
2645 lsquare
*sqf
= static_cast<lsquare
*>(GetSquareUnder()->GetArea()->GetSquare(nxyf
));
2646 lsquare
*sqff
= static_cast<lsquare
*>(GetSquareUnder()->GetArea()->GetSquare(nxyff
));
2647 if (sqf
&& CanMoveOn(sqf
)) {
2648 Go
->SetPrevWasTurn(sqff
&& CanMoveOn(sqff
));
2651 } else if (!IsInCorridor()) {
2652 Go
->Terminate(false);
2656 Go
->SetPrevWasTurn(false);
2657 //if (OKDirectionsCounter <= 2) Go->SetIsWalkingInOpen(false);
2658 Go
->SetIsWalkingInOpen(!IsInCorridor());
2661 square
*BeginSquare
= GetSquareUnder();
2664 for (int c
= 0; c
< Squares
; ++c
) {
2665 if (MoveToSquare
[c
]->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
2672 truth moveOk
= TryMove(MoveVector
, true, game::PlayerIsRunning());
2673 if (!moveOk
|| BeginSquare
== GetSquareUnder() || (CurrentRoomIndex
&& (OldRoomIndex
!= CurrentRoomIndex
))) {
2675 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2676 game::DrawEverything();
2678 Go
->Terminate(false);
2682 if (doStop
/* || GetStackUnder()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())*/) {
2683 Go
->Terminate(false);
2685 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2687 game::DrawEverything();
2691 void character::SetTeam (team
*What
) {
2692 /*k8 if(Team) int esko = esko = 2; */
2694 SetTeamIterator(What
->Add(this));
2698 void character::ChangeTeam (team
*What
) {
2699 if (Team
) Team
->Remove(GetTeamIterator());
2701 SendNewDrawRequest();
2702 if (Team
) SetTeamIterator(Team
->Add(this));
2706 truth
character::ChangeRandomAttribute (int HowMuch
) {
2707 for (int c
= 0; c
< 50; ++c
) {
2708 int AttribID
= RAND()%ATTRIBUTES
;
2709 if (EditAttribute(AttribID
, HowMuch
)) return true;
2715 int character::RandomizeReply (sLong
&Said
, int Replies
) {
2716 truth NotSaid
= false;
2717 for (int c
= 0; c
< Replies
; ++c
) {
2718 if (!(Said
& (1 << c
))) {
2723 if (!NotSaid
) Said
= 0;
2725 while (Said
& 1 << (ToSay
= RAND() % Replies
));
2731 void character::DisplayInfo (festring
&Msg
) {
2733 Msg
<< " You are " << GetStandVerb() << " here.";
2735 Msg
<< ' ' << GetName(INDEFINITE
).CapitalizeCopy() << " is " << GetStandVerb() << " here. " << GetPersonalPronoun().CapitalizeCopy();
2736 cchar
*Separator1
= GetAction() ? "," : " and";
2737 cchar
*Separator2
= " and";
2738 if (GetTeam() == PLAYER
->GetTeam()) {
2741 int Relation
= GetRelation(PLAYER
);
2742 if (Relation
== HOSTILE
) Msg
<< " is hostile";
2743 else if (Relation
== UNCARING
) {
2744 Msg
<< " does not care about you";
2745 Separator1
= Separator2
= " and is";
2747 Msg
<< " is friendly";
2750 if (StateIsActivated(PANIC
)) {
2751 Msg
<< Separator1
<< " panicked";
2752 Separator2
= " and";
2754 if (GetAction()) Msg
<< Separator2
<< ' ' << GetAction()->GetDescription();
2760 void character::TestWalkability () {
2761 if (!IsEnabled()) return;
2762 square
*SquareUnder
= !game::IsInWilderness() ? GetSquareUnder() : PLAYER
->GetSquareUnder();
2763 if (SquareUnder
->IsFatalToStay() && !CanMoveOn(SquareUnder
)) {
2764 truth Alive
= false;
2765 if (!game::IsInWilderness() || IsPlayer()) {
2766 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2767 square
*Square
= GetNeighbourSquare(d
);
2768 if (Square
&& CanMoveOn(Square
) && IsFreeForMe(Square
)) {
2769 if (IsPlayer()) ADD_MESSAGE("%s.", SquareUnder
->SurviveMessage(this));
2770 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE
), SquareUnder
->MonsterSurviveMessage(this));
2771 Move(Square
->GetPos(), true); // actually, this shouldn't be a teleport move
2772 SquareUnder
->SurviveEffect(this);
2782 festring DeathMsg
= festring(SquareUnder
->DeathMessage(this));
2783 game::AskForEscPress(DeathMsg
+".");
2784 festring Msg
= SquareUnder
->ScoreEntry(this);
2785 PLAYER
->AddScoreEntry(Msg
);
2788 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE
), SquareUnder
->MonsterDeathVerb(this));
2789 Die(0, SquareUnder
->ScoreEntry(this), DISALLOW_MSG
);
2796 int character::GetSize () const {
2797 return GetTorso()->GetSize();
2801 void character::SetMainMaterial (material
*NewMaterial
, int SpecialFlags
) {
2802 NewMaterial
->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
2803 GetBodyPart(0)->SetMainMaterial(NewMaterial
, SpecialFlags
);
2804 for (int c
= 1; c
< BodyParts
; ++c
) {
2805 NewMaterial
= NewMaterial
->SpawnMore(GetBodyPart(c
)->GetMainMaterial()->GetVolume());
2806 GetBodyPart(c
)->SetMainMaterial(NewMaterial
, SpecialFlags
);
2811 void character::ChangeMainMaterial (material
*NewMaterial
, int SpecialFlags
) {
2812 NewMaterial
->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
2813 GetBodyPart(0)->ChangeMainMaterial(NewMaterial
, SpecialFlags
);
2814 for (int c
= 1; c
< BodyParts
; ++c
) {
2815 NewMaterial
= NewMaterial
->SpawnMore(GetBodyPart(c
)->GetMainMaterial()->GetVolume());
2816 GetBodyPart(c
)->ChangeMainMaterial(NewMaterial
, SpecialFlags
);
2821 void character::SetSecondaryMaterial (material
*, int) {
2822 ABORT("Illegal character::SetSecondaryMaterial call!");
2826 void character::ChangeSecondaryMaterial (material
*, int) {
2827 ABORT("Illegal character::ChangeSecondaryMaterial call!");
2831 void character::TeleportRandomly (truth Intentional
) {
2832 v2 TelePos
= ERROR_V2
;
2833 if (StateIsActivated(TELEPORT_CONTROL
)) {
2835 v2 Input
= game::PositionQuestion(CONST_S("Where do you wish to teleport? [direction keys move cursor, space accepts]"), GetPos(), &game::TeleportHandler
, 0, false);
2836 if (Input
== ERROR_V2
) Input
= GetPos(); // esc pressed
2837 lsquare
*Square
= GetNearLSquare(Input
);
2838 if (CanMoveOn(Square
) || game::GoThroughWallsCheatIsActive()) {
2839 if (Square
->GetPos() == GetPos()) {
2840 ADD_MESSAGE("You disappear and reappear.");
2843 if (IsFreeForMe(Square
)) {
2844 if ((Input
-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
2845 EditExperience(INTELLIGENCE
, 100, 1 << 10);
2848 ADD_MESSAGE("You cannot concentrate yourself enough to control a teleport that far.");
2851 character
*C
= Square
->GetCharacter();
2852 if (C
) ADD_MESSAGE("For a moment you feel very much like %s.", C
->CHAR_NAME(INDEFINITE
));
2853 else ADD_MESSAGE("You feel that something weird has happened, but can't really tell what exactly.");
2856 ADD_MESSAGE("You feel like having been hit by something really hard from the inside.");
2858 } else if (!Intentional
) {
2859 if (IsGoingSomeWhere() && GetLevel()->IsValidPos(GoingTo
)) {
2860 v2 Where
= GetLevel()->GetNearestFreeSquare(this, GoingTo
);
2861 if (Where
!= ERROR_V2
&& (Where
-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
2862 EditExperience(INTELLIGENCE
, 100, 1 << 10);
2870 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.");
2873 //if (TelePos != ERROR_V2) Move(TelePos, true);
2874 //else Move(GetLevel()->GetRandomSquare(this), true);
2875 //if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE));
2876 //if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
2878 if (TelePos
== ERROR_V2
) TelePos
= GetLevel()->GetRandomSquare(this);
2880 room
*PossibleRoom
= game::GetCurrentLevel()->GetLSquare(TelePos
)->GetRoom();
2882 if (!PossibleRoom
) {
2883 //if it's outside of a room
2884 if (TelePos
!= ERROR_V2
) Move(TelePos
, true);
2885 else Move(GetLevel()->GetRandomSquare(this), true);
2886 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE
));
2887 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
2888 } else if (PossibleRoom
&& PossibleRoom
->IsOKToTeleportInto()) {
2889 // If it's inside of a room, check whether a ward is active that might impede the player
2890 if (TelePos
!= ERROR_V2
) Move(TelePos
, true);
2891 else Move(GetLevel()->GetRandomSquare(this), true);
2892 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE
));
2893 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
2896 ADD_MESSAGE("A mighty force blasts you back to where you were standing. A ward prevents you from teleporting.");
2898 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);
2903 CONST_S("killed by an explosion triggered when attempting to teleport into room protected by a ward"),
2907 lsquare* Square = GetNearLSquare(GetPos());
2908 Square->DrawParticles(RED);
2909 Square->FireBall(Beam);*/
2914 void character::DoDetecting () {
2915 material
*TempMaterial
;
2918 festring Temp
= game::DefaultQuestion(CONST_S("What material do you want to detect?"), game::GetDefaultDetectMaterial());
2919 TempMaterial
= protosystem::CreateMaterial(Temp
);
2920 if (TempMaterial
) break;
2921 game::DrawEverythingNoBlit();
2924 level
*Level
= GetLevel();
2925 int Squares
= Level
->DetectMaterial(TempMaterial
);
2927 if (Squares
> GetAttribute(INTELLIGENCE
) * (25+RAND()%51)) {
2928 ADD_MESSAGE("An enormous burst of geographical information overwhelms your consciousness. Your mind cannot cope with it and your memories blur.");
2929 Level
->BlurMemory();
2930 BeginTemporaryState(CONFUSED
, 1000 + RAND() % 1000);
2931 EditExperience(INTELLIGENCE
, -100, 1 << 12);
2932 } else if (!Squares
) {
2933 ADD_MESSAGE("You feel a sudden urge to imagine the dark void of a starless night sky.");
2934 EditExperience(INTELLIGENCE
, 200, 1 << 12);
2936 ADD_MESSAGE("You feel attracted to all things made of %s.", TempMaterial
->GetName(false, false).CStr());
2937 game::PositionQuestion(CONST_S("Detecting material [direction keys move cursor, space exits]"), GetPos(), 0, 0, false);
2938 EditExperience(INTELLIGENCE
, 300, 1 << 12);
2941 delete TempMaterial
;
2942 Level
->CalculateLuminances();
2943 game::SendLOSUpdateRequest();
2947 void character::RestoreHP () {
2948 doforbodyparts()(this, &bodypart::FastRestoreHP
);
2953 void character::RestoreLivingHP () {
2955 for (int c
= 0; c
< BodyParts
; ++c
) {
2956 bodypart
*BodyPart
= GetBodyPart(c
);
2957 if (BodyPart
&& BodyPart
->CanRegenerate()) {
2958 BodyPart
->FastRestoreHP();
2959 HP
+= BodyPart
->GetHP();
2965 truth
character::AllowDamageTypeBloodSpill (int Type
) {
2966 switch (Type
&0xFFF) {
2967 case PHYSICAL_DAMAGE
:
2976 case MUSTARD_GAS_DAMAGE
:
2980 ABORT("Unknown blood effect destroyed the dungeon!");
2985 /* Returns truly done damage */
2986 int character::ReceiveBodyPartDamage (character
*Damager
, int Damage
, int Type
, int BodyPartIndex
,
2987 int Direction
, truth PenetrateResistance
, truth Critical
, truth ShowNoDamageMsg
, truth CaptureBodyPart
)
2989 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
2990 if (!Damager
|| Damager
->AttackMayDamageArmor()) BodyPart
->DamageArmor(Damager
, Damage
, Type
);
2991 if (!PenetrateResistance
) {
2992 Damage
-= (BodyPart
->GetTotalResistance(Type
)>>1)+RAND()%((BodyPart
->GetTotalResistance(Type
)>>1)+1);
2994 if (int(Damage
) < 1) {
2998 if (ShowNoDamageMsg
) {
2999 if (IsPlayer()) ADD_MESSAGE("You are not hurt.");
3000 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is not hurt.", GetPersonalPronoun().CStr());
3006 if (Critical
&& AllowDamageTypeBloodSpill(Type
) && !game::IsInWilderness()) {
3007 BodyPart
->SpillBlood(2+(RAND()&1));
3008 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
3009 lsquare
*Square
= GetNeighbourLSquare(d
);
3010 if (Square
&& Square
->IsFlyable()) BodyPart
->SpillBlood(1, Square
->GetPos());
3014 if (BodyPart
->ReceiveDamage(Damager
, Damage
, Type
, Direction
) && BodyPartCanBeSevered(BodyPartIndex
)) {
3015 if (DamageTypeDestroysBodyPart(Type
)) {
3016 if (IsPlayer()) ADD_MESSAGE("Your %s is destroyed!", BodyPart
->GetBodyPartName().CStr());
3017 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is destroyed!", GetPossessivePronoun().CStr(), BodyPart
->GetBodyPartName().CStr());
3018 GetBodyPart(BodyPartIndex
)->DropEquipment();
3019 item
*Severed
= SevereBodyPart(BodyPartIndex
);
3020 if (Severed
) Severed
->DestroyBodyPart(!game::IsInWilderness() ? GetStackUnder() : GetStack());
3021 SendNewDrawRequest();
3022 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart destroyed!"));
3024 if (IsPlayer()) ADD_MESSAGE("Your %s is severed off!", BodyPart
->GetBodyPartName().CStr());
3025 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is severed off!", GetPossessivePronoun().CStr(), BodyPart
->GetBodyPartName().CStr());
3026 item
*Severed
= SevereBodyPart(BodyPartIndex
);
3027 SendNewDrawRequest();
3029 if (CaptureBodyPart
) {
3030 Damager
->GetLSquareUnder()->AddItem(Severed
);
3031 } else if (!game::IsInWilderness()) {
3032 /** No multi-tile humanoid support! */
3033 GetStackUnder()->AddItem(Severed
);
3034 if (Direction
!= YOURSELF
) Severed
->Fly(0, Direction
, Damage
);
3036 GetStack()->AddItem(Severed
);
3038 Severed
->DropEquipment();
3039 } else if (IsPlayer() || CanBeSeenByPlayer()) {
3040 ADD_MESSAGE("It vanishes.");
3042 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart severed!"));
3044 if (CanPanicFromSeveredBodyPart() && RAND()%100 < GetPanicLevel() && !StateIsActivated(PANIC
) && !IsDead()) {
3045 BeginTemporaryState(PANIC
, 1000+RAND()%1001);
3047 SpecialBodyPartSeverReaction();
3050 if (!IsDead()) CheckPanic(500);
3056 /* Returns 0 if bodypart disappears */
3057 item
*character::SevereBodyPart (int BodyPartIndex
, truth ForceDisappearance
, stack
*EquipmentDropStack
) {
3058 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
3059 if (StateIsActivated(LEPROSY
)) BodyPart
->GetMainMaterial()->SetIsInfectedByLeprosy(true);
3060 if (ForceDisappearance
|| BodyPartsDisappearWhenSevered() || StateIsActivated(POLYMORPHED
) || game::AllBodyPartsVanish()) {
3061 BodyPart
->DropEquipment(EquipmentDropStack
);
3062 BodyPart
->RemoveFromSlot();
3063 CalculateAttributeBonuses();
3064 CalculateBattleInfo();
3065 BodyPart
->SendToHell();
3066 SignalPossibleTransparencyChange();
3067 RemoveTraps(BodyPartIndex
);
3070 BodyPart
->SetOwnerDescription("of " + GetName(INDEFINITE
));
3071 BodyPart
->SetIsUnique(LeftOversAreUnique());
3072 UpdateBodyPartPicture(BodyPartIndex
, true);
3073 BodyPart
->RemoveFromSlot();
3074 BodyPart
->RandomizePosition();
3075 CalculateAttributeBonuses();
3076 CalculateBattleInfo();
3078 SignalPossibleTransparencyChange();
3079 RemoveTraps(BodyPartIndex
);
3084 /* The second int is actually TargetFlags, which is not used here, but seems to be used in humanoid::ReceiveDamage.
3085 * Returns true if the character really receives damage */
3086 truth
character::ReceiveDamage (character
*Damager
, int Damage
, int Type
, int, int Direction
,
3087 truth
, truth PenetrateArmor
, truth Critical
, truth ShowMsg
)
3089 truth Affected
= ReceiveBodyPartDamage(Damager
, Damage
, Type
, 0, Direction
, PenetrateArmor
, Critical
, ShowMsg
);
3090 if (DamageTypeAffectsInventory(Type
)) {
3091 for (int c
= 0; c
< GetEquipments(); ++c
) {
3092 item
*Equipment
= GetEquipment(c
);
3093 if (Equipment
) Equipment
->ReceiveDamage(Damager
, Damage
, Type
);
3095 GetStack()->ReceiveDamage(Damager
, Damage
, Type
);
3101 festring
character::GetDescription (int Case
) const {
3102 if (IsPlayer()) return CONST_S("you");
3103 if (CanBeSeenByPlayer()) return GetName(Case
);
3104 return CONST_S("something");
3108 festring
character::GetPersonalPronoun (truth PlayersView
) const {
3109 if (IsPlayer() && PlayersView
) return CONST_S("you");
3110 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3111 if (GetSex() == MALE
) return CONST_S("he");
3112 return CONST_S("she");
3116 festring
character::GetPossessivePronoun (truth PlayersView
) const {
3117 if (IsPlayer() && PlayersView
) return CONST_S("your");
3118 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("its");
3119 if (GetSex() == MALE
) return CONST_S("his");
3120 return CONST_S("her");
3124 festring
character::GetObjectPronoun (truth PlayersView
) const {
3125 if (IsPlayer() && PlayersView
) return CONST_S("you");
3126 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3127 if (GetSex() == MALE
) return CONST_S("him");
3128 return CONST_S("her");
3132 void character::AddName (festring
&String
, int Case
) const {
3133 if (AssignedName
.IsEmpty()) {
3134 id::AddName(String
, Case
);
3135 } else if (!(Case
& PLURAL
)) {
3136 if (!ShowClassDescription()) {
3137 String
<< AssignedName
;
3139 String
<< AssignedName
<< ' ';
3140 id::AddName(String
, (Case
|ARTICLE_BIT
)&~INDEFINE_BIT
);
3143 id::AddName(String
, Case
);
3144 String
<< " named " << AssignedName
;
3149 int character::GetHungerState () const {
3150 if (!UsesNutrition()) return NOT_HUNGRY
;
3151 if (GetNP() > OVER_FED_LEVEL
) return OVER_FED
;
3152 if (GetNP() > BLOATED_LEVEL
) return BLOATED
;
3153 if (GetNP() > SATIATED_LEVEL
) return SATIATED
;
3154 if (GetNP() > NOT_HUNGER_LEVEL
) return NOT_HUNGRY
;
3155 if (GetNP() > HUNGER_LEVEL
) return HUNGRY
;
3156 if (GetNP() > VERY_HUNGER_LEVEL
) return VERY_HUNGRY
;
3161 truth
character::CanConsume (material
*Material
) const {
3162 return GetConsumeFlags() & Material
->GetConsumeType();
3166 void character::SetTemporaryStateCounter (sLong State
, int What
) {
3167 for (int c
= 0; c
< STATES
; ++c
) {
3168 if ((1 << c
) & State
) TemporaryStateCounter
[c
] = What
;
3173 void character::EditTemporaryStateCounter (sLong State
, int What
) {
3174 for (int c
= 0; c
< STATES
; ++c
) {
3175 if ((1 << c
) & State
) TemporaryStateCounter
[c
] += What
;
3180 int character::GetTemporaryStateCounter (sLong State
) const {
3181 for (int c
= 0; c
< STATES
; ++c
) {
3182 if ((1 << c
) & State
) return TemporaryStateCounter
[c
];
3184 ABORT("Illegal GetTemporaryStateCounter request!");
3189 truth
character::CheckKick () const {
3191 if (IsPlayer()) ADD_MESSAGE("This race can't kick.");
3198 int character::GetResistance (int Type
) const {
3199 switch (Type
&0xFFF) {
3200 case PHYSICAL_DAMAGE
:
3203 case MUSTARD_GAS_DAMAGE
:
3206 case ENERGY
: return GetEnergyResistance();
3207 case FIRE
: return GetFireResistance();
3208 case POISON
: return GetPoisonResistance();
3209 case ELECTRICITY
: return GetElectricityResistance();
3210 case ACID
: return GetAcidResistance();
3212 ABORT("Resistance lack detected!");
3217 void character::Regenerate () {
3218 if (HP
== MaxHP
) return;
3219 sLong RegenerationBonus
= 0;
3220 truth NoHealableBodyParts
= true;
3221 for (int c
= 0; c
< BodyParts
; ++c
) {
3222 bodypart
*BodyPart
= GetBodyPart(c
);
3223 if (BodyPart
&& BodyPart
->CanRegenerate()) {
3224 RegenerationBonus
+= BodyPart
->GetMaxHP();
3225 if (NoHealableBodyParts
&& BodyPart
->GetHP() < BodyPart
->GetMaxHP()) NoHealableBodyParts
= false;
3228 if (!RegenerationBonus
|| NoHealableBodyParts
) return;
3229 RegenerationBonus
*= (50+GetAttribute(ENDURANCE
));
3231 if (Action
&& Action
->IsRest()) {
3232 if (SquaresUnder
== 1) RegenerationBonus
*= GetSquareUnder()->GetRestModifier() << 1;
3234 int Lowest
= GetSquareUnder(0)->GetRestModifier();
3235 for (int c
= 1; c
< GetSquaresUnder(); ++c
) {
3236 int Mod
= GetSquareUnder(c
)->GetRestModifier();
3237 if (Mod
< Lowest
) Lowest
= Mod
;
3239 RegenerationBonus
*= Lowest
<< 1;
3243 RegenerationCounter
+= RegenerationBonus
;
3245 while (RegenerationCounter
> 1250000) {
3246 bodypart
*BodyPart
= HealHitPoint();
3247 if (!BodyPart
) break;
3248 EditNP(-Max(7500/MaxHP
, 1));
3249 RegenerationCounter
-= 1250000;
3250 int HP
= BodyPart
->GetHP();
3251 EditExperience(ENDURANCE
, Min(1000*BodyPart
->GetMaxHP()/(HP
*HP
), 300), 1000);
3256 void character::PrintInfo () const {
3257 felist
Info(CONST_S("Information about ")+GetName(DEFINITE
));
3258 for (int c
= 0; c
< GetEquipments(); ++c
) {
3259 item
*Equipment
= GetEquipment(c
);
3260 if ((EquipmentEasilyRecognized(c
) || game::WizardModeIsActive()) && Equipment
) {
3261 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
3262 Info
.AddEntry(festring(GetEquipmentName(c
))+": "+Equipment
->GetName(INDEFINITE
), LIGHT_GRAY
, 0, ImageKey
, true);
3265 if (Info
.IsEmpty()) {
3266 ADD_MESSAGE("There's nothing special to tell about %s.", CHAR_NAME(DEFINITE
));
3268 game::SetStandardListAttributes(Info
);
3269 Info
.SetEntryDrawer(game::ItemEntryDrawer
);
3272 game::ClearItemDrawVector();
3276 truth
character::TryToRiseFromTheDead () {
3277 for (int c
= 0; c
< BodyParts
; ++c
) {
3278 bodypart
*BodyPart
= GetBodyPart(c
);
3280 BodyPart
->ResetSpoiling();
3281 if (BodyPart
->CanRegenerate() || BodyPart
->GetHP() < 1) BodyPart
->SetHP(1);
3289 truth
character::RaiseTheDead (character
*) {
3290 truth Useful
= false;
3291 for (int c
= 0; c
< BodyParts
; ++c
) {
3292 bodypart
*BodyPart
= GetBodyPart(c
);
3293 if (!BodyPart
&& CanCreateBodyPart(c
)) {
3294 CreateBodyPart(c
)->SetHP(1);
3295 if (IsPlayer()) ADD_MESSAGE("Suddenly you grow a new %s.", GetBodyPartName(c
).CStr());
3296 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), GetBodyPartName(c
).CStr());
3298 } else if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < 1) {
3303 if (IsPlayer()) ADD_MESSAGE("You shudder.");
3304 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s shudders.", CHAR_NAME(DEFINITE
));
3310 void character::SetSize (int Size
) {
3311 for (int c
= 0; c
< BodyParts
; ++c
) {
3312 bodypart
*BodyPart
= GetBodyPart(c
);
3313 if (BodyPart
) BodyPart
->SetSize(GetBodyPartSize(c
, Size
));
3318 sLong
character::GetBodyPartSize (int I
, int TotalSize
) const {
3319 if (I
== TORSO_INDEX
) return TotalSize
;
3320 ABORT("Weird bodypart size request for a character!");
3325 sLong
character::GetBodyPartVolume (int I
) const {
3326 if (I
== TORSO_INDEX
) return GetTotalVolume();
3327 ABORT("Weird bodypart volume request for a character!");
3332 void character::CreateBodyParts (int SpecialFlags
) {
3333 for (int c
= 0; c
< BodyParts
; ++c
) if (CanCreateBodyPart(c
)) CreateBodyPart(c
, SpecialFlags
);
3337 void character::RestoreBodyParts () {
3338 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) CreateBodyPart(c
);
3342 void character::UpdatePictures () {
3343 if (!PictureUpdatesAreForbidden()) for (int c
= 0; c
< BodyParts
; ++c
) UpdateBodyPartPicture(c
, false);
3347 bodypart
*character::MakeBodyPart (int I
) const {
3348 if (I
== TORSO_INDEX
) return normaltorso::Spawn(0, NO_MATERIALS
);
3349 ABORT("Weird bodypart to make for a character!");
3354 bodypart
*character::CreateBodyPart (int I
, int SpecialFlags
) {
3355 bodypart
*BodyPart
= MakeBodyPart(I
);
3356 material
*Material
= CreateBodyPartMaterial(I
, GetBodyPartVolume(I
));
3357 BodyPart
->InitMaterials(Material
, false);
3358 BodyPart
->SetSize(GetBodyPartSize(I
, GetTotalSize()));
3359 BodyPart
->SetBloodMaterial(GetBloodMaterial());
3360 BodyPart
->SetNormalMaterial(Material
->GetConfig());
3361 SetBodyPart(I
, BodyPart
);
3362 BodyPart
->InitSpecialAttributes();
3363 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdateBodyPartPicture(I
, false);
3364 if (!IsInitializing()) {
3365 CalculateBattleInfo();
3366 SendNewDrawRequest();
3367 SignalPossibleTransparencyChange();
3373 v2
character::GetBodyPartBitmapPos (int I
, truth
) const {
3374 if (I
== TORSO_INDEX
) return GetTorsoBitmapPos();
3375 ABORT("Weird bodypart BitmapPos request for a character!");
3380 void character::UpdateBodyPartPicture (int I
, truth Severed
) {
3381 bodypart
*BP
= GetBodyPart(I
);
3383 BP
->SetBitmapPos(GetBodyPartBitmapPos(I
, Severed
));
3384 BP
->GetMainMaterial()->SetSkinColor(GetBodyPartColorA(I
, Severed
));
3385 BP
->GetMainMaterial()->SetSkinColorIsSparkling(GetBodyPartSparkleFlags(I
) & SPARKLING_A
);
3386 BP
->SetMaterialColorB(GetBodyPartColorB(I
, Severed
));
3387 BP
->SetMaterialColorC(GetBodyPartColorC(I
, Severed
));
3388 BP
->SetMaterialColorD(GetBodyPartColorD(I
, Severed
));
3389 BP
->SetSparkleFlags(GetBodyPartSparkleFlags(I
));
3390 BP
->SetSpecialFlags(GetSpecialBodyPartFlags(I
));
3391 BP
->SetWobbleData(GetBodyPartWobbleData(I
));
3392 BP
->UpdatePictures();
3397 void character::LoadDataBaseStats () {
3398 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) {
3399 BaseExperience
[c
] = DataBase
->NaturalExperience
[c
];
3400 if (BaseExperience
[c
]) LimitRef(BaseExperience
[c
], MIN_EXP
, MAX_EXP
);
3402 SetMoney(GetDefaultMoney());
3403 SetInitialSweatMaterial(GetSweatMaterial());
3404 const fearray
<sLong
> &Skills
= GetKnownCWeaponSkills();
3406 const fearray
<sLong
> &Hits
= GetCWeaponSkillHits();
3407 if (Hits
.Size
== 1) {
3408 for (uInt c
= 0; c
< Skills
.Size
; ++c
) {
3409 if (Skills
[c
] < AllowedWeaponSkillCategories
) CWeaponSkill
[Skills
[c
]].AddHit(Hits
[0]*100);
3411 } else if (Hits
.Size
== Skills
.Size
) {
3412 for (uInt c
= 0; c
< Skills
.Size
; ++c
) {
3413 if (Skills
[c
] < AllowedWeaponSkillCategories
) CWeaponSkill
[Skills
[c
]].AddHit(Hits
[c
]*100);
3416 ABORT("Illegal weapon skill hit array size detected!");
3422 character
*characterprototype::SpawnAndLoad (inputfile
&SaveFile
) const {
3423 character
*Char
= Spawner(0, LOAD
);
3424 Char
->Load(SaveFile
);
3425 Char
->CalculateAll();
3430 void character::Initialize (int NewConfig
, int SpecialFlags
) {
3431 Flags
|= C_INITIALIZING
|C_IN_NO_MSG_MODE
;
3432 CalculateBodyParts();
3433 CalculateAllowedWeaponSkillCategories();
3434 CalculateSquaresUnder();
3435 BodyPartSlot
= new bodypartslot
[BodyParts
];
3436 OriginalBodyPartID
= new std::list
<feuLong
>[BodyParts
];
3437 CWeaponSkill
= new cweaponskill
[AllowedWeaponSkillCategories
];
3438 SquareUnder
= new square
*[SquaresUnder
];
3440 if (SquaresUnder
== 1) *SquareUnder
= 0; else memset(SquareUnder
, 0, SquaresUnder
*sizeof(square
*));
3442 for (int c
= 0; c
< BodyParts
; ++c
) BodyPartSlot
[c
].SetMaster(this);
3444 if (!(SpecialFlags
& LOAD
)) {
3445 ID
= game::CreateNewCharacterID(this);
3446 databasecreator
<character
>::InstallDataBase(this, NewConfig
);
3447 LoadDataBaseStats();
3448 TemporaryState
|= GetClassStates();
3449 if (TemporaryState
) {
3450 for (int c
= 0; c
< STATES
; ++c
) if (TemporaryState
& (1 << c
)) TemporaryStateCounter
[c
] = PERMANENT
;
3453 CreateBodyParts(SpecialFlags
| NO_PIC_UPDATE
);
3454 InitSpecialAttributes();
3455 CommandFlags
= GetDefaultCommandFlags();
3457 if (GetAttribute(INTELLIGENCE
, false) < 8) CommandFlags
&= ~DONT_CONSUME_ANYTHING_VALUABLE
; // gum
3458 if (!GetDefaultName().IsEmpty()) SetAssignedName(GetDefaultName());
3461 if (!(SpecialFlags
& LOAD
)) PostConstruct();
3463 if (!(SpecialFlags
& LOAD
)) {
3464 if (!(SpecialFlags
& NO_EQUIPMENT
)) CreateInitialEquipment((SpecialFlags
& NO_EQUIPMENT_PIC_UPDATE
) >> 1);
3465 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdatePictures();
3471 Flags
&= ~(C_INITIALIZING
|C_IN_NO_MSG_MODE
);
3475 truth
character::TeleportNear (character
*Caller
) {
3476 v2 Where
= GetLevel()->GetNearestFreeSquare(this, Caller
->GetPos());
3477 if (Where
== ERROR_V2
) return false;
3483 void character::ReceiveHeal (sLong Amount
) {
3485 for (c
= 0; c
< Amount
/ 10; ++c
) if (!HealHitPoint()) break;
3487 if (RAND()%10 < Amount
) HealHitPoint();
3488 if (Amount
>= 250 || RAND()%250 < Amount
) {
3489 bodypart
*NewBodyPart
= GenerateRandomBodyPart();
3490 if (!NewBodyPart
) return;
3491 NewBodyPart
->SetHP(1);
3492 if (IsPlayer()) ADD_MESSAGE("You grow a new %s.", NewBodyPart
->GetBodyPartName().CStr());
3493 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), NewBodyPart
->GetBodyPartName().CStr());
3498 void character::AddHealingLiquidConsumeEndMessage () const {
3499 if (IsPlayer()) ADD_MESSAGE("You feel better.");
3500 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks healthier.", CHAR_NAME(DEFINITE
));
3504 void character::ReceiveSchoolFood (sLong SizeOfEffect
) {
3505 SizeOfEffect
+= RAND()%SizeOfEffect
;
3506 if (SizeOfEffect
>= 250) VomitAtRandomDirection(SizeOfEffect
);
3507 if (!(RAND() % 3) && SizeOfEffect
>= 500 && EditAttribute(ENDURANCE
, SizeOfEffect
/500)) {
3508 if (IsPlayer()) ADD_MESSAGE("You gain a little bit of toughness for surviving this stuff.");
3509 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks tougher.", CHAR_NAME(DEFINITE
));
3511 BeginTemporaryState(POISONED
, (SizeOfEffect
>>1));
3515 void character::AddSchoolFoodConsumeEndMessage () const {
3516 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff tasted like vomit and old mousepads.");
3520 void character::AddSchoolFoodHitMessage () const {
3521 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff feels like vomit and old mousepads.");
3525 void character::ReceiveNutrition (sLong SizeOfEffect
) {
3526 EditNP(SizeOfEffect
);
3530 void character::ReceiveOmmelUrine (sLong Amount
) {
3531 EditExperience(ARM_STRENGTH
, 500, Amount
<<4);
3532 EditExperience(LEG_STRENGTH
, 500, Amount
<<4);
3533 if (IsPlayer()) game::DoEvilDeed(Amount
/25);
3537 void character::ReceiveOmmelCerumen (sLong Amount
) {
3538 EditExperience(INTELLIGENCE
, 500, Amount
<< 5);
3539 EditExperience(WISDOM
, 500, Amount
<< 5);
3540 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3544 void character::ReceiveOmmelSweat (sLong Amount
) {
3545 EditExperience(AGILITY
, 500, Amount
<< 4);
3546 EditExperience(DEXTERITY
, 500, Amount
<< 4);
3548 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3552 void character::ReceiveOmmelTears (sLong Amount
) {
3553 EditExperience(PERCEPTION
, 500, Amount
<< 4);
3554 EditExperience(CHARISMA
, 500, Amount
<< 4);
3555 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3559 void character::ReceiveOmmelSnot (sLong Amount
) {
3560 EditExperience(ENDURANCE
, 500, Amount
<< 5);
3562 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3566 void character::ReceiveOmmelBone (sLong Amount
) {
3567 EditExperience(ARM_STRENGTH
, 500, Amount
<< 6);
3568 EditExperience(LEG_STRENGTH
, 500, Amount
<< 6);
3569 EditExperience(DEXTERITY
, 500, Amount
<< 6);
3570 EditExperience(AGILITY
, 500, Amount
<< 6);
3571 EditExperience(ENDURANCE
, 500, Amount
<< 6);
3572 EditExperience(PERCEPTION
, 500, Amount
<< 6);
3573 EditExperience(INTELLIGENCE
, 500, Amount
<< 6);
3574 EditExperience(WISDOM
, 500, Amount
<< 6);
3575 EditExperience(CHARISMA
, 500, Amount
<< 6);
3578 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3582 void character::AddOmmelConsumeEndMessage () const {
3583 if (IsPlayer()) ADD_MESSAGE("You feel a primitive force coursing through your veins.");
3584 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks more powerful.", CHAR_NAME(DEFINITE
));
3588 void character::ReceivePepsi (sLong Amount
) {
3589 ReceiveDamage(0, Amount
/ 100, POISON
, TORSO
);
3590 EditExperience(PERCEPTION
, Amount
, 1 << 14);
3591 if (CheckDeath(CONST_S("was poisoned by pepsi"), 0)) return;
3592 if (IsPlayer()) game::DoEvilDeed(Amount
/ 10);
3596 void character::AddPepsiConsumeEndMessage () const {
3597 if (IsPlayer()) ADD_MESSAGE("Urgh. You feel your guruism fading away.");
3598 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very lame.", CHAR_NAME(DEFINITE
));
3602 void character::ReceiveDarkness (sLong Amount
) {
3603 EditExperience(INTELLIGENCE
, -Amount
/ 5, 1 << 13);
3604 EditExperience(WISDOM
, -Amount
/ 5, 1 << 13);
3605 EditExperience(CHARISMA
, -Amount
/ 5, 1 << 13);
3606 if (IsPlayer()) game::DoEvilDeed(int(Amount
/ 50));
3610 void character::AddFrogFleshConsumeEndMessage () const {
3611 if (IsPlayer()) ADD_MESSAGE("Arg. You feel the fate of a navastater placed upon you...");
3612 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks like a navastater.", CHAR_NAME(DEFINITE
));
3616 void character::ReceiveKoboldFlesh (sLong
) {
3617 /* As it is commonly known, the possibility of fainting per 500 cubic
3618 centimeters of kobold flesh is exactly 5%. */
3619 if (!(RAND() % 20)) {
3620 if (IsPlayer()) ADD_MESSAGE("You lose control of your legs and fall down.");
3621 LoseConsciousness(250 + RAND_N(250));
3626 void character::AddKoboldFleshConsumeEndMessage () const {
3627 if (IsPlayer()) ADD_MESSAGE("This stuff tasted really funny.");
3631 void character::AddKoboldFleshHitMessage () const {
3632 if (IsPlayer()) ADD_MESSAGE("You feel very funny.");
3636 void character::AddBoneConsumeEndMessage () const {
3637 if (IsPlayer()) ADD_MESSAGE("You feel like a hippie.");
3638 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s barks happily.", CHAR_NAME(DEFINITE
)); // this suspects that nobody except dogs can eat bones
3641 truth
character::RawEditAttribute (double &Experience
, int Amount
) const {
3642 /* Check if the attribute is disabled for creature */
3643 if (!Experience
) return false;
3644 if ((Amount
< 0 && Experience
< 2 * EXP_MULTIPLIER
) || (Amount
> 0 && Experience
> 999 * EXP_MULTIPLIER
)) return false;
3645 Experience
+= Amount
* EXP_MULTIPLIER
;
3646 LimitRef
<double>(Experience
, MIN_EXP
, MAX_EXP
);
3651 void character::DrawPanel (truth AnimationDraw
) const {
3652 if (AnimationDraw
) { DrawStats(true); return; }
3653 igraph::BlitBackGround(v2(19 + (game::GetScreenXSize() << 4), 0), v2(RES
.X
- 19 - (game::GetScreenXSize() << 4), RES
.Y
));
3654 igraph::BlitBackGround(v2(16, 45 + (game::GetScreenYSize() << 4)), v2(game::GetScreenXSize() << 4, 9));
3655 FONT
->Printf(DOUBLE_BUFFER
, v2(16, 45 + (game::GetScreenYSize() << 4)), WHITE
, "%s", GetPanelName().CStr());
3656 game::UpdateAttributeMemory();
3657 int PanelPosX
= RES
.X
- 96;
3658 int PanelPosY
= DrawStats(false);
3659 PrintAttribute("End", ENDURANCE
, PanelPosX
, PanelPosY
++);
3660 PrintAttribute("Per", PERCEPTION
, PanelPosX
, PanelPosY
++);
3661 PrintAttribute("Int", INTELLIGENCE
, PanelPosX
, PanelPosY
++);
3662 PrintAttribute("Wis", WISDOM
, PanelPosX
, PanelPosY
++);
3663 PrintAttribute("Wil", WILL_POWER
, PanelPosX
, PanelPosY
++);
3664 PrintAttribute("Cha", CHARISMA
, PanelPosX
, PanelPosY
++);
3665 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Siz %d", GetSize());
3666 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), IsInBadCondition() ? RED
: WHITE
, "HP %d/%d", GetHP(), GetMaxHP());
3668 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Gold: %d", GetMoney());
3671 if (game::IsInWilderness())
3672 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Worldmap");
3674 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", game::GetCurrentDungeon()->GetShortLevelDescription(game::GetCurrentLevelIndex()).CapitalizeCopy().CStr());
3677 game::GetTime(Time
);
3678 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Day %d", Time
.Day
);
3679 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Time %d:%s%d", Time
.Hour
, Time
.Min
< 10 ? "0" : "", Time
.Min
);
3680 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Turn %d", game::GetTurn());
3685 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", festring(GetAction()->GetDescription()).CapitalizeCopy().CStr());
3687 for (int c
= 0; c
< STATES
; ++c
)
3688 if (!(StateData
[c
].Flags
& SECRET
) && StateIsActivated(1 << c
) && (1 << c
!= HASTE
|| !StateIsActivated(SLOW
)) && (1 << c
!= SLOW
|| !StateIsActivated(HASTE
)))
3689 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), (1 << c
) & EquipmentState
|| TemporaryStateCounter
[c
] == PERMANENT
? BLUE
: WHITE
, "%s", StateData
[c
].Description
);
3691 /* Make this more elegant!!! */
3692 if (GetHungerState() == STARVING
)
3693 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Starving");
3694 else if (GetHungerState() == VERY_HUNGRY
)
3695 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Very hungry");
3696 else if (GetHungerState() == HUNGRY
)
3697 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Hungry");
3698 else if (GetHungerState() == SATIATED
)
3699 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Satiated");
3700 else if (GetHungerState() == BLOATED
)
3701 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Bloated");
3702 else if (GetHungerState() == OVER_FED
)
3703 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Overfed!");
3705 switch (GetBurdenState()) {
3707 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Overload!");
3710 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Stressed");
3713 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Burdened");
3717 switch (GetTirednessState()) {
3719 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Fainting");
3722 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Exhausted");
3726 if (game::PlayerIsRunning()) {
3727 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", GetRunDescriptionLine(0));
3728 cchar
*SecondLine
= GetRunDescriptionLine(1);
3729 if (strlen(SecondLine
)) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", SecondLine
);
3734 void character::CalculateDodgeValue () {
3735 DodgeValue
= 0.05 * GetMoveEase() * GetAttribute(AGILITY
) / sqrt(GetSize());
3736 if (IsFlying()) DodgeValue
*= 2;
3737 if (DodgeValue
< 1) DodgeValue
= 1;
3741 truth
character::DamageTypeAffectsInventory (int Type
) {
3742 switch (Type
&0xFFF) {
3749 case PHYSICAL_DAMAGE
:
3752 case MUSTARD_GAS_DAMAGE
:
3756 ABORT("Unknown reaping effect destroyed dungeon!");
3761 int character::CheckForBlockWithArm (character
*Enemy
, item
*Weapon
, arm
*Arm
,
3762 double WeaponToHitValue
, int Damage
, int Success
, int Type
)
3764 int BlockStrength
= Arm
->GetBlockCapability();
3765 double BlockValue
= Arm
->GetBlockValue();
3766 if (BlockStrength
&& BlockValue
) {
3767 item
*Blocker
= Arm
->GetWielded();
3768 if (RAND() % int(100+WeaponToHitValue
/BlockValue
/(1<<BlocksSinceLastTurn
)*(100+Success
)) < 100) {
3769 int NewDamage
= BlockStrength
< Damage
? Damage
-BlockStrength
: 0;
3771 case UNARMED_ATTACK
: AddBlockMessage(Enemy
, Blocker
, Enemy
->UnarmedHitNoun(), NewDamage
); break;
3772 case WEAPON_ATTACK
: AddBlockMessage(Enemy
, Blocker
, "attack", NewDamage
); break;
3773 case KICK_ATTACK
: AddBlockMessage(Enemy
, Blocker
, Enemy
->KickNoun(), NewDamage
); break;
3774 case BITE_ATTACK
: AddBlockMessage(Enemy
, Blocker
, Enemy
->BiteNoun(), NewDamage
); break;
3776 sLong Weight
= Blocker
->GetWeight();
3777 sLong StrExp
= Limit(15 * Weight
/ 200, 75, 300);
3778 sLong DexExp
= Weight
? Limit(75000 / Weight
, 75, 300) : 300;
3779 Arm
->EditExperience(ARM_STRENGTH
, StrExp
, 1 << 8);
3780 Arm
->EditExperience(DEXTERITY
, DexExp
, 1 << 8);
3781 EditStamina(-10000 / GetAttribute(ARM_STRENGTH
), false);
3782 if (Arm
->TwoHandWieldIsActive()) {
3783 arm
*PairArm
= Arm
->GetPairArm();
3784 PairArm
->EditExperience(ARM_STRENGTH
, StrExp
, 1 << 8);
3785 PairArm
->EditExperience(DEXTERITY
, DexExp
, 1 << 8);
3787 Blocker
->WeaponSkillHit(Enemy
->CalculateWeaponSkillHits(this));
3788 Blocker
->ReceiveDamage(this, Damage
, PHYSICAL_DAMAGE
);
3789 Blocker
->BlockEffect(this, Enemy
, Weapon
, Type
);
3790 if (Weapon
) Weapon
->ReceiveDamage(Enemy
, Damage
- NewDamage
, PHYSICAL_DAMAGE
);
3791 if (BlocksSinceLastTurn
< 16) ++BlocksSinceLastTurn
;
3799 sLong
character::GetStateAPGain (sLong BaseAPGain
) const {
3800 if (!StateIsActivated(HASTE
) == !StateIsActivated(SLOW
)) return BaseAPGain
;
3801 if (StateIsActivated(HASTE
)) return (BaseAPGain
* 5) >> 2;
3802 return (BaseAPGain
<< 2) / 5;
3806 void character::SignalEquipmentAdd (int EquipmentIndex
) {
3807 item
*Equipment
= GetEquipment(EquipmentIndex
);
3808 if (Equipment
->IsInCorrectSlot(EquipmentIndex
)) {
3809 sLong AddedStates
= Equipment
->GetGearStates();
3811 for (int c
= 0; c
< STATES
; ++c
) {
3812 if (AddedStates
& (1 << c
)) {
3813 if (!StateIsActivated(1 << c
)) {
3814 if (!IsInNoMsgMode()) (this->*StateData
[c
].PrintBeginMessage
)();
3815 EquipmentState
|= 1 << c
;
3816 if (StateData
[c
].BeginHandler
) (this->*StateData
[c
].BeginHandler
)();
3818 EquipmentState
|= 1 << c
;
3824 if (!IsInitializing() && Equipment
->IsInCorrectSlot(EquipmentIndex
)) ApplyEquipmentAttributeBonuses(Equipment
);
3828 void character::SignalEquipmentRemoval (int, citem
*Item
) {
3829 CalculateEquipmentState();
3830 if (CalculateAttributeBonuses()) CheckDeath(festring("lost ")+GetPossessivePronoun(false)+" vital "+Item
->GetName(INDEFINITE
));
3834 void character::CalculateEquipmentState () {
3835 sLong Back
= EquipmentState
;
3837 for (int c
= 0; c
< GetEquipments(); ++c
) {
3838 item
*Equipment
= GetEquipment(c
);
3839 if (Equipment
&& Equipment
->IsInCorrectSlot(c
)) EquipmentState
|= Equipment
->GetGearStates();
3841 for (int c
= 0; c
< STATES
; ++c
) {
3842 if (Back
& (1 << c
) && !StateIsActivated(1 << c
)) {
3843 if (StateData
[c
].EndHandler
) {
3844 (this->*StateData
[c
].EndHandler
)();
3845 if (!IsEnabled()) return;
3847 if (!IsInNoMsgMode()) (this->*StateData
[c
].PrintEndMessage
)();
3853 /* Counter = duration in ticks */
3854 void character::BeginTemporaryState (sLong State
, int Counter
) {
3855 if (!Counter
) return;
3857 if (State
== POLYMORPHED
) ABORT("No Polymorphing with BeginTemporaryState!");
3858 for (Index
= 0; Index
< STATES
; ++Index
) if (1 << Index
== State
) break;
3859 if (Index
== STATES
) ABORT("BeginTemporaryState works only when State == 2Â ^ n!");
3860 if (TemporaryStateIsActivated(State
)) {
3861 int OldCounter
= GetTemporaryStateCounter(State
);
3862 if (OldCounter
!= PERMANENT
) EditTemporaryStateCounter(State
, Max(Counter
, 50-OldCounter
));
3863 } else if (StateData
[Index
].IsAllowed
== 0 || (this->*StateData
[Index
].IsAllowed
)()) {
3864 SetTemporaryStateCounter(State
, Max(Counter
, 50));
3865 if (!EquipmentStateIsActivated(State
)) {
3866 if (!IsInNoMsgMode()) (this->*StateData
[Index
].PrintBeginMessage
)();
3867 ActivateTemporaryState(State
);
3868 if (StateData
[Index
].BeginHandler
) (this->*StateData
[Index
].BeginHandler
)();
3870 ActivateTemporaryState(State
);
3876 void character::HandleStates () {
3877 if (!TemporaryState
&& !EquipmentState
) return;
3878 for (int c
= 0; c
< STATES
; ++c
) {
3879 if (TemporaryState
& (1 << c
) && TemporaryStateCounter
[c
] != PERMANENT
) {
3880 if (!--TemporaryStateCounter
[c
]) {
3881 TemporaryState
&= ~(1 << c
);
3882 if (!(EquipmentState
& (1 << c
))) {
3883 if (StateData
[c
].EndHandler
) {
3884 (this->*StateData
[c
].EndHandler
)();
3885 if (!IsEnabled()) return;
3887 if (!TemporaryStateCounter
[c
]) (this->*StateData
[c
].PrintEndMessage
)();
3891 if (StateIsActivated(1 << c
)) {
3892 if (StateData
[c
].Handler
) (this->*StateData
[c
].Handler
)();
3894 if (!IsEnabled()) return;
3899 void character::PrintBeginPolymorphControlMessage () const {
3900 if (IsPlayer()) ADD_MESSAGE("You feel your mind has total control over your body.");
3904 void character::PrintEndPolymorphControlMessage () const {
3905 if (IsPlayer()) ADD_MESSAGE("You are somehow uncertain of your willpower.");
3909 void character::PrintBeginLifeSaveMessage () const {
3910 if (IsPlayer()) ADD_MESSAGE("You hear Hell's gates being locked just now.");
3914 void character::PrintEndLifeSaveMessage () const {
3915 if (IsPlayer()) ADD_MESSAGE("You feel the Afterlife is welcoming you once again.");
3919 void character::PrintBeginLycanthropyMessage () const {
3920 if (IsPlayer()) ADD_MESSAGE("You suddenly notice you've always loved full moons.");
3924 void character::PrintEndLycanthropyMessage () const {
3925 if (IsPlayer()) ADD_MESSAGE("You feel the wolf inside you has had enough of your bad habits.");
3929 void character::PrintBeginVampirismMessage () const {
3930 if (IsPlayer()) ADD_MESSAGE("You suddenly decide you have always hated garlic.");
3934 void character::PrintEndVampirismMessage () const {
3935 if (IsPlayer()) ADD_MESSAGE("You recall your delight of the morning sunshine back in New Attnam. You are a vampire no longer.");
3939 void character::PrintBeginInvisibilityMessage () const {
3940 if ((PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm()) || (PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5)) {
3941 if (IsPlayer()) ADD_MESSAGE("You seem somehow transparent.");
3942 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems somehow transparent.", CHAR_NAME(DEFINITE
));
3944 if (IsPlayer()) ADD_MESSAGE("You fade away.");
3945 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears!", CHAR_NAME(DEFINITE
));
3950 void character::PrintEndInvisibilityMessage () const {
3951 if ((PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm()) || (PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5)) {
3952 if (IsPlayer()) ADD_MESSAGE("Your notice your transparency has ended.");
3953 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The appearance of %s seems far more solid now.", CHAR_NAME(INDEFINITE
));
3955 if (IsPlayer()) ADD_MESSAGE("You reappear.");
3956 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s appears from nowhere!", CHAR_NAME(INDEFINITE
));
3961 void character::PrintBeginInfraVisionMessage () const {
3963 if (StateIsActivated(INVISIBLE
) && IsWarm() && !(StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5))
3964 ADD_MESSAGE("You reappear.");
3966 ADD_MESSAGE("You feel your perception being magically altered.");
3971 void character::PrintEndInfraVisionMessage () const {
3973 if (StateIsActivated(INVISIBLE
) && IsWarm() && !(StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5))
3974 ADD_MESSAGE("You disappear.");
3976 ADD_MESSAGE("You feel your perception returning to normal.");
3981 void character::PrintBeginESPMessage () const {
3982 if (IsPlayer()) ADD_MESSAGE("You suddenly feel like being only a tiny part of a great network of intelligent minds.");
3986 void character::PrintEndESPMessage () const {
3987 if (IsPlayer()) ADD_MESSAGE("You are filled with desire to be just yourself from now on.");
3991 void character::PrintBeginHasteMessage () const {
3992 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
3993 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE
));
3997 void character::PrintEndHasteMessage () const {
3998 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
3999 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE
));
4003 void character::PrintBeginSlowMessage () const {
4004 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4005 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE
));
4009 void character::PrintEndSlowMessage () const {
4010 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4011 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE
));
4015 void character::EndPolymorph () {
4016 ForceEndPolymorph();
4020 character
*character::ForceEndPolymorph () {
4022 ADD_MESSAGE("You return to your true form.");
4023 } else if (game::IsInWilderness()) {
4024 ActivateTemporaryState(POLYMORPHED
);
4025 SetTemporaryStateCounter(POLYMORPHED
, 10);
4026 return this; // fast gum solution, state ends when the player enters a dungeon
4028 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s returns to %s true form.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
4030 if (GetAction()) GetAction()->Terminate(false);
4034 character
*Char
= GetPolymorphBackup();
4035 Flags
|= C_IN_NO_MSG_MODE
;
4036 Char
->Flags
|= C_IN_NO_MSG_MODE
;
4037 Char
->ChangeTeam(GetTeam());
4038 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(Char
);
4039 SetPolymorphBackup(0);
4040 Char
->PutToOrNear(Pos
);
4042 Char
->Flags
&= ~C_POLYMORPHED
;
4043 GetStack()->MoveItemsTo(Char
->GetStack());
4044 DonateEquipmentTo(Char
);
4045 Char
->SetMoney(GetMoney());
4046 Flags
&= ~C_IN_NO_MSG_MODE
;
4047 Char
->Flags
&= ~C_IN_NO_MSG_MODE
;
4048 Char
->CalculateAll();
4049 Char
->SetAssignedName(GetAssignedName());
4052 game::SetPlayer(Char
);
4053 game::SendLOSUpdateRequest();
4056 Char
->TestWalkability();
4061 void character::LycanthropyHandler () {
4062 if (!(RAND() % 2000)) {
4063 if (StateIsActivated(POLYMORPH_CONTROL
) && !game::TruthQuestion(CONST_S("Do you wish to change into a werewolf? [y/N]"))) return;
4064 Polymorph(werewolfwolf::Spawn(), 1000 + RAND() % 2000);
4069 void character::SaveLife () {
4070 if (TemporaryStateIsActivated(LIFE_SAVED
)) {
4072 ADD_MESSAGE("But wait! You glow briefly red and seem to be in a better shape!");
4073 else if (CanBeSeenByPlayer())
4074 ADD_MESSAGE("But wait, suddenly %s glows briefly red and seems to be in a better shape!", GetPersonalPronoun().CStr());
4075 DeActivateTemporaryState(LIFE_SAVED
);
4077 item
*LifeSaver
= 0;
4078 for (int c
= 0; c
< GetEquipments(); ++c
) {
4079 item
*Equipment
= GetEquipment(c
);
4080 if (Equipment
&& Equipment
->IsInCorrectSlot(c
) && Equipment
->GetGearStates() & LIFE_SAVED
) LifeSaver
= Equipment
;
4082 if (!LifeSaver
) ABORT("The Universe can only kill you once!");
4084 ADD_MESSAGE("But wait! Your %s glows briefly red and disappears and you seem to be in a better shape!", LifeSaver
->CHAR_NAME(UNARTICLED
));
4085 else if (CanBeSeenByPlayer())
4086 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());
4087 LifeSaver
->RemoveFromSlot();
4088 LifeSaver
->SendToHell();
4091 if (IsPlayer()) game::AskForEscPress(CONST_S("Life saved!"));
4099 if (GetNP() < SATIATED_LEVEL
) SetNP(SATIATED_LEVEL
);
4101 SendNewDrawRequest();
4103 if (GetAction()) GetAction()->Terminate(false);
4107 character
*character::PolymorphRandomly (int MinDanger
, int MaxDanger
, int Time
) {
4108 character
*NewForm
= 0;
4109 if (StateIsActivated(POLYMORPH_CONTROL
)) {
4111 if (!GetNewFormForPolymorphWithControl(NewForm
)) return NewForm
;
4114 NewForm
= protosystem::CreateMonster(MinDanger
* 10, MaxDanger
* 10, NO_EQUIPMENT
);
4117 NewForm
= protosystem::CreateMonster(MinDanger
, MaxDanger
, NO_EQUIPMENT
);
4119 Polymorph(NewForm
, Time
);
4124 /* In reality, the reading takes Time / (Intelligence * 10) turns */
4125 void character::StartReading (item
*Item
, sLong Time
) {
4126 study
*Read
= study::Spawn(this);
4127 Read
->SetLiteratureID(Item
->GetID());
4128 if (game::WizardModeIsActive()) Time
= 1;
4129 Read
->SetCounter(Time
);
4131 if (IsPlayer()) ADD_MESSAGE("You start reading %s.", Item
->CHAR_NAME(DEFINITE
));
4132 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s starts reading %s.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(DEFINITE
));
4136 /* Call when one makes something with his/her/its hands.
4137 * Difficulty of 5 takes about one turn, so it's the most common to use. */
4138 void character::DexterityAction (int Difficulty
) {
4139 EditAP(-20000 * Difficulty
/ APBonus(GetAttribute(DEXTERITY
)));
4140 EditExperience(DEXTERITY
, Difficulty
* 15, 1 << 7);
4144 /* If Theoretically != false, range is not a factor. */
4145 truth
character::CanBeSeenByPlayer (truth Theoretically
, truth IgnoreESP
) const {
4146 if (IsEnabled() && !game::IsGenerating() && (Theoretically
|| GetSquareUnder())) {
4147 truth MayBeESPSeen
= PLAYER
->IsEnabled() && !IgnoreESP
&& PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5;
4148 truth MayBeInfraSeen
= PLAYER
->IsEnabled() && PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm();
4149 truth Visible
= !StateIsActivated(INVISIBLE
) || MayBeESPSeen
|| MayBeInfraSeen
;
4150 if (game::IsInWilderness()) return Visible
;
4151 if (MayBeESPSeen
&& (Theoretically
|| GetDistanceSquareFrom(PLAYER
) <= PLAYER
->GetESPRangeSquare())) return true;
4152 if (!Visible
) return false;
4153 return Theoretically
|| SquareUnderCanBeSeenByPlayer(MayBeInfraSeen
);
4159 truth
character::CanBeSeenBy (ccharacter
*Who
, truth Theoretically
, truth IgnoreESP
) const {
4160 if (Who
->IsPlayer()) return CanBeSeenByPlayer(Theoretically
, IgnoreESP
);
4161 if (IsEnabled() && !game::IsGenerating() && (Theoretically
|| GetSquareUnder())) {
4162 truth MayBeESPSeen
= Who
->IsEnabled() && !IgnoreESP
&& Who
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5;
4163 truth MayBeInfraSeen
= Who
->IsEnabled() && Who
->StateIsActivated(INFRA_VISION
) && IsWarm();
4164 truth Visible
= !StateIsActivated(INVISIBLE
) || MayBeESPSeen
|| MayBeInfraSeen
;
4165 if (game::IsInWilderness()) return Visible
;
4166 if (MayBeESPSeen
&& (Theoretically
|| GetDistanceSquareFrom(Who
) <= Who
->GetESPRangeSquare())) return true;
4167 if (!Visible
) return false;
4168 return Theoretically
|| SquareUnderCanBeSeenBy(Who
, MayBeInfraSeen
);
4174 truth
character::SquareUnderCanBeSeenByPlayer (truth IgnoreDarkness
) const {
4175 if (!GetSquareUnder()) return false;
4176 int S1
= SquaresUnder
, S2
= PLAYER
->SquaresUnder
;
4177 if (S1
== 1 && S2
== 1) {
4178 if (GetSquareUnder()->CanBeSeenByPlayer(IgnoreDarkness
)) return true;
4179 if (IgnoreDarkness
) {
4180 int LOSRangeSquare
= PLAYER
->GetLOSRangeSquare();
4181 if ((GetPos() - PLAYER
->GetPos()).GetLengthSquare() <= LOSRangeSquare
) {
4182 eyecontroller::Map
= GetLevel()->GetMap();
4183 return mapmath
<eyecontroller
>::DoLine(PLAYER
->GetPos().X
, PLAYER
->GetPos().Y
, GetPos().X
, GetPos().Y
, SKIP_FIRST
);
4188 for (int c1
= 0; c1
< S1
; ++c1
) {
4189 lsquare
*Square
= GetLSquareUnder(c1
);
4190 if (Square
->CanBeSeenByPlayer(IgnoreDarkness
)) return true;
4191 else if (IgnoreDarkness
) {
4192 v2 Pos
= Square
->GetPos();
4193 int LOSRangeSquare
= PLAYER
->GetLOSRangeSquare();
4194 for (int c2
= 0; c2
< S2
; ++c2
) {
4195 v2 PlayerPos
= PLAYER
->GetPos(c2
);
4196 if ((Pos
-PlayerPos
).GetLengthSquare() <= LOSRangeSquare
) {
4197 eyecontroller::Map
= GetLevel()->GetMap();
4198 if (mapmath
<eyecontroller
>::DoLine(PlayerPos
.X
, PlayerPos
.Y
, Pos
.X
, Pos
.Y
, SKIP_FIRST
)) return true;
4208 truth
character::SquareUnderCanBeSeenBy (ccharacter
*Who
, truth IgnoreDarkness
) const {
4209 int S1
= SquaresUnder
, S2
= Who
->SquaresUnder
;
4210 int LOSRangeSquare
= Who
->GetLOSRangeSquare();
4211 if (S1
== 1 && S2
== 1) return GetSquareUnder()->CanBeSeenFrom(Who
->GetPos(), LOSRangeSquare
, IgnoreDarkness
);
4212 for (int c1
= 0; c1
< S1
; ++c1
) {
4213 lsquare
*Square
= GetLSquareUnder(c1
);
4214 for (int c2
= 0; c2
< S2
; ++c2
) if (Square
->CanBeSeenFrom(Who
->GetPos(c2
), LOSRangeSquare
, IgnoreDarkness
)) return true;
4220 int character::GetDistanceSquareFrom (ccharacter
*Who
) const {
4221 int S1
= SquaresUnder
, S2
= Who
->SquaresUnder
;
4222 if (S1
== 1 && S2
== 1) return (GetPos() - Who
->GetPos()).GetLengthSquare();
4223 v2
MinDist(0x7FFF, 0x7FFF);
4224 int MinLength
= 0xFFFF;
4225 for (int c1
= 0; c1
< S1
; ++c1
) {
4226 for (int c2
= 0; c2
< S2
; ++c2
) {
4227 v2 Dist
= GetPos(c1
)-Who
->GetPos(c2
);
4228 if (Dist
.X
< 0) Dist
.X
= -Dist
.X
;
4229 if (Dist
.Y
< 0) Dist
.Y
= -Dist
.Y
;
4230 if (Dist
.X
<= MinDist
.X
&& Dist
.Y
<= MinDist
.Y
) {
4232 MinLength
= Dist
.GetLengthSquare();
4233 } else if (Dist
.X
< MinDist
.X
|| Dist
.Y
< MinDist
.Y
) {
4234 int Length
= Dist
.GetLengthSquare();
4235 if (Length
< MinLength
) {
4246 void character::AttachBodyPart (bodypart
*BodyPart
) {
4247 SetBodyPart(BodyPart
->GetBodyPartIndex(), BodyPart
);
4248 if (!AllowSpoil()) BodyPart
->ResetSpoiling();
4249 BodyPart
->ResetPosition();
4250 BodyPart
->UpdatePictures();
4251 CalculateAttributeBonuses();
4252 CalculateBattleInfo();
4253 SendNewDrawRequest();
4254 SignalPossibleTransparencyChange();
4258 /* Returns true if the character has all bodyparts, false if not. */
4259 truth
character::HasAllBodyParts () const {
4260 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) return false;
4265 bodypart
*character::GenerateRandomBodyPart () {
4266 int NeededBodyPart
[MAX_BODYPARTS
];
4268 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) NeededBodyPart
[Index
++] = c
;
4269 return Index
? CreateBodyPart(NeededBodyPart
[RAND() % Index
]) : 0;
4273 /* Searches the character's Stack and if it find some bodyparts there that are the character's
4274 * old bodyparts returns a stackiterator to one of them (choosen in random).
4275 * If no fitting bodyparts are found the function returns 0 */
4276 bodypart
*character::FindRandomOwnBodyPart (truth AllowNonLiving
) const {
4277 itemvector LostAndFound
;
4278 for (int c
= 0; c
< BodyParts
; ++c
) {
4279 if (!GetBodyPart(c
)) {
4280 for (std::list
<feuLong
>::iterator i
= OriginalBodyPartID
[c
].begin(); i
!= OriginalBodyPartID
[c
].end(); ++i
) {
4281 bodypart
*Found
= static_cast<bodypart
*>(SearchForItem(*i
));
4282 if (Found
&& (AllowNonLiving
|| Found
->CanRegenerate())) LostAndFound
.push_back(Found
);
4286 if (LostAndFound
.empty()) return 0;
4287 return static_cast<bodypart
*>(LostAndFound
[RAND() % LostAndFound
.size()]);
4291 void character::PrintBeginPoisonedMessage () const {
4292 if (IsPlayer()) ADD_MESSAGE("You seem to be very ill.");
4293 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very ill.", CHAR_NAME(DEFINITE
));
4297 void character::PrintEndPoisonedMessage () const {
4298 if (IsPlayer()) ADD_MESSAGE("You feel better again.");
4299 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks better.", CHAR_NAME(DEFINITE
));
4303 void character::PoisonedHandler () {
4304 if (!(RAND() % 100)) VomitAtRandomDirection(500 + RAND_N(250));
4306 for (int Used
= 0; Used
< GetTemporaryStateCounter(POISONED
); Used
+= 100) if (!(RAND() % 100)) ++Damage
;
4308 ReceiveDamage(0, Damage
, POISON
, ALL
, 8, false, false, false, false);
4309 CheckDeath(CONST_S("died of acute poisoning"), 0);
4314 truth
character::IsWarm () const {
4315 return combinebodypartpredicates()(this, &bodypart::IsWarm
, 1);
4319 void character::BeginInvisibility () {
4321 SendNewDrawRequest();
4322 SignalPossibleTransparencyChange();
4326 void character::BeginInfraVision () {
4327 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4331 void character::BeginESP () {
4332 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4336 void character::EndInvisibility () {
4338 SendNewDrawRequest();
4339 SignalPossibleTransparencyChange();
4343 void character::EndInfraVision () {
4344 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4348 void character::EndESP () {
4349 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4353 void character::Draw (blitdata
&BlitData
) const {
4354 col24 L
= BlitData
.Luminance
;
4355 if (PLAYER
->IsEnabled() &&
4356 ((PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5 &&
4357 (PLAYER
->GetPos() - GetPos()).GetLengthSquare() <= PLAYER
->GetESPRangeSquare()) ||
4358 (PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm())))
4359 BlitData
.Luminance
= ivanconfig::GetContrastLuminance();
4361 DrawBodyParts(BlitData
);
4362 BlitData
.Luminance
= ivanconfig::GetContrastLuminance();
4363 BlitData
.Src
.Y
= 16;
4364 cint SquareIndex
= BlitData
.CustomData
& SQUARE_INDEX_MASK
;
4366 if (GetTeam() == PLAYER
->GetTeam() && !IsPlayer() && SquareIndex
== GetTameSymbolSquareIndex()) {
4367 BlitData
.Src
.X
= 32;
4368 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4371 if (IsFlying() && SquareIndex
== GetFlySymbolSquareIndex()) {
4372 BlitData
.Src
.X
= 128;
4373 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4376 if (IsSwimming() && SquareIndex
== GetSwimmingSymbolSquareIndex()) {
4377 BlitData
.Src
.X
= 240;
4378 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4381 if (GetAction() && GetAction()->IsUnconsciousness() && SquareIndex
== GetUnconsciousSymbolSquareIndex()) {
4382 BlitData
.Src
.X
= 224;
4383 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4386 BlitData
.Src
.X
= BlitData
.Src
.Y
= 0;
4387 BlitData
.Luminance
= L
;
4391 void character::DrawBodyParts (blitdata
&BlitData
) const {
4392 GetTorso()->Draw(BlitData
);
4396 void character::PrintBeginTeleportMessage () const {
4397 if (IsPlayer()) ADD_MESSAGE("You feel jumpy.");
4401 void character::PrintEndTeleportMessage () const {
4402 if (IsPlayer()) ADD_MESSAGE("You suddenly realize you've always preferred walking to jumping.");
4406 void character::PrintBeginDetectMessage () const {
4407 if (IsPlayer()) ADD_MESSAGE("You feel curious about your surroundings.");
4411 void character::PrintEndDetectMessage () const {
4412 if (IsPlayer()) ADD_MESSAGE("You decide to rely on your intuition from now on.");
4416 void character::TeleportHandler () {
4417 if (!(RAND() % 1500) && !game::IsInWilderness()) {
4418 if (IsPlayer()) ADD_MESSAGE("You feel an urgent spatial relocation is now appropriate.");
4419 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears.", CHAR_NAME(DEFINITE
));
4425 void character::DetectHandler () {
4427 //the AI can't be asked position questions! So only the player can hav this state really :/ a bit daft of me
4428 if (!(RAND()%3000) && !game::IsInWilderness()) {
4429 ADD_MESSAGE("Your mind wanders in search of something.");
4430 DoDetecting(); //in fact, who knows what would happen if a dark frog had the detecting state?
4436 void character::PrintBeginPolymorphMessage () const {
4437 if (IsPlayer()) ADD_MESSAGE("An unconfortable uncertainty of who you really are overwhelms you.");
4441 void character::PrintEndPolymorphMessage () const {
4442 if (IsPlayer()) ADD_MESSAGE("You feel you are you and no one else.");
4446 void character::PolymorphHandler () {
4447 if (!(RAND() % 1500)) PolymorphRandomly(1, 999999, 200 + RAND() % 800);
4450 void character::PrintBeginTeleportControlMessage () const {
4451 if (IsPlayer()) ADD_MESSAGE("You feel very controlled.");
4455 void character::PrintEndTeleportControlMessage () const {
4456 if (IsPlayer()) ADD_MESSAGE("You feel your control slipping.");
4460 void character::DisplayStethoscopeInfo (character
*) const {
4461 felist
Info(CONST_S("Information about ") + GetDescription(DEFINITE
));
4462 AddSpecialStethoscopeInfo(Info
);
4463 Info
.AddEntry(CONST_S("Endurance: ") + GetAttribute(ENDURANCE
), LIGHT_GRAY
);
4464 Info
.AddEntry(CONST_S("Perception: ") + GetAttribute(PERCEPTION
), LIGHT_GRAY
);
4465 Info
.AddEntry(CONST_S("Intelligence: ") + GetAttribute(INTELLIGENCE
), LIGHT_GRAY
);
4466 Info
.AddEntry(CONST_S("Wisdom: ") + GetAttribute(WISDOM
), LIGHT_GRAY
);
4467 //Info.AddEntry(CONST_S("Willpower: ") + GetAttribute(WILL_POWER), LIGHT_GRAY);
4468 Info
.AddEntry(CONST_S("Charisma: ") + GetAttribute(CHARISMA
), LIGHT_GRAY
);
4469 Info
.AddEntry(CONST_S("HP: ") + GetHP() + "/" + GetMaxHP(), IsInBadCondition() ? RED
: LIGHT_GRAY
);
4470 if (GetAction()) Info
.AddEntry(festring(GetAction()->GetDescription()).CapitalizeCopy(), LIGHT_GRAY
);
4471 for (int c
= 0; c
< STATES
; ++c
) {
4472 if (StateIsActivated(1 << c
) && (1 << c
!= HASTE
|| !StateIsActivated(SLOW
)) && (1 << c
!= SLOW
|| !StateIsActivated(HASTE
)))
4473 Info
.AddEntry(StateData
[c
].Description
, LIGHT_GRAY
);
4475 switch (GetTirednessState()) {
4476 case FAINTING
: Info
.AddEntry("Fainting", RED
); break;
4477 case EXHAUSTED
: Info
.AddEntry("Exhausted", LIGHT_GRAY
); break;
4479 game::SetStandardListAttributes(Info
);
4484 truth
character::CanUseStethoscope (truth PrintReason
) const {
4485 if (PrintReason
) ADD_MESSAGE("This type of monster can't use a stethoscope.");
4490 /* Effect used by at least Sophos.
4491 * NOTICE: Doesn't check for death! */
4492 void character::TeleportSomePartsAway (int NumberToTeleport
) {
4493 for (int c
= 0; c
< NumberToTeleport
; ++c
) {
4494 int RandomBodyPart
= GetRandomNonVitalBodyPart();
4495 if (RandomBodyPart
== NONE_INDEX
) {
4496 for (; c
< NumberToTeleport
; ++c
) {
4497 GetTorso()->SetHP((GetTorso()->GetHP() << 2) / 5);
4498 sLong TorsosVolume
= GetTorso()->GetMainMaterial()->GetVolume() / 10;
4499 if (!TorsosVolume
) break;
4500 sLong Amount
= (RAND() % TorsosVolume
)+1;
4501 item
*Lump
= GetTorso()->GetMainMaterial()->CreateNaturalForm(Amount
);
4502 GetTorso()->GetMainMaterial()->EditVolume(-Amount
);
4503 Lump
->MoveTo(GetNearLSquare(GetLevel()->GetRandomSquare())->GetStack());
4504 if (IsPlayer()) ADD_MESSAGE("Parts of you teleport away.");
4505 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Parts of %s teleport away.", CHAR_NAME(DEFINITE
));
4508 item
*SeveredBodyPart
= SevereBodyPart(RandomBodyPart
);
4509 if (SeveredBodyPart
) {
4510 GetNearLSquare(GetLevel()->GetRandomSquare())->AddItem(SeveredBodyPart
);
4511 SeveredBodyPart
->DropEquipment();
4512 if (IsPlayer()) ADD_MESSAGE("Your %s teleports away.", GetBodyPartName(RandomBodyPart
).CStr());
4513 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s teleports away.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4515 if (IsPlayer()) ADD_MESSAGE("Your %s disappears.", GetBodyPartName(RandomBodyPart
).CStr());
4516 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s disappears.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4523 /* Returns an index of a random bodypart that is not vital. If no non-vital bodypart is found returns NONE_INDEX */
4524 int character::GetRandomNonVitalBodyPart () const {
4525 int OKBodyPart
[MAX_BODYPARTS
];
4526 int OKBodyParts
= 0;
4527 for (int c
= 0; c
< BodyParts
; ++c
) if (GetBodyPart(c
) && !BodyPartIsVital(c
)) OKBodyPart
[OKBodyParts
++] = c
;
4528 return OKBodyParts
? OKBodyPart
[RAND() % OKBodyParts
] : NONE_INDEX
;
4532 void character::CalculateVolumeAndWeight () {
4533 Volume
= Stack
->GetVolume();
4534 Weight
= Stack
->GetWeight();
4536 CarriedWeight
= Weight
;
4537 for (int c
= 0; c
< BodyParts
; ++c
) {
4538 bodypart
*BodyPart
= GetBodyPart(c
);
4540 BodyVolume
+= BodyPart
->GetBodyPartVolume();
4541 Volume
+= BodyPart
->GetVolume();
4542 CarriedWeight
+= BodyPart
->GetCarriedWeight();
4543 Weight
+= BodyPart
->GetWeight();
4549 void character::SignalVolumeAndWeightChange () {
4550 if (!IsInitializing()) {
4551 CalculateVolumeAndWeight();
4552 if (IsEnabled()) CalculateBurdenState();
4553 if (MotherEntity
) MotherEntity
->SignalVolumeAndWeightChange();
4558 void character::SignalEmitationIncrease (col24 EmitationUpdate
) {
4559 if (game::CompareLights(EmitationUpdate
, Emitation
) > 0) {
4560 game::CombineLights(Emitation
, EmitationUpdate
);
4561 if (MotherEntity
) MotherEntity
->SignalEmitationIncrease(EmitationUpdate
);
4562 else if (SquareUnder
[0] && !game::IsInWilderness()) {
4563 for(int c
= 0; c
< GetSquaresUnder(); ++c
) GetLSquareUnder()->SignalEmitationIncrease(EmitationUpdate
);
4569 void character::SignalEmitationDecrease (col24 EmitationUpdate
) {
4570 if (game::CompareLights(EmitationUpdate
, Emitation
) >= 0 && Emitation
) {
4571 col24 Backup
= Emitation
;
4572 CalculateEmitation();
4573 if (Backup
!= Emitation
) {
4574 if (MotherEntity
) MotherEntity
->SignalEmitationDecrease(EmitationUpdate
);
4575 else if (SquareUnder
[0] && !game::IsInWilderness()) {
4576 for (int c
= 0; c
< GetSquaresUnder(); ++c
) GetLSquareUnder(c
)->SignalEmitationDecrease(EmitationUpdate
);
4583 void character::CalculateEmitation () {
4584 Emitation
= GetBaseEmitation();
4585 for (int c
= 0; c
< BodyParts
; ++c
) {
4586 bodypart
*BodyPart
= GetBodyPart(c
);
4587 if (BodyPart
) game::CombineLights(Emitation
, BodyPart
->GetEmitation());
4589 game::CombineLights(Emitation
, Stack
->GetEmitation());
4593 void character::CalculateAll () {
4594 Flags
|= C_INITIALIZING
;
4595 CalculateAttributeBonuses();
4596 CalculateVolumeAndWeight();
4597 CalculateEmitation();
4598 CalculateBodyPartMaxHPs(0);
4599 CalculateMaxStamina();
4600 CalculateBurdenState();
4601 CalculateBattleInfo();
4602 Flags
&= ~C_INITIALIZING
;
4606 void character::CalculateHP () {
4607 HP
= sumbodypartproperties()(this, &bodypart::GetHP
);
4611 void character::CalculateMaxHP () {
4612 MaxHP
= sumbodypartproperties()(this, &bodypart::GetMaxHP
);
4616 void character::CalculateBodyPartMaxHPs (feuLong Flags
) {
4617 doforbodypartswithparam
<feuLong
>()(this, &bodypart::CalculateMaxHP
, Flags
);
4623 truth
character::EditAttribute (int Identifier
, int Value
) {
4624 if (Identifier
== ENDURANCE
&& UseMaterialAttributes()) return false;
4625 if (RawEditAttribute(BaseExperience
[Identifier
], Value
)) {
4626 if (!IsInitializing()) {
4627 if (Identifier
== LEG_STRENGTH
) CalculateBurdenState();
4628 else if (Identifier
== ENDURANCE
) CalculateBodyPartMaxHPs();
4629 else if (IsPlayer() && Identifier
== PERCEPTION
) game::SendLOSUpdateRequest();
4630 else if (IsPlayerKind() && (Identifier
== INTELLIGENCE
|| Identifier
== WISDOM
|| Identifier
== CHARISMA
)) UpdatePictures();
4631 CalculateBattleInfo();
4639 truth
character::ActivateRandomState (int Flags
, int Time
, sLong Seed
) {
4641 if (Seed
) femath::SetSeed(Seed
);
4642 sLong ToBeActivated
= GetRandomState(Flags
|DUR_TEMPORARY
);
4644 if (!ToBeActivated
) return false;
4645 BeginTemporaryState(ToBeActivated
, Time
);
4650 truth
character::GainRandomIntrinsic (int Flags
) {
4651 sLong ToBeActivated
= GetRandomState(Flags
|DUR_PERMANENT
);
4652 if (!ToBeActivated
) return false;
4653 GainIntrinsic(ToBeActivated
);
4658 /* Returns 0 if state not found */
4659 sLong
character::GetRandomState (int Flags
) const {
4660 sLong OKStates
[STATES
];
4661 int NumberOfOKStates
= 0;
4662 for (int c
= 0; c
< STATES
; ++c
) {
4663 if (StateData
[c
].Flags
& Flags
& DUR_FLAGS
&& StateData
[c
].Flags
& Flags
& SRC_FLAGS
) OKStates
[NumberOfOKStates
++] = 1 << c
;
4665 return NumberOfOKStates
? OKStates
[RAND() % NumberOfOKStates
] : 0;
4669 int characterprototype::CreateSpecialConfigurations (characterdatabase
**TempConfig
, int Configs
, int Level
) {
4670 if (Level
== 0 && TempConfig
[0]->CreateDivineConfigurations
) {
4671 Configs
= databasecreator
<character
>::CreateDivineConfigurations(this, TempConfig
, Configs
);
4673 if (Level
== 1 && TempConfig
[0]->CreateUndeadConfigurations
) {
4674 for (int c
= 1; c
< protocontainer
<character
>::GetSize(); ++c
) {
4675 const character::prototype
*Proto
= protocontainer
<character
>::GetProto(c
);
4676 const character::database
*const *CharacterConfigData
= Proto
->GetConfigData();
4677 if (!CharacterConfigData
) ABORT("No database entry for character <%s>!", Proto
->GetClassID());
4678 const character::database
*const* End
= CharacterConfigData
+ Proto
->GetConfigSize();
4679 for (++CharacterConfigData
; CharacterConfigData
!= End
; ++CharacterConfigData
) {
4680 const character::database
*CharacterDataBase
= *CharacterConfigData
;
4681 if (CharacterDataBase
->UndeadVersions
) {
4682 character::database
* ConfigDataBase
= new character::database(**TempConfig
);
4683 ConfigDataBase
->InitDefaults(this, (c
<< 8) | CharacterDataBase
->Config
);
4684 ConfigDataBase
->PostFix
<< "of ";
4685 if (CharacterDataBase
->Adjective
.GetSize()) {
4686 if (CharacterDataBase
->UsesLongAdjectiveArticle
) ConfigDataBase
->PostFix
<< "an ";
4687 else ConfigDataBase
->PostFix
<< "a ";
4688 ConfigDataBase
->PostFix
<< CharacterDataBase
->Adjective
<< ' ';
4690 if (CharacterDataBase
->UsesLongArticle
) ConfigDataBase
->PostFix
<< "an ";
4691 else ConfigDataBase
->PostFix
<< "a ";
4693 ConfigDataBase
->PostFix
<< CharacterDataBase
->NameSingular
;
4694 if (CharacterDataBase
->PostFix
.GetSize()) ConfigDataBase
->PostFix
<< ' ' << CharacterDataBase
->PostFix
;
4695 int P1
= TempConfig
[0]->UndeadAttributeModifier
;
4696 int P2
= TempConfig
[0]->UndeadVolumeModifier
;
4698 for (c2
= 0; c2
< ATTRIBUTES
; ++c2
) ConfigDataBase
->*ExpPtr
[c2
] = CharacterDataBase
->*ExpPtr
[c2
] * P1
/ 100;
4699 for (c2
= 0; c2
< EQUIPMENT_DATAS
; ++c2
) ConfigDataBase
->*EquipmentDataPtr
[c2
] = contentscript
<item
>();
4700 ConfigDataBase
->DefaultIntelligence
= 5;
4701 ConfigDataBase
->DefaultWisdom
= 5;
4702 ConfigDataBase
->DefaultCharisma
= 5;
4703 ConfigDataBase
->TotalSize
= CharacterDataBase
->TotalSize
;
4704 ConfigDataBase
->Sex
= CharacterDataBase
->Sex
;
4705 ConfigDataBase
->AttributeBonus
= CharacterDataBase
->AttributeBonus
;
4706 ConfigDataBase
->TotalVolume
= CharacterDataBase
->TotalVolume
* P2
/ 100;
4707 if (TempConfig
[0]->UndeadCopyMaterials
) {
4708 ConfigDataBase
->HeadBitmapPos
= CharacterDataBase
->HeadBitmapPos
;
4709 ConfigDataBase
->HairColor
= CharacterDataBase
->HairColor
;
4710 ConfigDataBase
->EyeColor
= CharacterDataBase
->EyeColor
;
4711 ConfigDataBase
->CapColor
= CharacterDataBase
->CapColor
;
4712 ConfigDataBase
->FleshMaterial
= CharacterDataBase
->FleshMaterial
;
4713 ConfigDataBase
->BloodMaterial
= CharacterDataBase
->BloodMaterial
;
4714 ConfigDataBase
->VomitMaterial
= CharacterDataBase
->VomitMaterial
;
4715 ConfigDataBase
->SweatMaterial
= CharacterDataBase
->SweatMaterial
;
4717 ConfigDataBase
->KnownCWeaponSkills
= CharacterDataBase
->KnownCWeaponSkills
;
4718 ConfigDataBase
->CWeaponSkillHits
= CharacterDataBase
->CWeaponSkillHits
;
4719 ConfigDataBase
->PostProcess();
4720 TempConfig
[Configs
++] = ConfigDataBase
;
4725 if (Level
== 0 && TempConfig
[0]->CreateGolemMaterialConfigurations
) {
4726 for (int c
= 1; c
< protocontainer
<material
>::GetSize(); ++c
) {
4727 const material::prototype
* Proto
= protocontainer
<material
>::GetProto(c
);
4728 const material::database
*const* MaterialConfigData
= Proto
->GetConfigData();
4729 const material::database
*const* End
= MaterialConfigData
+ Proto
->GetConfigSize();
4730 for (++MaterialConfigData
; MaterialConfigData
!= End
; ++MaterialConfigData
) {
4731 const material::database
* MaterialDataBase
= *MaterialConfigData
;
4732 if (MaterialDataBase
->CategoryFlags
& IS_GOLEM_MATERIAL
) {
4733 character::database
* ConfigDataBase
= new character::database(**TempConfig
);
4734 ConfigDataBase
->InitDefaults(this, MaterialDataBase
->Config
);
4735 ConfigDataBase
->Adjective
= MaterialDataBase
->NameStem
;
4736 ConfigDataBase
->UsesLongAdjectiveArticle
= MaterialDataBase
->NameFlags
& USE_AN
;
4737 ConfigDataBase
->AttachedGod
= MaterialDataBase
->AttachedGod
;
4738 TempConfig
[Configs
++] = ConfigDataBase
;
4747 double character::GetTimeToDie (ccharacter
*Enemy
, int Damage
, double ToHitValue
, truth AttackIsBlockable
, truth UseMaxHP
) const {
4748 double DodgeValue
= GetDodgeValue();
4749 if (!Enemy
->CanBeSeenBy(this, true)) ToHitValue
*= 2;
4750 if (!CanBeSeenBy(Enemy
, true)) DodgeValue
*= 2;
4751 double MinHits
= 1000;
4753 for (int c
= 0; c
< BodyParts
; ++c
) {
4754 if (BodyPartIsVital(c
) && GetBodyPart(c
)) {
4755 double Hits
= GetBodyPart(c
)->GetTimeToDie(Damage
, ToHitValue
, DodgeValue
, AttackIsBlockable
, UseMaxHP
);
4756 if (First
) { MinHits
= Hits
; First
= false; } else MinHits
= 1 / (1 / MinHits
+ 1 / Hits
);
4763 double character::GetRelativeDanger (ccharacter
*Enemy
, truth UseMaxHP
) const {
4764 double Danger
= Enemy
->GetTimeToKill(this, UseMaxHP
) / GetTimeToKill(Enemy
, UseMaxHP
);
4765 int EnemyAP
= Enemy
->GetMoveAPRequirement(1);
4766 int ThisAP
= GetMoveAPRequirement(1);
4767 if (EnemyAP
> ThisAP
) Danger
*= 1.25;
4768 else if (ThisAP
> EnemyAP
) Danger
*= 0.80;
4769 if (!Enemy
->CanBeSeenBy(this, true)) Danger
*= Enemy
->IsPlayer() ? 0.2 : 0.5;
4770 if (!CanBeSeenBy(Enemy
, true)) Danger
*= IsPlayer() ? 5. : 2.;
4771 if (GetAttribute(INTELLIGENCE
) < 10 && !IsPlayer()) Danger
*= 0.80;
4772 if (Enemy
->GetAttribute(INTELLIGENCE
) < 10 && !Enemy
->IsPlayer()) Danger
*= 1.25;
4773 return Limit(Danger
, 0.001, 1000.0);
4777 festring
character::GetBodyPartName (int I
, truth Articled
) const {
4778 if (I
== TORSO_INDEX
) return Articled
? CONST_S("a torso") : CONST_S("torso");
4779 ABORT("Illegal character bodypart name request!");
4784 item
*character::SearchForItem(feuLong ID
) const {
4785 item
*Equipment
= findequipment
<feuLong
>()(this, &item::HasID
, ID
);
4786 if (Equipment
) return Equipment
;
4787 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (i
->GetID() == ID
) return *i
;
4792 truth
character::ContentsCanBeSeenBy (ccharacter
*Viewer
) const {
4793 return Viewer
== this;
4797 truth
character::HitEffect (character
*Enemy
, item
* Weapon
, v2 HitPos
, int Type
, int BodyPartIndex
,
4798 int Direction
, truth BlockedByArmour
)
4800 if (Weapon
) return Weapon
->HitEffect(this, Enemy
, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
4802 case UNARMED_ATTACK
: return Enemy
->SpecialUnarmedEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
4803 case KICK_ATTACK
: return Enemy
->SpecialKickEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
4804 case BITE_ATTACK
: return Enemy
->SpecialBiteEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
4810 void character::WeaponSkillHit (item
*Weapon
, int Type
, int Hits
) {
4813 case UNARMED_ATTACK
: Category
= UNARMED
; break;
4814 case WEAPON_ATTACK
: Weapon
->WeaponSkillHit(Hits
); return;
4815 case KICK_ATTACK
: Category
= KICK
; break;
4816 case BITE_ATTACK
: Category
= BITE
; break;
4818 if (!IsHumanoid()) return;
4819 Category
= Weapon
->GetWeaponCategory();
4822 ABORT("Illegal Type %d passed to character::WeaponSkillHit()!", Type
);
4825 if (GetCWeaponSkill(Category
)->AddHit(Hits
)) {
4826 CalculateBattleInfo();
4827 if (IsPlayer()) GetCWeaponSkill(Category
)->AddLevelUpMessage(Category
);
4832 /* Returns 0 if character cannot be duplicated */
4833 character
*character::Duplicate (feuLong Flags
) {
4834 if (!(Flags
& IGNORE_PROHIBITIONS
) && !CanBeCloned()) return 0;
4835 character
*Char
= GetProtoType()->Clone(this);
4836 if (Flags
& MIRROR_IMAGE
) {
4837 DuplicateEquipment(Char
, Flags
& ~IGNORE_PROHIBITIONS
);
4838 Char
->SetLifeExpectancy(Flags
>> LE_BASE_SHIFT
& LE_BASE_RANGE
, Flags
>> LE_RAND_SHIFT
& LE_RAND_RANGE
);
4840 Char
->CalculateAll();
4841 Char
->CalculateEmitation();
4842 Char
->UpdatePictures();
4843 Char
->Flags
&= ~(C_INITIALIZING
|C_IN_NO_MSG_MODE
);
4848 truth
character::TryToEquip (item
*Item
) {
4849 if (!Item
->AllowEquip() || !CanUseEquipment() || GetAttribute(WISDOM
) >= Item
->GetWearWisdomLimit() || Item
->GetSquaresUnder() != 1)
4851 for (int e
= 0; e
< GetEquipments(); ++e
) {
4852 if (GetBodyPartOfEquipment(e
) && EquipmentIsAllowed(e
)) {
4853 sorter Sorter
= EquipmentSorter(e
);
4854 if ((Sorter
== 0 || (Item
->*Sorter
)(this)) &&
4855 ((e
!= RIGHT_WIELDED_INDEX
&& e
!= LEFT_WIELDED_INDEX
) ||
4856 Item
->IsWeapon(this) || Item
->IsShield(this)) && AllowEquipment(Item
, e
)) {
4857 item
*OldEquipment
= GetEquipment(e
);
4858 if (BoundToUse(OldEquipment
, e
)) continue;
4859 lsquare
*LSquareUnder
= GetLSquareUnder();
4860 stack
*StackUnder
= LSquareUnder
->GetStack();
4861 msgsystem::DisableMessages();
4862 Flags
|= C_PICTURE_UPDATES_FORBIDDEN
;
4863 LSquareUnder
->Freeze();
4864 StackUnder
->Freeze();
4865 double Danger
= GetRelativeDanger(PLAYER
);
4866 if (OldEquipment
) OldEquipment
->RemoveFromSlot();
4867 Item
->RemoveFromSlot();
4868 SetEquipment(e
, Item
);
4869 double NewDanger
= GetRelativeDanger(PLAYER
);
4870 Item
->RemoveFromSlot();
4871 StackUnder
->AddItem(Item
);
4872 if (OldEquipment
) SetEquipment(e
, OldEquipment
);
4873 msgsystem::EnableMessages();
4874 Flags
&= ~C_PICTURE_UPDATES_FORBIDDEN
;
4875 LSquareUnder
->UnFreeze();
4876 StackUnder
->UnFreeze();
4878 if (NewDanger
> Danger
|| BoundToUse(Item
, e
)) {
4879 room
*Room
= GetRoom();
4880 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
4881 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
));
4882 if (Room
) Room
->DropItem(this, OldEquipment
, 1);
4883 OldEquipment
->MoveTo(StackUnder
);
4884 Item
->RemoveFromSlot();
4885 SetEquipment(e
, Item
);
4891 if (NewDanger
> Danger
|| (NewDanger
== Danger
&& e
!= RIGHT_WIELDED_INDEX
&& e
!= LEFT_WIELDED_INDEX
) || BoundToUse(Item
, e
)) {
4892 room
*Room
= GetRoom();
4893 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
4894 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up and equips %s.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(INDEFINITE
));
4895 Item
->RemoveFromSlot();
4896 SetEquipment(e
, Item
);
4909 truth
character::TryToConsume (item
*Item
) {
4910 return Item
->CanBeEatenByAI(this) && ConsumeItem(Item
, Item
->GetConsumeMaterial(this)->GetConsumeVerb());
4914 void character::UpdateESPLOS () const {
4915 if (StateIsActivated(ESP
) && !game::IsInWilderness()) {
4916 for (int c
= 0; c
< game::GetTeams(); ++c
) {
4917 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
4918 const character
*ch
= *i
;
4919 if (ch
->IsEnabled()) ch
->SendNewDrawRequest();
4926 int character::GetCWeaponSkillLevel (citem
*Item
) const {
4927 if (Item
->GetWeaponCategory() < GetAllowedWeaponSkillCategories()) return GetCWeaponSkill(Item
->GetWeaponCategory())->GetLevel();
4932 void character::PrintBeginPanicMessage () const {
4933 if (IsPlayer()) ADD_MESSAGE("You panic!");
4934 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s panics.", CHAR_NAME(DEFINITE
));
4938 void character::PrintEndPanicMessage () const {
4939 if (IsPlayer()) ADD_MESSAGE("You finally calm down.");
4940 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s calms down.", CHAR_NAME(DEFINITE
));
4944 void character::CheckPanic (int Ticks
) {
4945 if (GetPanicLevel() > 1 && !StateIsActivated(PANIC
) && GetHP() * 100 < RAND() % (GetPanicLevel() * GetMaxHP() << 1))
4946 BeginTemporaryState(PANIC
, ((Ticks
* 3) >> 2) + RAND() % ((Ticks
>> 1) + 1)); // 25% randomness to ticks...
4950 /* returns 0 if fails else the newly created character */
4951 character
*character::DuplicateToNearestSquare (character
*Cloner
, feuLong Flags
) {
4952 character
*NewlyCreated
= Duplicate(Flags
);
4953 if (!NewlyCreated
) return 0;
4954 if (Flags
& CHANGE_TEAM
&& Cloner
) NewlyCreated
->ChangeTeam(Cloner
->GetTeam());
4955 NewlyCreated
->PutNear(GetPos());
4956 return NewlyCreated
;
4960 void character::SignalSpoil (material
*m
) {
4961 if (GetMotherEntity()) GetMotherEntity()->SignalSpoil(m
);
4962 else Disappear(0, "spoil", &item::IsVeryCloseToSpoiling
);
4966 truth
character::CanHeal () const {
4967 for (int c
= 0; c
< BodyParts
; ++c
) {
4968 bodypart
*BodyPart
= GetBodyPart(c
);
4969 if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < BodyPart
->GetMaxHP()) return true;
4975 int character::GetRelation (ccharacter
*Who
) const {
4976 return GetTeam()->GetRelation(Who
->GetTeam());
4980 truth (item::*AffectTest
[BASE_ATTRIBUTES
])() const = {
4981 &item::AffectsEndurance
,
4982 &item::AffectsPerception
,
4983 &item::AffectsIntelligence
,
4984 &item::AffectsWisdom
,
4985 &item::AffectsWillPower
,
4986 &item::AffectsCharisma
,
4991 /* Returns nonzero if endurance has decreased and death may occur */
4992 truth
character::CalculateAttributeBonuses () {
4993 doforbodyparts()(this, &bodypart::CalculateAttributeBonuses
);
4994 int BackupBonus
[BASE_ATTRIBUTES
];
4995 int BackupCarryingBonus
= CarryingBonus
;
4998 for (c1
= 0; c1
< BASE_ATTRIBUTES
; ++c1
) {
4999 BackupBonus
[c1
] = AttributeBonus
[c1
];
5000 AttributeBonus
[c1
] = 0;
5002 for (c1
= 0; c1
< GetEquipments(); ++c1
) {
5003 item
*Equipment
= GetEquipment(c1
);
5004 if (!Equipment
|| !Equipment
->IsInCorrectSlot(c1
)) continue;
5005 for (int c2
= 0; c2
< BASE_ATTRIBUTES
; ++c2
) {
5006 if ((Equipment
->*AffectTest
[c2
])()) AttributeBonus
[c2
] += Equipment
->GetEnchantment();
5008 if (Equipment
->AffectsCarryingCapacity()) CarryingBonus
+= Equipment
->GetCarryingBonus();
5011 ApplySpecialAttributeBonuses();
5013 if (IsPlayer() && !IsInitializing() && AttributeBonus
[PERCEPTION
] != BackupBonus
[PERCEPTION
]) game::SendLOSUpdateRequest();
5014 if (IsPlayer() && !IsInitializing() && AttributeBonus
[INTELLIGENCE
] != BackupBonus
[INTELLIGENCE
]) UpdateESPLOS();
5016 if (!IsInitializing() && CarryingBonus
!= BackupCarryingBonus
) CalculateBurdenState();
5018 if (!IsInitializing() && AttributeBonus
[ENDURANCE
] != BackupBonus
[ENDURANCE
]) {
5019 CalculateBodyPartMaxHPs();
5020 CalculateMaxStamina();
5021 return AttributeBonus
[ENDURANCE
] < BackupBonus
[ENDURANCE
];
5028 void character::ApplyEquipmentAttributeBonuses (item
*Equipment
) {
5029 if (Equipment
->AffectsEndurance()) {
5030 AttributeBonus
[ENDURANCE
] += Equipment
->GetEnchantment();
5031 CalculateBodyPartMaxHPs();
5032 CalculateMaxStamina();
5034 if (Equipment
->AffectsPerception()) {
5035 AttributeBonus
[PERCEPTION
] += Equipment
->GetEnchantment();
5036 if (IsPlayer()) game::SendLOSUpdateRequest();
5038 if (Equipment
->AffectsIntelligence()) {
5039 AttributeBonus
[INTELLIGENCE
] += Equipment
->GetEnchantment();
5040 if (IsPlayer()) UpdateESPLOS();
5042 if (Equipment
->AffectsWisdom()) AttributeBonus
[WISDOM
] += Equipment
->GetEnchantment();
5043 if (Equipment
->AffectsWillPower()) AttributeBonus
[WILL_POWER
] += Equipment
->GetEnchantment();
5044 if (Equipment
->AffectsCharisma()) AttributeBonus
[CHARISMA
] += Equipment
->GetEnchantment();
5045 if (Equipment
->AffectsMana()) AttributeBonus
[MANA
] += Equipment
->GetEnchantment();
5046 if (Equipment
->AffectsCarryingCapacity()) {
5047 CarryingBonus
+= Equipment
->GetCarryingBonus();
5048 CalculateBurdenState();
5053 void character::ReceiveAntidote (sLong Amount
) {
5054 if (StateIsActivated(POISONED
)) {
5055 if (GetTemporaryStateCounter(POISONED
) > Amount
) {
5056 EditTemporaryStateCounter(POISONED
, -Amount
);
5059 if (IsPlayer()) ADD_MESSAGE("Aaaah... You feel much better.");
5060 Amount
-= GetTemporaryStateCounter(POISONED
);
5061 DeActivateTemporaryState(POISONED
);
5064 if ((Amount
>= 100 || RAND_N(100) < Amount
) && StateIsActivated(PARASITIZED
)) {
5065 if (IsPlayer()) ADD_MESSAGE("Something in your belly didn't seem to like this stuff.");
5066 DeActivateTemporaryState(PARASITIZED
);
5067 Amount
-= Min(100, Amount
);
5069 if ((Amount
>= 100 || RAND_N(100) < Amount
) && StateIsActivated(LEPROSY
)) {
5070 if (IsPlayer()) ADD_MESSAGE("You are not falling to pieces anymore.");
5071 DeActivateTemporaryState(LEPROSY
);
5072 Amount
-= Min(100, Amount
);
5077 void character::AddAntidoteConsumeEndMessage () const {
5078 if (StateIsActivated(POISONED
)) {
5079 // true only if the antidote didn't cure the poison completely
5080 if (IsPlayer()) ADD_MESSAGE("Your body processes the poison in your veins with rapid speed.");
5085 truth
character::IsDead () const {
5086 for (int c
= 0; c
< BodyParts
; ++c
) {
5087 bodypart
*BodyPart
= GetBodyPart(c
);
5088 if (BodyPartIsVital(c
) && (!BodyPart
|| BodyPart
->GetHP() < 1)) return true;
5094 void character::SignalSpoilLevelChange (material
*m
) {
5095 if (GetMotherEntity()) GetMotherEntity()->SignalSpoilLevelChange(m
); else UpdatePictures();
5099 void character::AddOriginalBodyPartID (int I
, feuLong What
) {
5100 if (std::find(OriginalBodyPartID
[I
].begin(), OriginalBodyPartID
[I
].end(), What
) == OriginalBodyPartID
[I
].end()) {
5101 OriginalBodyPartID
[I
].push_back(What
);
5102 if (OriginalBodyPartID
[I
].size() > 100) OriginalBodyPartID
[I
].erase(OriginalBodyPartID
[I
].begin());
5107 void character::AddToInventory (const fearray
<contentscript
<item
> > &ItemArray
, int SpecialFlags
) {
5108 for (uInt c1
= 0; c1
< ItemArray
.Size
; ++c1
) {
5109 if (ItemArray
[c1
].IsValid()) {
5110 const interval
*TimesPtr
= ItemArray
[c1
].GetTimes();
5111 int Times
= TimesPtr
? TimesPtr
->Randomize() : 1;
5112 for (int c2
= 0; c2
< Times
; ++c2
) {
5113 item
*Item
= ItemArray
[c1
].Instantiate(SpecialFlags
);
5115 Stack
->AddItem(Item
);
5116 Item
->SpecialGenerationHandler();
5124 truth
character::HasHadBodyPart (citem
*Item
) const {
5125 for (int c
= 0; c
< BodyParts
; ++c
)
5126 if (std::find(OriginalBodyPartID
[c
].begin(), OriginalBodyPartID
[c
].end(), Item
->GetID()) != OriginalBodyPartID
[c
].end())
5128 return GetPolymorphBackup() && GetPolymorphBackup()->HasHadBodyPart(Item
);
5132 festring
&character::ProcessMessage (festring
&Msg
) const {
5133 SEARCH_N_REPLACE(Msg
, "@nu", GetName(UNARTICLED
));
5134 SEARCH_N_REPLACE(Msg
, "@ni", GetName(INDEFINITE
));
5135 SEARCH_N_REPLACE(Msg
, "@nd", GetName(DEFINITE
));
5136 SEARCH_N_REPLACE(Msg
, "@du", GetDescription(UNARTICLED
));
5137 SEARCH_N_REPLACE(Msg
, "@di", GetDescription(INDEFINITE
));
5138 SEARCH_N_REPLACE(Msg
, "@dd", GetDescription(DEFINITE
));
5139 SEARCH_N_REPLACE(Msg
, "@pp", GetPersonalPronoun());
5140 SEARCH_N_REPLACE(Msg
, "@sp", GetPossessivePronoun());
5141 SEARCH_N_REPLACE(Msg
, "@op", GetObjectPronoun());
5142 SEARCH_N_REPLACE(Msg
, "@Nu", GetName(UNARTICLED
).CapitalizeCopy());
5143 SEARCH_N_REPLACE(Msg
, "@Ni", GetName(INDEFINITE
).CapitalizeCopy());
5144 SEARCH_N_REPLACE(Msg
, "@Nd", GetName(DEFINITE
).CapitalizeCopy());
5145 SEARCH_N_REPLACE(Msg
, "@Du", GetDescription(UNARTICLED
).CapitalizeCopy());
5146 SEARCH_N_REPLACE(Msg
, "@Di", GetDescription(INDEFINITE
).CapitalizeCopy());
5147 SEARCH_N_REPLACE(Msg
, "@Dd", GetDescription(DEFINITE
).CapitalizeCopy());
5148 SEARCH_N_REPLACE(Msg
, "@Pp", GetPersonalPronoun().CapitalizeCopy());
5149 SEARCH_N_REPLACE(Msg
, "@Sp", GetPossessivePronoun().CapitalizeCopy());
5150 SEARCH_N_REPLACE(Msg
, "@Op", GetObjectPronoun().CapitalizeCopy());
5151 SEARCH_N_REPLACE(Msg
, "@Gd", GetMasterGod()->GetName());
5156 void character::ProcessAndAddMessage (festring Msg
) const {
5157 ADD_MESSAGE("%s", ProcessMessage(Msg
).CStr());
5161 void character::BeTalkedTo () {
5163 if (GetRelation(PLAYER
) == HOSTILE
)
5164 ProcessAndAddMessage(GetHostileReplies()[RandomizeReply(Said
, GetHostileReplies().Size
)]);
5166 ProcessAndAddMessage(GetFriendlyReplies()[RandomizeReply(Said
, GetFriendlyReplies().Size
)]);
5170 truth
character::CheckZap () {
5172 ADD_MESSAGE("This monster type can't zap.");
5179 void character::DamageAllItems (character
*Damager
, int Damage
, int Type
) {
5180 GetStack()->ReceiveDamage(Damager
, Damage
, Type
);
5181 for (int c
= 0; c
< GetEquipments(); ++c
) {
5182 item
*Equipment
= GetEquipment(c
);
5183 if (Equipment
) Equipment
->ReceiveDamage(Damager
, Damage
, Type
);
5188 truth
character::Equips (citem
*Item
) const {
5189 return combineequipmentpredicateswithparam
<feuLong
>()(this, &item::HasID
, Item
->GetID(), 1);
5193 void character::PrintBeginConfuseMessage () const {
5194 if (IsPlayer()) ADD_MESSAGE("You feel quite happy.");
5198 void character::PrintEndConfuseMessage () const {
5199 if (IsPlayer()) ADD_MESSAGE("The world is boring again.");
5203 v2
character::ApplyStateModification (v2 TryDirection
) const {
5204 if (!StateIsActivated(CONFUSED
) || RAND() & 15 || game::IsInWilderness()) return TryDirection
;
5205 v2 To
= GetLevel()->GetFreeAdjacentSquare(this, GetPos(), true);
5206 if (To
== ERROR_V2
) return TryDirection
;
5208 if (To
!= TryDirection
&& IsPlayer()) ADD_MESSAGE("Whoa! You somehow don't manage to walk straight.");
5213 void character::AddConfuseHitMessage () const {
5214 if (IsPlayer()) ADD_MESSAGE("This stuff is confusing.");
5218 item
*character::SelectFromPossessions (cfestring
&Topic
, sorter Sorter
) {
5219 itemvector ReturnVector
;
5220 SelectFromPossessions(ReturnVector
, Topic
, NO_MULTI_SELECT
, Sorter
);
5221 return !ReturnVector
.empty() ? ReturnVector
[0] : 0;
5225 truth
character::SelectFromPossessions (itemvector
&ReturnVector
, cfestring
&Topic
, int Flags
, sorter Sorter
) {
5227 truth InventoryPossible
= GetStack()->SortedItems(this, Sorter
);
5228 if (InventoryPossible
) List
.AddEntry(CONST_S("choose from inventory"), LIGHT_GRAY
, 20, game::AddToItemDrawVector(itemvector()));
5233 for (c
= 0; c
< BodyParts
; ++c
) {
5234 bodypart
*BodyPart
= GetBodyPart(c
);
5235 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) {
5236 Item
.push_back(BodyPart
);
5238 BodyPart
->AddName(Entry
, UNARTICLED
);
5239 int ImageKey
= game::AddToItemDrawVector(itemvector(1, BodyPart
));
5240 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
5244 for (c
= 0; c
< GetEquipments(); ++c
) {
5245 item
*Equipment
= GetEquipment(c
);
5246 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) {
5247 Item
.push_back(Equipment
);
5248 Entry
= GetEquipmentName(c
);
5251 Equipment
->AddInventoryEntry(this, Entry
, 1, true);
5252 AddSpecialEquipmentInfo(Entry
, c
);
5253 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
5254 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
5259 game::SetStandardListAttributes(List
);
5260 List
.SetFlags(SELECTABLE
|DRAW_BACKGROUND_AFTERWARDS
);
5261 List
.SetEntryDrawer(game::ItemEntryDrawer
);
5262 game::DrawEverythingNoBlit();
5263 int Chosen
= List
.Draw();
5264 game::ClearItemDrawVector();
5265 if (Chosen
!= ESCAPED
) {
5266 if ((InventoryPossible
&& !Chosen
) || Chosen
& FELIST_ERROR_BIT
) {
5267 GetStack()->DrawContents(ReturnVector
, this, Topic
, Flags
, Sorter
);
5269 ReturnVector
.push_back(Item
[InventoryPossible
? Chosen
- 1 : Chosen
]);
5270 if (Flags
& SELECT_PAIR
&& ReturnVector
[0]->HandleInPairs()) {
5271 item
*PairEquipment
= GetPairEquipment(ReturnVector
[0]->GetEquipmentIndex());
5272 if (PairEquipment
&& PairEquipment
->CanBePiledWith(ReturnVector
[0], this)) ReturnVector
.push_back(PairEquipment
);
5277 if (!GetStack()->SortedItems(this, Sorter
)) return false;
5278 game::ClearItemDrawVector();
5279 GetStack()->DrawContents(ReturnVector
, this, Topic
, Flags
, Sorter
);
5285 truth
character::EquipsSomething (sorter Sorter
) {
5286 for (int c
= 0; c
< GetEquipments(); ++c
) {
5287 item
*Equipment
= GetEquipment(c
);
5288 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) return true;
5294 material
*character::CreateBodyPartMaterial (int, sLong Volume
) const {
5295 return MAKE_MATERIAL(GetFleshMaterial(), Volume
);
5299 truth
character::CheckTalk () {
5301 ADD_MESSAGE("This monster does not know the art of talking.");
5308 truth
character::MoveTowardsHomePos () {
5309 if (HomeDataIsValid() && IsEnabled()) {
5310 SetGoingTo(HomeData
->Pos
);
5311 return MoveTowardsTarget(false) || (!GetPos().IsAdjacent(HomeData
->Pos
) && MoveRandomly());
5317 truth
character::TryToChangeEquipment (stack
*MainStack
, stack
*SecStack
, int Chosen
) {
5318 if (!GetBodyPartOfEquipment(Chosen
)) {
5319 ADD_MESSAGE("Bodypart missing!");
5322 item
*OldEquipment
= GetEquipment(Chosen
);
5323 if (!IsPlayer() && BoundToUse(OldEquipment
, Chosen
)) {
5324 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE
), OldEquipment
->CHAR_NAME(DEFINITE
));
5327 if (OldEquipment
) OldEquipment
->MoveTo(MainStack
);
5328 sorter Sorter
= EquipmentSorter(Chosen
);
5329 if (!MainStack
->SortedItems(this, Sorter
) && (!SecStack
|| !SecStack
->SortedItems(this, Sorter
))) {
5330 ADD_MESSAGE("You haven't got any item that could be used for this purpose.");
5333 game::DrawEverythingNoBlit();
5334 itemvector ItemVector
;
5335 int Return
= MainStack
->DrawContents(ItemVector
, SecStack
, this,
5336 CONST_S("Choose ") + GetEquipmentName(Chosen
) + ':',
5337 SecStack
? CONST_S("Items in your inventory") : CONST_S(""),
5338 SecStack
? festring(CONST_S("Items in ") + GetPossessivePronoun() + " inventory") : CONST_S(""),
5339 SecStack
? festring(GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState()) : CONST_S(""),
5340 GetVerbalBurdenStateColor(),
5341 NONE_AS_CHOICE
|NO_MULTI_SELECT
,
5343 if (Return
== ESCAPED
) {
5345 OldEquipment
->RemoveFromSlot();
5346 SetEquipment(Chosen
, OldEquipment
);
5350 item
*Item
= ItemVector
.empty() ? 0 : ItemVector
[0];
5352 if (!IsPlayer() && !AllowEquipment(Item
, Chosen
)) {
5353 ADD_MESSAGE("%s refuses to equip %s.", CHAR_DESCRIPTION(DEFINITE
), Item
->CHAR_NAME(DEFINITE
));
5356 Item
->RemoveFromSlot();
5357 SetEquipment(Chosen
, Item
);
5358 if (CheckIfEquipmentIsNotUsable(Chosen
)) Item
->MoveTo(MainStack
); // small bug?
5360 return Item
!= OldEquipment
;
5364 void character::PrintBeginParasitizedMessage () const {
5365 if (IsPlayer()) ADD_MESSAGE("You feel you are no longer alone.");
5369 void character::PrintEndParasitizedMessage () const {
5370 if (IsPlayer()) ADD_MESSAGE("A feeling of sLong welcome emptiness overwhelms you.");
5374 void character::ParasitizedHandler () {
5376 if (!(RAND() % 250)) {
5377 if (IsPlayer()) ADD_MESSAGE("Ugh. You feel something violently carving its way through your intestines.");
5378 ReceiveDamage(0, 1, POISON
, TORSO
, 8, false, false, false, false);
5379 CheckDeath(CONST_S("killed by a vile parasite"), 0);
5384 truth
character::CanFollow () const {
5385 return CanMove() && !StateIsActivated(PANIC
) && !IsStuck();
5389 festring
character::GetKillName () const {
5390 if (!GetPolymorphBackup()) return GetName(INDEFINITE
);
5392 GetPolymorphBackup()->AddName(KillName
, INDEFINITE
);
5393 KillName
<< " polymorphed into ";
5394 id::AddName(KillName
, INDEFINITE
);
5399 festring
character::GetPanelName () const {
5401 Name
<< AssignedName
<< " the " << game::GetVerbalPlayerAlignment() << ' ';
5402 id::AddName(Name
, UNARTICLED
);
5407 sLong
character::GetMoveAPRequirement (int Difficulty
) const {
5408 return (!StateIsActivated(PANIC
) ? 10000000 : 8000000) * Difficulty
/ (APBonus(GetAttribute(AGILITY
)) * GetMoveEase());
5412 bodypart
*character::HealHitPoint() {
5413 int NeedHeal
= 0, NeedHealIndex
[MAX_BODYPARTS
];
5414 for (int c
= 0; c
< BodyParts
; ++c
) {
5415 bodypart
*BodyPart
= GetBodyPart(c
);
5416 if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < BodyPart
->GetMaxHP()) NeedHealIndex
[NeedHeal
++] = c
;
5419 bodypart
*BodyPart
= GetBodyPart(NeedHealIndex
[RAND() % NeedHeal
]);
5420 BodyPart
->IncreaseHP();
5428 void character::CreateHomeData () {
5429 HomeData
= new homedata
;
5430 lsquare
*Square
= GetLSquareUnder();
5431 HomeData
->Pos
= Square
->GetPos();
5432 HomeData
->Dungeon
= Square
->GetDungeonIndex();
5433 HomeData
->Level
= Square
->GetLevelIndex();
5434 HomeData
->Room
= Square
->GetRoomIndex();
5438 room
*character::GetHomeRoom() const {
5439 if (HomeDataIsValid() && HomeData
->Room
) return GetLevel()->GetRoom(HomeData
->Room
);
5444 void character::RemoveHomeData () {
5450 void character::AddESPConsumeMessage () const {
5451 if (IsPlayer()) ADD_MESSAGE("You feel a strange mental activity.");
5455 void character::SetBodyPart (int I
, bodypart
*What
) {
5456 BodyPartSlot
[I
].PutInItem(What
);
5458 What
->SignalPossibleUsabilityChange();
5460 AddOriginalBodyPartID(I
, What
->GetID());
5461 if (What
->GetMainMaterial()->IsInfectedByLeprosy()) GainIntrinsic(LEPROSY
);
5462 else if (StateIsActivated(LEPROSY
)) What
->GetMainMaterial()->SetIsInfectedByLeprosy(true);
5467 truth
character::ConsumeItem (item
*Item
, cfestring
&ConsumeVerb
) {
5468 if (IsPlayer() && HasHadBodyPart(Item
) && !game::TruthQuestion(CONST_S("Are you sure? You may be able to put it back... [y/N]")))
5470 if (Item
->IsOnGround() && GetRoom() && !GetRoom()->ConsumeItem(this, Item
, 1))
5472 if (IsPlayer()) ADD_MESSAGE("You begin %s %s.", ConsumeVerb
.CStr(), Item
->CHAR_NAME(DEFINITE
));
5473 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins %s %s.", CHAR_NAME(DEFINITE
), ConsumeVerb
.CStr(), Item
->CHAR_NAME(DEFINITE
));
5474 consume
*Consume
= consume::Spawn(this);
5475 Consume
->SetDescription(ConsumeVerb
);
5476 Consume
->SetConsumingID(Item
->GetID());
5483 truth
character::CheckThrow () const {
5485 ADD_MESSAGE("This monster type cannot throw.");
5492 void character::GetHitByExplosion (const explosion
*Explosion
, int Damage
) {
5493 int DamageDirection
= GetPos() == Explosion
->Pos
? RANDOM_DIR
: game::CalculateRoughDirection(GetPos() - Explosion
->Pos
);
5494 if (!IsPet() && Explosion
->Terrorist
&& Explosion
->Terrorist
->IsPet()) Explosion
->Terrorist
->Hostility(this);
5495 GetTorso()->SpillBlood((8 - Explosion
->Size
+ RAND() % (8 - Explosion
->Size
)) >> 1);
5496 v2 SpillPos
= GetPos() + game::GetMoveVector(DamageDirection
);
5497 if (GetArea()->IsValidPos(SpillPos
)) GetTorso()->SpillBlood((8-Explosion
->Size
+RAND()%(8-Explosion
->Size
))>>1, SpillPos
);
5498 if (IsPlayer()) ADD_MESSAGE("You are hit by the explosion!");
5499 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is hit by the explosion.", CHAR_NAME(DEFINITE
));
5500 truth WasUnconscious
= GetAction() && GetAction()->IsUnconsciousness();
5501 ReceiveDamage(Explosion
->Terrorist
, Damage
>> 1, FIRE
, ALL
, DamageDirection
, true, false, false, false);
5503 ReceiveDamage(Explosion
->Terrorist
, Damage
>> 1, PHYSICAL_DAMAGE
, ALL
, DamageDirection
, true, false, false, false);
5504 CheckDeath(Explosion
->DeathMsg
, Explosion
->Terrorist
, !WasUnconscious
? IGNORE_UNCONSCIOUSNESS
: 0);
5509 void character::SortAllItems (const sortdata
&SortData
) {
5510 GetStack()->SortAllItems(SortData
);
5511 doforequipmentswithparam
<const sortdata
&>()(this, &item::SortAllItems
, SortData
);
5515 void character::PrintBeginSearchingMessage () const {
5516 if (IsPlayer()) ADD_MESSAGE("You feel you can now notice even the very smallest details around you.");
5520 void character::PrintEndSearchingMessage () const {
5521 if (IsPlayer()) ADD_MESSAGE("You feel less perceptive.");
5525 void character::SearchingHandler () {
5526 if (!game::IsInWilderness()) Search(15);
5530 void character::Search (int Perception
) {
5531 for (int d
= 0; d
< GetExtendedNeighbourSquares(); ++d
) {
5532 lsquare
*LSquare
= GetNeighbourLSquare(d
);
5533 if (LSquare
) LSquare
->GetStack()->Search(this, Min(Perception
, 200));
5538 // surprisingly returns 0 if fails
5539 character
*character::GetRandomNeighbour (int RelationFlags
) const {
5540 character
*Chars
[MAX_NEIGHBOUR_SQUARES
];
5542 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
5543 lsquare
*LSquare
= GetNeighbourLSquare(d
);
5545 character
*Char
= LSquare
->GetCharacter();
5546 if (Char
&& (GetRelation(Char
) & RelationFlags
)) Chars
[Index
++] = Char
;
5549 return Index
? Chars
[RAND() % Index
] : 0;
5553 void character::ResetStates () {
5554 for (int c
= 0; c
< STATES
; ++c
) {
5555 if (1 << c
!= POLYMORPHED
&& TemporaryStateIsActivated(1 << c
) && TemporaryStateCounter
[c
] != PERMANENT
) {
5556 TemporaryState
&= ~(1 << c
);
5557 if (StateData
[c
].EndHandler
) {
5558 (this->*StateData
[c
].EndHandler
)();
5559 if (!IsEnabled())return;
5566 void characterdatabase::InitDefaults (const characterprototype
*NewProtoType
, int NewConfig
) {
5568 ProtoType
= NewProtoType
;
5574 void character::PrintBeginGasImmunityMessage () const {
5575 if (IsPlayer()) ADD_MESSAGE("All smells fade away.");
5579 void character::PrintEndGasImmunityMessage () const {
5580 if (IsPlayer()) ADD_MESSAGE("Yuck! The world smells bad again.");
5584 void character::ShowAdventureInfo () const {
5585 static const char *lists
[4][4] = {
5586 { "Show massacre history",
5588 "Show message history",
5591 "Show message history",
5594 { "Show message history",
5598 { "Show massacre history",
5599 "Show message history",
5603 // massacre, inventory, messages
5604 static const int nums
[4][3] = {
5611 if (GetStack()->GetItems()) {
5612 idx
= game::MassacreListsEmpty() ? 1 : 0;
5614 idx
= game::MassacreListsEmpty() ? 2 : 3;
5618 sel
= game::ListSelectorArray(sel
, CONST_S("Do you want to see some funny history?"), lists
[idx
]);
5620 if (sel
== nums
[idx
][0] && !game::MassacreListsEmpty()) {
5621 game::DisplayMassacreLists();
5623 if (sel
== nums
[idx
][1] && GetStack()->GetItems()) {
5624 GetStack()->DrawContents(this, CONST_S("Your inventory"), NO_SELECT
);
5625 for(stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) i
->DrawContents(this);
5626 doforequipmentswithparam
<ccharacter
*>()(this, &item::DrawContents
, this);
5628 if (sel
== nums
[idx
][2]) {
5629 msgsystem::DrawMessageHistory();
5635 truth
character::EditAllAttributes (int Amount
) {
5636 if (!Amount
) return true;
5638 truth MayEditMore
= false;
5639 for (c
= 0; c
< BodyParts
; ++c
) {
5640 bodypart
*BodyPart
= GetBodyPart(c
);
5641 if (BodyPart
&& BodyPart
->EditAllAttributes(Amount
)) MayEditMore
= true;
5643 for (c
= 0; c
< BASE_ATTRIBUTES
; ++c
) {
5644 if (BaseExperience
[c
]) {
5645 BaseExperience
[c
] += Amount
* EXP_MULTIPLIER
;
5646 LimitRef(BaseExperience
[c
], MIN_EXP
, MAX_EXP
);
5647 if ((Amount
< 0 && BaseExperience
[c
] != MIN_EXP
) || (Amount
> 0 && BaseExperience
[c
] != MAX_EXP
)) MayEditMore
= true;
5654 game::SendLOSUpdateRequest();
5657 if (IsPlayerKind()) UpdatePictures();
5663 void character::AddAttributeInfo (festring
&Entry
) const {
5665 Entry
<< GetAttribute(ENDURANCE
);
5667 Entry
<< GetAttribute(PERCEPTION
);
5669 Entry
<< GetAttribute(INTELLIGENCE
);
5671 Entry
<< GetAttribute(WISDOM
);
5673 Entry
<< GetAttribute(CHARISMA
);
5675 Entry
<< GetAttribute(MANA
);
5679 void character::AddDefenceInfo (felist
&List
) const {
5681 for (int c
= 0; c
< BodyParts
; ++c
) {
5682 bodypart
*BodyPart
= GetBodyPart(c
);
5684 Entry
= CONST_S(" ");
5685 BodyPart
->AddName(Entry
, UNARTICLED
);
5687 Entry
<< BodyPart
->GetMaxHP();
5689 Entry
<< BodyPart
->GetTotalResistance(PHYSICAL_DAMAGE
);
5690 List
.AddEntry(Entry
, LIGHT_GRAY
);
5696 void character::DetachBodyPart () {
5697 ADD_MESSAGE("You haven't got any extra bodyparts.");
5702 void character::ReceiveHolyBanana (sLong Amount
) {
5704 EditExperience(ARM_STRENGTH
, Amount
, 1 << 13);
5705 EditExperience(LEG_STRENGTH
, Amount
, 1 << 13);
5706 EditExperience(DEXTERITY
, Amount
, 1 << 13);
5707 EditExperience(AGILITY
, Amount
, 1 << 13);
5708 EditExperience(ENDURANCE
, Amount
, 1 << 13);
5709 EditExperience(PERCEPTION
, Amount
, 1 << 13);
5710 EditExperience(INTELLIGENCE
, Amount
, 1 << 13);
5711 EditExperience(WISDOM
, Amount
, 1 << 13);
5712 EditExperience(CHARISMA
, Amount
, 1 << 13);
5717 void character::AddHolyBananaConsumeEndMessage () const {
5718 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
5719 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE
));
5723 void character::ReceiveHolyMango (sLong Amount
) {
5725 EditExperience(ARM_STRENGTH
, Amount
, 1 << 13);
5726 EditExperience(LEG_STRENGTH
, Amount
, 1 << 13);
5727 EditExperience(DEXTERITY
, Amount
, 1 << 13);
5728 EditExperience(AGILITY
, Amount
, 1 << 13);
5729 EditExperience(ENDURANCE
, Amount
, 1 << 13);
5730 EditExperience(PERCEPTION
, Amount
, 1 << 13);
5731 EditExperience(INTELLIGENCE
, Amount
, 1 << 13);
5732 EditExperience(WISDOM
, Amount
, 1 << 13);
5733 EditExperience(CHARISMA
, Amount
, 1 << 13);
5738 void character::AddHolyMangoConsumeEndMessage () const {
5739 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
5740 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE
));
5744 truth
character::PreProcessForBone () {
5745 if (IsPet() && IsEnabled()) {
5746 Die(0, CONST_S(""), FORBID_REINCARNATION
);
5749 if (GetAction()) GetAction()->Terminate(false);
5750 if (TemporaryStateIsActivated(POLYMORPHED
)) {
5751 character
*PolymorphBackup
= GetPolymorphBackup();
5753 PolymorphBackup
->PreProcessForBone();
5756 if (MustBeRemovedFromBone()) return false;
5757 if (IsUnique() && !CanBeGenerated()) game::SignalQuestMonsterFound();
5761 GetStack()->PreProcessForBone();
5762 doforequipments()(this, &item::PreProcessForBone
);
5763 doforbodyparts()(this, &bodypart::PreProcessForBone
);
5764 game::RemoveCharacterID(ID
);
5766 game::AddCharacterID(this, ID
);
5771 truth
character::PostProcessForBone (double &DangerSum
, int& Enemies
) {
5772 if (PostProcessForBone()) {
5773 if (GetRelation(PLAYER
) == HOSTILE
) {
5774 double Danger
= GetRelativeDanger(PLAYER
, true);
5775 if (Danger
> 99.0) game::SetTooGreatDangerFound(true);
5776 else if (!IsUnique() && !IgnoreDanger()) {
5777 DangerSum
+= Danger
;
5787 truth
character::PostProcessForBone () {
5788 feuLong NewID
= game::CreateNewCharacterID(this);
5789 game::GetBoneCharacterIDMap().insert(std::make_pair(-ID
, NewID
));
5790 game::RemoveCharacterID(ID
);
5792 if (IsUnique() && CanBeGenerated()) {
5793 if (DataBase
->Flags
& HAS_BEEN_GENERATED
) return false;
5796 GetStack()->PostProcessForBone();
5797 doforequipments()(this, &item::PostProcessForBone
);
5798 doforbodyparts()(this, &bodypart::PostProcessForBone
);
5803 void character::FinalProcessForBone () {
5805 GetStack()->FinalProcessForBone();
5806 doforequipments()(this, &item::FinalProcessForBone
);
5808 for (c
= 0; c
< BodyParts
; ++c
) {
5809 for (std::list
<feuLong
>::iterator i
= OriginalBodyPartID
[c
].begin(); i
!= OriginalBodyPartID
[c
].end();) {
5810 boneidmap::iterator BI
= game::GetBoneItemIDMap().find(*i
);
5811 if (BI
== game::GetBoneItemIDMap().end()) {
5812 std::list
<feuLong
>::iterator Dirt
= i
++;
5813 OriginalBodyPartID
[c
].erase(Dirt
);
5823 void character::SetSoulID (feuLong What
) {
5824 if (GetPolymorphBackup()) GetPolymorphBackup()->SetSoulID(What
);
5828 truth
character::SearchForItem (citem
*Item
) const {
5829 if (combineequipmentpredicateswithparam
<feuLong
>()(this, &item::HasID
, Item
->GetID(), 1)) return true;
5830 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (*i
== Item
) return true;
5835 item
*character::SearchForItem (const sweaponskill
*SWeaponSkill
) const {
5836 for (int c
= 0; c
< GetEquipments(); ++c
) {
5837 item
*Equipment
= GetEquipment(c
);
5838 if (Equipment
&& SWeaponSkill
->IsSkillOf(Equipment
)) return Equipment
;
5840 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (SWeaponSkill
->IsSkillOf(*i
)) return *i
;
5845 void character::PutNear (v2 Pos
) {
5846 v2 NewPos
= game::GetCurrentLevel()->GetNearestFreeSquare(this, Pos
, false);
5847 if (NewPos
== ERROR_V2
) {
5848 do { NewPos
= game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos
== Pos
);
5854 void character::PutToOrNear (v2 Pos
) {
5855 if (game::IsInWilderness() || (CanMoveOn(game::GetCurrentLevel()->GetLSquare(Pos
)) && IsFreeForMe(game::GetCurrentLevel()->GetLSquare(Pos
))))
5862 void character::PutTo (v2 Pos
) {
5863 SquareUnder
[0] = game::GetCurrentArea()->GetSquare(Pos
);
5864 SquareUnder
[0]->AddCharacter(this);
5868 void character::Remove () {
5869 SquareUnder
[0]->RemoveCharacter();
5874 void character::SendNewDrawRequest () const {
5875 for (int c
= 0; c
< SquaresUnder
; ++c
) {
5876 square
*Square
= GetSquareUnder(c
);
5877 if (Square
) Square
->SendNewDrawRequest();
5882 truth
character::IsOver (v2 Pos
) const {
5883 for (int c
= 0; c
< SquaresUnder
; ++c
) {
5884 square
*Square
= GetSquareUnder(c
);
5885 if (Square
&& Square
->GetPos() == Pos
) return true;
5891 truth
character::CanTheoreticallyMoveOn (const lsquare
*LSquare
) const { return GetMoveType() & LSquare
->GetTheoreticalWalkability(); }
5892 truth
character::CanMoveOn (const lsquare
*LSquare
) const { return GetMoveType() & LSquare
->GetWalkability(); }
5893 truth
character::CanMoveOn (const square
*Square
) const { return GetMoveType() & Square
->GetSquareWalkability(); }
5894 truth
character::CanMoveOn (const olterrain
*OLTerrain
) const { return GetMoveType() & OLTerrain
->GetWalkability(); }
5895 truth
character::CanMoveOn (const oterrain
*OTerrain
) const { return GetMoveType() & OTerrain
->GetWalkability(); }
5896 truth
character::IsFreeForMe(square
*Square
) const { return !Square
->GetCharacter() || Square
->GetCharacter() == this; }
5897 void character::LoadSquaresUnder () { SquareUnder
[0] = game::GetSquareInLoad(); }
5899 truth
character::AttackAdjacentEnemyAI () {
5900 if (!IsEnabled()) return false;
5901 character
*Char
[MAX_NEIGHBOUR_SQUARES
];
5902 v2 Pos
[MAX_NEIGHBOUR_SQUARES
];
5903 int Dir
[MAX_NEIGHBOUR_SQUARES
];
5905 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
5906 square
*Square
= GetNeighbourSquare(d
);
5908 character
*Enemy
= Square
->GetCharacter();
5909 if (Enemy
&& (GetRelation(Enemy
) == HOSTILE
|| StateIsActivated(CONFUSED
))) {
5911 Pos
[Index
] = Square
->GetPos();
5912 Char
[Index
++] = Enemy
;
5917 int ChosenIndex
= RAND() % Index
;
5918 Hit(Char
[ChosenIndex
], Pos
[ChosenIndex
], Dir
[ChosenIndex
]);
5925 void character::SignalStepFrom (lsquare
**OldSquareUnder
) {
5927 lsquare
*NewSquareUnder
[MAX_SQUARES_UNDER
];
5928 for (c
= 0; c
< GetSquaresUnder(); ++c
) NewSquareUnder
[c
] = GetLSquareUnder(c
);
5929 for (c
= 0; c
< GetSquaresUnder(); ++c
) {
5930 if (IsEnabled() && GetLSquareUnder(c
) == NewSquareUnder
[c
]) NewSquareUnder
[c
]->StepOn(this, OldSquareUnder
);
5935 int character::GetSumOfAttributes () const {
5936 return GetAttribute(ENDURANCE
) + GetAttribute(PERCEPTION
) + GetAttribute(INTELLIGENCE
) + GetAttribute(WISDOM
) + GetAttribute(CHARISMA
) + GetAttribute(ARM_STRENGTH
) + GetAttribute(AGILITY
);
5940 void character::IntelligenceAction (int Difficulty
) {
5941 EditAP(-20000 * Difficulty
/ APBonus(GetAttribute(INTELLIGENCE
)));
5942 EditExperience(INTELLIGENCE
, Difficulty
* 15, 1 << 7);
5946 struct walkabilitycontroller
{
5947 static truth
Handler (int x
, int y
) {
5948 return x
>= 0 && y
>= 0 && x
< LevelXSize
&& y
< LevelYSize
&& Map
[x
][y
]->GetTheoreticalWalkability() & MoveType
;
5950 static lsquare
***Map
;
5951 static int LevelXSize
, LevelYSize
;
5952 static int MoveType
;
5956 lsquare
***walkabilitycontroller::Map
;
5957 int walkabilitycontroller::LevelXSize
, walkabilitycontroller::LevelYSize
;
5958 int walkabilitycontroller::MoveType
;
5961 truth
character::CreateRoute () {
5963 if (GetAttribute(INTELLIGENCE
) >= 10 && !StateIsActivated(CONFUSED
)) {
5965 walkabilitycontroller::Map
= GetLevel()->GetMap();
5966 walkabilitycontroller::LevelXSize
= GetLevel()->GetXSize();
5967 walkabilitycontroller::LevelYSize
= GetLevel()->GetYSize();
5968 walkabilitycontroller::MoveType
= GetMoveType();
5970 for (int c
= 0; c
< game::GetTeams(); ++c
)
5971 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
5972 character
*Char
= *i
;
5973 if (Char
->IsEnabled() && !Char
->Route
.empty() && (Char
->GetMoveType()&GetMoveType()) == Char
->GetMoveType()) {
5974 v2 CharGoingTo
= Char
->Route
[0];
5975 v2 iPos
= Char
->Route
.back();
5976 if ((GoingTo
-CharGoingTo
).GetLengthSquare() <= 100 && (Pos
- iPos
).GetLengthSquare() <= 100 &&
5977 mapmath
<walkabilitycontroller
>::DoLine(CharGoingTo
.X
, CharGoingTo
.Y
, GoingTo
.X
, GoingTo
.Y
, SKIP_FIRST
) &&
5978 mapmath
<walkabilitycontroller
>::DoLine(Pos
.X
, Pos
.Y
, iPos
.X
, iPos
.Y
, SKIP_FIRST
)) {
5979 if (!Illegal
.empty() && Illegal
.find(Char
->Route
.back()) != Illegal
.end()) continue;
5980 Node
= GetLevel()->FindRoute(CharGoingTo
, GoingTo
, Illegal
, GetMoveType());
5981 if (Node
) { while(Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
5982 else { Route
.clear(); continue; }
5983 Route
.insert(Route
.end(), Char
->Route
.begin(), Char
->Route
.end());
5984 Node
= GetLevel()->FindRoute(Pos
, iPos
, Illegal
, GetMoveType());
5985 if (Node
) { while (Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
5986 else { Route
.clear(); continue; }
5987 IntelligenceAction(1);
5992 Node
= GetLevel()->FindRoute(Pos
, GoingTo
, Illegal
, GetMoveType());
5993 if (Node
) { while(Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
5994 else TerminateGoingTo();
5995 IntelligenceAction(5);
6002 void character::SetGoingTo (v2 What
) {
6003 if (GoingTo
!= What
) {
6011 void character::TerminateGoingTo () {
6018 truth
character::CheckForFood (int Radius
) {
6019 if (StateIsActivated(PANIC
) || !UsesNutrition() || !IsEnabled()) return false;
6022 for (int r
= 1; r
<= Radius
; ++r
) {
6025 for (y
= Pos
.Y
-r
; y
<= Pos
.Y
+r
; ++y
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6028 if (x
< GetLevel()->GetXSize()) {
6029 for (y
= Pos
.Y
-r
; y
<= Pos
.Y
+r
; ++y
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6033 for (x
= Pos
.X
-r
; x
<= Pos
.X
+r
; ++x
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6036 if (y
< GetLevel()->GetYSize()) {
6037 for (x
= Pos
.X
-r
; x
<= Pos
.X
+r
; ++x
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6044 truth
character::CheckForFoodInSquare (v2 Pos
) {
6045 level
*Level
= GetLevel();
6046 if (Level
->IsValidPos(Pos
)) {
6047 lsquare
*Square
= Level
->GetLSquare(Pos
);
6048 stack
*Stack
= Square
->GetStack();
6049 if (Stack
->GetItems()) {
6050 for (stackiterator i
= Stack
->GetBottom(); i
.HasItem(); ++i
) {
6051 if (i
->IsPickable(this) && i
->CanBeSeenBy(this) && i
->CanBeEatenByAI(this) && (!Square
->GetRoomIndex() || Square
->GetRoom()->AllowFoodSearch())) {
6053 return MoveTowardsTarget(false);
6062 void character::SetConfig (int NewConfig
, int SpecialFlags
) {
6063 databasecreator
<character
>::InstallDataBase(this, NewConfig
);
6066 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdatePictures();
6070 truth
character::IsOver (citem
*Item
) const {
6071 for (int c1
= 0; c1
< Item
->GetSquaresUnder(); ++c1
)
6072 for (int c2
= 0; c2
< SquaresUnder
; ++c2
)
6073 if (Item
->GetPos(c1
) == GetPos(c2
)) return true;
6078 truth
character::CheckConsume (cfestring
&Verb
) const {
6079 if (!UsesNutrition()) {
6080 if (IsPlayer()) ADD_MESSAGE("In this form you can't and don't need to %s.", Verb
.CStr());
6087 void character::PutTo (lsquare
*To
) {
6088 PutTo(To
->GetPos());
6092 double character::RandomizeBabyExperience (double SumE
) {
6093 if (!SumE
) return 0;
6094 double E
= (SumE
/ 4) - (SumE
/ 32) + (double(RAND()) / MAX_RAND
) * (SumE
/ 16 + 1);
6095 return Limit(E
, MIN_EXP
, MAX_EXP
);
6099 liquid
*character::CreateBlood (sLong Volume
) const {
6100 return liquid::Spawn(GetBloodMaterial(), Volume
);
6104 void character::SpillFluid (character
*Spiller
, liquid
*Liquid
, int SquareIndex
) {
6105 sLong ReserveVolume
= Liquid
->GetVolume() >> 1;
6106 Liquid
->EditVolume(-ReserveVolume
);
6107 GetStack()->SpillFluid(Spiller
, Liquid
, sLong(Liquid
->GetVolume() * sqrt(double(GetStack()->GetVolume()) / GetVolume())));
6108 Liquid
->EditVolume(ReserveVolume
);
6110 sLong Modifier
[MAX_BODYPARTS
], ModifierSum
= 0;
6111 for (c
= 0; c
< BodyParts
; ++c
) {
6112 if (GetBodyPart(c
)) {
6113 Modifier
[c
] = sLong(sqrt(GetBodyPart(c
)->GetVolume()));
6114 if (Modifier
[c
]) Modifier
[c
] *= 1 + (RAND() & 3);
6115 ModifierSum
+= Modifier
[c
];
6120 for (c
= 1; c
< GetBodyParts(); ++c
) {
6121 if (GetBodyPart(c
) && IsEnabled())
6122 GetBodyPart(c
)->SpillFluid(Spiller
, Liquid
->SpawnMoreLiquid(Liquid
->GetVolume() * Modifier
[c
] / ModifierSum
), SquareIndex
);
6125 Liquid
->SetVolume(Liquid
->GetVolume() * Modifier
[TORSO_INDEX
] / ModifierSum
);
6126 GetTorso()->SpillFluid(Spiller
, Liquid
, SquareIndex
);
6131 void character::StayOn (liquid
*Liquid
) {
6132 Liquid
->TouchEffect(this, TORSO_INDEX
);
6136 truth
character::IsAlly (ccharacter
*Char
) const {
6137 return Char
->GetTeam()->GetID() == GetTeam()->GetID();
6141 void character::ResetSpoiling () {
6142 doforbodyparts()(this, &bodypart::ResetSpoiling
);
6146 item
*character::SearchForItem (ccharacter
*Char
, sorter Sorter
) const {
6147 item
*Equipment
= findequipment
<ccharacter
*>()(this, Sorter
, Char
);
6148 if (Equipment
) return Equipment
;
6149 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (((*i
)->*Sorter
)(Char
)) return *i
;
6154 truth
character::DetectMaterial (cmaterial
*Material
) const {
6155 return GetStack()->DetectMaterial(Material
) ||
6156 combinebodypartpredicateswithparam
<cmaterial
*>()(this, &bodypart::DetectMaterial
, Material
, 1) ||
6157 combineequipmentpredicateswithparam
<cmaterial
*>()(this, &item::DetectMaterial
, Material
, 1);
6161 truth
character::DamageTypeDestroysBodyPart (int Type
) {
6162 return (Type
&0xFFF) != PHYSICAL_DAMAGE
;
6166 truth
character::CheckIfTooScaredToHit (ccharacter
*Enemy
) const {
6167 if (IsPlayer() && StateIsActivated(PANIC
)) {
6168 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
6169 square
*Square
= GetNeighbourSquare(d
);
6171 if(CanMoveOn(Square
) && (!Square
->GetCharacter() || Square
->GetCharacter()->IsPet())) {
6172 ADD_MESSAGE("You are too scared to attack %s.", Enemy
->CHAR_DESCRIPTION(DEFINITE
));
6182 void character::PrintBeginLevitationMessage () const {
6184 if (IsPlayer()) ADD_MESSAGE("You rise into the air like a small hot-air balloon.");
6185 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins to float.", CHAR_NAME(DEFINITE
));
6190 void character::PrintEndLevitationMessage () const {
6192 if (IsPlayer()) ADD_MESSAGE("You descend gently onto the ground.");
6193 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s drops onto the ground.", CHAR_NAME(DEFINITE
));
6198 truth
character::IsLimbIndex (int I
) {
6200 case RIGHT_ARM_INDEX
:
6201 case LEFT_ARM_INDEX
:
6202 case RIGHT_LEG_INDEX
:
6203 case LEFT_LEG_INDEX
:
6210 void character::EditExperience (int Identifier
, double Value
, double Speed
) {
6211 if (!AllowExperience() || (Identifier
== ENDURANCE
&& UseMaterialAttributes())) return;
6212 int Change
= RawEditExperience(BaseExperience
[Identifier
], GetNaturalExperience(Identifier
), Value
, Speed
);
6213 if (!Change
) return;
6214 cchar
*PlayerMsg
= 0, *NPCMsg
= 0;
6215 switch (Identifier
) {
6218 PlayerMsg
= "You feel tougher than anything!";
6219 if (IsPet()) NPCMsg
= "Suddenly %s looks tougher.";
6221 PlayerMsg
= "You feel less healthy.";
6222 if (IsPet()) NPCMsg
= "Suddenly %s looks less healthy.";
6224 CalculateBodyPartMaxHPs();
6225 CalculateMaxStamina();
6230 PlayerMsg
= "You now see the world in much better detail than before.";
6232 PlayerMsg
= "You feel very guru.";
6233 game::GetGod(VALPURUS
)->AdjustRelation(100);
6235 game::SendLOSUpdateRequest();
6240 if (Change
> 0) PlayerMsg
= "Suddenly the inner structure of the Multiverse around you looks quite simple.";
6241 else PlayerMsg
= "It surely is hard to think today.";
6244 if (IsPlayerKind()) UpdatePictures();
6248 if (Change
> 0) PlayerMsg
= "You feel your life experience increasing all the time.";
6249 else PlayerMsg
= "You feel like having done something unwise.";
6251 if (IsPlayerKind()) UpdatePictures();
6255 PlayerMsg
= "You feel very confident of your social skills.";
6257 if (GetAttribute(CHARISMA
) <= 15) NPCMsg
= "%s looks less ugly.";
6258 else NPCMsg
= "%s looks more attractive.";
6261 PlayerMsg
= "You feel somehow disliked.";
6263 if (GetAttribute(CHARISMA
) < 15) NPCMsg
= "%s looks more ugly.";
6264 else NPCMsg
= "%s looks less attractive.";
6267 if (IsPlayerKind()) UpdatePictures();
6271 PlayerMsg
= "You feel magical forces coursing through your body!";
6272 NPCMsg
= "You notice an odd glow around %s.";
6274 PlayerMsg
= "You feel your magical abilities withering slowly.";
6275 NPCMsg
= "You notice strange vibrations in the air around %s. But they disappear rapidly.";
6280 if (IsPlayer()) ADD_MESSAGE("%s", PlayerMsg
);
6281 else if (NPCMsg
&& CanBeSeenByPlayer()) ADD_MESSAGE(NPCMsg
, CHAR_NAME(DEFINITE
));
6283 CalculateBattleInfo();
6287 int character::RawEditExperience (double &Exp
, double NaturalExp
, double Value
, double Speed
) const {
6288 double OldExp
= Exp
;
6293 if(!OldExp
|| !Value
|| (Value
> 0 && OldExp
>= NaturalExp
* (100 + Value
) / 100) ||
6294 (Value
< 0 && OldExp
<= NaturalExp
* (100 + Value
) / 100)) return 0;
6295 if (!IsPlayer()) Speed
*= 1.5;
6296 Exp
+= (NaturalExp
* (100 + Value
) - 100 * OldExp
) * Speed
* EXP_DIVISOR
;
6297 LimitRef(Exp
, MIN_EXP
, MAX_EXP
);
6298 int NewA
= int(Exp
* EXP_DIVISOR
);
6299 int OldA
= int(OldExp
* EXP_DIVISOR
);
6300 int Delta
= NewA
- OldA
;
6301 if (Delta
> 0) Exp
= Max(Exp
, (NewA
+ 0.05) * EXP_MULTIPLIER
);
6302 else if (Delta
< 0) Exp
= Min(Exp
, (NewA
+ 0.95) * EXP_MULTIPLIER
);
6303 LimitRef(Exp
, MIN_EXP
, MAX_EXP
);
6308 int character::GetAttribute (int Identifier
, truth AllowBonus
) const {
6309 int A
= int(BaseExperience
[Identifier
] * EXP_DIVISOR
);
6310 if (AllowBonus
&& Identifier
== INTELLIGENCE
&& BrainsHurt()) return Max((A
+ AttributeBonus
[INTELLIGENCE
]) / 3, 1);
6311 return A
&& AllowBonus
? Max(A
+ AttributeBonus
[Identifier
], 1) : A
;
6315 void characterdatabase::PostProcess () {
6316 double AM
= (100 + AttributeBonus
) * EXP_MULTIPLIER
/ 100;
6317 for (int c
= 0; c
< ATTRIBUTES
; ++c
) NaturalExperience
[c
] = this->*ExpPtr
[c
] * AM
;
6321 void character::EditDealExperience (sLong Price
) {
6322 EditExperience(CHARISMA
, sqrt(Price
) / 5, 1 << 9);
6326 void character::PrintBeginLeprosyMessage () const {
6327 if (IsPlayer()) ADD_MESSAGE("You feel you're falling in pieces.");
6331 void character::PrintEndLeprosyMessage () const {
6332 if (IsPlayer()) ADD_MESSAGE("You feel your limbs are stuck in place tightly."); // CHANGE OR DIE
6336 void character::TryToInfectWithLeprosy (ccharacter
*Infector
) {
6337 if (!IsImmuneToLeprosy() &&
6338 ((GetRelation(Infector
) == HOSTILE
&& !RAND_N(50 * GetAttribute(ENDURANCE
))) ||
6339 !RAND_N(500 * GetAttribute(ENDURANCE
)))) GainIntrinsic(LEPROSY
);
6343 void character::SignalGeneration () {
6344 const_cast<database
*>(DataBase
)->Flags
|= HAS_BEEN_GENERATED
;
6348 void character::CheckIfSeen () {
6349 if (IsPlayer() || CanBeSeenByPlayer()) SignalSeen();
6353 void character::SignalSeen () {
6354 if (!(WarnFlags
& WARNED
) && GetRelation(PLAYER
) == HOSTILE
) {
6355 double Danger
= GetRelativeDanger(PLAYER
);
6357 game::SetDangerFound(Max(game::GetDangerFound(), Danger
));
6358 if (Danger
> 500.0 && !(WarnFlags
& HAS_CAUSED_PANIC
)) {
6359 WarnFlags
|= HAS_CAUSED_PANIC
;
6360 game::SetCausePanicFlag(true);
6362 WarnFlags
|= WARNED
;
6365 const_cast<database
*>(DataBase
)->Flags
|= HAS_BEEN_SEEN
;
6369 int character::GetPolymorphIntelligenceRequirement () const {
6370 if (DataBase
->PolymorphIntelligenceRequirement
== DEPENDS_ON_ATTRIBUTES
) return Max(GetAttributeAverage() - 5, 0);
6371 return DataBase
->PolymorphIntelligenceRequirement
;
6375 void character::RemoveAllItems () {
6376 GetStack()->Clean();
6377 for (int c
= 0; c
< GetEquipments(); ++c
) {
6378 item
*Equipment
= GetEquipment(c
);
6380 Equipment
->RemoveFromSlot();
6381 Equipment
->SendToHell();
6387 int character::CalculateWeaponSkillHits (ccharacter
*Enemy
) const {
6388 if (Enemy
->IsPlayer()) {
6389 configid
ConfigID(GetType(), GetConfig());
6390 const dangerid
& DangerID
= game::GetDangerMap().find(ConfigID
)->second
;
6391 return Min(int(DangerID
.EquippedDanger
* 2000), 1000);
6393 return Min(int(GetRelativeDanger(Enemy
, true) * 2000), 1000);
6397 truth
character::CanUseEquipment (int I
) const {
6398 return CanUseEquipment() && I
< GetEquipments() && GetBodyPartOfEquipment(I
) && EquipmentIsAllowed(I
);
6402 /* Target mustn't have any equipment */
6403 void character::DonateEquipmentTo (character
*Character
) {
6405 feuLong
*EquipmentMemory
= game::GetEquipmentMemory();
6406 for (int c
= 0; c
< MAX_EQUIPMENT_SLOTS
; ++c
) {
6407 item
*Item
= GetEquipment(c
);
6409 if (Character
->CanUseEquipment(c
)) {
6410 Item
->RemoveFromSlot();
6411 Character
->SetEquipment(c
, Item
);
6413 EquipmentMemory
[c
] = Item
->GetID();
6414 Item
->MoveTo(Character
->GetStack());
6416 } else if (CanUseEquipment(c
)) {
6417 EquipmentMemory
[c
] = 0;
6418 } else if (EquipmentMemory
[c
] && Character
->CanUseEquipment(c
)) {
6419 for (stackiterator i
= Character
->GetStack()->GetBottom(); i
.HasItem(); ++i
) {
6420 if (i
->GetID() == EquipmentMemory
[c
]) {
6422 Item
->RemoveFromSlot();
6423 Character
->SetEquipment(c
, Item
);
6427 EquipmentMemory
[c
] = 0;
6431 for (int c
= 0; c
< GetEquipments(); ++c
) {
6432 item
*Item
= GetEquipment(c
);
6434 if (Character
->CanUseEquipment(c
)) {
6435 Item
->RemoveFromSlot();
6436 Character
->SetEquipment(c
, Item
);
6438 Item
->MoveTo(Character
->GetStackUnder());
6446 void character::ReceivePeaSoup (sLong
) {
6447 lsquare
*Square
= GetLSquareUnder();
6448 if (Square
->IsFlyable()) Square
->AddSmoke(gas::Spawn(FART
, 250));
6452 void character::AddPeaSoupConsumeEndMessage () const {
6454 if (CanHear()) ADD_MESSAGE("Mmmh! The soup is very tasty. You hear a small puff.");
6455 else ADD_MESSAGE("Mmmh! The soup is very tasty.");
6456 } else if (CanBeSeenByPlayer() && PLAYER
->CanHear()) {
6458 ADD_MESSAGE("You hear a small puff.");
6463 void character::CalculateMaxStamina () {
6464 MaxStamina
= TorsoIsAlive() ? GetAttribute(ENDURANCE
) * 10000 : 0;
6468 void character::EditStamina (int Amount
, truth CanCauseUnconsciousness
) {
6469 if (!TorsoIsAlive()) return;
6470 int UnconsciousnessStamina
= MaxStamina
>> 3;
6471 if (!CanCauseUnconsciousness
&& Amount
< 0) {
6472 if (Stamina
> UnconsciousnessStamina
) {
6474 if (Stamina
< UnconsciousnessStamina
) Stamina
= UnconsciousnessStamina
;
6478 int OldStamina
= Stamina
;
6480 if (Stamina
> MaxStamina
) {
6481 Stamina
= MaxStamina
;
6482 } else if (Stamina
< 0) {
6484 LoseConsciousness(250 + RAND_N(250));
6485 } else if (IsPlayer()) {
6486 if (OldStamina
>= MaxStamina
>> 2 && Stamina
< MaxStamina
>> 2) {
6487 ADD_MESSAGE("You are getting a little tired.");
6488 } else if(OldStamina
>= UnconsciousnessStamina
&& Stamina
< UnconsciousnessStamina
) {
6489 ADD_MESSAGE("You are seriously out of breath!");
6490 game::SetPlayerIsRunning(false);
6493 if (IsPlayer() && StateIsActivated(PANIC
) && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6497 void character::RegenerateStamina () {
6498 if (GetTirednessState() != UNTIRED
) {
6499 EditExperience(ENDURANCE
, 50, 1);
6500 if (Sweats() && TorsoIsAlive() && !RAND_N(30) && !game::IsInWilderness()) {
6501 sLong Volume
= sLong(0.05 * sqrt(GetBodyVolume()));
6502 if (GetTirednessState() == FAINTING
) Volume
<<= 1;
6503 for (int c
= 0; c
< SquaresUnder
; ++c
) GetLSquareUnder(c
)->SpillFluid(0, CreateSweat(Volume
), false, false);
6508 if (Action
->IsRest()) {
6509 if (SquaresUnder
== 1) {
6510 Bonus
= GetSquareUnder()->GetRestModifier() << 1;
6512 int Lowest
= GetSquareUnder(0)->GetRestModifier();
6513 for (int c
= 1; c
< GetSquaresUnder(); ++c
) {
6514 int Mod
= GetSquareUnder(c
)->GetRestModifier();
6515 if (Mod
< Lowest
) Lowest
= Mod
;
6517 Bonus
= Lowest
<< 1;
6519 } else if (Action
->IsUnconsciousness()) Bonus
= 2;
6522 switch (GetBurdenState()) {
6523 case OVER_LOADED
: Plus1
= 25; break;
6524 case STRESSED
: Plus1
= 50; break;
6525 case BURDENED
: Plus1
= 75; break;
6529 switch (GetHungerState()) {
6530 case STARVING
: Plus2
= 25; break;
6531 case VERY_HUNGRY
: Plus2
= 50; break;
6532 case HUNGRY
: Plus2
= 75; break;
6535 Stamina
+= Plus1
* Plus2
* Bonus
/ 1000;
6536 if (Stamina
> MaxStamina
) Stamina
= MaxStamina
;
6537 if (IsPlayer() && StateIsActivated(PANIC
) && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6541 void character::BeginPanic () {
6542 if (IsPlayer() && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6543 DeActivateVoluntaryAction();
6547 void character::EndPanic () {
6548 if (IsPlayer()) game::SetPlayerIsRunning(false);
6552 int character::GetTirednessState () const {
6553 if (Stamina
>= MaxStamina
>> 2) return UNTIRED
;
6554 if (Stamina
>= MaxStamina
>> 3) return EXHAUSTED
;
6559 void character::ReceiveBlackUnicorn (sLong Amount
) {
6560 if (!(RAND() % 160)) game::DoEvilDeed(Amount
/ 50);
6561 BeginTemporaryState(TELEPORT
, Amount
/ 100);
6562 for (int c
= 0; c
< STATES
; ++c
) {
6563 if (StateData
[c
].Flags
& DUR_TEMPORARY
) {
6564 BeginTemporaryState(1 << c
, Amount
/ 100);
6565 if (!IsEnabled()) return;
6566 } else if (StateData
[c
].Flags
& DUR_PERMANENT
) {
6567 GainIntrinsic(1 << c
);
6568 if (!IsEnabled()) return;
6574 void character::ReceiveGrayUnicorn (sLong Amount
) {
6575 if (!(RAND() % 80)) game::DoEvilDeed(Amount
/ 50);
6576 BeginTemporaryState(TELEPORT
, Amount
/ 100);
6577 for (int c
= 0; c
< STATES
; ++c
) {
6578 if (1 << c
!= TELEPORT
) {
6579 DecreaseStateCounter(1 << c
, -Amount
/ 100);
6580 if (!IsEnabled()) return;
6586 void character::ReceiveWhiteUnicorn (sLong Amount
) {
6587 if (!(RAND() % 40)) game::DoEvilDeed(Amount
/ 50);
6588 BeginTemporaryState(TELEPORT
, Amount
/ 100);
6589 DecreaseStateCounter(LYCANTHROPY
, -Amount
/ 100);
6590 DecreaseStateCounter(POISONED
, -Amount
/ 100);
6591 DecreaseStateCounter(PARASITIZED
, -Amount
/ 100);
6592 DecreaseStateCounter(LEPROSY
, -Amount
/ 100);
6593 DecreaseStateCounter(VAMPIRISM
, -Amount
/ 100);
6597 /* Counter should be negative. Removes intrinsics. */
6598 void character::DecreaseStateCounter (sLong State
, int Counter
) {
6600 for (Index
= 0; Index
< STATES
; ++Index
) if (1 << Index
== State
) break;
6601 if (Index
== STATES
) ABORT("DecreaseTemporaryStateCounter works only when State == 2Â ^ n!");
6602 if (TemporaryState
& State
) {
6603 if (TemporaryStateCounter
[Index
] == PERMANENT
|| (TemporaryStateCounter
[Index
] += Counter
) <= 0) {
6604 TemporaryState
&= ~State
;
6605 if (!(EquipmentState
& State
)) {
6606 if (StateData
[Index
].EndHandler
) {
6607 (this->*StateData
[Index
].EndHandler
)();
6608 if (!IsEnabled()) return;
6610 (this->*StateData
[Index
].PrintEndMessage
)();
6617 truth
character::IsImmuneToLeprosy () const {
6618 return DataBase
->IsImmuneToLeprosy
|| UseMaterialAttributes();
6622 void character::LeprosyHandler () {
6623 EditExperience(ARM_STRENGTH
, -25, 1 << 1);
6624 EditExperience(LEG_STRENGTH
, -25, 1 << 1);
6625 EditExperience(DEXTERITY
, -25, 1 << 1);
6626 EditExperience(AGILITY
, -25, 1 << 1);
6627 EditExperience(ENDURANCE
, -25, 1 << 1);
6628 EditExperience(CHARISMA
, -25, 1 << 1);
6629 CheckDeath(CONST_S("killed by leprosy"));
6633 bodypart
*character::SearchForOriginalBodyPart (int I
) const {
6634 for (stackiterator i1
= GetStackUnder()->GetBottom(); i1
.HasItem(); ++i1
) {
6635 for (std::list
<feuLong
>::iterator i2
= OriginalBodyPartID
[I
].begin(); i2
!= OriginalBodyPartID
[I
].end(); ++i2
)
6636 if (i1
->GetID() == *i2
) return static_cast<bodypart
*>(*i1
);
6642 void character::SetLifeExpectancy (int Base
, int RandPlus
) {
6644 for (c
= 0; c
< BodyParts
; ++c
) {
6645 bodypart
*BodyPart
= GetBodyPart(c
);
6646 if (BodyPart
) BodyPart
->SetLifeExpectancy(Base
, RandPlus
);
6648 for (c
= 0; c
< GetEquipments(); ++c
) {
6649 item
*Equipment
= GetEquipment(c
);
6650 if (Equipment
) Equipment
->SetLifeExpectancy(Base
, RandPlus
);
6655 /* Receiver should be a fresh duplicate of this */
6656 void character::DuplicateEquipment (character
*Receiver
, feuLong Flags
) {
6657 for (int c
= 0; c
< GetEquipments(); ++c
) {
6658 item
*Equipment
= GetEquipment(c
);
6660 item
*Duplicate
= Equipment
->Duplicate(Flags
);
6661 Receiver
->SetEquipment(c
, Duplicate
);
6667 void character::Disappear (corpse
*Corpse
, cchar
*Verb
, truth (item::*ClosePredicate
)() const) {
6668 truth TorsoDisappeared
= false;
6669 truth CanBeSeen
= Corpse
? Corpse
->CanBeSeenByPlayer() : IsPlayer() || CanBeSeenByPlayer();
6671 if ((GetTorso()->*ClosePredicate
)()) {
6673 if (Corpse
) ADD_MESSAGE("%s %ss.", Corpse
->CHAR_NAME(DEFINITE
), Verb
);
6674 else if (IsPlayer()) ADD_MESSAGE("You %s.", Verb
);
6675 else ADD_MESSAGE("%s %ss.", CHAR_NAME(DEFINITE
), Verb
);
6677 TorsoDisappeared
= true;
6678 for (c
= 0; c
< GetEquipments(); ++c
) {
6679 item
*Equipment
= GetEquipment(c
);
6680 if (Equipment
&& (Equipment
->*ClosePredicate
)()) {
6681 Equipment
->RemoveFromSlot();
6682 Equipment
->SendToHell();
6685 itemvector ItemVector
;
6686 GetStack()->FillItemVector(ItemVector
);
6687 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
6688 if (ItemVector
[c
] && (ItemVector
[c
]->*ClosePredicate
)()) {
6689 ItemVector
[c
]->RemoveFromSlot();
6690 ItemVector
[c
]->SendToHell();
6694 for (c
= 1; c
< GetBodyParts(); ++c
) {
6695 bodypart
*BodyPart
= GetBodyPart(c
);
6697 if ((BodyPart
->*ClosePredicate
)()) {
6698 if (!TorsoDisappeared
&& CanBeSeen
) {
6699 if(IsPlayer()) ADD_MESSAGE("Your %s %ss.", GetBodyPartName(c
).CStr(), Verb
);
6700 else ADD_MESSAGE("The %s of %s %ss.", GetBodyPartName(c
).CStr(), CHAR_NAME(DEFINITE
), Verb
);
6702 BodyPart
->DropEquipment();
6703 item
*BodyPart
= SevereBodyPart(c
);
6704 if (BodyPart
) BodyPart
->SendToHell();
6705 } else if (TorsoDisappeared
) {
6706 BodyPart
->DropEquipment();
6707 item
*BodyPart
= SevereBodyPart(c
);
6709 if (Corpse
) Corpse
->GetSlot()->AddFriendItem(BodyPart
);
6710 else if (!game::IsInWilderness()) GetStackUnder()->AddItem(BodyPart
);
6711 else BodyPart
->SendToHell();
6716 if (TorsoDisappeared
) {
6718 Corpse
->RemoveFromSlot();
6719 Corpse
->SendToHell();
6721 CheckDeath(festring(Verb
) + "ed", 0, FORCE_DEATH
|DISALLOW_CORPSE
|DISALLOW_MSG
);
6724 CheckDeath(festring(Verb
) + "ed", 0, DISALLOW_MSG
);
6729 void character::SignalDisappearance () {
6730 if (GetMotherEntity()) GetMotherEntity()->SignalDisappearance();
6731 else Disappear(0, "disappear", &item::IsVeryCloseToDisappearance
);
6735 truth
character::HornOfFearWorks () const {
6736 return CanHear() && GetPanicLevel() > RAND() % 33;
6740 void character::BeginLeprosy () {
6741 doforbodypartswithparam
<truth
>()(this, &bodypart::SetIsInfectedByLeprosy
, true);
6745 void character::EndLeprosy () {
6746 doforbodypartswithparam
<truth
>()(this, &bodypart::SetIsInfectedByLeprosy
, false);
6750 truth
character::IsSameAs (ccharacter
*What
) const {
6751 return What
->GetType() == GetType() && What
->GetConfig() == GetConfig();
6755 feuLong
character::GetCommandFlags () const {
6756 return !StateIsActivated(PANIC
) ? CommandFlags
: CommandFlags
|FLEE_FROM_ENEMIES
;
6760 feuLong
character::GetConstantCommandFlags () const {
6761 return !StateIsActivated(PANIC
) ? DataBase
->ConstantCommandFlags
: DataBase
->ConstantCommandFlags
|FLEE_FROM_ENEMIES
;
6765 feuLong
character::GetPossibleCommandFlags () const {
6766 int Int
= GetAttribute(INTELLIGENCE
);
6767 feuLong Flags
= ALL_COMMAND_FLAGS
;
6768 if (!CanMove() || Int
< 4) Flags
&= ~FOLLOW_LEADER
;
6769 if (!CanMove() || Int
< 6) Flags
&= ~FLEE_FROM_ENEMIES
;
6770 if (!CanUseEquipment() || Int
< 8) Flags
&= ~DONT_CHANGE_EQUIPMENT
;
6771 if (!UsesNutrition() || Int
< 8) Flags
&= ~DONT_CONSUME_ANYTHING_VALUABLE
;
6776 truth
character::IsRetreating () const {
6777 return StateIsActivated(PANIC
) || (CommandFlags
& FLEE_FROM_ENEMIES
&& IsPet());
6781 truth
character::ChatMenu () {
6782 if (GetAction() && !GetAction()->CanBeTalkedTo()) {
6783 ADD_MESSAGE("%s is silent.", CHAR_DESCRIPTION(DEFINITE
));
6784 PLAYER
->EditAP(-200);
6787 feuLong ManagementFlags
= GetManagementFlags();
6788 if (ManagementFlags
== CHAT_IDLY
|| !IsPet()) return ChatIdly();
6789 static cchar
*const ChatMenuEntry
[CHAT_MENU_ENTRIES
] = {
6796 static const petmanagementfunction PMF
[CHAT_MENU_ENTRIES
] = {
6797 &character::ChangePetEquipment
,
6798 &character::TakePetItems
,
6799 &character::GivePetItems
,
6800 &character::IssuePetCommands
,
6801 &character::ChatIdly
6803 felist
List(CONST_S("Choose action:"));
6804 game::SetStandardListAttributes(List
);
6805 List
.AddFlags(SELECTABLE
);
6807 for (c
= 0; c
< CHAT_MENU_ENTRIES
; ++c
) if (1 << c
& ManagementFlags
) List
.AddEntry(ChatMenuEntry
[c
], LIGHT_GRAY
);
6808 int Chosen
= List
.Draw();
6809 if (Chosen
& FELIST_ERROR_BIT
) return false;
6810 for (c
= 0, i
= 0; c
< CHAT_MENU_ENTRIES
; ++c
) {
6811 if (1 << c
& ManagementFlags
&& i
++ == Chosen
) return (this->*PMF
[c
])();
6813 return false; // dummy
6817 truth
character::ChangePetEquipment () {
6818 if (EquipmentScreen(PLAYER
->GetStack(), GetStack())) {
6826 truth
character::TakePetItems () {
6827 truth Success
= false;
6828 stack::SetSelected(0);
6831 game::DrawEverythingNoBlit();
6832 GetStack()->DrawContents(
6836 CONST_S("What do you want to take from ") + CHAR_DESCRIPTION(DEFINITE
) + '?',
6839 GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState(),
6840 GetVerbalBurdenStateColor(),
6842 if (ToTake
.empty()) break;
6843 for (uInt c
= 0; c
< ToTake
.size(); ++c
) ToTake
[c
]->MoveTo(PLAYER
->GetStack());
6844 ADD_MESSAGE("You take %s.", ToTake
[0]->GetName(DEFINITE
, ToTake
.size()).CStr());
6849 PLAYER
->DexterityAction(2);
6855 truth
character::GivePetItems () {
6856 truth Success
= false;
6857 stack::SetSelected(0);
6860 game::DrawEverythingNoBlit();
6861 PLAYER
->GetStack()->DrawContents(
6865 CONST_S("What do you want to give to ") + CHAR_DESCRIPTION(DEFINITE
) + '?',
6868 GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState(),
6869 GetVerbalBurdenStateColor(),
6871 if (ToGive
.empty()) break;
6872 for (uInt c
= 0; c
< ToGive
.size(); ++c
) ToGive
[c
]->MoveTo(GetStack());
6873 ADD_MESSAGE("You give %s to %s.", ToGive
[0]->GetName(DEFINITE
, ToGive
.size()).CStr(), CHAR_DESCRIPTION(DEFINITE
));
6878 PLAYER
->DexterityAction(2);
6884 truth
character::IssuePetCommands () {
6885 if (!IsConscious()) {
6886 ADD_MESSAGE("%s is unconscious.", CHAR_DESCRIPTION(DEFINITE
));
6889 feuLong PossibleC
= GetPossibleCommandFlags();
6891 ADD_MESSAGE("%s cannot be commanded.", CHAR_DESCRIPTION(DEFINITE
));
6894 feuLong OldC
= GetCommandFlags();
6895 feuLong NewC
= OldC
, VaryFlags
= 0;
6896 game::CommandScreen(CONST_S("Issue commands to ")+GetDescription(DEFINITE
), PossibleC
, GetConstantCommandFlags(), VaryFlags
, NewC
);
6897 if (NewC
== OldC
) return false;
6898 SetCommandFlags(NewC
);
6899 PLAYER
->EditAP(-500);
6900 PLAYER
->EditExperience(CHARISMA
, 25, 1 << 7);
6905 truth
character::ChatIdly () {
6906 if (!TryToTalkAboutScience()) {
6908 PLAYER
->EditExperience(CHARISMA
, 75, 1 << 7);
6910 PLAYER
->EditAP(-1000);
6915 truth
character::EquipmentScreen (stack
*MainStack
, stack
*SecStack
) {
6916 if (!CanUseEquipment()) {
6917 ADD_MESSAGE("%s cannot use equipment.", CHAR_DESCRIPTION(DEFINITE
));
6921 truth EquipmentChanged
= false;
6922 felist
List(CONST_S("Equipment menu [ESC exits]"));
6926 List
.EmptyDescription();
6928 List
.AddDescription(CONST_S(""));
6929 List
.AddDescription(festring(GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState()).CapitalizeCopy(), GetVerbalBurdenStateColor());
6931 for (int c
= 0; c
< GetEquipments(); ++c
) {
6932 Entry
= GetEquipmentName(c
);
6935 item
*Equipment
= GetEquipment(c
);
6937 Equipment
->AddInventoryEntry(this, Entry
, 1, true);
6938 AddSpecialEquipmentInfo(Entry
, c
);
6939 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
6940 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
6942 Entry
<< (GetBodyPartOfEquipment(c
) ? "-" : "can't use");
6943 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, game::AddToItemDrawVector(itemvector()));
6946 game::DrawEverythingNoBlit();
6947 game::SetStandardListAttributes(List
);
6948 List
.SetFlags(SELECTABLE
|DRAW_BACKGROUND_AFTERWARDS
);
6949 List
.SetEntryDrawer(game::ItemEntryDrawer
);
6950 Chosen
= List
.Draw();
6951 game::ClearItemDrawVector();
6952 if (Chosen
>= GetEquipments()) break;
6953 EquipmentChanged
= TryToChangeEquipment(MainStack
, SecStack
, Chosen
);
6955 if (EquipmentChanged
) DexterityAction(5);
6956 return EquipmentChanged
;
6960 feuLong
character::GetManagementFlags () const {
6961 feuLong Flags
= ALL_MANAGEMENT_FLAGS
;
6962 if (!CanUseEquipment() || !AllowPlayerToChangeEquipment()) Flags
&= ~CHANGE_EQUIPMENT
;
6963 if (!GetStack()->GetItems()) Flags
&= ~TAKE_ITEMS
;
6964 if (!WillCarryItems()) Flags
&= ~GIVE_ITEMS
;
6965 if (!GetPossibleCommandFlags()) Flags
&= ~ISSUE_COMMANDS
;
6970 cchar
*VerbalBurdenState
[] = { "overloaded", "stressed", "burdened", "unburdened" };
6971 col16 VerbalBurdenStateColor
[] = { RED
, BLUE
, BLUE
, WHITE
};
6973 cchar
*character::GetVerbalBurdenState () const { return VerbalBurdenState
[BurdenState
]; }
6974 col16
character::GetVerbalBurdenStateColor () const { return VerbalBurdenStateColor
[BurdenState
]; }
6975 int character::GetAttributeAverage () const { return GetSumOfAttributes()/7; }
6977 cfestring
&character::GetStandVerb() const {
6978 if (ForceCustomStandVerb()) return DataBase
->StandVerb
;
6979 static festring Hovering
= "hovering";
6980 static festring Swimming
= "swimming";
6981 if (StateIsActivated(LEVITATION
)) return Hovering
;
6982 if (IsSwimming()) return Swimming
;
6983 return DataBase
->StandVerb
;
6987 truth
character::CheckApply () const {
6989 ADD_MESSAGE("This monster type cannot apply.");
6996 void character::EndLevitation () {
6997 if (!IsFlying() && GetSquareUnder()) {
6998 if (!game::IsInWilderness()) SignalStepFrom(0);
6999 if (game::IsInWilderness() || !GetLSquareUnder()->IsFreezed()) TestWalkability();
7004 truth
character::CanMove () const {
7005 return !IsRooted() || StateIsActivated(LEVITATION
);
7009 void character::CalculateEnchantments () {
7010 doforequipments()(this, &item::CalculateEnchantment
);
7011 GetStack()->CalculateEnchantments();
7015 truth
character::GetNewFormForPolymorphWithControl (character
*&NewForm
) {
7016 festring Topic
, Temp
;
7019 festring Temp
= game::DefaultQuestion(CONST_S("What do you want to become? [press '?' for a list]"), game::GetDefaultPolymorphTo(), &game::PolymorphControlKeyHandler
);
7020 NewForm
= protosystem::CreateMonster(Temp
);
7022 if (NewForm
->IsSameAs(this)) {
7024 ADD_MESSAGE("You choose not to polymorph.");
7028 if (PolymorphBackup
&& NewForm
->IsSameAs(PolymorphBackup
)) {
7030 NewForm
= ForceEndPolymorph();
7033 if (NewForm
->GetPolymorphIntelligenceRequirement() > GetAttribute(INTELLIGENCE
) && !game::WizardModeIsActive()) {
7034 ADD_MESSAGE("You feel your mind isn't yet powerful enough to call forth the form of %s.", NewForm
->CHAR_NAME(INDEFINITE
));
7038 NewForm
->RemoveAllItems();
7046 liquid
*character::CreateSweat(sLong Volume
) const {
7047 //return liquid::Spawn(GetSweatMaterial(), Volume);
7048 return liquid::Spawn(GetCurrentSweatMaterial(), Volume
);
7052 truth
character::TeleportRandomItem (truth TryToHinderVisibility
) {
7053 if (IsImmuneToItemTeleport()) return false;
7054 itemvector ItemVector
;
7055 std::vector
<sLong
> PossibilityVector
;
7056 int TotalPossibility
= 0;
7057 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
7058 ItemVector
.push_back(*i
);
7059 int Possibility
= i
->GetTeleportPriority();
7060 if (TryToHinderVisibility
) Possibility
+= i
->GetHinderVisibilityBonus(this);
7061 PossibilityVector
.push_back(Possibility
);
7062 TotalPossibility
+= Possibility
;
7064 for (int c
= 0; c
< GetEquipments(); ++c
) {
7065 item
*Equipment
= GetEquipment(c
);
7067 ItemVector
.push_back(Equipment
);
7068 int Possibility
= Equipment
->GetTeleportPriority();
7069 if (TryToHinderVisibility
) Possibility
+= Equipment
->GetHinderVisibilityBonus(this);
7070 PossibilityVector
.push_back(Possibility
<<= 1);
7071 TotalPossibility
+= Possibility
;
7074 if (!TotalPossibility
) return false;
7075 int Chosen
= femath::WeightedRand(PossibilityVector
, TotalPossibility
);
7076 item
*Item
= ItemVector
[Chosen
];
7077 truth Equipped
= PLAYER
->Equips(Item
);
7078 truth Seen
= Item
->CanBeSeenByPlayer();
7079 Item
->RemoveFromSlot();
7080 if (Seen
) ADD_MESSAGE("%s disappears.", Item
->CHAR_NAME(DEFINITE
));
7081 if (Equipped
) game::AskForEscPress(CONST_S("Equipment lost!"));
7083 int Range
= Item
->GetEmitation() && TryToHinderVisibility
? 25 : 5;
7084 rect
Border(Pos
+ v2(-Range
, -Range
), Pos
+ v2(Range
, Range
));
7085 Pos
= GetLevel()->GetRandomSquare(this, 0, &Border
);
7086 if (Pos
== ERROR_V2
) Pos
= GetLevel()->GetRandomSquare();
7087 GetNearLSquare(Pos
)->GetStack()->AddItem(Item
);
7088 if (Item
->CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", Item
->CHAR_NAME(INDEFINITE
));
7093 truth
character::HasClearRouteTo (v2 Pos
) const {
7094 pathcontroller::Map
= GetLevel()->GetMap();
7095 pathcontroller::Character
= this;
7096 v2 ThisPos
= GetPos();
7097 return mapmath
<pathcontroller
>::DoLine(ThisPos
.X
, ThisPos
.Y
, Pos
.X
, Pos
.Y
, SKIP_FIRST
);
7101 truth
character::IsTransparent () const {
7102 return !IsEnormous() || GetTorso()->GetMainMaterial()->IsTransparent() || StateIsActivated(INVISIBLE
);
7106 void character::SignalPossibleTransparencyChange () {
7107 if (!game::IsInWilderness()) {
7108 for (int c
= 0; c
< SquaresUnder
; ++c
) {
7109 lsquare
*Square
= GetLSquareUnder(c
);
7110 if (Square
) Square
->SignalPossibleTransparencyChange();
7116 int character::GetCursorData () const {
7118 int Color
= game::PlayerIsRunning() ? BLUE_CURSOR
: DARK_CURSOR
;
7119 for (int c
= 0; c
< BodyParts
; ++c
) {
7120 bodypart
*BodyPart
= GetBodyPart(c
);
7121 if (BodyPart
&& BodyPart
->IsUsable()) {
7122 int ConditionColorIndex
= BodyPart
->GetConditionColorIndex();
7123 if ((BodyPartIsVital(c
) && !ConditionColorIndex
) || (ConditionColorIndex
<= 1 && ++Bad
== 2)) return Color
|CURSOR_FLASH
;
7124 } else if (++Bad
== 2) return Color
|CURSOR_FLASH
;
7126 Color
= game::PlayerIsRunning() ? YELLOW_CURSOR
: RED_CURSOR
;
7127 return Bad
? Color
|CURSOR_FLASH
: Color
;
7131 void character::TryToName () {
7132 if (!IsPet()) ADD_MESSAGE("%s refuses to let YOU decide what %s's called.", CHAR_NAME(DEFINITE
), CHAR_PERSONAL_PRONOUN
);
7133 else if (IsPlayer()) ADD_MESSAGE("You can't rename yourself.");
7134 else if (!IsNameable()) ADD_MESSAGE("%s refuses to be called anything else but %s.", CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
));
7136 festring Topic
= CONST_S("What name will you give to ")+GetName(DEFINITE
)+'?';
7137 festring Name
= game::StringQuestion(Topic
, WHITE
, 0, 80, true);
7138 if (Name
.GetSize()) SetAssignedName(Name
);
7143 double character::GetSituationDanger (ccharacter
*Enemy
, v2 ThisPos
, v2 EnemyPos
, truth SeesEnemy
) const {
7145 if (IgnoreDanger() && !IsPlayer()) {
7146 if (Enemy
->IgnoreDanger() && !Enemy
->IsPlayer()) {
7147 Danger
= double(GetHP())*GetHPRequirementForGeneration()/(Enemy
->GetHP()*Enemy
->GetHPRequirementForGeneration());
7150 Danger
= 0.25*GetHPRequirementForGeneration()/Enemy
->GetHP();
7152 } else if (Enemy
->IgnoreDanger() && !Enemy
->IsPlayer()) {
7153 Danger
= 4.0*GetHP()/Enemy
->GetHPRequirementForGeneration();
7155 Danger
= GetRelativeDanger(Enemy
);
7157 Danger
*= 3.0/((EnemyPos
-ThisPos
).GetManhattanLength()+2);
7158 if (!SeesEnemy
) Danger
*= 0.2;
7159 if (StateIsActivated(PANIC
)) Danger
*= 0.2;
7160 Danger
*= double(GetHP())*Enemy
->GetMaxHP()/(Enemy
->GetHP()*GetMaxHP());
7165 void character::ModifySituationDanger (double &Danger
) const {
7166 switch (GetTirednessState()) {
7167 case FAINTING
: Danger
*= 1.5;
7168 case EXHAUSTED
: Danger
*= 1.25;
7170 for (int c
= 0; c
< STATES
; ++c
) {
7171 if (StateIsActivated(1 << c
) && StateData
[c
].SituationDangerModifier
!= 0) (this->*StateData
[c
].SituationDangerModifier
)(Danger
);
7176 void character::LycanthropySituationDangerModifier (double &Danger
) const {
7177 character
*Wolf
= werewolfwolf::Spawn();
7178 double DangerToWolf
= GetRelativeDanger(Wolf
);
7179 Danger
*= pow(DangerToWolf
, 0.1);
7184 void character::PoisonedSituationDangerModifier (double &Danger
) const {
7185 int C
= GetTemporaryStateCounter(POISONED
);
7186 Danger
*= (1+(C
*C
)/(GetHP()*10000.0*(GetGlobalResistance(POISON
)+1)));
7190 void character::PolymorphingSituationDangerModifier (double &Danger
) const {
7191 if (!StateIsActivated(POLYMORPH_CONTROL
)) Danger
*= 1.5;
7195 void character::PanicSituationDangerModifier (double &Danger
) const {
7200 void character::ConfusedSituationDangerModifier (double &Danger
) const {
7205 void character::ParasitizedSituationDangerModifier (double &Danger
) const {
7210 void character::LeprosySituationDangerModifier (double &Danger
) const {
7215 void character::AddRandomScienceName (festring
&String
) const {
7216 festring Science
= GetScienceTalkName().GetRandomElement().CStr();
7217 if (Science
[0] == '!') {
7218 String
<< Science
.CStr()+1;
7221 festring Attribute
= GetScienceTalkAdjectiveAttribute().GetRandomElement();
7223 truth NoAttrib
= Attribute
.IsEmpty(), NoSecondAdjective
= false;
7224 if (!Attribute
.IsEmpty() && Attribute
[0] == '!') {
7225 NoSecondAdjective
= true;
7226 Attribute
.Erase(0, 1);
7228 if (!Science
.Find("the ")) {
7229 Science
.Erase(0, 4);
7230 if (!Attribute
.Find("the ", 0, 4)) Attribute
<< " the"; else Attribute
.Insert(0, "the ", 4);
7232 if (islower(Science
[0]) && Science
.Find(' ') == festring::NPos
&& Science
.Find('-') == festring::NPos
&&
7233 Science
.Find("phobia") == festring::NPos
) {
7234 Prefix
= GetScienceTalkPrefix().GetRandomElement();
7235 if (!Prefix
.IsEmpty() && Science
.Find(Prefix
) != festring::NPos
) Prefix
.Empty();
7237 int L
= Prefix
.GetSize();
7238 if (L
&& Prefix
[L
-1] == Science
[0]) Science
.Erase(0, 1);
7239 if (!NoAttrib
&& !NoSecondAdjective
== !RAND_GOOD(3)) {
7240 int S1
= NoSecondAdjective
? 0 : GetScienceTalkAdjectiveAttribute().Size
;
7241 int S2
= GetScienceTalkSubstantiveAttribute().Size
;
7242 festring OtherAttribute
;
7243 int Chosen
= RAND_GOOD(S1
+S2
);
7244 if (Chosen
< S1
) OtherAttribute
= GetScienceTalkAdjectiveAttribute()[Chosen
];
7245 else OtherAttribute
= GetScienceTalkSubstantiveAttribute()[Chosen
- S1
];
7246 if (!OtherAttribute
.IsEmpty() && OtherAttribute
.Find("the ", 0, 4) && Attribute
.Find(OtherAttribute
) == festring::NPos
) {
7247 String
<< Attribute
<< ' ' << OtherAttribute
<< ' ' << Prefix
<< Science
;
7251 String
<< Attribute
;
7252 if (!NoAttrib
) String
<< ' ';
7253 String
<< Prefix
<< Science
;
7257 truth
character::TryToTalkAboutScience () {
7258 if (GetRelation(PLAYER
) == HOSTILE
||
7259 GetScienceTalkPossibility() <= RAND_GOOD(100) ||
7260 PLAYER
->GetAttribute(INTELLIGENCE
) < GetScienceTalkIntelligenceRequirement() ||
7261 PLAYER
->GetAttribute(WISDOM
) < GetScienceTalkWisdomRequirement() ||
7262 PLAYER
->GetAttribute(CHARISMA
) < GetScienceTalkCharismaRequirement())
7266 AddRandomScienceName(Science
);
7269 AddRandomScienceName(S1
);
7270 AddRandomScienceName(S2
);
7271 if (S1
.Find(S2
) == festring::NPos
&& S2
.Find(S1
) == festring::NPos
) {
7272 switch (RAND_GOOD(3)) {
7273 case 0: Science
= "the relation of "; break;
7274 case 1: Science
= "the differences of "; break;
7275 case 2: Science
= "the similarities of "; break;
7277 Science
<< S1
<< " and " << S2
;
7280 AddRandomScienceName(Science
);
7283 switch ((RAND() + GET_TICK()) % 10) {
7285 ADD_MESSAGE("You have a rather pleasant chat about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7288 ADD_MESSAGE("%s explains a few of %s opinions regarding %s to you.", CHAR_DESCRIPTION(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, Science
.CStr());
7291 ADD_MESSAGE("%s reveals a number of %s insightful views of %s to you.", CHAR_DESCRIPTION(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, Science
.CStr());
7294 ADD_MESSAGE("You exhange some information pertaining to %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7297 ADD_MESSAGE("You engage in a pretty intriguing conversation about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7300 ADD_MESSAGE("You discuss at length about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7303 ADD_MESSAGE("You have a somewhat boring talk concerning %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7306 ADD_MESSAGE("You are drawn into a heated argument regarding %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7309 ADD_MESSAGE("%s delivers a sLong monologue concerning eg. %s.", CHAR_DESCRIPTION(DEFINITE
), Science
.CStr());
7312 ADD_MESSAGE("You dive into a brief but thought-provoking debate over %s with %s", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7315 PLAYER
->EditExperience(INTELLIGENCE
, 1000, 50. * GetScienceTalkIntelligenceModifier() / ++ScienceTalks
);
7316 PLAYER
->EditExperience(WISDOM
, 1000, 50. * GetScienceTalkWisdomModifier() / ++ScienceTalks
);
7317 PLAYER
->EditExperience(CHARISMA
, 1000, 50. * GetScienceTalkCharismaModifier() / ++ScienceTalks
);
7322 truth
character::IsUsingWeaponOfCategory (int Category
) const {
7324 ((GetMainWielded() && GetMainWielded()->GetWeaponCategory() == Category
) ||
7325 (GetSecondaryWielded() && GetSecondaryWielded()->GetWeaponCategory() == Category
));
7329 truth
character::TryToUnStickTraps (v2 Dir
) {
7330 if (!TrapData
) return true;
7331 std::vector
<trapdata
> TrapVector
;
7332 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) TrapVector
.push_back(*TrapData
);
7333 for (uInt c
= 0; c
< TrapVector
.size(); ++c
) {
7335 entity
*Trap
= game::SearchTrap(TrapVector
[c
].TrapID
);
7336 /*k8:??? if(!Trap->Exists()) int esko = esko = 2; */
7337 if (!Trap
->Exists()) continue; /*k8: ??? added by me; what this means? */
7338 if (Trap
->GetVictimID() == GetID() && Trap
->TryToUnStick(this, Dir
)) break;
7341 return !TrapData
&& IsEnabled();
7345 struct trapidcomparer
{
7346 trapidcomparer (feuLong ID
) : ID(ID
) {}
7347 truth
operator () (const trapdata
*T
) const { return T
->TrapID
== ID
; }
7352 void character::RemoveTrap (feuLong ID
) {
7353 trapdata
*&T
= ListFind(TrapData
, trapidcomparer(ID
));
7355 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7359 void character::AddTrap (feuLong ID
, feuLong BodyParts
) {
7360 trapdata
*&T
= ListFind(TrapData
, trapidcomparer(ID
));
7361 if (T
) T
->BodyParts
|= BodyParts
;
7362 else T
= new trapdata(ID
, GetID(), BodyParts
);
7363 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7367 truth
character::IsStuckToTrap (feuLong ID
) const {
7368 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) if (T
->TrapID
== ID
) return true;
7373 void character::RemoveTraps () {
7374 for (trapdata
*T
= TrapData
; T
;) {
7375 entity
*Trap
= game::SearchTrap(T
->TrapID
);
7376 if (Trap
) Trap
->UnStick();
7377 trapdata
*ToDel
= T
;
7382 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7386 void character::RemoveTraps (int BodyPartIndex
) {
7387 feuLong Flag
= 1 << BodyPartIndex
;
7388 for (trapdata
**T
= &TrapData
; *T
;) {
7389 if ((*T
)->BodyParts
& Flag
) {
7390 entity
*Trap
= game::SearchTrap((*T
)->TrapID
);
7391 if (!((*T
)->BodyParts
&= ~Flag
)) {
7392 if (Trap
) Trap
->UnStick();
7393 trapdata
*ToDel
= *T
;
7397 if (Trap
) Trap
->UnStick(BodyPartIndex
);
7405 if (GetBodyPart(BodyPartIndex
)) GetBodyPart(BodyPartIndex
)->SignalPossibleUsabilityChange();
7409 festring
character::GetTrapDescription () const {
7411 std::pair
<entity
*, int> TrapStack
[3];
7413 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) {
7415 entity
*Trap
= game::SearchTrap(T
->TrapID
);
7418 for (c
= 0; c
< Index
; ++c
) if (TrapStack
[c
].first
->GetTrapType() == Trap
->GetTrapType()) ++TrapStack
[c
].second
;
7419 if (c
== Index
) TrapStack
[Index
++] = std::make_pair(Trap
, 1);
7427 TrapStack
[0].first
->AddTrapName(Desc
, TrapStack
[0].second
);
7430 TrapStack
[1].first
->AddTrapName(Desc
, TrapStack
[1].second
);
7431 } else if (Index
== 3) {
7433 TrapStack
[1].first
->AddTrapName(Desc
, TrapStack
[1].second
);
7435 TrapStack
[2].first
->AddTrapName(Desc
, TrapStack
[2].second
);
7438 Desc
<< "lots of traps";
7444 int character::RandomizeHurtBodyPart (feuLong BodyParts
) const {
7445 int BodyPartIndex
[MAX_BODYPARTS
];
7447 for (int c
= 0; c
< GetBodyParts(); ++c
) {
7448 if (1 << c
& BodyParts
) {
7449 /*k8: ??? if(!GetBodyPart(c)) int esko = esko = 2; */
7450 if (!GetBodyPart(c
)) continue;
7451 BodyPartIndex
[Index
++] = c
;
7453 /*k8: ??? if(!Index) int esko = esko = 2;*/
7455 if (!Index
) abort();
7456 return BodyPartIndex
[RAND_N(Index
)];
7460 truth
character::BodyPartIsStuck (int I
) const {
7461 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) if (1 << I
& T
->BodyParts
) return true;
7466 void character::PrintAttribute (cchar
*Desc
, int I
, int PanelPosX
, int PanelPosY
) const {
7467 int Attribute
= GetAttribute(I
);
7468 int NoBonusAttribute
= GetAttribute(I
, false);
7469 col16 C
= game::GetAttributeColor(I
);
7470 festring String
= Desc
;
7472 String
<< Attribute
;
7474 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
* 10), C
, "%s", String
.CStr());
7475 if (Attribute
!= NoBonusAttribute
) {
7476 int Where
= PanelPosX
+ ((String
.GetSize() + 1) << 3);
7477 FONT
->Printf(DOUBLE_BUFFER
, v2(Where
, PanelPosY
* 10), LIGHT_GRAY
, "%d", NoBonusAttribute
);
7482 truth
character::AllowUnconsciousness () const {
7483 return DataBase
->AllowUnconsciousness
&& TorsoIsAlive();
7487 truth
character::CanPanic () const {
7488 return !Action
|| !Action
->IsUnconsciousness();
7492 int character::GetRandomBodyPart (feuLong Possible
) const {
7493 int OKBodyPart
[MAX_BODYPARTS
];
7494 int OKBodyParts
= 0;
7495 for (int c
= 0; c
< BodyParts
; ++c
) if (1 << c
& Possible
&& GetBodyPart(c
)) OKBodyPart
[OKBodyParts
++] = c
;
7496 return OKBodyParts
? OKBodyPart
[RAND_N(OKBodyParts
)] : NONE_INDEX
;
7500 void character::EditNP (sLong What
) {
7501 int OldState
= GetHungerState();
7503 int NewState
= GetHungerState();
7504 if (OldState
> VERY_HUNGRY
&& NewState
== VERY_HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
7505 if (OldState
> STARVING
&& NewState
== STARVING
) DeActivateVoluntaryAction(CONST_S("You are getting extremely hungry."));
7509 truth
character::IsSwimming () const {
7510 return !IsFlying() && GetSquareUnder() && GetSquareUnder()->GetSquareWalkability() & SWIM
;
7514 void character::AddBlackUnicornConsumeEndMessage () const {
7515 if (IsPlayer()) ADD_MESSAGE("You feel dirty and loathsome.");
7519 void character::AddGrayUnicornConsumeEndMessage () const {
7520 if (IsPlayer()) ADD_MESSAGE("You feel neutralized.");
7524 void character::AddWhiteUnicornConsumeEndMessage () const {
7525 if (IsPlayer()) ADD_MESSAGE("You feel purified.");
7529 void character::AddOmmelBoneConsumeEndMessage () const {
7530 if (IsPlayer()) ADD_MESSAGE("You feel the power of all your canine ancestors combining in your body.");
7531 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s looks extremely ferocious. You shudder.", CHAR_NAME(DEFINITE
));
7535 void character::AddLiquidHorrorConsumeEndMessage () const {
7536 if (IsPlayer()) ADD_MESSAGE("Untold horrors flash before your eyes. The melancholy of the world is on your shoulders!");
7537 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks as if the melancholy of the world is on %s shoulders!.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
7541 int character::GetBodyPartSparkleFlags (int) const {
7543 ((GetNaturalSparkleFlags() & SKIN_COLOR
? SPARKLING_A
: 0) |
7544 (GetNaturalSparkleFlags() & TORSO_MAIN_COLOR
? SPARKLING_B
: 0) |
7545 (GetNaturalSparkleFlags() & TORSO_SPECIAL_COLOR
? SPARKLING_D
: 0));
7549 truth
character::IsAnimated () const {
7550 return combinebodypartpredicates()(this, &bodypart::IsAnimated
, 1);
7554 double character::GetNaturalExperience (int Identifier
) const {
7555 return DataBase
->NaturalExperience
[Identifier
];
7559 truth
character::HasBodyPart (sorter Sorter
) const {
7560 if (Sorter
== 0) return true;
7561 return combinebodypartpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1);
7565 truth
character::PossessesItem (sorter Sorter
) const {
7566 if (Sorter
== 0) return true;
7568 (GetStack()->SortedItems(this, Sorter
) ||
7569 combinebodypartpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1) ||
7570 combineequipmentpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1));
7575 cchar
*character::GetRunDescriptionLine (int I
) const {
7576 if (!GetRunDescriptionLineOne().IsEmpty()) return !I
? GetRunDescriptionLineOne().CStr() : GetRunDescriptionLineTwo().CStr();
7577 if (IsFlying()) return !I
? "Flying" : "very fast";
7578 if (IsSwimming()) return !I
? "Swimming" : "very fast";
7579 return !I
? "Running" : "";
7583 void character::VomitAtRandomDirection (int Amount
) {
7584 if (game::IsInWilderness()) return;
7585 /* Lacks support of multitile monsters */
7588 for (int d
= 0; d
< 9; ++d
) {
7589 lsquare
*Square
= GetLSquareUnder()->GetNeighbourLSquare(d
);
7590 if (Square
&& !Square
->VomitingIsDangerous(this)) Possible
[Index
++] = Square
->GetPos();
7592 if (Index
) Vomit(Possible
[RAND_N(Index
)], Amount
);
7593 else Vomit(GetPos(), Amount
);
7597 void character::RemoveLifeSavers () {
7598 for (int c
= 0; c
< GetEquipments(); ++c
) {
7599 item
*Equipment
= GetEquipment(c
);
7600 if (Equipment
&& Equipment
->IsInCorrectSlot(c
) && Equipment
->GetGearStates() & LIFE_SAVED
) {
7601 Equipment
->SendToHell();
7602 Equipment
->RemoveFromSlot();
7608 ccharacter
*character::FindCarrier () const {
7609 return this; //check
7613 void character::PrintBeginHiccupsMessage () const {
7614 if (IsPlayer()) ADD_MESSAGE("Your diaphragm is spasming vehemently.");
7618 void character::PrintEndHiccupsMessage () const {
7619 if (IsPlayer()) ADD_MESSAGE("You feel your annoying hiccoughs have finally subsided.");
7623 void character::HiccupsHandler () {
7625 if (!(RAND() % 2000)) {
7626 if (IsPlayer()) ADD_MESSAGE("");
7627 else if (CanBeSeenByPlayer()) ADD_MESSAGE("");
7628 else if ((PLAYER->GetPos()-GetPos()).GetLengthSquare() <= 400) ADD_MESSAGE("");
7629 game::CallForAttention(GetPos(), 400);
7635 void character::VampirismHandler () {
7636 //EditExperience(ARM_STRENGTH, -25, 1 << 1);
7637 //EditExperience(LEG_STRENGTH, -25, 1 << 1);
7638 //EditExperience(DEXTERITY, -25, 1 << 1);
7639 //EditExperience(AGILITY, -25, 1 << 1);
7640 //EditExperience(ENDURANCE, -25, 1 << 1);
7641 EditExperience(CHARISMA
, -25, 1 << 1);
7642 EditExperience(WISDOM
, -25, 1 << 1);
7643 EditExperience(INTELLIGENCE
, -25, 1 << 1);
7644 CheckDeath(CONST_S("killed by vampirism"));
7648 void character::HiccupsSituationDangerModifier (double &Danger
) const {
7653 void character::VampirismSituationDangerModifier (double &Danger
) const {
7654 character
*Vampire
= vampire::Spawn();
7655 double DangerToVampire
= GetRelativeDanger(Vampire
);
7656 Danger
*= pow(DangerToVampire
, 0.1);
7661 bool character::IsConscious () const {
7662 return !Action
|| !Action
->IsUnconsciousness();
7666 wsquare
*character::GetNearWSquare (v2 Pos
) const {
7667 return static_cast<wsquare
*>(GetSquareUnder()->GetArea()->GetSquare(Pos
));
7671 wsquare
*character::GetNearWSquare (int x
, int y
) const {
7672 return static_cast<wsquare
*>(GetSquareUnder()->GetArea()->GetSquare(x
, y
));
7676 void character::ForcePutNear (v2 Pos
) {
7677 /* GUM SOLUTION!!! */
7678 v2 NewPos
= game::GetCurrentLevel()->GetNearestFreeSquare(PLAYER
, Pos
, false);
7679 if (NewPos
== ERROR_V2
) do { NewPos
= game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos
== Pos
);
7684 void character::ReceiveMustardGas (int BodyPart
, sLong Volume
) {
7685 if (Volume
) GetBodyPart(BodyPart
)->AddFluid(liquid::Spawn(MUSTARD_GAS_LIQUID
, Volume
), CONST_S("skin"), 0, true);
7689 void character::ReceiveMustardGasLiquid (int BodyPartIndex
, sLong Modifier
) {
7690 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
7691 if (BodyPart
->GetMainMaterial()->GetInteractionFlags() & IS_AFFECTED_BY_MUSTARD_GAS
) {
7692 sLong Tries
= Modifier
;
7693 Modifier
-= Tries
; //opt%?
7695 for (sLong c
= 0; c
< Tries
; ++c
) if (!(RAND() % 100)) ++Damage
;
7696 if (Modifier
&& !(RAND() % 1000 / Modifier
)) ++Damage
;
7698 feuLong Minute
= game::GetTotalMinutes();
7699 if (GetLastAcidMsgMin() != Minute
&& (CanBeSeenByPlayer() || IsPlayer())) {
7700 SetLastAcidMsgMin(Minute
);
7701 if (IsPlayer()) ADD_MESSAGE("Mustard gas dissolves the skin of your %s.", BodyPart
->GetBodyPartName().CStr());
7702 else ADD_MESSAGE("Mustard gas dissolves %s.", CHAR_NAME(DEFINITE
));
7704 ReceiveBodyPartDamage(0, Damage
, MUSTARD_GAS_DAMAGE
, BodyPartIndex
, YOURSELF
, false, false, false);
7705 CheckDeath(CONST_S("killed by a fatal exposure to mustard gas"));
7711 truth
character::IsBadPath (v2 Pos
) const {
7712 if (!IsGoingSomeWhere()) return false;
7713 v2 TPos
= !Route
.empty() ? Route
.back() : GoingTo
;
7714 return ((TPos
- Pos
).GetManhattanLength() > (TPos
- GetPos()).GetManhattanLength());
7718 double &character::GetExpModifierRef (expid E
) {
7719 return ExpModifierMap
.insert(std::make_pair(E
, 1.)).first
->second
;
7723 /* Should probably do more. Now only makes Player forget gods */
7724 truth
character::ForgetRandomThing () {
7726 /* hopefully this code isn't some where else */
7727 std::vector
<god
*> Known
;
7728 for (int c
= 1; c
<= GODS
; ++c
) if (game::GetGod(c
)->IsKnown()) Known
.push_back(game::GetGod(c
));
7729 if (Known
.empty()) return false;
7730 int RandomGod
= RAND_N(Known
.size());
7731 Known
.at(RAND_N(Known
.size()))->SetIsKnown(false);
7732 ADD_MESSAGE("You forget how to pray to %s.", Known
.at(RandomGod
)->GetName());
7739 int character::CheckForBlock (character
*Enemy
, item
*Weapon
, double ToHitValue
, int Damage
, int Success
, int Type
) {
7744 void character::ApplyAllGodsKnownBonus () {
7745 stack
*AddPlace
= GetStackUnder();
7746 if (game::IsInWilderness()) AddPlace
= GetStack(); else AddPlace
= GetStackUnder();
7747 pantheonbook
*NewBook
= pantheonbook::Spawn();
7748 AddPlace
->AddItem(NewBook
);
7749 ADD_MESSAGE("\"MORTAL! BEHOLD THE HOLY SAGA\"");
7750 ADD_MESSAGE("%s materializes near your feet.", NewBook
->CHAR_NAME(INDEFINITE
));
7754 void character::ReceiveSirenSong (character
*Siren
) {
7755 if (Siren
->GetTeam() == GetTeam()) return;
7757 if (IsPlayer()) ADD_MESSAGE("The beautiful melody of %s makes you feel sleepy.", Siren
->CHAR_NAME(DEFINITE
));
7758 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The beautiful melody of %s makes %s look sleepy.", Siren
->CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
)); /*k8*/
7759 Stamina
-= (1 + RAND_N(4)) * 10000;
7762 if (!IsPlayer() && IsCharmable() && !RAND_N(5)) {
7763 ChangeTeam(Siren
->GetTeam());
7764 ADD_MESSAGE("%s seems to be totally brainwashed by %s melodies.", CHAR_NAME(DEFINITE
), Siren
->CHAR_NAME(DEFINITE
));
7768 item
*What
= GiveMostExpensiveItem(Siren
);
7771 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
);
7773 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
);
7776 if (IsPlayer()) ADD_MESSAGE("You would like to give something to %s.", Siren
->CHAR_NAME(DEFINITE
));
7783 // return 0, if no item found
7784 item
*character::FindMostExpensiveItem () const {
7786 item
*MostExpensive
= 0;
7787 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
7788 if ((*i
)->GetPrice() > MaxPrice
) {
7789 MaxPrice
= (*i
)->GetPrice();
7790 MostExpensive
= (*i
);
7793 for (int c
= 0; c
< GetEquipments(); ++c
) {
7794 item
*Equipment
= GetEquipment(c
);
7795 if (Equipment
&& Equipment
->GetPrice() > MaxPrice
) {
7796 MaxPrice
= Equipment
->GetPrice();
7797 MostExpensive
= Equipment
;
7800 return MostExpensive
;
7804 // returns 0 if no items available
7805 item
*character::GiveMostExpensiveItem(character
*ToWhom
) {
7806 item
*ToGive
= FindMostExpensiveItem();
7807 if (!ToGive
) return 0;
7808 truth Equipped
= PLAYER
->Equips(ToGive
);
7809 ToGive
->RemoveFromSlot();
7810 if (Equipped
) game::AskForEscPress(CONST_S("Equipment lost!"));
7811 ToWhom
->ReceiveItemAsPresent(ToGive
);
7817 void character::ReceiveItemAsPresent (item
*Present
) {
7818 if (TestForPickup(Present
)) GetStack()->AddItem(Present
); else GetStackUnder()->AddItem(Present
);
7822 /* returns 0 if no enemies in sight */
7823 character
*character::GetNearestEnemy () const {
7824 character
*NearestEnemy
= 0;
7825 sLong NearestEnemyDistance
= 0x7FFFFFFF;
7827 for (int c
= 0; c
< game::GetTeams(); ++c
) {
7828 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
7829 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
7830 if ((*i
)->IsEnabled()) {
7831 sLong ThisDistance
= Max
<sLong
>(abs((*i
)->GetPos().X
- Pos
.X
), abs((*i
)->GetPos().Y
- Pos
.Y
));
7832 if ((ThisDistance
< NearestEnemyDistance
|| (ThisDistance
== NearestEnemyDistance
&& !(RAND() % 3))) && (*i
)->CanBeSeenBy(this)) {
7834 NearestEnemyDistance
= ThisDistance
;
7840 return NearestEnemy
;
7844 truth
character::MindWormCanPenetrateSkull (mindworm
*) const {
7849 truth
character::CanTameWithDulcis (const character
*Tamer
) const {
7850 int TamingDifficulty
= GetTamingDifficulty();
7851 if (TamingDifficulty
== NO_TAMING
) return false;
7852 if (GetAttachedGod() == DULCIS
) return true;
7853 int Modifier
= Tamer
->GetAttribute(WISDOM
) + Tamer
->GetAttribute(CHARISMA
);
7854 if (Tamer
->IsPlayer()) Modifier
+= game::GetGod(DULCIS
)->GetRelation() / 20;
7855 else if (Tamer
->GetAttachedGod() == DULCIS
) Modifier
+= 50;
7856 if (TamingDifficulty
== 0) {
7857 if (!IgnoreDanger()) TamingDifficulty
= int(10 * GetRelativeDanger(Tamer
));
7858 else TamingDifficulty
= 10 * GetHPRequirementForGeneration()/Max(Tamer
->GetHP(), 1);
7860 return Modifier
>= TamingDifficulty
* 3;
7864 truth
character::CanTameWithLyre (const character
*Tamer
) const {
7865 int TamingDifficulty
= GetTamingDifficulty();
7866 if (TamingDifficulty
== NO_TAMING
) return false;
7867 if (TamingDifficulty
== 0) {
7868 if (!IgnoreDanger()) TamingDifficulty
= int(10 * GetRelativeDanger(Tamer
));
7869 else TamingDifficulty
= 10*GetHPRequirementForGeneration()/Max(Tamer
->GetHP(), 1);
7871 return Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
;
7875 truth
character::CanTameWithScroll (const character
*Tamer
) const {
7876 int TamingDifficulty
= GetTamingDifficulty();
7878 (TamingDifficulty
!= NO_TAMING
&&
7879 (TamingDifficulty
== 0 ||
7880 Tamer
->GetAttribute(INTELLIGENCE
) * 4 + Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
* 5));
7884 truth
character::CheckSadism () {
7885 if (!IsSadist() || !HasSadistAttackMode() || !IsSmall()) return false; // gum
7887 for (int d
= 0; d
< 8; ++d
) {
7888 square
*Square
= GetNeighbourSquare(d
);
7890 character
*Char
= Square
->GetCharacter();
7891 if (Char
&& Char
->IsMasochist() && GetRelation(Char
) == FRIEND
&&
7892 Char
->GetHP() * 3 >= Char
->GetMaxHP() * 2 && Hit(Char
, Square
->GetPos(), d
, SADIST_HIT
)) {
7903 truth
character::CheckForBeverage () {
7904 if (StateIsActivated(PANIC
) || !IsEnabled() || !UsesNutrition() || CheckIfSatiated()) return false;
7905 itemvector ItemVector
;
7906 GetStack()->FillItemVector(ItemVector
);
7907 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) if (ItemVector
[c
]->IsBeverage(this) && TryToConsume(ItemVector
[c
])) return true;
7912 void character::Haste () {
7913 doforbodyparts()(this, &bodypart::Haste
);
7914 doforequipments()(this, &item::Haste
);
7915 BeginTemporaryState(HASTE
, 500 + RAND() % 1000);
7919 void character::Slow () {
7920 doforbodyparts()(this, &bodypart::Slow
);
7921 doforequipments()(this, &item::Slow
);
7922 //BeginTemporaryState(HASTE, 500 + RAND() % 1000); // this seems to be a bug
7923 BeginTemporaryState(SLOW
, 500 + RAND() % 1000);
7927 void character::SurgicallyDetachBodyPart () {
7928 ADD_MESSAGE("You haven't got any extra bodyparts.");
7932 truth
character::CanHear() const
7934 return DataBase
->CanHear
&& HasHead();