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 int character::GeneralRemoveItem (ItemCheckerCB chk
, truth allItems
) {
1361 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1363 if (Item
&& chk(Item
)) {
1364 Item
->RemoveFromSlot();
1367 if (!allItems
) return cnt
;
1376 for (int c
= 0; c
< GetEquipments(); ++c
) {
1377 item
*Item
= GetEquipment(c
);
1378 if (Item
&& chk(Item
)) {
1379 Item
->RemoveFromSlot();
1382 if (!allItems
) return cnt
;
1392 static truth
isEncryptedScroll (item
*i
) { return i
->IsEncryptedScroll(); }
1393 truth
character::RemoveEncryptedScroll () { return GeneralRemoveItem(::isEncryptedScroll
) != 0; }
1396 static truth
isMondedrPass (item
*i
) { return i
->IsMondedrPass(); }
1397 truth
character::RemoveMondedrPass () { return GeneralRemoveItem(::isMondedrPass
) != 0; }
1400 static truth
isRingOfThieves (item
*i
) { return i
->IsRingOfThieves(); }
1401 truth
character::RemoveRingOfThieves () { return GeneralRemoveItem(::isRingOfThieves
) != 0; }
1404 truth
character::ReadItem (item
*ToBeRead
) {
1405 if (!ToBeRead
->CanBeRead(this)) {
1406 if (IsPlayer()) ADD_MESSAGE("You can't read this.");
1409 if (!GetLSquareUnder()->IsDark() || game::GetSeeWholeMapCheatMode()) {
1410 if (StateIsActivated(CONFUSED
) && !(RAND()&7)) {
1411 if (!ToBeRead
->IsDestroyable(this)) {
1412 ADD_MESSAGE("You read some words of %s and understand exactly nothing.", ToBeRead
->CHAR_NAME(DEFINITE
));
1414 ADD_MESSAGE("%s is very confusing. Or perhaps you are just too confused?", ToBeRead
->CHAR_NAME(DEFINITE
));
1415 ActivateRandomState(SRC_CONFUSE_READ
, 1000+RAND()%1500);
1416 ToBeRead
->RemoveFromSlot();
1417 ToBeRead
->SendToHell();
1422 if (ToBeRead
->Read(this)) {
1423 if (!game::WizardModeIsActive()) {
1424 /* This AP is used to take the stuff out of backpack */
1431 if (IsPlayer()) ADD_MESSAGE("It's too dark here to read.");
1436 void character::CalculateBurdenState () {
1437 int OldBurdenState
= BurdenState
;
1438 sLong SumOfMasses
= GetCarriedWeight();
1439 sLong CarryingStrengthUnits
= sLong(GetCarryingStrength())*2500;
1440 if (SumOfMasses
> (CarryingStrengthUnits
<< 1) + CarryingStrengthUnits
) BurdenState
= OVER_LOADED
;
1441 else if (SumOfMasses
> CarryingStrengthUnits
<< 1) BurdenState
= STRESSED
;
1442 else if (SumOfMasses
> CarryingStrengthUnits
) BurdenState
= BURDENED
;
1443 else BurdenState
= UNBURDENED
;
1444 if (!IsInitializing() && BurdenState
!= OldBurdenState
) CalculateBattleInfo();
1448 void character::Save (outputfile
&SaveFile
) const {
1449 SaveFile
<< (uShort
)GetType();
1450 Stack
->Save(SaveFile
);
1452 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) SaveFile
<< BaseExperience
[c
];
1454 SaveFile
<< ExpModifierMap
;
1455 SaveFile
<< NP
<< AP
<< Stamina
<< GenerationDanger
<< ScienceTalks
<< CounterToMindWormHatch
;
1456 SaveFile
<< TemporaryState
<< EquipmentState
<< Money
<< GoingTo
<< RegenerationCounter
<< Route
<< Illegal
;
1457 SaveFile
<< CurrentSweatMaterial
;
1458 SaveFile
.Put(!!IsEnabled());
1459 SaveFile
<< HomeData
<< BlocksSinceLastTurn
<< CommandFlags
;
1460 SaveFile
<< WarnFlags
<< (uShort
)Flags
;
1462 for (int c
= 0; c
< BodyParts
; ++c
) SaveFile
<< BodyPartSlot
[c
] << OriginalBodyPartID
[c
];
1464 SaveLinkedList(SaveFile
, TrapData
);
1467 for (int c
= 0; c
< STATES
; ++c
) SaveFile
<< TemporaryStateCounter
[c
];
1471 SaveFile
<< Team
->GetID(); // feuLong
1473 SaveFile
.Put(false);
1476 if (GetTeam() && GetTeam()->GetLeader() == this) SaveFile
.Put(true); else SaveFile
.Put(false);
1478 SaveFile
<< AssignedName
<< PolymorphBackup
;
1480 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) SaveFile
<< CWeaponSkill
[c
];
1482 SaveFile
<< (uShort
)GetConfig();
1486 void character::Load (inputfile
&SaveFile
) {
1488 Stack
->Load(SaveFile
);
1490 game::AddCharacterID(this, ID
);
1492 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) SaveFile
>> BaseExperience
[c
];
1494 SaveFile
>> ExpModifierMap
;
1495 SaveFile
>> NP
>> AP
>> Stamina
>> GenerationDanger
>> ScienceTalks
>> CounterToMindWormHatch
;
1496 SaveFile
>> TemporaryState
>> EquipmentState
>> Money
>> GoingTo
>> RegenerationCounter
>> Route
>> Illegal
;
1497 SaveFile
>> CurrentSweatMaterial
;
1499 if (!SaveFile
.Get()) Disable();
1501 SaveFile
>> HomeData
>> BlocksSinceLastTurn
>> CommandFlags
;
1502 SaveFile
>> WarnFlags
;
1503 WarnFlags
&= ~WARNED
;
1504 Flags
|= ReadType
<uShort
>(SaveFile
) & ~ENTITY_FLAGS
;
1506 for (int c
= 0; c
< BodyParts
; ++c
) {
1507 SaveFile
>> BodyPartSlot
[c
] >> OriginalBodyPartID
[c
];
1508 item
*BodyPart
= *BodyPartSlot
[c
];
1509 if (BodyPart
) BodyPart
->Disable();
1512 LoadLinkedList(SaveFile
, TrapData
);
1515 if (Action
) Action
->SetActor(this);
1517 for (int c
= 0; c
< STATES
; ++c
) SaveFile
>> TemporaryStateCounter
[c
];
1519 if (SaveFile
.Get()) SetTeam(game::GetTeam(ReadType
<feuLong
>(SaveFile
)));
1521 if (SaveFile
.Get()) GetTeam()->SetLeader(this);
1523 SaveFile
>> AssignedName
>> PolymorphBackup
;
1525 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) SaveFile
>> CWeaponSkill
[c
];
1527 databasecreator
<character
>::InstallDataBase(this, ReadType
<uShort
>(SaveFile
));
1529 if (IsEnabled() && !game::IsInWilderness()) {
1530 for (int c
= 1; c
< GetSquaresUnder(); ++c
) GetSquareUnder(c
)->SetCharacter(this);
1535 truth
character::Engrave (cfestring
&What
) {
1536 GetLSquareUnder()->Engrave(What
);
1540 truth
character::MoveRandomly () {
1541 if (!IsEnabled()) return false;
1542 for (int c
= 0; c
< 10; ++c
) {
1543 v2 ToTry
= game::GetMoveVector(RAND()&7);
1544 if (GetLevel()->IsValidPos(GetPos()+ToTry
)) {
1545 lsquare
*Square
= GetNearLSquare(GetPos()+ToTry
);
1546 if (!Square
->IsDangerous(this) && !Square
->IsScary(this) && TryMove(ToTry
, false, false)) return true;
1553 truth
character::TestForPickup (item
*ToBeTested
) const {
1554 if (MakesBurdened(ToBeTested
->GetWeight()+GetCarriedWeight())) return false;
1559 void character::AddScoreEntry (cfestring
&Description
, double Multiplier
, truth AddEndLevel
) const {
1560 if (!game::WizardModeIsReallyActive()) {
1562 if (!HScore
.CheckVersion()) {
1563 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;
1566 festring Desc
= game::GetPlayerName();
1567 Desc
<< ", " << Description
;
1568 if (AddEndLevel
) Desc
<< " in "+(game::IsInWilderness() ? "the world map" : game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex()));
1569 HScore
.Add(sLong(game::GetScore()*Multiplier
), Desc
);
1575 truth
character::CheckDeath (cfestring
&Msg
, ccharacter
*Murderer
, feuLong DeathFlags
) {
1576 if (!IsEnabled()) return true;
1577 if (game::IsSumoWrestling() && IsDead()) {
1578 game::EndSumoWrestling(!!IsPlayer());
1581 if (DeathFlags
& FORCE_DEATH
|| IsDead()) {
1582 if (Murderer
&& Murderer
->IsPlayer() && GetTeam()->GetKillEvilness()) game::DoEvilDeed(GetTeam()->GetKillEvilness());
1583 festring SpecifierMsg
;
1584 int SpecifierParts
= 0;
1585 if (GetPolymorphBackup()) {
1586 SpecifierMsg
<< " polymorphed into ";
1587 id::AddName(SpecifierMsg
, INDEFINITE
);
1590 if (!(DeathFlags
& IGNORE_TRAPS
) && IsStuck()) {
1591 if (SpecifierParts
++) SpecifierMsg
<< " and";
1592 SpecifierMsg
<< " caught in " << GetTrapDescription();
1594 if (GetAction() && !(DeathFlags
& IGNORE_UNCONSCIOUSNESS
&& GetAction()->IsUnconsciousness())) {
1595 festring ActionMsg
= GetAction()->GetDeathExplanation();
1596 if (!ActionMsg
.IsEmpty()) {
1597 if (SpecifierParts
> 1) {
1598 SpecifierMsg
= ActionMsg
<< ',' << SpecifierMsg
;
1600 if (SpecifierParts
) SpecifierMsg
<< " and";
1601 SpecifierMsg
<< ActionMsg
;
1606 festring NewMsg
= Msg
;
1607 if (Murderer
== this) {
1608 SEARCH_N_REPLACE(NewMsg
, "@bkp", CONST_S("by ") + GetPossessivePronoun(false) + " own");
1609 SEARCH_N_REPLACE(NewMsg
, "@bk", CONST_S("by ") + GetObjectPronoun(false) + "self");
1610 SEARCH_N_REPLACE(NewMsg
, "@k", GetObjectPronoun(false) + "self");
1612 SEARCH_N_REPLACE(NewMsg
, "@bkp", CONST_S("by ") + Murderer
->GetName(INDEFINITE
) + "'s");
1613 SEARCH_N_REPLACE(NewMsg
, "@bk", CONST_S("by ") + Murderer
->GetName(INDEFINITE
));
1614 SEARCH_N_REPLACE(NewMsg
, "@k", CONST_S("by ") + Murderer
->GetName(INDEFINITE
));
1616 if (SpecifierParts
) NewMsg
<< " while" << SpecifierMsg
;
1617 if (IsPlayer() && game::WizardModeIsActive()) ADD_MESSAGE("Death message: %s. Score: %d.", NewMsg
.CStr(), game::GetScore());
1618 Die(Murderer
, NewMsg
, DeathFlags
);
1625 truth
character::CheckStarvationDeath (cfestring
&Msg
) {
1626 if (GetNP() < 1 && UsesNutrition()) return CheckDeath(Msg
, 0, FORCE_DEATH
);
1631 void character::ThrowItem (int Direction
, item
*ToBeThrown
) {
1632 if (Direction
> 7) ABORT("Throw in TOO odd direction...");
1633 ToBeThrown
->Fly(this, Direction
, GetAttribute(ARM_STRENGTH
));
1637 void character::HasBeenHitByItem (character
*Thrower
, item
*Thingy
, int Damage
, double ToHitValue
, int Direction
) {
1638 if (IsPlayer()) ADD_MESSAGE("%s hits you.", Thingy
->CHAR_NAME(DEFINITE
));
1639 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s.", Thingy
->CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
));
1640 int BodyPart
= ChooseBodyPartToReceiveHit(ToHitValue
, DodgeValue
);
1641 int WeaponSkillHits
= Thrower
? CalculateWeaponSkillHits(Thrower
) : 0;
1642 int DoneDamage
= ReceiveBodyPartDamage(Thrower
, Damage
, PHYSICAL_DAMAGE
, BodyPart
, Direction
);
1643 truth Succeeded
= (GetBodyPart(BodyPart
) && HitEffect(Thrower
, Thingy
, Thingy
->GetPos(), THROW_ATTACK
, BodyPart
, Direction
, !DoneDamage
)) || DoneDamage
;
1644 if (Succeeded
&& Thrower
) Thrower
->WeaponSkillHit(Thingy
, THROW_ATTACK
, WeaponSkillHits
);
1645 festring DeathMsg
= CONST_S("killed by a flying ")+Thingy
->GetName(UNARTICLED
);
1646 if (CheckDeath(DeathMsg
, Thrower
)) return;
1648 if (Thrower
->CanBeSeenByPlayer())
1649 DeActivateVoluntaryAction(CONST_S("The attack of ")+Thrower
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
1651 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
1653 DeActivateVoluntaryAction(CONST_S("The hit interrupts you."));
1658 truth
character::DodgesFlyingItem (item
*Item
, double ToHitValue
) {
1659 return !Item
->EffectIsGood() && RAND() % int(100+ToHitValue
/DodgeValue
*100) < 100;
1663 void character::GetPlayerCommand () {
1665 truth HasActed
= false;
1667 game::DrawEverything();
1668 if (game::GetDangerFound()) {
1669 if (game::GetDangerFound() > 500.) {
1670 if (game::GetCausePanicFlag()) {
1671 game::SetCausePanicFlag(false);
1672 BeginTemporaryState(PANIC
, 500+RAND_N(500));
1674 game::AskForEscPress(CONST_S("You are horrified by your situation!"));
1675 } else if (ivanconfig::GetWarnAboutDanger()) {
1676 if (game::GetDangerFound() > 50.) game::AskForEscPress(CONST_S("You sense great danger!"));
1677 else game::AskForEscPress(CONST_S("You sense danger!"));
1679 game::SetDangerFound(0);
1681 game::SetIsInGetCommand(true);
1682 int Key
= GET_KEY();
1683 game::SetIsInGetCommand(false);
1684 if (Key
!= '+' && Key
!= '-' && Key
!= 'M') msgsystem::ThyMessagesAreNowOld(); // gum
1685 truth ValidKeyPressed
= false;
1687 for (c
= 0; c
< DIRECTION_COMMAND_KEYS
; ++c
) {
1688 if (Key
== game::GetMoveCommandKey(c
)) {
1689 HasActed
= TryMove(ApplyStateModification(game::GetMoveVector(c
)), true, game::PlayerIsRunning());
1690 ValidKeyPressed
= true;
1693 for (c
= 1; (cmd
= commandsystem::GetCommand(c
)); ++c
) {
1695 /* Numpad aliases for most commonly used commands */
1696 if (Key
== KEY_DEL
&& cmd
->GetLinkedFunction() == commandsystem::Eat
) Key
= cmd
->GetKey();
1697 if (Key
== KEY_INS
&& cmd
->GetLinkedFunction() == commandsystem::PickUp
) Key
= cmd
->GetKey();
1698 if (Key
== KEY_PLUS
&& cmd
->GetLinkedFunction() == commandsystem::EquipmentScreen
) Key
= cmd
->GetKey();
1700 if (Key
== cmd
->GetKey()) {
1701 if (game::IsInWilderness() && !commandsystem::GetCommand(c
)->IsUsableInWilderness())
1702 ADD_MESSAGE("This function cannot be used while in wilderness.");
1703 else if (!game::WizardModeIsActive() && commandsystem::GetCommand(c
)->IsWizardModeFunction())
1704 ADD_MESSAGE("Activate wizardmode to use this function.");
1706 HasActed
= commandsystem::GetCommand(c
)->GetLinkedFunction()(this);
1707 ValidKeyPressed
= true;
1711 if (!ValidKeyPressed
) ADD_MESSAGE("Unknown key. Press '?' for a list of commands.");
1713 game::IncreaseTurn();
1717 void character::Vomit (v2 Pos
, int Amount
, truth ShowMsg
) {
1718 if (!CanVomit()) return;
1720 if (IsPlayer()) ADD_MESSAGE("You vomit.");
1721 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s vomits.", CHAR_NAME(DEFINITE
));
1723 if (VomittingIsUnhealthy()) {
1724 EditExperience(ARM_STRENGTH
, -75, 1 << 9);
1725 EditExperience(LEG_STRENGTH
, -75, 1 << 9);
1728 EditNP(-2500-RAND()%2501);
1729 CheckStarvationDeath(CONST_S("vomited himself to death"));
1731 if (StateIsActivated(PARASITIZED
) && !(RAND() & 7)) {
1732 if (IsPlayer()) ADD_MESSAGE("You notice a dead broad tapeworm among your former stomach contents.");
1733 DeActivateTemporaryState(PARASITIZED
);
1735 if (!game::IsInWilderness()) {
1736 GetNearLSquare(Pos
)->ReceiveVomit(this, liquid::Spawn(GetVomitMaterial(), sLong(sqrt(GetBodyVolume())*Amount
/1000)));
1741 truth
character::Polymorph (character
*NewForm
, int Counter
) {
1742 if (!IsPolymorphable() || (!IsPlayer() && game::IsInWilderness())) {
1747 if (GetAction()) GetAction()->Terminate(false);
1748 NewForm
->SetAssignedName("");
1750 ADD_MESSAGE("Your body glows in a crimson light. You transform into %s!", NewForm
->CHAR_NAME(INDEFINITE
));
1751 else if (CanBeSeenByPlayer())
1752 ADD_MESSAGE("%s glows in a crimson light and %s transforms into %s!", CHAR_NAME(DEFINITE
), GetPersonalPronoun().CStr(), NewForm
->CHAR_NAME(INDEFINITE
));
1754 Flags
|= C_IN_NO_MSG_MODE
;
1755 NewForm
->Flags
|= C_IN_NO_MSG_MODE
;
1756 NewForm
->ChangeTeam(GetTeam());
1757 NewForm
->GenerationDanger
= GenerationDanger
;
1758 NewForm
->mOnEvents
= this->mOnEvents
;
1760 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(NewForm
);
1764 NewForm
->PutToOrNear(Pos
);
1765 NewForm
->SetAssignedName(GetAssignedName());
1766 NewForm
->ActivateTemporaryState(POLYMORPHED
);
1767 NewForm
->SetTemporaryStateCounter(POLYMORPHED
, Counter
);
1769 if (TemporaryStateIsActivated(POLYMORPHED
)) {
1770 NewForm
->SetPolymorphBackup(GetPolymorphBackup());
1771 SetPolymorphBackup(0);
1774 NewForm
->SetPolymorphBackup(this);
1775 Flags
|= C_POLYMORPHED
;
1779 GetStack()->MoveItemsTo(NewForm
->GetStack());
1780 NewForm
->SetMoney(GetMoney());
1781 DonateEquipmentTo(NewForm
);
1782 Flags
&= ~C_IN_NO_MSG_MODE
;
1783 NewForm
->Flags
&= ~C_IN_NO_MSG_MODE
;
1784 NewForm
->CalculateAll();
1788 game::SetPlayer(NewForm
);
1789 game::SendLOSUpdateRequest();
1793 NewForm
->TestWalkability();
1798 void character::BeKicked (character
*Kicker
, item
*Boot
, bodypart
*Leg
, v2 HitPos
, double KickDamage
,
1799 double ToHitValue
, int Success
, int Direction
, truth Critical
, truth ForceHit
)
1802 game::ClearEventData();
1803 game::mActor
= Kicker
;
1804 if (game::RunOnCharEvent(this, CONST_S("before_be_kicked"))) { game::ClearEventData(); return; }
1805 game::ClearEventData();
1807 switch (TakeHit(Kicker
, Boot
, Leg
, HitPos
, KickDamage
, ToHitValue
, Success
, KICK_ATTACK
, Direction
, Critical
, ForceHit
)) {
1811 if (IsEnabled() && !CheckBalance(KickDamage
)) {
1812 if (IsPlayer()) ADD_MESSAGE("The kick throws you off balance.");
1813 else if (Kicker
->IsPlayer()) ADD_MESSAGE("The kick throws %s off balance.", CHAR_DESCRIPTION(DEFINITE
));
1814 v2 FallToPos
= GetPos()+game::GetMoveVector(Direction
);
1815 FallTo(Kicker
, FallToPos
);
1821 /* Return true if still in balance */
1822 truth
character::CheckBalance (double KickDamage
) {
1823 return !CanMove() || IsStuck() || !KickDamage
|| (!IsFlying() && KickDamage
*5 < RAND()%GetSize());
1827 void character::FallTo (character
*GuiltyGuy
, v2 Where
) {
1829 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
1830 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, Where
);
1832 truth NoRoom
= false;
1833 for (int c
= 0; c
< Squares
; ++c
) {
1834 olterrain
*Terrain
= MoveToSquare
[c
]->GetOLTerrain();
1835 if (Terrain
&& !CanMoveOn(Terrain
)) { NoRoom
= true; break; }
1839 if (IsPlayer()) ADD_MESSAGE("You hit your head on the wall.");
1840 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s head on the wall.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
1842 ReceiveDamage(GuiltyGuy
, 1+RAND()%5, PHYSICAL_DAMAGE
, HEAD
);
1843 CheckDeath(CONST_S("killed by hitting a wall due to being kicked @bk"), GuiltyGuy
);
1845 if (IsFreeForMe(MoveToSquare
[0])) Move(Where
, true);
1846 // Place code that handles characters bouncing to each other here
1852 truth
character::CheckCannibalism (cmaterial
*What
) const {
1853 return GetTorso()->GetMainMaterial()->IsSameAs(What
);
1857 void character::StandIdleAI () {
1858 SeekLeader(GetLeader());
1859 if (CheckForEnemies(true, true, true)) return;
1860 if (CheckForUsefulItemsOnGround()) return;
1861 if (FollowLeader(GetLeader())) return;
1862 if (CheckForDoors()) return;
1863 if (MoveTowardsHomePos()) return;
1864 if (CheckSadism()) return;
1869 truth
character::LoseConsciousness (int Counter
, truth HungerFaint
) {
1870 if (!AllowUnconsciousness()) return false;
1871 action
*Action
= GetAction();
1873 if (HungerFaint
&& !Action
->AllowUnconsciousness()) return false;
1874 if (Action
->IsUnconsciousness()) {
1875 static_cast<unconsciousness
*>(Action
)->RaiseCounterTo(Counter
);
1878 Action
->Terminate(false);
1880 if (IsPlayer()) ADD_MESSAGE("You lose consciousness.");
1881 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s loses consciousness.", CHAR_NAME(DEFINITE
));
1882 unconsciousness
*Unconsciousness
= unconsciousness::Spawn(this);
1883 Unconsciousness
->SetCounter(Counter
);
1884 SetAction(Unconsciousness
);
1889 void character::DeActivateVoluntaryAction (cfestring
&Reason
) {
1890 if (GetAction() && GetAction()->IsVoluntary()) {
1892 if (Reason
.GetSize()) ADD_MESSAGE("%s", Reason
.CStr());
1893 if (game::TruthQuestion(CONST_S("Continue ") + GetAction()->GetDescription()+"? [y/N]")) GetAction()->ActivateInDNDMode();
1894 else GetAction()->Terminate(false);
1897 GetAction()->Terminate(false);
1903 void character::ActionAutoTermination () {
1904 if (!GetAction() || !GetAction()->IsVoluntary() || GetAction()->InDNDMode()) return;
1906 for (int c
= 0; c
< game::GetTeams(); ++c
) {
1907 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
1908 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
1910 if (ch
->IsEnabled() && ch
->CanBeSeenBy(this, false, true) && (ch
->CanMove() || ch
->GetPos().IsAdjacent(Pos
)) && ch
->CanAttack()) {
1912 ADD_MESSAGE("%s seems to be hostile.", ch
->CHAR_NAME(DEFINITE
));
1913 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"? [y/N]")) GetAction()->ActivateInDNDMode();
1914 else GetAction()->Terminate(false);
1916 GetAction()->Terminate(false);
1926 truth
character::CheckForEnemies (truth CheckDoors
, truth CheckGround
, truth MayMoveRandomly
, truth RunTowardsTarget
) {
1927 if (!IsEnabled()) return false;
1928 truth HostileCharsNear
= false;
1929 character
*NearestChar
= 0;
1930 sLong NearestDistance
= 0x7FFFFFFF;
1932 for (int c
= 0; c
< game::GetTeams(); ++c
) {
1933 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
1934 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
1936 if (ch
->IsEnabled() && GetAttribute(WISDOM
) < ch
->GetAttackWisdomLimit()) {
1937 sLong ThisDistance
= Max
<sLong
>(abs(ch
->GetPos().X
- Pos
.X
), abs(ch
->GetPos().Y
- Pos
.Y
));
1938 if (ThisDistance
<= GetLOSRangeSquare()) HostileCharsNear
= true;
1939 if ((ThisDistance
< NearestDistance
|| (ThisDistance
== NearestDistance
&& !(RAND() % 3))) &&
1940 ch
->CanBeSeenBy(this, false, IsGoingSomeWhere()) &&
1941 (!IsGoingSomeWhere() || HasClearRouteTo(ch
->GetPos()))) {
1943 NearestDistance
= ThisDistance
;
1951 if (GetAttribute(INTELLIGENCE
) >= 10 || IsSpy()) game::CallForAttention(GetPos(), 100);
1952 if (SpecialEnemySightedReaction(NearestChar
)) return true;
1953 if (IsExtraCoward() && !StateIsActivated(PANIC
) && NearestChar
->GetRelativeDanger(this) >= 0.5) {
1954 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s sees %s.", CHAR_NAME(DEFINITE
), NearestChar
->CHAR_DESCRIPTION(DEFINITE
));
1955 BeginTemporaryState(PANIC
, 500+RAND()%500);
1957 if (!IsRetreating()) {
1958 if (CheckGround
&& NearestDistance
> 2 && CheckForUsefulItemsOnGround(false)) return true;
1959 SetGoingTo(NearestChar
->GetPos());
1961 SetGoingTo(Pos
-((NearestChar
->GetPos()-Pos
)<<4));
1963 return MoveTowardsTarget(true);
1965 character
*Leader
= GetLeader();
1966 if (Leader
== this) Leader
= 0;
1967 if (!Leader
&& IsGoingSomeWhere()) {
1968 if (!MoveTowardsTarget(RunTowardsTarget
)) {
1972 if (!IsEnabled()) return true;
1973 if (GetPos() == GoingTo
) TerminateGoingTo();
1977 if ((!Leader
|| (Leader
&& !IsGoingSomeWhere())) && HostileCharsNear
) {
1978 if (CheckDoors
&& CheckForDoors()) return true;
1979 if (CheckGround
&& CheckForUsefulItemsOnGround()) return true;
1980 if (MayMoveRandomly
&& MoveRandomly()) return true; // one has heard that an enemy is near but doesn't know where
1988 truth
character::CheckForDoors () {
1989 if (!CanOpen() || !IsEnabled()) return false;
1990 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
1991 lsquare
*Square
= GetNeighbourLSquare(d
);
1992 if (Square
&& Square
->GetOLTerrain() && Square
->GetOLTerrain()->Open(this)) return true;
1998 truth
character::CheckForUsefulItemsOnGround (truth CheckFood
) {
1999 if (StateIsActivated(PANIC
) || !IsEnabled()) return false;
2000 itemvector ItemVector
;
2001 GetStackUnder()->FillItemVector(ItemVector
);
2002 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
2003 if (ItemVector
[c
]->CanBeSeenBy(this) && ItemVector
[c
]->IsPickable(this)) {
2004 if (!(CommandFlags
& DONT_CHANGE_EQUIPMENT
) && TryToEquip(ItemVector
[c
])) return true;
2005 if (CheckFood
&& UsesNutrition() && !CheckIfSatiated() && TryToConsume(ItemVector
[c
])) return true;
2006 if (IsRangedAttacker() && (ItemVector
[c
])->GetThrowItemTypes() && TryToAddToInventory(ItemVector
[c
])) return true;
2013 truth
character::TryToAddToInventory (item
*Item
) {
2014 if (!(GetBurdenState() > STRESSED
) || !CanUseEquipment() || Item
->GetSquaresUnder() != 1) return false;
2015 room
*Room
= GetRoom();
2016 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
2017 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up %s from the ground.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(INDEFINITE
));
2018 Item
->MoveTo(GetStack());
2026 truth
character::CheckInventoryForItemToThrow (item
*ToBeChecked
) {
2027 return (ToBeChecked
->GetThrowItemTypes() & GetWhatThrowItemTypesToThrow()) ? true : false; //hehe
2031 truth
character::CheckThrowItemOpportunity () {
2032 if (!IsRangedAttacker() || !CanThrow() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2033 //fprintf(stderr, "character::CheckThrowItemOpportunity...\n");
2035 // (1) - Acquire target as nearest enemy
2036 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2037 // (3) - check inventory for throwing weapon, select this weapon
2038 // (4) - throw item in direction where the enemy is
2040 //Check the visible area for hostiles
2041 int ThrowDirection
= 0;
2042 int TargetFound
= 0;
2045 int RangeMax
= GetLOSRange();
2046 int CandidateDirections
[7] = {0, 0, 0, 0, 0, 0, 0};
2047 int HostileFound
= 0;
2048 item
*ToBeThrown
= 0;
2049 level
*Level
= GetLevel();
2051 for (int r
= 1; r
<= RangeMax
; ++r
) {
2052 for (int dir
= 0; dir
< 8; ++dir
) {
2055 case 0: TestPos
= v2(Pos
.X
-r
, Pos
.Y
-r
); break;
2056 case 1: TestPos
= v2(Pos
.X
, Pos
.Y
-r
); break;
2057 case 2: TestPos
= v2(Pos
.X
+r
, Pos
.Y
-r
); break;
2058 case 3: TestPos
= v2(Pos
.X
-r
, Pos
.Y
); break;
2059 case 4: TestPos
= v2(Pos
.X
+r
, Pos
.Y
); break;
2060 case 5: TestPos
= v2(Pos
.X
-r
, Pos
.Y
+r
); break;
2061 case 6: TestPos
= v2(Pos
.X
, Pos
.Y
+r
); break;
2062 case 7: TestPos
= v2(Pos
.X
+r
, Pos
.Y
+r
); break;
2064 if (Level
->IsValidPos(TestPos
)) {
2065 square
*TestSquare
= GetNearSquare(TestPos
);
2066 character
*Dude
= TestSquare
->GetCharacter();
2068 if (Dude
&& Dude
->IsEnabled() && Dude
->CanBeSeenBy(this, false, true)) {
2069 if (GetRelation(Dude
) != HOSTILE
) CandidateDirections
[dir
] = BLOCKED
;
2070 else if (GetRelation(Dude
) == HOSTILE
&& CandidateDirections
[dir
] != BLOCKED
) {
2071 //then load this candidate position direction into the vector of possible throw directions
2072 CandidateDirections
[dir
] = SUCCESS
;
2081 for (int dir
= 0; dir
< 8; ++dir
) {
2082 if (CandidateDirections
[dir
] == SUCCESS
&& !TargetFound
) {
2083 ThrowDirection
= dir
;
2088 if (!TargetFound
) return false;
2092 //fprintf(stderr, "throw: has target.\n");
2093 // check inventory for throwing weapon
2094 itemvector ItemVector
;
2095 GetStack()->FillItemVector(ItemVector
);
2096 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
2097 if (ItemVector
[c
]->IsThrowingWeapon()) {
2098 ToBeThrown
= ItemVector
[c
];
2102 if (!ToBeThrown
) return false;
2103 //fprintf(stderr, "throw: has throwing weapon.\n");
2104 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s throws %s.", CHAR_NAME(DEFINITE
), ToBeThrown
->CHAR_NAME(INDEFINITE
));
2105 ThrowItem(ThrowDirection
, ToBeThrown
);
2106 EditExperience(ARM_STRENGTH
, 75, 1<<8);
2107 EditExperience(DEXTERITY
, 75, 1<<8);
2108 EditExperience(PERCEPTION
, 75, 1<<8);
2116 truth
character::CheckAIZapOpportunity () {
2117 if (/*!IsRangedAttacker() || */ !CanZap() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2119 // (1) - Acquire target as nearest enemy
2120 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2121 // (3) - check inventory for zappable item
2122 // (4) - zap item in direction where the enemy is
2123 //Check the rest of the visible area for hostiles
2126 int SensibleRange
= 5;
2127 int RangeMax
= GetLOSRange();
2128 if (RangeMax
< SensibleRange
) SensibleRange
= RangeMax
;
2129 int CandidateDirections
[7] = {0, 0, 0, 0, 0, 0, 0};
2130 int HostileFound
= 0;
2131 int ZapDirection
= 0;
2132 int TargetFound
= 0;
2133 item
*ToBeZapped
= 0;
2134 level
*Level
= GetLevel();
2136 for (int r
= 2; r
<= SensibleRange
; ++r
) {
2137 for (int dir
= 0; dir
< 8; ++dir
) {
2139 case 0: TestPos
= v2(Pos
.X
-r
, Pos
.Y
-r
); break;
2140 case 1: TestPos
= v2(Pos
.X
, Pos
.Y
-r
); break;
2141 case 2: TestPos
= v2(Pos
.X
+r
, Pos
.Y
-r
); break;
2142 case 3: TestPos
= v2(Pos
.X
-r
, Pos
.Y
); break;
2143 case 4: TestPos
= v2(Pos
.X
+r
, Pos
.Y
); break;
2144 case 5: TestPos
= v2(Pos
.X
-r
, Pos
.Y
+r
); break;
2145 case 6: TestPos
= v2(Pos
.X
, Pos
.Y
+r
); break;
2146 case 7: TestPos
= v2(Pos
.X
+r
, Pos
.Y
+r
); break;
2148 if (Level
->IsValidPos(TestPos
)) {
2149 square
*TestSquare
= GetNearSquare(TestPos
);
2150 character
*Dude
= TestSquare
->GetCharacter();
2152 if (Dude
&& Dude
->IsEnabled() && Dude
->CanBeSeenBy(this, false, true)) {
2153 if (GetRelation(Dude
) != HOSTILE
) CandidateDirections
[dir
] = BLOCKED
;
2154 else if (GetRelation(Dude
) == HOSTILE
&& CandidateDirections
[dir
] != BLOCKED
) {
2155 //then load this candidate position direction into the vector of possible zap directions
2156 CandidateDirections
[dir
] = SUCCESS
;
2165 for (int dir
= 0; dir
< 8; ++dir
) {
2166 if (CandidateDirections
[dir
] == SUCCESS
&& !TargetFound
) {
2172 if (!TargetFound
) return false;
2176 // check inventory for zappable item
2177 itemvector ItemVector
;
2178 GetStack()->FillItemVector(ItemVector
);
2179 for (unsigned int c
= 0; c
< ItemVector
.size(); ++c
) {
2180 if (ItemVector
[c
]->GetMinCharges() > 0 && ItemVector
[c
]->GetPrice()) {
2181 // bald-faced gum solution for choosing zappables that have shots left.
2182 // MinCharges needs to be replaced. Empty wands have zero price!
2183 ToBeZapped
= ItemVector
[c
];
2187 if (!ToBeZapped
) return false;
2188 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s zaps %s.", CHAR_NAME(DEFINITE
), ToBeZapped
->CHAR_NAME(INDEFINITE
));
2189 if (ToBeZapped
->Zap(this, GetPos(), ZapDirection
)) {
2190 EditAP(-100000/APBonus(GetAttribute(PERCEPTION
)));
2200 truth
character::FollowLeader (character
*Leader
) {
2201 if (!Leader
|| Leader
== this || !IsEnabled()) return false;
2202 if (CommandFlags
& FOLLOW_LEADER
&& Leader
->CanBeSeenBy(this) && Leader
->SquareUnderCanBeSeenBy(this, true)) {
2203 v2 Distance
= GetPos()-GoingTo
;
2204 if (abs(Distance
.X
) <= 2 && abs(Distance
.Y
) <= 2) return false;
2205 return MoveTowardsTarget(false);
2207 if (IsGoingSomeWhere()) {
2208 if (!MoveTowardsTarget(true)) {
2218 void character::SeekLeader (ccharacter
*Leader
) {
2219 if (Leader
&& Leader
!= this) {
2220 if (Leader
->CanBeSeenBy(this) && (Leader
->SquareUnderCanBeSeenBy(this, true) || !IsGoingSomeWhere())) {
2221 if (CommandFlags
& FOLLOW_LEADER
) SetGoingTo(Leader
->GetPos());
2222 } else if (!IsGoingSomeWhere()) {
2223 team
*Team
= GetTeam();
2224 for (std::list
<character
*>::const_iterator i
= Team
->GetMember().begin(); i
!= Team
->GetMember().end(); ++i
) {
2226 if (ch
->IsEnabled() && ch
->GetID() != GetID() &&
2227 (CommandFlags
& FOLLOW_LEADER
) == (ch
->CommandFlags
& FOLLOW_LEADER
) && ch
->CanBeSeenBy(this)) {
2228 v2 Pos
= ch
->GetPos();
2229 v2 Distance
= GetPos()-Pos
;
2230 if (abs(Distance
.X
) > 2 && abs(Distance
.Y
) > 2) {
2241 int character::GetMoveEase () const {
2242 switch (BurdenState
) {
2244 case STRESSED
: return 50;
2245 case BURDENED
: return 75;
2246 case UNBURDENED
: return 100;
2252 int character::GetLOSRange () const {
2253 if (!game::IsInWilderness()) return GetAttribute(PERCEPTION
)*GetLevel()->GetLOSModifier()/48;
2258 truth
character::Displace (character
*Who
, truth Forced
) {
2259 if (GetBurdenState() == OVER_LOADED
) {
2261 cchar
*CrawlVerb
= StateIsActivated(LEVITATION
) ? "float" : "crawl";
2262 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb
);
2269 double Danger
= GetRelativeDanger(Who
);
2270 int PriorityDifference
= Limit(GetDisplacePriority()-Who
->GetDisplacePriority(), -31, 31);
2272 if (IsPlayer()) ++PriorityDifference
;
2273 else if (Who
->IsPlayer()) --PriorityDifference
;
2275 if (PriorityDifference
>= 0) Danger
*= 1 << PriorityDifference
;
2276 else Danger
/= 1 << -PriorityDifference
;
2278 if (IsSmall() && Who
->IsSmall() &&
2279 (Forced
|| Danger
> 1.0 || !(Who
->IsPlayer() || Who
->IsBadPath(GetPos()))) &&
2280 !IsStuck() && !Who
->IsStuck() && (!Who
->GetAction() || Who
->GetAction()->TryDisplace()) &&
2281 CanMove() && Who
->CanMove() && Who
->CanMoveOn(GetLSquareUnder())) {
2282 if (IsPlayer()) ADD_MESSAGE("You displace %s!", Who
->CHAR_DESCRIPTION(DEFINITE
));
2283 else if (Who
->IsPlayer()) ADD_MESSAGE("%s displaces you!", CHAR_DESCRIPTION(DEFINITE
));
2284 else if (CanBeSeenByPlayer() || Who
->CanBeSeenByPlayer()) ADD_MESSAGE("%s displaces %s!", CHAR_DESCRIPTION(DEFINITE
), Who
->CHAR_DESCRIPTION(DEFINITE
));
2285 lsquare
*OldSquareUnder1
[MAX_SQUARES_UNDER
];
2286 lsquare
*OldSquareUnder2
[MAX_SQUARES_UNDER
];
2287 for (int c
= 0; c
< GetSquaresUnder(); ++c
) OldSquareUnder1
[c
] = GetLSquareUnder(c
);
2288 for (int c
= 0; c
< Who
->GetSquaresUnder(); ++c
) OldSquareUnder2
[c
] = Who
->GetLSquareUnder(c
);
2290 v2 WhoPos
= Who
->GetPos();
2295 EditAP(-GetMoveAPRequirement(GetSquareUnder()->GetEntryDifficulty()) - 500);
2296 EditNP(-12*GetSquareUnder()->GetEntryDifficulty());
2297 EditExperience(AGILITY
, 75, GetSquareUnder()->GetEntryDifficulty() << 7);
2298 if (IsPlayer()) ShowNewPosInfo();
2299 if (Who
->IsPlayer()) Who
->ShowNewPosInfo();
2300 SignalStepFrom(OldSquareUnder1
);
2301 Who
->SignalStepFrom(OldSquareUnder2
);
2305 ADD_MESSAGE("%s resists!", Who
->CHAR_DESCRIPTION(DEFINITE
));
2314 void character::SetNP (sLong What
) {
2315 int OldState
= GetHungerState();
2318 int NewState
= GetHungerState();
2319 if (NewState
== STARVING
&& OldState
> STARVING
) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
2320 else if (NewState
== VERY_HUNGRY
&& OldState
> VERY_HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting very hungry."));
2321 else if (NewState
== HUNGRY
&& OldState
> HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting hungry."));
2326 void character::ShowNewPosInfo () const {
2327 msgsystem::EnterBigMessageMode();
2330 if (ivanconfig::GetAutoCenterMap()) {
2331 game::UpdateCameraX();
2332 game::UpdateCameraY();
2334 if (Pos
.X
< game::GetCamera().X
+3 || Pos
.X
>= game::GetCamera().X
+game::GetScreenXSize()-3) game::UpdateCameraX();
2335 if (Pos
.Y
< game::GetCamera().Y
+3 || Pos
.Y
>= game::GetCamera().Y
+game::GetScreenYSize()-3) game::UpdateCameraY();
2338 game::SendLOSUpdateRequest();
2339 game::DrawEverythingNoBlit();
2342 if (!game::IsInWilderness()) {
2343 if (GetLSquareUnder()->IsDark() && !game::GetSeeWholeMapCheatMode()) ADD_MESSAGE("It's dark in here!");
2345 GetLSquareUnder()->ShowSmokeMessage();
2346 itemvectorvector PileVector
;
2347 GetStackUnder()->Pile(PileVector
, this, CENTER
);
2349 if (PileVector
.size()) {
2350 truth Feel
= !GetLSquareUnder()->IsTransparent() || GetLSquareUnder()->IsDark();
2351 if (PileVector
.size() == 1) {
2352 if (Feel
) ADD_MESSAGE("You feel %s lying here.", PileVector
[0][0]->GetName(INDEFINITE
, PileVector
[0].size()).CStr());
2353 else ADD_MESSAGE("%s %s lying here.", PileVector
[0][0]->GetName(INDEFINITE
, PileVector
[0].size()).CStr(), PileVector
[0].size() == 1 ? "is" : "are");
2356 for (uInt c
= 0; c
< PileVector
.size(); ++c
) {
2357 if ((Items
+= PileVector
[c
].size()) > 3) break;
2360 if (Feel
) ADD_MESSAGE("You feel several items lying here.");
2361 else ADD_MESSAGE("Several items are lying here.");
2363 if (Feel
) ADD_MESSAGE("You feel a few items lying here.");
2364 else ADD_MESSAGE("A few items are lying here.");
2370 GetLSquareUnder()->GetSideItemDescription(SideItems
);
2372 if (!SideItems
.IsEmpty()) ADD_MESSAGE("There is %s.", SideItems
.CStr());
2374 if (GetLSquareUnder()->HasEngravings()) {
2375 if (CanRead()) ADD_MESSAGE("Something has been engraved here: \"%s\"", GetLSquareUnder()->GetEngraved());
2376 else ADD_MESSAGE("Something has been engraved here.");
2380 msgsystem::LeaveBigMessageMode();
2384 void character::Hostility (character
*Enemy
) {
2385 if (Enemy
== this || !Enemy
|| !Team
|| !Enemy
->Team
) return;
2386 if (Enemy
->IsMasochist() && GetRelation(Enemy
) == FRIEND
) return;
2387 if (!IsAlly(Enemy
)) {
2388 GetTeam()->Hostility(Enemy
->GetTeam());
2389 } else if (IsPlayer() && !Enemy
->IsPlayer()) {
2390 // I believe both may be players due to polymorph feature...
2391 if (Enemy
->CanBeSeenByPlayer()) ADD_MESSAGE("%s becomes enraged.", Enemy
->CHAR_NAME(DEFINITE
));
2392 Enemy
->ChangeTeam(game::GetTeam(BETRAYED_TEAM
));
2397 stack
*character::GetGiftStack () const {
2398 if (GetLSquareUnder()->GetRoomIndex() && !GetLSquareUnder()->GetRoom()->AllowDropGifts()) return GetStack();
2399 return GetStackUnder();
2403 truth
character::MoveRandomlyInRoom () {
2404 for (int c
= 0; c
< 10; ++c
) {
2405 v2 ToTry
= game::GetMoveVector(RAND()&7);
2406 if (GetLevel()->IsValidPos(GetPos()+ToTry
)) {
2407 lsquare
*Square
= GetNearLSquare(GetPos()+ToTry
);
2408 if (!Square
->IsDangerous(this) && !Square
->IsScary(this) &&
2409 (!Square
->GetOLTerrain() || !Square
->GetOLTerrain()->IsDoor()) &&
2410 TryMove(ToTry
, false, false)) return true;
2417 truth
character::IsPassableSquare (int x
, int y
) const {
2418 area
*ca
= GetSquareUnder()->GetArea();
2419 if (x
< 0 || y
< 0 || x
>= ca
->GetXSize() || y
>= ca
->GetYSize()) return false;
2420 lsquare
*sq
= static_cast<lsquare
*>(ca
->GetSquare(x
, y
));
2421 return sq
&& CanMoveOn(sq
);
2425 truth
character::IsInCorridor () const { return IsInCorridor(GetPos().X
, GetPos().Y
); }
2427 truth
character::IsInCorridor (int x
, int y
) const {
2428 if (!IsPassableSquare(x
, y
-1) && !IsPassableSquare(x
, y
+1)) return true;
2429 if (!IsPassableSquare(x
-1, y
) && !IsPassableSquare(x
+1, y
)) return true;
2430 if (IsPassableSquare(x
, y
-1) && IsPassableSquare(x
, y
+1) &&
2431 (IsPassableSquare(x
-1, y
) || IsPassableSquare(x
+1, y
))) return false;
2432 if (IsPassableSquare(x
-1, y
) && IsPassableSquare(x
+1, y
) &&
2433 (IsPassableSquare(x
, y
-1) || IsPassableSquare(x
, y
+1))) return false;
2434 if (!IsPassableSquare(x
-1, y
-1) && !IsPassableSquare(x
+1, y
-1) &&
2435 !IsPassableSquare(x
-1, y
+1) && !IsPassableSquare(x
+1, y
+1)) return true;
2440 truth
character::IsInCorridor (const v2 dir
) const {
2441 v2 nxy
= GetPos()+dir
;
2442 return IsInCorridor(nxy
.X
, nxy
.Y
);
2446 void character::GoOn (go
*Go
, truth FirstStep
) {
2447 //fprintf(stderr, "corridor: %s\n", IsInCorridor() ? "tan" : "ona");
2448 if (FirstStep
) Go
->SetIsWalkingInOpen(!IsInCorridor());
2450 v2 MoveVector
= ApplyStateModification(game::GetMoveVector(Go
->GetDirection()));
2451 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
2453 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, GetPos()+MoveVector
);
2454 if (!Squares
|| !CanMoveOn(MoveToSquare
[0])) {
2455 Go
->Terminate(false);
2459 if (!FirstStep
&& !Go
->IsWalkingInOpen() && !IsInCorridor(MoveVector
)) {
2460 Go
->Terminate(false);
2464 uInt OldRoomIndex
= GetLSquareUnder()->GetRoomIndex();
2465 uInt CurrentRoomIndex
= MoveToSquare
[0]->GetRoomIndex();
2467 if ((OldRoomIndex
&& (CurrentRoomIndex
!= OldRoomIndex
)) && !FirstStep
) {
2468 Go
->Terminate(false);
2472 for (int c
= 0; c
< Squares
; ++c
) {
2473 if ((MoveToSquare
[c
]->GetCharacter() && GetTeam() != MoveToSquare
[c
]->GetCharacter()->GetTeam()) || MoveToSquare
[c
]->IsDangerous(this)) {
2474 Go
->Terminate(false);
2490 static const int revDir
[8] = { 7, 6, 5, 4, 3, 3, 1, 0 };
2491 static const bool orthoDir
[8] = { false, true, false, true, true, false, true, false };
2493 int OKDirectionsCounter
= 0, gd
= Go
->GetDirection(), odc
= 0;
2494 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2495 lsquare
*Square
= GetNeighbourLSquare(d
);
2496 if (Square
&& CanMoveOn(Square
)) ++OKDirectionsCounter
;
2500 if (orthoDir
[gd
] && !Go
->IsWalkingInOpen()) {
2501 // check if there is dead end forward and we have only one turn
2502 v2 nxy
= GetPos()+MoveVector
;
2503 v2 nxyff
= nxy
+MoveVector
;
2505 lsquare
*sqf
= static_cast<lsquare
*>(GetSquareUnder()->GetArea()->GetSquare(nxy
));
2506 lsquare
*sqff
= static_cast<lsquare
*>(GetSquareUnder()->GetArea()->GetSquare(nxyff
));
2507 if (sqf
&& CanMoveOn(sqf
) && (!sqff
|| !CanMoveOn(sqff
))) {
2509 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2510 if (d
== gd
|| d
== revDir
[gd
] || !orthoDir
[d
]) continue;
2511 v2 sqxy
= nxy
+game::GetMoveVector(d
);
2512 lsquare
*sq
= static_cast<lsquare
*>(GetSquareUnder()->GetArea()->GetSquare(sqxy
));
2513 if (sq
&& CanMoveOn(sq
)) {
2519 //fprintf(stderr, "*: %d; nDir: %d\n", odc, nDir);
2522 bool doStop
= false;
2523 if (!Go
->IsWalkingInOpen()) {
2525 //fprintf(stderr, "dc: %d\n", OKDirectionsCounter);
2526 if (Go
->prevWasTurn()) {
2527 Go
->SetPrevWasTurn(false);
2528 } else if (odc
== 1) {
2529 // if we will step on something, do it
2530 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2531 lsquare
*Square
= GetNeighbourLSquare(d
);
2532 if (Square
&& Square
->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
2538 // follow the turn; 3: back, forward and turn
2543 if (gd
== 3) newDir
= 0;
2544 else if (gd
== 4) newDir
= 2;
2547 if (gd
== 1) newDir
= 0;
2548 else if (gd
== 6) newDir
= 5;
2551 if (gd
== 1) newDir
= 2;
2552 else if (gd
== 6) newDir
= 7;
2555 if (gd
== 3) newDir
= 5;
2556 else if (gd
== 4) newDir
= 7;
2559 //if (newDir < 0) ABORT("go error");
2560 if (newDir
< 0) { Go
->Terminate(false); return; }
2561 lsquare
*Square
= GetNeighbourLSquare(newDir
);
2562 if (Square
&& CanMoveOn(Square
)) {
2563 // fuckin' copypasta
2564 MoveVector
= ApplyStateModification(game::GetMoveVector(newDir
));
2565 int squares
= CalculateNewSquaresUnder(MoveToSquare
, GetPos()+MoveVector
);
2567 for (int c
= 0; c
< squares
; ++c
) {
2568 if ((MoveToSquare
[c
]->GetCharacter() && GetTeam() != MoveToSquare
[c
]->GetCharacter()->GetTeam()) || MoveToSquare
[c
]->IsDangerous(this)) {
2579 if (newDir
< 0) { Go
->Terminate(false); return; }
2581 //fprintf(stderr, "newDir: %d\n", newDir);
2582 Go
->SetDirection(newDir
);
2583 Go
->SetPrevWasTurn(true);
2584 v2 nxyf
= GetPos()+MoveVector
+game::GetMoveVector(newDir
);
2585 v2 nxyff
= nxyf
+game::GetMoveVector(newDir
);
2586 lsquare
*sqf
= static_cast<lsquare
*>(GetSquareUnder()->GetArea()->GetSquare(nxyf
));
2587 lsquare
*sqff
= static_cast<lsquare
*>(GetSquareUnder()->GetArea()->GetSquare(nxyff
));
2588 if (sqf
&& CanMoveOn(sqf
)) {
2589 Go
->SetPrevWasTurn(sqff
&& CanMoveOn(sqff
));
2592 } else if (!IsInCorridor()) {
2593 Go
->Terminate(false);
2597 Go
->SetPrevWasTurn(false);
2598 //if (OKDirectionsCounter <= 2) Go->SetIsWalkingInOpen(false);
2599 Go
->SetIsWalkingInOpen(!IsInCorridor());
2602 square
*BeginSquare
= GetSquareUnder();
2605 for (int c
= 0; c
< Squares
; ++c
) {
2606 if (MoveToSquare
[c
]->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
2613 truth moveOk
= TryMove(MoveVector
, true, game::PlayerIsRunning());
2614 if (!moveOk
|| BeginSquare
== GetSquareUnder() || (CurrentRoomIndex
&& (OldRoomIndex
!= CurrentRoomIndex
))) {
2616 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2617 game::DrawEverything();
2619 Go
->Terminate(false);
2623 if (doStop
/* || GetStackUnder()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())*/) {
2624 Go
->Terminate(false);
2626 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2628 game::DrawEverything();
2632 void character::SetTeam (team
*What
) {
2633 /*k8 if(Team) int esko = esko = 2; */
2635 SetTeamIterator(What
->Add(this));
2639 void character::ChangeTeam (team
*What
) {
2640 if (Team
) Team
->Remove(GetTeamIterator());
2642 SendNewDrawRequest();
2643 if (Team
) SetTeamIterator(Team
->Add(this));
2647 truth
character::ChangeRandomAttribute (int HowMuch
) {
2648 for (int c
= 0; c
< 50; ++c
) {
2649 int AttribID
= RAND()%ATTRIBUTES
;
2650 if (EditAttribute(AttribID
, HowMuch
)) return true;
2656 int character::RandomizeReply (sLong
&Said
, int Replies
) {
2657 truth NotSaid
= false;
2658 for (int c
= 0; c
< Replies
; ++c
) {
2659 if (!(Said
& (1 << c
))) {
2664 if (!NotSaid
) Said
= 0;
2666 while (Said
& 1 << (ToSay
= RAND() % Replies
));
2672 void character::DisplayInfo (festring
&Msg
) {
2674 Msg
<< " You are " << GetStandVerb() << " here.";
2676 Msg
<< ' ' << GetName(INDEFINITE
).CapitalizeCopy() << " is " << GetStandVerb() << " here. " << GetPersonalPronoun().CapitalizeCopy();
2677 cchar
*Separator1
= GetAction() ? "," : " and";
2678 cchar
*Separator2
= " and";
2679 if (GetTeam() == PLAYER
->GetTeam()) {
2682 int Relation
= GetRelation(PLAYER
);
2683 if (Relation
== HOSTILE
) Msg
<< " is hostile";
2684 else if (Relation
== UNCARING
) {
2685 Msg
<< " does not care about you";
2686 Separator1
= Separator2
= " and is";
2688 Msg
<< " is friendly";
2691 if (StateIsActivated(PANIC
)) {
2692 Msg
<< Separator1
<< " panicked";
2693 Separator2
= " and";
2695 if (GetAction()) Msg
<< Separator2
<< ' ' << GetAction()->GetDescription();
2701 void character::TestWalkability () {
2702 if (!IsEnabled()) return;
2703 square
*SquareUnder
= !game::IsInWilderness() ? GetSquareUnder() : PLAYER
->GetSquareUnder();
2704 if (SquareUnder
->IsFatalToStay() && !CanMoveOn(SquareUnder
)) {
2705 truth Alive
= false;
2706 if (!game::IsInWilderness() || IsPlayer()) {
2707 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2708 square
*Square
= GetNeighbourSquare(d
);
2709 if (Square
&& CanMoveOn(Square
) && IsFreeForMe(Square
)) {
2710 if (IsPlayer()) ADD_MESSAGE("%s.", SquareUnder
->SurviveMessage(this));
2711 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE
), SquareUnder
->MonsterSurviveMessage(this));
2712 Move(Square
->GetPos(), true); // actually, this shouldn't be a teleport move
2713 SquareUnder
->SurviveEffect(this);
2723 festring DeathMsg
= festring(SquareUnder
->DeathMessage(this));
2724 game::AskForEscPress(DeathMsg
+".");
2725 festring Msg
= SquareUnder
->ScoreEntry(this);
2726 PLAYER
->AddScoreEntry(Msg
);
2729 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE
), SquareUnder
->MonsterDeathVerb(this));
2730 Die(0, SquareUnder
->ScoreEntry(this), DISALLOW_MSG
);
2737 int character::GetSize () const {
2738 return GetTorso()->GetSize();
2742 void character::SetMainMaterial (material
*NewMaterial
, int SpecialFlags
) {
2743 NewMaterial
->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
2744 GetBodyPart(0)->SetMainMaterial(NewMaterial
, SpecialFlags
);
2745 for (int c
= 1; c
< BodyParts
; ++c
) {
2746 NewMaterial
= NewMaterial
->SpawnMore(GetBodyPart(c
)->GetMainMaterial()->GetVolume());
2747 GetBodyPart(c
)->SetMainMaterial(NewMaterial
, SpecialFlags
);
2752 void character::ChangeMainMaterial (material
*NewMaterial
, int SpecialFlags
) {
2753 NewMaterial
->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
2754 GetBodyPart(0)->ChangeMainMaterial(NewMaterial
, SpecialFlags
);
2755 for (int c
= 1; c
< BodyParts
; ++c
) {
2756 NewMaterial
= NewMaterial
->SpawnMore(GetBodyPart(c
)->GetMainMaterial()->GetVolume());
2757 GetBodyPart(c
)->ChangeMainMaterial(NewMaterial
, SpecialFlags
);
2762 void character::SetSecondaryMaterial (material
*, int) {
2763 ABORT("Illegal character::SetSecondaryMaterial call!");
2767 void character::ChangeSecondaryMaterial (material
*, int) {
2768 ABORT("Illegal character::ChangeSecondaryMaterial call!");
2772 void character::TeleportRandomly (truth Intentional
) {
2773 v2 TelePos
= ERROR_V2
;
2774 if (StateIsActivated(TELEPORT_CONTROL
)) {
2776 v2 Input
= game::PositionQuestion(CONST_S("Where do you wish to teleport? [direction keys move cursor, space accepts]"), GetPos(), &game::TeleportHandler
, 0, false);
2777 if (Input
== ERROR_V2
) Input
= GetPos(); // esc pressed
2778 lsquare
*Square
= GetNearLSquare(Input
);
2779 if (CanMoveOn(Square
) || game::GoThroughWallsCheatIsActive()) {
2780 if (Square
->GetPos() == GetPos()) {
2781 ADD_MESSAGE("You disappear and reappear.");
2784 if (IsFreeForMe(Square
)) {
2785 if ((Input
-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
2786 EditExperience(INTELLIGENCE
, 100, 1 << 10);
2789 ADD_MESSAGE("You cannot concentrate yourself enough to control a teleport that far.");
2792 character
*C
= Square
->GetCharacter();
2793 if (C
) ADD_MESSAGE("For a moment you feel very much like %s.", C
->CHAR_NAME(INDEFINITE
));
2794 else ADD_MESSAGE("You feel that something weird has happened, but can't really tell what exactly.");
2797 ADD_MESSAGE("You feel like having been hit by something really hard from the inside.");
2799 } else if (!Intentional
) {
2800 if (IsGoingSomeWhere() && GetLevel()->IsValidPos(GoingTo
)) {
2801 v2 Where
= GetLevel()->GetNearestFreeSquare(this, GoingTo
);
2802 if (Where
!= ERROR_V2
&& (Where
-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
2803 EditExperience(INTELLIGENCE
, 100, 1 << 10);
2811 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.");
2814 //if (TelePos != ERROR_V2) Move(TelePos, true);
2815 //else Move(GetLevel()->GetRandomSquare(this), true);
2816 //if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE));
2817 //if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
2819 if (TelePos
== ERROR_V2
) TelePos
= GetLevel()->GetRandomSquare(this);
2821 room
*PossibleRoom
= game::GetCurrentLevel()->GetLSquare(TelePos
)->GetRoom();
2823 if (!PossibleRoom
) {
2824 //if it's outside of a room
2825 if (TelePos
!= ERROR_V2
) Move(TelePos
, true);
2826 else Move(GetLevel()->GetRandomSquare(this), true);
2827 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE
));
2828 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
2829 } else if (PossibleRoom
&& PossibleRoom
->IsOKToTeleportInto()) {
2830 // If it's inside of a room, check whether a ward is active that might impede the player
2831 if (TelePos
!= ERROR_V2
) Move(TelePos
, true);
2832 else Move(GetLevel()->GetRandomSquare(this), true);
2833 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE
));
2834 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
2837 ADD_MESSAGE("A mighty force blasts you back to where you were standing. A ward prevents you from teleporting.");
2839 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);
2844 CONST_S("killed by an explosion triggered when attempting to teleport into room protected by a ward"),
2848 lsquare* Square = GetNearLSquare(GetPos());
2849 Square->DrawParticles(RED);
2850 Square->FireBall(Beam);*/
2855 void character::DoDetecting () {
2856 material
*TempMaterial
;
2859 festring Temp
= game::DefaultQuestion(CONST_S("What material do you want to detect?"), game::GetDefaultDetectMaterial());
2860 TempMaterial
= protosystem::CreateMaterial(Temp
);
2861 if (TempMaterial
) break;
2862 game::DrawEverythingNoBlit();
2865 level
*Level
= GetLevel();
2866 int Squares
= Level
->DetectMaterial(TempMaterial
);
2868 if (Squares
> GetAttribute(INTELLIGENCE
) * (25+RAND()%51)) {
2869 ADD_MESSAGE("An enormous burst of geographical information overwhelms your consciousness. Your mind cannot cope with it and your memories blur.");
2870 Level
->BlurMemory();
2871 BeginTemporaryState(CONFUSED
, 1000 + RAND() % 1000);
2872 EditExperience(INTELLIGENCE
, -100, 1 << 12);
2873 } else if (!Squares
) {
2874 ADD_MESSAGE("You feel a sudden urge to imagine the dark void of a starless night sky.");
2875 EditExperience(INTELLIGENCE
, 200, 1 << 12);
2877 ADD_MESSAGE("You feel attracted to all things made of %s.", TempMaterial
->GetName(false, false).CStr());
2878 game::PositionQuestion(CONST_S("Detecting material [direction keys move cursor, space exits]"), GetPos(), 0, 0, false);
2879 EditExperience(INTELLIGENCE
, 300, 1 << 12);
2882 delete TempMaterial
;
2883 Level
->CalculateLuminances();
2884 game::SendLOSUpdateRequest();
2888 void character::RestoreHP () {
2889 doforbodyparts()(this, &bodypart::FastRestoreHP
);
2894 void character::RestoreLivingHP () {
2896 for (int c
= 0; c
< BodyParts
; ++c
) {
2897 bodypart
*BodyPart
= GetBodyPart(c
);
2898 if (BodyPart
&& BodyPart
->CanRegenerate()) {
2899 BodyPart
->FastRestoreHP();
2900 HP
+= BodyPart
->GetHP();
2906 truth
character::AllowDamageTypeBloodSpill (int Type
) {
2907 switch (Type
&0xFFF) {
2908 case PHYSICAL_DAMAGE
:
2917 case MUSTARD_GAS_DAMAGE
:
2921 ABORT("Unknown blood effect destroyed the dungeon!");
2926 /* Returns truly done damage */
2927 int character::ReceiveBodyPartDamage (character
*Damager
, int Damage
, int Type
, int BodyPartIndex
,
2928 int Direction
, truth PenetrateResistance
, truth Critical
, truth ShowNoDamageMsg
, truth CaptureBodyPart
)
2930 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
2931 if (!Damager
|| Damager
->AttackMayDamageArmor()) BodyPart
->DamageArmor(Damager
, Damage
, Type
);
2932 if (!PenetrateResistance
) {
2933 Damage
-= (BodyPart
->GetTotalResistance(Type
)>>1)+RAND()%((BodyPart
->GetTotalResistance(Type
)>>1)+1);
2935 if (int(Damage
) < 1) {
2939 if (ShowNoDamageMsg
) {
2940 if (IsPlayer()) ADD_MESSAGE("You are not hurt.");
2941 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is not hurt.", GetPersonalPronoun().CStr());
2947 if (Critical
&& AllowDamageTypeBloodSpill(Type
) && !game::IsInWilderness()) {
2948 BodyPart
->SpillBlood(2+(RAND()&1));
2949 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2950 lsquare
*Square
= GetNeighbourLSquare(d
);
2951 if (Square
&& Square
->IsFlyable()) BodyPart
->SpillBlood(1, Square
->GetPos());
2955 if (BodyPart
->ReceiveDamage(Damager
, Damage
, Type
, Direction
) && BodyPartCanBeSevered(BodyPartIndex
)) {
2956 if (DamageTypeDestroysBodyPart(Type
)) {
2957 if (IsPlayer()) ADD_MESSAGE("Your %s is destroyed!", BodyPart
->GetBodyPartName().CStr());
2958 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is destroyed!", GetPossessivePronoun().CStr(), BodyPart
->GetBodyPartName().CStr());
2959 GetBodyPart(BodyPartIndex
)->DropEquipment();
2960 item
*Severed
= SevereBodyPart(BodyPartIndex
);
2961 if (Severed
) Severed
->DestroyBodyPart(!game::IsInWilderness() ? GetStackUnder() : GetStack());
2962 SendNewDrawRequest();
2963 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart destroyed!"));
2965 if (IsPlayer()) ADD_MESSAGE("Your %s is severed off!", BodyPart
->GetBodyPartName().CStr());
2966 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is severed off!", GetPossessivePronoun().CStr(), BodyPart
->GetBodyPartName().CStr());
2967 item
*Severed
= SevereBodyPart(BodyPartIndex
);
2968 SendNewDrawRequest();
2970 if (CaptureBodyPart
) {
2971 Damager
->GetLSquareUnder()->AddItem(Severed
);
2972 } else if (!game::IsInWilderness()) {
2973 /** No multi-tile humanoid support! */
2974 GetStackUnder()->AddItem(Severed
);
2975 if (Direction
!= YOURSELF
) Severed
->Fly(0, Direction
, Damage
);
2977 GetStack()->AddItem(Severed
);
2979 Severed
->DropEquipment();
2980 } else if (IsPlayer() || CanBeSeenByPlayer()) {
2981 ADD_MESSAGE("It vanishes.");
2983 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart severed!"));
2985 if (CanPanicFromSeveredBodyPart() && RAND()%100 < GetPanicLevel() && !StateIsActivated(PANIC
) && !IsDead()) {
2986 BeginTemporaryState(PANIC
, 1000+RAND()%1001);
2988 SpecialBodyPartSeverReaction();
2991 if (!IsDead()) CheckPanic(500);
2997 /* Returns 0 if bodypart disappears */
2998 item
*character::SevereBodyPart (int BodyPartIndex
, truth ForceDisappearance
, stack
*EquipmentDropStack
) {
2999 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
3000 if (StateIsActivated(LEPROSY
)) BodyPart
->GetMainMaterial()->SetIsInfectedByLeprosy(true);
3001 if (ForceDisappearance
|| BodyPartsDisappearWhenSevered() || StateIsActivated(POLYMORPHED
) || game::AllBodyPartsVanish()) {
3002 BodyPart
->DropEquipment(EquipmentDropStack
);
3003 BodyPart
->RemoveFromSlot();
3004 CalculateAttributeBonuses();
3005 CalculateBattleInfo();
3006 BodyPart
->SendToHell();
3007 SignalPossibleTransparencyChange();
3008 RemoveTraps(BodyPartIndex
);
3011 BodyPart
->SetOwnerDescription("of " + GetName(INDEFINITE
));
3012 BodyPart
->SetIsUnique(LeftOversAreUnique());
3013 UpdateBodyPartPicture(BodyPartIndex
, true);
3014 BodyPart
->RemoveFromSlot();
3015 BodyPart
->RandomizePosition();
3016 CalculateAttributeBonuses();
3017 CalculateBattleInfo();
3019 SignalPossibleTransparencyChange();
3020 RemoveTraps(BodyPartIndex
);
3025 /* The second int is actually TargetFlags, which is not used here, but seems to be used in humanoid::ReceiveDamage.
3026 * Returns true if the character really receives damage */
3027 truth
character::ReceiveDamage (character
*Damager
, int Damage
, int Type
, int, int Direction
,
3028 truth
, truth PenetrateArmor
, truth Critical
, truth ShowMsg
)
3030 truth Affected
= ReceiveBodyPartDamage(Damager
, Damage
, Type
, 0, Direction
, PenetrateArmor
, Critical
, ShowMsg
);
3031 if (DamageTypeAffectsInventory(Type
)) {
3032 for (int c
= 0; c
< GetEquipments(); ++c
) {
3033 item
*Equipment
= GetEquipment(c
);
3034 if (Equipment
) Equipment
->ReceiveDamage(Damager
, Damage
, Type
);
3036 GetStack()->ReceiveDamage(Damager
, Damage
, Type
);
3042 festring
character::GetDescription (int Case
) const {
3043 if (IsPlayer()) return CONST_S("you");
3044 if (CanBeSeenByPlayer()) return GetName(Case
);
3045 return CONST_S("something");
3049 festring
character::GetPersonalPronoun (truth PlayersView
) const {
3050 if (IsPlayer() && PlayersView
) return CONST_S("you");
3051 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3052 if (GetSex() == MALE
) return CONST_S("he");
3053 return CONST_S("she");
3057 festring
character::GetPossessivePronoun (truth PlayersView
) const {
3058 if (IsPlayer() && PlayersView
) return CONST_S("your");
3059 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("its");
3060 if (GetSex() == MALE
) return CONST_S("his");
3061 return CONST_S("her");
3065 festring
character::GetObjectPronoun (truth PlayersView
) const {
3066 if (IsPlayer() && PlayersView
) return CONST_S("you");
3067 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3068 if (GetSex() == MALE
) return CONST_S("him");
3069 return CONST_S("her");
3073 void character::AddName (festring
&String
, int Case
) const {
3074 if (AssignedName
.IsEmpty()) {
3075 id::AddName(String
, Case
);
3076 } else if (!(Case
& PLURAL
)) {
3077 if (!ShowClassDescription()) {
3078 String
<< AssignedName
;
3080 String
<< AssignedName
<< ' ';
3081 id::AddName(String
, (Case
|ARTICLE_BIT
)&~INDEFINE_BIT
);
3084 id::AddName(String
, Case
);
3085 String
<< " named " << AssignedName
;
3090 int character::GetHungerState () const {
3091 if (!UsesNutrition()) return NOT_HUNGRY
;
3092 if (GetNP() > OVER_FED_LEVEL
) return OVER_FED
;
3093 if (GetNP() > BLOATED_LEVEL
) return BLOATED
;
3094 if (GetNP() > SATIATED_LEVEL
) return SATIATED
;
3095 if (GetNP() > NOT_HUNGER_LEVEL
) return NOT_HUNGRY
;
3096 if (GetNP() > HUNGER_LEVEL
) return HUNGRY
;
3097 if (GetNP() > VERY_HUNGER_LEVEL
) return VERY_HUNGRY
;
3102 truth
character::CanConsume (material
*Material
) const {
3103 return GetConsumeFlags() & Material
->GetConsumeType();
3107 void character::SetTemporaryStateCounter (sLong State
, int What
) {
3108 for (int c
= 0; c
< STATES
; ++c
) {
3109 if ((1 << c
) & State
) TemporaryStateCounter
[c
] = What
;
3114 void character::EditTemporaryStateCounter (sLong State
, int What
) {
3115 for (int c
= 0; c
< STATES
; ++c
) {
3116 if ((1 << c
) & State
) TemporaryStateCounter
[c
] += What
;
3121 int character::GetTemporaryStateCounter (sLong State
) const {
3122 for (int c
= 0; c
< STATES
; ++c
) {
3123 if ((1 << c
) & State
) return TemporaryStateCounter
[c
];
3125 ABORT("Illegal GetTemporaryStateCounter request!");
3130 truth
character::CheckKick () const {
3132 if (IsPlayer()) ADD_MESSAGE("This race can't kick.");
3139 int character::GetResistance (int Type
) const {
3140 switch (Type
&0xFFF) {
3141 case PHYSICAL_DAMAGE
:
3144 case MUSTARD_GAS_DAMAGE
:
3147 case ENERGY
: return GetEnergyResistance();
3148 case FIRE
: return GetFireResistance();
3149 case POISON
: return GetPoisonResistance();
3150 case ELECTRICITY
: return GetElectricityResistance();
3151 case ACID
: return GetAcidResistance();
3153 ABORT("Resistance lack detected!");
3158 void character::Regenerate () {
3159 if (HP
== MaxHP
) return;
3160 sLong RegenerationBonus
= 0;
3161 truth NoHealableBodyParts
= true;
3162 for (int c
= 0; c
< BodyParts
; ++c
) {
3163 bodypart
*BodyPart
= GetBodyPart(c
);
3164 if (BodyPart
&& BodyPart
->CanRegenerate()) {
3165 RegenerationBonus
+= BodyPart
->GetMaxHP();
3166 if (NoHealableBodyParts
&& BodyPart
->GetHP() < BodyPart
->GetMaxHP()) NoHealableBodyParts
= false;
3169 if (!RegenerationBonus
|| NoHealableBodyParts
) return;
3170 RegenerationBonus
*= (50+GetAttribute(ENDURANCE
));
3172 if (Action
&& Action
->IsRest()) {
3173 if (SquaresUnder
== 1) RegenerationBonus
*= GetSquareUnder()->GetRestModifier() << 1;
3175 int Lowest
= GetSquareUnder(0)->GetRestModifier();
3176 for (int c
= 1; c
< GetSquaresUnder(); ++c
) {
3177 int Mod
= GetSquareUnder(c
)->GetRestModifier();
3178 if (Mod
< Lowest
) Lowest
= Mod
;
3180 RegenerationBonus
*= Lowest
<< 1;
3184 RegenerationCounter
+= RegenerationBonus
;
3186 while (RegenerationCounter
> 1250000) {
3187 bodypart
*BodyPart
= HealHitPoint();
3188 if (!BodyPart
) break;
3189 EditNP(-Max(7500/MaxHP
, 1));
3190 RegenerationCounter
-= 1250000;
3191 int HP
= BodyPart
->GetHP();
3192 EditExperience(ENDURANCE
, Min(1000*BodyPart
->GetMaxHP()/(HP
*HP
), 300), 1000);
3197 void character::PrintInfo () const {
3198 felist
Info(CONST_S("Information about ")+GetName(DEFINITE
));
3199 for (int c
= 0; c
< GetEquipments(); ++c
) {
3200 item
*Equipment
= GetEquipment(c
);
3201 if ((EquipmentEasilyRecognized(c
) || game::WizardModeIsActive()) && Equipment
) {
3202 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
3203 Info
.AddEntry(festring(GetEquipmentName(c
))+": "+Equipment
->GetName(INDEFINITE
), LIGHT_GRAY
, 0, ImageKey
, true);
3206 if (Info
.IsEmpty()) {
3207 ADD_MESSAGE("There's nothing special to tell about %s.", CHAR_NAME(DEFINITE
));
3209 game::SetStandardListAttributes(Info
);
3210 Info
.SetEntryDrawer(game::ItemEntryDrawer
);
3213 game::ClearItemDrawVector();
3217 truth
character::TryToRiseFromTheDead () {
3218 for (int c
= 0; c
< BodyParts
; ++c
) {
3219 bodypart
*BodyPart
= GetBodyPart(c
);
3221 BodyPart
->ResetSpoiling();
3222 if (BodyPart
->CanRegenerate() || BodyPart
->GetHP() < 1) BodyPart
->SetHP(1);
3230 truth
character::RaiseTheDead (character
*) {
3231 truth Useful
= false;
3232 for (int c
= 0; c
< BodyParts
; ++c
) {
3233 bodypart
*BodyPart
= GetBodyPart(c
);
3234 if (!BodyPart
&& CanCreateBodyPart(c
)) {
3235 CreateBodyPart(c
)->SetHP(1);
3236 if (IsPlayer()) ADD_MESSAGE("Suddenly you grow a new %s.", GetBodyPartName(c
).CStr());
3237 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), GetBodyPartName(c
).CStr());
3239 } else if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < 1) {
3244 if (IsPlayer()) ADD_MESSAGE("You shudder.");
3245 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s shudders.", CHAR_NAME(DEFINITE
));
3251 void character::SetSize (int Size
) {
3252 for (int c
= 0; c
< BodyParts
; ++c
) {
3253 bodypart
*BodyPart
= GetBodyPart(c
);
3254 if (BodyPart
) BodyPart
->SetSize(GetBodyPartSize(c
, Size
));
3259 sLong
character::GetBodyPartSize (int I
, int TotalSize
) const {
3260 if (I
== TORSO_INDEX
) return TotalSize
;
3261 ABORT("Weird bodypart size request for a character!");
3266 sLong
character::GetBodyPartVolume (int I
) const {
3267 if (I
== TORSO_INDEX
) return GetTotalVolume();
3268 ABORT("Weird bodypart volume request for a character!");
3273 void character::CreateBodyParts (int SpecialFlags
) {
3274 for (int c
= 0; c
< BodyParts
; ++c
) if (CanCreateBodyPart(c
)) CreateBodyPart(c
, SpecialFlags
);
3278 void character::RestoreBodyParts () {
3279 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) CreateBodyPart(c
);
3283 void character::UpdatePictures () {
3284 if (!PictureUpdatesAreForbidden()) for (int c
= 0; c
< BodyParts
; ++c
) UpdateBodyPartPicture(c
, false);
3288 bodypart
*character::MakeBodyPart (int I
) const {
3289 if (I
== TORSO_INDEX
) return normaltorso::Spawn(0, NO_MATERIALS
);
3290 ABORT("Weird bodypart to make for a character!");
3295 bodypart
*character::CreateBodyPart (int I
, int SpecialFlags
) {
3296 bodypart
*BodyPart
= MakeBodyPart(I
);
3297 material
*Material
= CreateBodyPartMaterial(I
, GetBodyPartVolume(I
));
3298 BodyPart
->InitMaterials(Material
, false);
3299 BodyPart
->SetSize(GetBodyPartSize(I
, GetTotalSize()));
3300 BodyPart
->SetBloodMaterial(GetBloodMaterial());
3301 BodyPart
->SetNormalMaterial(Material
->GetConfig());
3302 SetBodyPart(I
, BodyPart
);
3303 BodyPart
->InitSpecialAttributes();
3304 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdateBodyPartPicture(I
, false);
3305 if (!IsInitializing()) {
3306 CalculateBattleInfo();
3307 SendNewDrawRequest();
3308 SignalPossibleTransparencyChange();
3314 v2
character::GetBodyPartBitmapPos (int I
, truth
) const {
3315 if (I
== TORSO_INDEX
) return GetTorsoBitmapPos();
3316 ABORT("Weird bodypart BitmapPos request for a character!");
3321 void character::UpdateBodyPartPicture (int I
, truth Severed
) {
3322 bodypart
*BP
= GetBodyPart(I
);
3324 BP
->SetBitmapPos(GetBodyPartBitmapPos(I
, Severed
));
3325 BP
->GetMainMaterial()->SetSkinColor(GetBodyPartColorA(I
, Severed
));
3326 BP
->GetMainMaterial()->SetSkinColorIsSparkling(GetBodyPartSparkleFlags(I
) & SPARKLING_A
);
3327 BP
->SetMaterialColorB(GetBodyPartColorB(I
, Severed
));
3328 BP
->SetMaterialColorC(GetBodyPartColorC(I
, Severed
));
3329 BP
->SetMaterialColorD(GetBodyPartColorD(I
, Severed
));
3330 BP
->SetSparkleFlags(GetBodyPartSparkleFlags(I
));
3331 BP
->SetSpecialFlags(GetSpecialBodyPartFlags(I
));
3332 BP
->SetWobbleData(GetBodyPartWobbleData(I
));
3333 BP
->UpdatePictures();
3338 void character::LoadDataBaseStats () {
3339 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) {
3340 BaseExperience
[c
] = DataBase
->NaturalExperience
[c
];
3341 if (BaseExperience
[c
]) LimitRef(BaseExperience
[c
], MIN_EXP
, MAX_EXP
);
3343 SetMoney(GetDefaultMoney());
3344 SetInitialSweatMaterial(GetSweatMaterial());
3345 const fearray
<sLong
> &Skills
= GetKnownCWeaponSkills();
3347 const fearray
<sLong
> &Hits
= GetCWeaponSkillHits();
3348 if (Hits
.Size
== 1) {
3349 for (uInt c
= 0; c
< Skills
.Size
; ++c
) {
3350 if (Skills
[c
] < AllowedWeaponSkillCategories
) CWeaponSkill
[Skills
[c
]].AddHit(Hits
[0]*100);
3352 } else if (Hits
.Size
== Skills
.Size
) {
3353 for (uInt c
= 0; c
< Skills
.Size
; ++c
) {
3354 if (Skills
[c
] < AllowedWeaponSkillCategories
) CWeaponSkill
[Skills
[c
]].AddHit(Hits
[c
]*100);
3357 ABORT("Illegal weapon skill hit array size detected!");
3363 character
*characterprototype::SpawnAndLoad (inputfile
&SaveFile
) const {
3364 character
*Char
= Spawner(0, LOAD
);
3365 Char
->Load(SaveFile
);
3366 Char
->CalculateAll();
3371 void character::Initialize (int NewConfig
, int SpecialFlags
) {
3372 Flags
|= C_INITIALIZING
|C_IN_NO_MSG_MODE
;
3373 CalculateBodyParts();
3374 CalculateAllowedWeaponSkillCategories();
3375 CalculateSquaresUnder();
3376 BodyPartSlot
= new bodypartslot
[BodyParts
];
3377 OriginalBodyPartID
= new std::list
<feuLong
>[BodyParts
];
3378 CWeaponSkill
= new cweaponskill
[AllowedWeaponSkillCategories
];
3379 SquareUnder
= new square
*[SquaresUnder
];
3381 if (SquaresUnder
== 1) *SquareUnder
= 0; else memset(SquareUnder
, 0, SquaresUnder
*sizeof(square
*));
3383 for (int c
= 0; c
< BodyParts
; ++c
) BodyPartSlot
[c
].SetMaster(this);
3385 if (!(SpecialFlags
& LOAD
)) {
3386 ID
= game::CreateNewCharacterID(this);
3387 databasecreator
<character
>::InstallDataBase(this, NewConfig
);
3388 LoadDataBaseStats();
3389 TemporaryState
|= GetClassStates();
3390 if (TemporaryState
) {
3391 for (int c
= 0; c
< STATES
; ++c
) if (TemporaryState
& (1 << c
)) TemporaryStateCounter
[c
] = PERMANENT
;
3394 CreateBodyParts(SpecialFlags
| NO_PIC_UPDATE
);
3395 InitSpecialAttributes();
3396 CommandFlags
= GetDefaultCommandFlags();
3398 if (GetAttribute(INTELLIGENCE
, false) < 8) CommandFlags
&= ~DONT_CONSUME_ANYTHING_VALUABLE
; // gum
3399 if (!GetDefaultName().IsEmpty()) SetAssignedName(GetDefaultName());
3402 if (!(SpecialFlags
& LOAD
)) PostConstruct();
3404 if (!(SpecialFlags
& LOAD
)) {
3405 if (!(SpecialFlags
& NO_EQUIPMENT
)) CreateInitialEquipment((SpecialFlags
& NO_EQUIPMENT_PIC_UPDATE
) >> 1);
3406 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdatePictures();
3412 Flags
&= ~(C_INITIALIZING
|C_IN_NO_MSG_MODE
);
3416 truth
character::TeleportNear (character
*Caller
) {
3417 v2 Where
= GetLevel()->GetNearestFreeSquare(this, Caller
->GetPos());
3418 if (Where
== ERROR_V2
) return false;
3424 void character::ReceiveHeal (sLong Amount
) {
3426 for (c
= 0; c
< Amount
/ 10; ++c
) if (!HealHitPoint()) break;
3428 if (RAND()%10 < Amount
) HealHitPoint();
3429 if (Amount
>= 250 || RAND()%250 < Amount
) {
3430 bodypart
*NewBodyPart
= GenerateRandomBodyPart();
3431 if (!NewBodyPart
) return;
3432 NewBodyPart
->SetHP(1);
3433 if (IsPlayer()) ADD_MESSAGE("You grow a new %s.", NewBodyPart
->GetBodyPartName().CStr());
3434 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), NewBodyPart
->GetBodyPartName().CStr());
3439 void character::AddHealingLiquidConsumeEndMessage () const {
3440 if (IsPlayer()) ADD_MESSAGE("You feel better.");
3441 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks healthier.", CHAR_NAME(DEFINITE
));
3445 void character::ReceiveSchoolFood (sLong SizeOfEffect
) {
3446 SizeOfEffect
+= RAND()%SizeOfEffect
;
3447 if (SizeOfEffect
>= 250) VomitAtRandomDirection(SizeOfEffect
);
3448 if (!(RAND() % 3) && SizeOfEffect
>= 500 && EditAttribute(ENDURANCE
, SizeOfEffect
/500)) {
3449 if (IsPlayer()) ADD_MESSAGE("You gain a little bit of toughness for surviving this stuff.");
3450 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks tougher.", CHAR_NAME(DEFINITE
));
3452 BeginTemporaryState(POISONED
, (SizeOfEffect
>>1));
3456 void character::AddSchoolFoodConsumeEndMessage () const {
3457 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff tasted like vomit and old mousepads.");
3461 void character::AddSchoolFoodHitMessage () const {
3462 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff feels like vomit and old mousepads.");
3466 void character::ReceiveNutrition (sLong SizeOfEffect
) {
3467 EditNP(SizeOfEffect
);
3471 void character::ReceiveOmmelUrine (sLong Amount
) {
3472 EditExperience(ARM_STRENGTH
, 500, Amount
<<4);
3473 EditExperience(LEG_STRENGTH
, 500, Amount
<<4);
3474 if (IsPlayer()) game::DoEvilDeed(Amount
/25);
3478 void character::ReceiveOmmelCerumen (sLong Amount
) {
3479 EditExperience(INTELLIGENCE
, 500, Amount
<< 5);
3480 EditExperience(WISDOM
, 500, Amount
<< 5);
3481 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3485 void character::ReceiveOmmelSweat (sLong Amount
) {
3486 EditExperience(AGILITY
, 500, Amount
<< 4);
3487 EditExperience(DEXTERITY
, 500, Amount
<< 4);
3489 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3493 void character::ReceiveOmmelTears (sLong Amount
) {
3494 EditExperience(PERCEPTION
, 500, Amount
<< 4);
3495 EditExperience(CHARISMA
, 500, Amount
<< 4);
3496 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3500 void character::ReceiveOmmelSnot (sLong Amount
) {
3501 EditExperience(ENDURANCE
, 500, Amount
<< 5);
3503 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3507 void character::ReceiveOmmelBone (sLong Amount
) {
3508 EditExperience(ARM_STRENGTH
, 500, Amount
<< 6);
3509 EditExperience(LEG_STRENGTH
, 500, Amount
<< 6);
3510 EditExperience(DEXTERITY
, 500, Amount
<< 6);
3511 EditExperience(AGILITY
, 500, Amount
<< 6);
3512 EditExperience(ENDURANCE
, 500, Amount
<< 6);
3513 EditExperience(PERCEPTION
, 500, Amount
<< 6);
3514 EditExperience(INTELLIGENCE
, 500, Amount
<< 6);
3515 EditExperience(WISDOM
, 500, Amount
<< 6);
3516 EditExperience(CHARISMA
, 500, Amount
<< 6);
3519 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3523 void character::AddOmmelConsumeEndMessage () const {
3524 if (IsPlayer()) ADD_MESSAGE("You feel a primitive force coursing through your veins.");
3525 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks more powerful.", CHAR_NAME(DEFINITE
));
3529 void character::ReceivePepsi (sLong Amount
) {
3530 ReceiveDamage(0, Amount
/ 100, POISON
, TORSO
);
3531 EditExperience(PERCEPTION
, Amount
, 1 << 14);
3532 if (CheckDeath(CONST_S("was poisoned by pepsi"), 0)) return;
3533 if (IsPlayer()) game::DoEvilDeed(Amount
/ 10);
3537 void character::AddPepsiConsumeEndMessage () const {
3538 if (IsPlayer()) ADD_MESSAGE("Urgh. You feel your guruism fading away.");
3539 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very lame.", CHAR_NAME(DEFINITE
));
3543 void character::ReceiveDarkness (sLong Amount
) {
3544 EditExperience(INTELLIGENCE
, -Amount
/ 5, 1 << 13);
3545 EditExperience(WISDOM
, -Amount
/ 5, 1 << 13);
3546 EditExperience(CHARISMA
, -Amount
/ 5, 1 << 13);
3547 if (IsPlayer()) game::DoEvilDeed(int(Amount
/ 50));
3551 void character::AddFrogFleshConsumeEndMessage () const {
3552 if (IsPlayer()) ADD_MESSAGE("Arg. You feel the fate of a navastater placed upon you...");
3553 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks like a navastater.", CHAR_NAME(DEFINITE
));
3557 void character::ReceiveKoboldFlesh (sLong
) {
3558 /* As it is commonly known, the possibility of fainting per 500 cubic
3559 centimeters of kobold flesh is exactly 5%. */
3560 if (!(RAND() % 20)) {
3561 if (IsPlayer()) ADD_MESSAGE("You lose control of your legs and fall down.");
3562 LoseConsciousness(250 + RAND_N(250));
3567 void character::AddKoboldFleshConsumeEndMessage () const {
3568 if (IsPlayer()) ADD_MESSAGE("This stuff tasted really funny.");
3572 void character::AddKoboldFleshHitMessage () const {
3573 if (IsPlayer()) ADD_MESSAGE("You feel very funny.");
3577 void character::AddBoneConsumeEndMessage () const {
3578 if (IsPlayer()) ADD_MESSAGE("You feel like a hippie.");
3579 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s barks happily.", CHAR_NAME(DEFINITE
)); // this suspects that nobody except dogs can eat bones
3582 truth
character::RawEditAttribute (double &Experience
, int Amount
) const {
3583 /* Check if the attribute is disabled for creature */
3584 if (!Experience
) return false;
3585 if ((Amount
< 0 && Experience
< 2 * EXP_MULTIPLIER
) || (Amount
> 0 && Experience
> 999 * EXP_MULTIPLIER
)) return false;
3586 Experience
+= Amount
* EXP_MULTIPLIER
;
3587 LimitRef
<double>(Experience
, MIN_EXP
, MAX_EXP
);
3592 void character::DrawPanel (truth AnimationDraw
) const {
3593 if (AnimationDraw
) { DrawStats(true); return; }
3594 igraph::BlitBackGround(v2(19 + (game::GetScreenXSize() << 4), 0), v2(RES
.X
- 19 - (game::GetScreenXSize() << 4), RES
.Y
));
3595 igraph::BlitBackGround(v2(16, 45 + (game::GetScreenYSize() << 4)), v2(game::GetScreenXSize() << 4, 9));
3596 FONT
->Printf(DOUBLE_BUFFER
, v2(16, 45 + (game::GetScreenYSize() << 4)), WHITE
, "%s", GetPanelName().CStr());
3597 game::UpdateAttributeMemory();
3598 int PanelPosX
= RES
.X
- 96;
3599 int PanelPosY
= DrawStats(false);
3600 PrintAttribute("End", ENDURANCE
, PanelPosX
, PanelPosY
++);
3601 PrintAttribute("Per", PERCEPTION
, PanelPosX
, PanelPosY
++);
3602 PrintAttribute("Int", INTELLIGENCE
, PanelPosX
, PanelPosY
++);
3603 PrintAttribute("Wis", WISDOM
, PanelPosX
, PanelPosY
++);
3604 PrintAttribute("Wil", WILL_POWER
, PanelPosX
, PanelPosY
++);
3605 PrintAttribute("Cha", CHARISMA
, PanelPosX
, PanelPosY
++);
3606 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Siz %d", GetSize());
3607 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), IsInBadCondition() ? RED
: WHITE
, "HP %d/%d", GetHP(), GetMaxHP());
3609 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Gold: %d", GetMoney());
3612 if (game::IsInWilderness())
3613 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Worldmap");
3615 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", game::GetCurrentDungeon()->GetShortLevelDescription(game::GetCurrentLevelIndex()).CapitalizeCopy().CStr());
3618 game::GetTime(Time
);
3619 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Day %d", Time
.Day
);
3620 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Time %d:%s%d", Time
.Hour
, Time
.Min
< 10 ? "0" : "", Time
.Min
);
3621 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Turn %d", game::GetTurn());
3626 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", festring(GetAction()->GetDescription()).CapitalizeCopy().CStr());
3628 for (int c
= 0; c
< STATES
; ++c
)
3629 if (!(StateData
[c
].Flags
& SECRET
) && StateIsActivated(1 << c
) && (1 << c
!= HASTE
|| !StateIsActivated(SLOW
)) && (1 << c
!= SLOW
|| !StateIsActivated(HASTE
)))
3630 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), (1 << c
) & EquipmentState
|| TemporaryStateCounter
[c
] == PERMANENT
? BLUE
: WHITE
, "%s", StateData
[c
].Description
);
3632 /* Make this more elegant!!! */
3633 if (GetHungerState() == STARVING
)
3634 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Starving");
3635 else if (GetHungerState() == VERY_HUNGRY
)
3636 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Very hungry");
3637 else if (GetHungerState() == HUNGRY
)
3638 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Hungry");
3639 else if (GetHungerState() == SATIATED
)
3640 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Satiated");
3641 else if (GetHungerState() == BLOATED
)
3642 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Bloated");
3643 else if (GetHungerState() == OVER_FED
)
3644 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Overfed!");
3646 switch (GetBurdenState()) {
3648 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Overload!");
3651 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Stressed");
3654 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Burdened");
3658 switch (GetTirednessState()) {
3660 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Fainting");
3663 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Exhausted");
3667 if (game::PlayerIsRunning()) {
3668 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", GetRunDescriptionLine(0));
3669 cchar
*SecondLine
= GetRunDescriptionLine(1);
3670 if (strlen(SecondLine
)) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", SecondLine
);
3675 void character::CalculateDodgeValue () {
3676 DodgeValue
= 0.05 * GetMoveEase() * GetAttribute(AGILITY
) / sqrt(GetSize());
3677 if (IsFlying()) DodgeValue
*= 2;
3678 if (DodgeValue
< 1) DodgeValue
= 1;
3682 truth
character::DamageTypeAffectsInventory (int Type
) {
3683 switch (Type
&0xFFF) {
3690 case PHYSICAL_DAMAGE
:
3693 case MUSTARD_GAS_DAMAGE
:
3697 ABORT("Unknown reaping effect destroyed dungeon!");
3702 int character::CheckForBlockWithArm (character
*Enemy
, item
*Weapon
, arm
*Arm
,
3703 double WeaponToHitValue
, int Damage
, int Success
, int Type
)
3705 int BlockStrength
= Arm
->GetBlockCapability();
3706 double BlockValue
= Arm
->GetBlockValue();
3707 if (BlockStrength
&& BlockValue
) {
3708 item
*Blocker
= Arm
->GetWielded();
3709 if (RAND() % int(100+WeaponToHitValue
/BlockValue
/(1<<BlocksSinceLastTurn
)*(100+Success
)) < 100) {
3710 int NewDamage
= BlockStrength
< Damage
? Damage
-BlockStrength
: 0;
3712 case UNARMED_ATTACK
: AddBlockMessage(Enemy
, Blocker
, Enemy
->UnarmedHitNoun(), NewDamage
); break;
3713 case WEAPON_ATTACK
: AddBlockMessage(Enemy
, Blocker
, "attack", NewDamage
); break;
3714 case KICK_ATTACK
: AddBlockMessage(Enemy
, Blocker
, Enemy
->KickNoun(), NewDamage
); break;
3715 case BITE_ATTACK
: AddBlockMessage(Enemy
, Blocker
, Enemy
->BiteNoun(), NewDamage
); break;
3717 sLong Weight
= Blocker
->GetWeight();
3718 sLong StrExp
= Limit(15 * Weight
/ 200, 75, 300);
3719 sLong DexExp
= Weight
? Limit(75000 / Weight
, 75, 300) : 300;
3720 Arm
->EditExperience(ARM_STRENGTH
, StrExp
, 1 << 8);
3721 Arm
->EditExperience(DEXTERITY
, DexExp
, 1 << 8);
3722 EditStamina(-10000 / GetAttribute(ARM_STRENGTH
), false);
3723 if (Arm
->TwoHandWieldIsActive()) {
3724 arm
*PairArm
= Arm
->GetPairArm();
3725 PairArm
->EditExperience(ARM_STRENGTH
, StrExp
, 1 << 8);
3726 PairArm
->EditExperience(DEXTERITY
, DexExp
, 1 << 8);
3728 Blocker
->WeaponSkillHit(Enemy
->CalculateWeaponSkillHits(this));
3729 Blocker
->ReceiveDamage(this, Damage
, PHYSICAL_DAMAGE
);
3730 Blocker
->BlockEffect(this, Enemy
, Weapon
, Type
);
3731 if (Weapon
) Weapon
->ReceiveDamage(Enemy
, Damage
- NewDamage
, PHYSICAL_DAMAGE
);
3732 if (BlocksSinceLastTurn
< 16) ++BlocksSinceLastTurn
;
3740 sLong
character::GetStateAPGain (sLong BaseAPGain
) const {
3741 if (!StateIsActivated(HASTE
) == !StateIsActivated(SLOW
)) return BaseAPGain
;
3742 if (StateIsActivated(HASTE
)) return (BaseAPGain
* 5) >> 2;
3743 return (BaseAPGain
<< 2) / 5;
3747 void character::SignalEquipmentAdd (int EquipmentIndex
) {
3748 item
*Equipment
= GetEquipment(EquipmentIndex
);
3749 if (Equipment
->IsInCorrectSlot(EquipmentIndex
)) {
3750 sLong AddedStates
= Equipment
->GetGearStates();
3752 for (int c
= 0; c
< STATES
; ++c
) {
3753 if (AddedStates
& (1 << c
)) {
3754 if (!StateIsActivated(1 << c
)) {
3755 if (!IsInNoMsgMode()) (this->*StateData
[c
].PrintBeginMessage
)();
3756 EquipmentState
|= 1 << c
;
3757 if (StateData
[c
].BeginHandler
) (this->*StateData
[c
].BeginHandler
)();
3759 EquipmentState
|= 1 << c
;
3765 if (!IsInitializing() && Equipment
->IsInCorrectSlot(EquipmentIndex
)) ApplyEquipmentAttributeBonuses(Equipment
);
3769 void character::SignalEquipmentRemoval (int, citem
*Item
) {
3770 CalculateEquipmentState();
3771 if (CalculateAttributeBonuses()) CheckDeath(festring("lost ")+GetPossessivePronoun(false)+" vital "+Item
->GetName(INDEFINITE
));
3775 void character::CalculateEquipmentState () {
3776 sLong Back
= EquipmentState
;
3778 for (int c
= 0; c
< GetEquipments(); ++c
) {
3779 item
*Equipment
= GetEquipment(c
);
3780 if (Equipment
&& Equipment
->IsInCorrectSlot(c
)) EquipmentState
|= Equipment
->GetGearStates();
3782 for (int c
= 0; c
< STATES
; ++c
) {
3783 if (Back
& (1 << c
) && !StateIsActivated(1 << c
)) {
3784 if (StateData
[c
].EndHandler
) {
3785 (this->*StateData
[c
].EndHandler
)();
3786 if (!IsEnabled()) return;
3788 if (!IsInNoMsgMode()) (this->*StateData
[c
].PrintEndMessage
)();
3794 /* Counter = duration in ticks */
3795 void character::BeginTemporaryState (sLong State
, int Counter
) {
3796 if (!Counter
) return;
3798 if (State
== POLYMORPHED
) ABORT("No Polymorphing with BeginTemporaryState!");
3799 for (Index
= 0; Index
< STATES
; ++Index
) if (1 << Index
== State
) break;
3800 if (Index
== STATES
) ABORT("BeginTemporaryState works only when State == 2Â ^ n!");
3801 if (TemporaryStateIsActivated(State
)) {
3802 int OldCounter
= GetTemporaryStateCounter(State
);
3803 if (OldCounter
!= PERMANENT
) EditTemporaryStateCounter(State
, Max(Counter
, 50-OldCounter
));
3804 } else if (StateData
[Index
].IsAllowed
== 0 || (this->*StateData
[Index
].IsAllowed
)()) {
3805 SetTemporaryStateCounter(State
, Max(Counter
, 50));
3806 if (!EquipmentStateIsActivated(State
)) {
3807 if (!IsInNoMsgMode()) (this->*StateData
[Index
].PrintBeginMessage
)();
3808 ActivateTemporaryState(State
);
3809 if (StateData
[Index
].BeginHandler
) (this->*StateData
[Index
].BeginHandler
)();
3811 ActivateTemporaryState(State
);
3817 void character::HandleStates () {
3818 if (!TemporaryState
&& !EquipmentState
) return;
3819 for (int c
= 0; c
< STATES
; ++c
) {
3820 if (TemporaryState
& (1 << c
) && TemporaryStateCounter
[c
] != PERMANENT
) {
3821 if (!--TemporaryStateCounter
[c
]) {
3822 TemporaryState
&= ~(1 << c
);
3823 if (!(EquipmentState
& (1 << c
))) {
3824 if (StateData
[c
].EndHandler
) {
3825 (this->*StateData
[c
].EndHandler
)();
3826 if (!IsEnabled()) return;
3828 if (!TemporaryStateCounter
[c
]) (this->*StateData
[c
].PrintEndMessage
)();
3832 if (StateIsActivated(1 << c
)) {
3833 if (StateData
[c
].Handler
) (this->*StateData
[c
].Handler
)();
3835 if (!IsEnabled()) return;
3840 void character::PrintBeginPolymorphControlMessage () const {
3841 if (IsPlayer()) ADD_MESSAGE("You feel your mind has total control over your body.");
3845 void character::PrintEndPolymorphControlMessage () const {
3846 if (IsPlayer()) ADD_MESSAGE("You are somehow uncertain of your willpower.");
3850 void character::PrintBeginLifeSaveMessage () const {
3851 if (IsPlayer()) ADD_MESSAGE("You hear Hell's gates being locked just now.");
3855 void character::PrintEndLifeSaveMessage () const {
3856 if (IsPlayer()) ADD_MESSAGE("You feel the Afterlife is welcoming you once again.");
3860 void character::PrintBeginLycanthropyMessage () const {
3861 if (IsPlayer()) ADD_MESSAGE("You suddenly notice you've always loved full moons.");
3865 void character::PrintEndLycanthropyMessage () const {
3866 if (IsPlayer()) ADD_MESSAGE("You feel the wolf inside you has had enough of your bad habits.");
3870 void character::PrintBeginVampirismMessage () const {
3871 if (IsPlayer()) ADD_MESSAGE("You suddenly decide you have always hated garlic.");
3875 void character::PrintEndVampirismMessage () const {
3876 if (IsPlayer()) ADD_MESSAGE("You recall your delight of the morning sunshine back in New Attnam. You are a vampire no longer.");
3880 void character::PrintBeginInvisibilityMessage () const {
3881 if ((PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm()) || (PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5)) {
3882 if (IsPlayer()) ADD_MESSAGE("You seem somehow transparent.");
3883 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems somehow transparent.", CHAR_NAME(DEFINITE
));
3885 if (IsPlayer()) ADD_MESSAGE("You fade away.");
3886 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears!", CHAR_NAME(DEFINITE
));
3891 void character::PrintEndInvisibilityMessage () const {
3892 if ((PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm()) || (PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5)) {
3893 if (IsPlayer()) ADD_MESSAGE("Your notice your transparency has ended.");
3894 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The appearance of %s seems far more solid now.", CHAR_NAME(INDEFINITE
));
3896 if (IsPlayer()) ADD_MESSAGE("You reappear.");
3897 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s appears from nowhere!", CHAR_NAME(INDEFINITE
));
3902 void character::PrintBeginInfraVisionMessage () const {
3904 if (StateIsActivated(INVISIBLE
) && IsWarm() && !(StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5))
3905 ADD_MESSAGE("You reappear.");
3907 ADD_MESSAGE("You feel your perception being magically altered.");
3912 void character::PrintEndInfraVisionMessage () const {
3914 if (StateIsActivated(INVISIBLE
) && IsWarm() && !(StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5))
3915 ADD_MESSAGE("You disappear.");
3917 ADD_MESSAGE("You feel your perception returning to normal.");
3922 void character::PrintBeginESPMessage () const {
3923 if (IsPlayer()) ADD_MESSAGE("You suddenly feel like being only a tiny part of a great network of intelligent minds.");
3927 void character::PrintEndESPMessage () const {
3928 if (IsPlayer()) ADD_MESSAGE("You are filled with desire to be just yourself from now on.");
3932 void character::PrintBeginHasteMessage () const {
3933 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
3934 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE
));
3938 void character::PrintEndHasteMessage () const {
3939 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
3940 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE
));
3944 void character::PrintBeginSlowMessage () const {
3945 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
3946 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE
));
3950 void character::PrintEndSlowMessage () const {
3951 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
3952 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE
));
3956 void character::EndPolymorph () {
3957 ForceEndPolymorph();
3961 character
*character::ForceEndPolymorph () {
3963 ADD_MESSAGE("You return to your true form.");
3964 } else if (game::IsInWilderness()) {
3965 ActivateTemporaryState(POLYMORPHED
);
3966 SetTemporaryStateCounter(POLYMORPHED
, 10);
3967 return this; // fast gum solution, state ends when the player enters a dungeon
3969 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s returns to %s true form.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
3971 if (GetAction()) GetAction()->Terminate(false);
3975 character
*Char
= GetPolymorphBackup();
3976 Flags
|= C_IN_NO_MSG_MODE
;
3977 Char
->Flags
|= C_IN_NO_MSG_MODE
;
3978 Char
->ChangeTeam(GetTeam());
3979 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(Char
);
3980 SetPolymorphBackup(0);
3981 Char
->PutToOrNear(Pos
);
3983 Char
->Flags
&= ~C_POLYMORPHED
;
3984 GetStack()->MoveItemsTo(Char
->GetStack());
3985 DonateEquipmentTo(Char
);
3986 Char
->SetMoney(GetMoney());
3987 Flags
&= ~C_IN_NO_MSG_MODE
;
3988 Char
->Flags
&= ~C_IN_NO_MSG_MODE
;
3989 Char
->CalculateAll();
3990 Char
->SetAssignedName(GetAssignedName());
3993 game::SetPlayer(Char
);
3994 game::SendLOSUpdateRequest();
3997 Char
->TestWalkability();
4002 void character::LycanthropyHandler () {
4003 if (!(RAND() % 2000)) {
4004 if (StateIsActivated(POLYMORPH_CONTROL
) && !game::TruthQuestion(CONST_S("Do you wish to change into a werewolf? [y/N]"))) return;
4005 Polymorph(werewolfwolf::Spawn(), 1000 + RAND() % 2000);
4010 void character::SaveLife () {
4011 if (TemporaryStateIsActivated(LIFE_SAVED
)) {
4013 ADD_MESSAGE("But wait! You glow briefly red and seem to be in a better shape!");
4014 else if (CanBeSeenByPlayer())
4015 ADD_MESSAGE("But wait, suddenly %s glows briefly red and seems to be in a better shape!", GetPersonalPronoun().CStr());
4016 DeActivateTemporaryState(LIFE_SAVED
);
4018 item
*LifeSaver
= 0;
4019 for (int c
= 0; c
< GetEquipments(); ++c
) {
4020 item
*Equipment
= GetEquipment(c
);
4021 if (Equipment
&& Equipment
->IsInCorrectSlot(c
) && Equipment
->GetGearStates() & LIFE_SAVED
) LifeSaver
= Equipment
;
4023 if (!LifeSaver
) ABORT("The Universe can only kill you once!");
4025 ADD_MESSAGE("But wait! Your %s glows briefly red and disappears and you seem to be in a better shape!", LifeSaver
->CHAR_NAME(UNARTICLED
));
4026 else if (CanBeSeenByPlayer())
4027 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());
4028 LifeSaver
->RemoveFromSlot();
4029 LifeSaver
->SendToHell();
4032 if (IsPlayer()) game::AskForEscPress(CONST_S("Life saved!"));
4040 if (GetNP() < SATIATED_LEVEL
) SetNP(SATIATED_LEVEL
);
4042 SendNewDrawRequest();
4044 if (GetAction()) GetAction()->Terminate(false);
4048 character
*character::PolymorphRandomly (int MinDanger
, int MaxDanger
, int Time
) {
4049 character
*NewForm
= 0;
4050 if (StateIsActivated(POLYMORPH_CONTROL
)) {
4052 if (!GetNewFormForPolymorphWithControl(NewForm
)) return NewForm
;
4055 NewForm
= protosystem::CreateMonster(MinDanger
* 10, MaxDanger
* 10, NO_EQUIPMENT
);
4058 NewForm
= protosystem::CreateMonster(MinDanger
, MaxDanger
, NO_EQUIPMENT
);
4060 Polymorph(NewForm
, Time
);
4065 /* In reality, the reading takes Time / (Intelligence * 10) turns */
4066 void character::StartReading (item
*Item
, sLong Time
) {
4067 study
*Read
= study::Spawn(this);
4068 Read
->SetLiteratureID(Item
->GetID());
4069 if (game::WizardModeIsActive()) Time
= 1;
4070 Read
->SetCounter(Time
);
4072 if (IsPlayer()) ADD_MESSAGE("You start reading %s.", Item
->CHAR_NAME(DEFINITE
));
4073 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s starts reading %s.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(DEFINITE
));
4077 /* Call when one makes something with his/her/its hands.
4078 * Difficulty of 5 takes about one turn, so it's the most common to use. */
4079 void character::DexterityAction (int Difficulty
) {
4080 EditAP(-20000 * Difficulty
/ APBonus(GetAttribute(DEXTERITY
)));
4081 EditExperience(DEXTERITY
, Difficulty
* 15, 1 << 7);
4085 /* If Theoretically != false, range is not a factor. */
4086 truth
character::CanBeSeenByPlayer (truth Theoretically
, truth IgnoreESP
) const {
4087 if (IsEnabled() && !game::IsGenerating() && (Theoretically
|| GetSquareUnder())) {
4088 truth MayBeESPSeen
= PLAYER
->IsEnabled() && !IgnoreESP
&& PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5;
4089 truth MayBeInfraSeen
= PLAYER
->IsEnabled() && PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm();
4090 truth Visible
= !StateIsActivated(INVISIBLE
) || MayBeESPSeen
|| MayBeInfraSeen
;
4091 if (game::IsInWilderness()) return Visible
;
4092 if (MayBeESPSeen
&& (Theoretically
|| GetDistanceSquareFrom(PLAYER
) <= PLAYER
->GetESPRangeSquare())) return true;
4093 if (!Visible
) return false;
4094 return Theoretically
|| SquareUnderCanBeSeenByPlayer(MayBeInfraSeen
);
4100 truth
character::CanBeSeenBy (ccharacter
*Who
, truth Theoretically
, truth IgnoreESP
) const {
4101 if (Who
->IsPlayer()) return CanBeSeenByPlayer(Theoretically
, IgnoreESP
);
4102 if (IsEnabled() && !game::IsGenerating() && (Theoretically
|| GetSquareUnder())) {
4103 truth MayBeESPSeen
= Who
->IsEnabled() && !IgnoreESP
&& Who
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5;
4104 truth MayBeInfraSeen
= Who
->IsEnabled() && Who
->StateIsActivated(INFRA_VISION
) && IsWarm();
4105 truth Visible
= !StateIsActivated(INVISIBLE
) || MayBeESPSeen
|| MayBeInfraSeen
;
4106 if (game::IsInWilderness()) return Visible
;
4107 if (MayBeESPSeen
&& (Theoretically
|| GetDistanceSquareFrom(Who
) <= Who
->GetESPRangeSquare())) return true;
4108 if (!Visible
) return false;
4109 return Theoretically
|| SquareUnderCanBeSeenBy(Who
, MayBeInfraSeen
);
4115 truth
character::SquareUnderCanBeSeenByPlayer (truth IgnoreDarkness
) const {
4116 if (!GetSquareUnder()) return false;
4117 int S1
= SquaresUnder
, S2
= PLAYER
->SquaresUnder
;
4118 if (S1
== 1 && S2
== 1) {
4119 if (GetSquareUnder()->CanBeSeenByPlayer(IgnoreDarkness
)) return true;
4120 if (IgnoreDarkness
) {
4121 int LOSRangeSquare
= PLAYER
->GetLOSRangeSquare();
4122 if ((GetPos() - PLAYER
->GetPos()).GetLengthSquare() <= LOSRangeSquare
) {
4123 eyecontroller::Map
= GetLevel()->GetMap();
4124 return mapmath
<eyecontroller
>::DoLine(PLAYER
->GetPos().X
, PLAYER
->GetPos().Y
, GetPos().X
, GetPos().Y
, SKIP_FIRST
);
4129 for (int c1
= 0; c1
< S1
; ++c1
) {
4130 lsquare
*Square
= GetLSquareUnder(c1
);
4131 if (Square
->CanBeSeenByPlayer(IgnoreDarkness
)) return true;
4132 else if (IgnoreDarkness
) {
4133 v2 Pos
= Square
->GetPos();
4134 int LOSRangeSquare
= PLAYER
->GetLOSRangeSquare();
4135 for (int c2
= 0; c2
< S2
; ++c2
) {
4136 v2 PlayerPos
= PLAYER
->GetPos(c2
);
4137 if ((Pos
-PlayerPos
).GetLengthSquare() <= LOSRangeSquare
) {
4138 eyecontroller::Map
= GetLevel()->GetMap();
4139 if (mapmath
<eyecontroller
>::DoLine(PlayerPos
.X
, PlayerPos
.Y
, Pos
.X
, Pos
.Y
, SKIP_FIRST
)) return true;
4149 truth
character::SquareUnderCanBeSeenBy (ccharacter
*Who
, truth IgnoreDarkness
) const {
4150 int S1
= SquaresUnder
, S2
= Who
->SquaresUnder
;
4151 int LOSRangeSquare
= Who
->GetLOSRangeSquare();
4152 if (S1
== 1 && S2
== 1) return GetSquareUnder()->CanBeSeenFrom(Who
->GetPos(), LOSRangeSquare
, IgnoreDarkness
);
4153 for (int c1
= 0; c1
< S1
; ++c1
) {
4154 lsquare
*Square
= GetLSquareUnder(c1
);
4155 for (int c2
= 0; c2
< S2
; ++c2
) if (Square
->CanBeSeenFrom(Who
->GetPos(c2
), LOSRangeSquare
, IgnoreDarkness
)) return true;
4161 int character::GetDistanceSquareFrom (ccharacter
*Who
) const {
4162 int S1
= SquaresUnder
, S2
= Who
->SquaresUnder
;
4163 if (S1
== 1 && S2
== 1) return (GetPos() - Who
->GetPos()).GetLengthSquare();
4164 v2
MinDist(0x7FFF, 0x7FFF);
4165 int MinLength
= 0xFFFF;
4166 for (int c1
= 0; c1
< S1
; ++c1
) {
4167 for (int c2
= 0; c2
< S2
; ++c2
) {
4168 v2 Dist
= GetPos(c1
)-Who
->GetPos(c2
);
4169 if (Dist
.X
< 0) Dist
.X
= -Dist
.X
;
4170 if (Dist
.Y
< 0) Dist
.Y
= -Dist
.Y
;
4171 if (Dist
.X
<= MinDist
.X
&& Dist
.Y
<= MinDist
.Y
) {
4173 MinLength
= Dist
.GetLengthSquare();
4174 } else if (Dist
.X
< MinDist
.X
|| Dist
.Y
< MinDist
.Y
) {
4175 int Length
= Dist
.GetLengthSquare();
4176 if (Length
< MinLength
) {
4187 void character::AttachBodyPart (bodypart
*BodyPart
) {
4188 SetBodyPart(BodyPart
->GetBodyPartIndex(), BodyPart
);
4189 if (!AllowSpoil()) BodyPart
->ResetSpoiling();
4190 BodyPart
->ResetPosition();
4191 BodyPart
->UpdatePictures();
4192 CalculateAttributeBonuses();
4193 CalculateBattleInfo();
4194 SendNewDrawRequest();
4195 SignalPossibleTransparencyChange();
4199 /* Returns true if the character has all bodyparts, false if not. */
4200 truth
character::HasAllBodyParts () const {
4201 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) return false;
4206 bodypart
*character::GenerateRandomBodyPart () {
4207 int NeededBodyPart
[MAX_BODYPARTS
];
4209 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) NeededBodyPart
[Index
++] = c
;
4210 return Index
? CreateBodyPart(NeededBodyPart
[RAND() % Index
]) : 0;
4214 /* Searches the character's Stack and if it find some bodyparts there that are the character's
4215 * old bodyparts returns a stackiterator to one of them (choosen in random).
4216 * If no fitting bodyparts are found the function returns 0 */
4217 bodypart
*character::FindRandomOwnBodyPart (truth AllowNonLiving
) const {
4218 itemvector LostAndFound
;
4219 for (int c
= 0; c
< BodyParts
; ++c
) {
4220 if (!GetBodyPart(c
)) {
4221 for (std::list
<feuLong
>::iterator i
= OriginalBodyPartID
[c
].begin(); i
!= OriginalBodyPartID
[c
].end(); ++i
) {
4222 bodypart
*Found
= static_cast<bodypart
*>(SearchForItem(*i
));
4223 if (Found
&& (AllowNonLiving
|| Found
->CanRegenerate())) LostAndFound
.push_back(Found
);
4227 if (LostAndFound
.empty()) return 0;
4228 return static_cast<bodypart
*>(LostAndFound
[RAND() % LostAndFound
.size()]);
4232 void character::PrintBeginPoisonedMessage () const {
4233 if (IsPlayer()) ADD_MESSAGE("You seem to be very ill.");
4234 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very ill.", CHAR_NAME(DEFINITE
));
4238 void character::PrintEndPoisonedMessage () const {
4239 if (IsPlayer()) ADD_MESSAGE("You feel better again.");
4240 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks better.", CHAR_NAME(DEFINITE
));
4244 void character::PoisonedHandler () {
4245 if (!(RAND() % 100)) VomitAtRandomDirection(500 + RAND_N(250));
4247 for (int Used
= 0; Used
< GetTemporaryStateCounter(POISONED
); Used
+= 100) if (!(RAND() % 100)) ++Damage
;
4249 ReceiveDamage(0, Damage
, POISON
, ALL
, 8, false, false, false, false);
4250 CheckDeath(CONST_S("died of acute poisoning"), 0);
4255 truth
character::IsWarm () const {
4256 return combinebodypartpredicates()(this, &bodypart::IsWarm
, 1);
4260 void character::BeginInvisibility () {
4262 SendNewDrawRequest();
4263 SignalPossibleTransparencyChange();
4267 void character::BeginInfraVision () {
4268 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4272 void character::BeginESP () {
4273 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4277 void character::EndInvisibility () {
4279 SendNewDrawRequest();
4280 SignalPossibleTransparencyChange();
4284 void character::EndInfraVision () {
4285 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4289 void character::EndESP () {
4290 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4294 void character::Draw (blitdata
&BlitData
) const {
4295 col24 L
= BlitData
.Luminance
;
4296 if (PLAYER
->IsEnabled() &&
4297 ((PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5 &&
4298 (PLAYER
->GetPos() - GetPos()).GetLengthSquare() <= PLAYER
->GetESPRangeSquare()) ||
4299 (PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm())))
4300 BlitData
.Luminance
= ivanconfig::GetContrastLuminance();
4302 DrawBodyParts(BlitData
);
4303 BlitData
.Luminance
= ivanconfig::GetContrastLuminance();
4304 BlitData
.Src
.Y
= 16;
4305 cint SquareIndex
= BlitData
.CustomData
& SQUARE_INDEX_MASK
;
4307 if (GetTeam() == PLAYER
->GetTeam() && !IsPlayer() && SquareIndex
== GetTameSymbolSquareIndex()) {
4308 BlitData
.Src
.X
= 32;
4309 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4312 if (IsFlying() && SquareIndex
== GetFlySymbolSquareIndex()) {
4313 BlitData
.Src
.X
= 128;
4314 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4317 if (IsSwimming() && SquareIndex
== GetSwimmingSymbolSquareIndex()) {
4318 BlitData
.Src
.X
= 240;
4319 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4322 if (GetAction() && GetAction()->IsUnconsciousness() && SquareIndex
== GetUnconsciousSymbolSquareIndex()) {
4323 BlitData
.Src
.X
= 224;
4324 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4327 BlitData
.Src
.X
= BlitData
.Src
.Y
= 0;
4328 BlitData
.Luminance
= L
;
4332 void character::DrawBodyParts (blitdata
&BlitData
) const {
4333 GetTorso()->Draw(BlitData
);
4337 void character::PrintBeginTeleportMessage () const {
4338 if (IsPlayer()) ADD_MESSAGE("You feel jumpy.");
4342 void character::PrintEndTeleportMessage () const {
4343 if (IsPlayer()) ADD_MESSAGE("You suddenly realize you've always preferred walking to jumping.");
4347 void character::PrintBeginDetectMessage () const {
4348 if (IsPlayer()) ADD_MESSAGE("You feel curious about your surroundings.");
4352 void character::PrintEndDetectMessage () const {
4353 if (IsPlayer()) ADD_MESSAGE("You decide to rely on your intuition from now on.");
4357 void character::TeleportHandler () {
4358 if (!(RAND() % 1500) && !game::IsInWilderness()) {
4359 if (IsPlayer()) ADD_MESSAGE("You feel an urgent spatial relocation is now appropriate.");
4360 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears.", CHAR_NAME(DEFINITE
));
4366 void character::DetectHandler () {
4368 //the AI can't be asked position questions! So only the player can hav this state really :/ a bit daft of me
4369 if (!(RAND()%3000) && !game::IsInWilderness()) {
4370 ADD_MESSAGE("Your mind wanders in search of something.");
4371 DoDetecting(); //in fact, who knows what would happen if a dark frog had the detecting state?
4377 void character::PrintBeginPolymorphMessage () const {
4378 if (IsPlayer()) ADD_MESSAGE("An unconfortable uncertainty of who you really are overwhelms you.");
4382 void character::PrintEndPolymorphMessage () const {
4383 if (IsPlayer()) ADD_MESSAGE("You feel you are you and no one else.");
4387 void character::PolymorphHandler () {
4388 if (!(RAND() % 1500)) PolymorphRandomly(1, 999999, 200 + RAND() % 800);
4391 void character::PrintBeginTeleportControlMessage () const {
4392 if (IsPlayer()) ADD_MESSAGE("You feel very controlled.");
4396 void character::PrintEndTeleportControlMessage () const {
4397 if (IsPlayer()) ADD_MESSAGE("You feel your control slipping.");
4401 void character::DisplayStethoscopeInfo (character
*) const {
4402 felist
Info(CONST_S("Information about ") + GetDescription(DEFINITE
));
4403 AddSpecialStethoscopeInfo(Info
);
4404 Info
.AddEntry(CONST_S("Endurance: ") + GetAttribute(ENDURANCE
), LIGHT_GRAY
);
4405 Info
.AddEntry(CONST_S("Perception: ") + GetAttribute(PERCEPTION
), LIGHT_GRAY
);
4406 Info
.AddEntry(CONST_S("Intelligence: ") + GetAttribute(INTELLIGENCE
), LIGHT_GRAY
);
4407 Info
.AddEntry(CONST_S("Wisdom: ") + GetAttribute(WISDOM
), LIGHT_GRAY
);
4408 //Info.AddEntry(CONST_S("Willpower: ") + GetAttribute(WILL_POWER), LIGHT_GRAY);
4409 Info
.AddEntry(CONST_S("Charisma: ") + GetAttribute(CHARISMA
), LIGHT_GRAY
);
4410 Info
.AddEntry(CONST_S("HP: ") + GetHP() + "/" + GetMaxHP(), IsInBadCondition() ? RED
: LIGHT_GRAY
);
4411 if (GetAction()) Info
.AddEntry(festring(GetAction()->GetDescription()).CapitalizeCopy(), LIGHT_GRAY
);
4412 for (int c
= 0; c
< STATES
; ++c
) {
4413 if (StateIsActivated(1 << c
) && (1 << c
!= HASTE
|| !StateIsActivated(SLOW
)) && (1 << c
!= SLOW
|| !StateIsActivated(HASTE
)))
4414 Info
.AddEntry(StateData
[c
].Description
, LIGHT_GRAY
);
4416 switch (GetTirednessState()) {
4417 case FAINTING
: Info
.AddEntry("Fainting", RED
); break;
4418 case EXHAUSTED
: Info
.AddEntry("Exhausted", LIGHT_GRAY
); break;
4420 game::SetStandardListAttributes(Info
);
4425 truth
character::CanUseStethoscope (truth PrintReason
) const {
4426 if (PrintReason
) ADD_MESSAGE("This type of monster can't use a stethoscope.");
4431 /* Effect used by at least Sophos.
4432 * NOTICE: Doesn't check for death! */
4433 void character::TeleportSomePartsAway (int NumberToTeleport
) {
4434 for (int c
= 0; c
< NumberToTeleport
; ++c
) {
4435 int RandomBodyPart
= GetRandomNonVitalBodyPart();
4436 if (RandomBodyPart
== NONE_INDEX
) {
4437 for (; c
< NumberToTeleport
; ++c
) {
4438 GetTorso()->SetHP((GetTorso()->GetHP() << 2) / 5);
4439 sLong TorsosVolume
= GetTorso()->GetMainMaterial()->GetVolume() / 10;
4440 if (!TorsosVolume
) break;
4441 sLong Amount
= (RAND() % TorsosVolume
)+1;
4442 item
*Lump
= GetTorso()->GetMainMaterial()->CreateNaturalForm(Amount
);
4443 GetTorso()->GetMainMaterial()->EditVolume(-Amount
);
4444 Lump
->MoveTo(GetNearLSquare(GetLevel()->GetRandomSquare())->GetStack());
4445 if (IsPlayer()) ADD_MESSAGE("Parts of you teleport away.");
4446 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Parts of %s teleport away.", CHAR_NAME(DEFINITE
));
4449 item
*SeveredBodyPart
= SevereBodyPart(RandomBodyPart
);
4450 if (SeveredBodyPart
) {
4451 GetNearLSquare(GetLevel()->GetRandomSquare())->AddItem(SeveredBodyPart
);
4452 SeveredBodyPart
->DropEquipment();
4453 if (IsPlayer()) ADD_MESSAGE("Your %s teleports away.", GetBodyPartName(RandomBodyPart
).CStr());
4454 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s teleports away.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4456 if (IsPlayer()) ADD_MESSAGE("Your %s disappears.", GetBodyPartName(RandomBodyPart
).CStr());
4457 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s disappears.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4464 /* Returns an index of a random bodypart that is not vital. If no non-vital bodypart is found returns NONE_INDEX */
4465 int character::GetRandomNonVitalBodyPart () const {
4466 int OKBodyPart
[MAX_BODYPARTS
];
4467 int OKBodyParts
= 0;
4468 for (int c
= 0; c
< BodyParts
; ++c
) if (GetBodyPart(c
) && !BodyPartIsVital(c
)) OKBodyPart
[OKBodyParts
++] = c
;
4469 return OKBodyParts
? OKBodyPart
[RAND() % OKBodyParts
] : NONE_INDEX
;
4473 void character::CalculateVolumeAndWeight () {
4474 Volume
= Stack
->GetVolume();
4475 Weight
= Stack
->GetWeight();
4477 CarriedWeight
= Weight
;
4478 for (int c
= 0; c
< BodyParts
; ++c
) {
4479 bodypart
*BodyPart
= GetBodyPart(c
);
4481 BodyVolume
+= BodyPart
->GetBodyPartVolume();
4482 Volume
+= BodyPart
->GetVolume();
4483 CarriedWeight
+= BodyPart
->GetCarriedWeight();
4484 Weight
+= BodyPart
->GetWeight();
4490 void character::SignalVolumeAndWeightChange () {
4491 if (!IsInitializing()) {
4492 CalculateVolumeAndWeight();
4493 if (IsEnabled()) CalculateBurdenState();
4494 if (MotherEntity
) MotherEntity
->SignalVolumeAndWeightChange();
4499 void character::SignalEmitationIncrease (col24 EmitationUpdate
) {
4500 if (game::CompareLights(EmitationUpdate
, Emitation
) > 0) {
4501 game::CombineLights(Emitation
, EmitationUpdate
);
4502 if (MotherEntity
) MotherEntity
->SignalEmitationIncrease(EmitationUpdate
);
4503 else if (SquareUnder
[0] && !game::IsInWilderness()) {
4504 for(int c
= 0; c
< GetSquaresUnder(); ++c
) GetLSquareUnder()->SignalEmitationIncrease(EmitationUpdate
);
4510 void character::SignalEmitationDecrease (col24 EmitationUpdate
) {
4511 if (game::CompareLights(EmitationUpdate
, Emitation
) >= 0 && Emitation
) {
4512 col24 Backup
= Emitation
;
4513 CalculateEmitation();
4514 if (Backup
!= Emitation
) {
4515 if (MotherEntity
) MotherEntity
->SignalEmitationDecrease(EmitationUpdate
);
4516 else if (SquareUnder
[0] && !game::IsInWilderness()) {
4517 for (int c
= 0; c
< GetSquaresUnder(); ++c
) GetLSquareUnder(c
)->SignalEmitationDecrease(EmitationUpdate
);
4524 void character::CalculateEmitation () {
4525 Emitation
= GetBaseEmitation();
4526 for (int c
= 0; c
< BodyParts
; ++c
) {
4527 bodypart
*BodyPart
= GetBodyPart(c
);
4528 if (BodyPart
) game::CombineLights(Emitation
, BodyPart
->GetEmitation());
4530 game::CombineLights(Emitation
, Stack
->GetEmitation());
4534 void character::CalculateAll () {
4535 Flags
|= C_INITIALIZING
;
4536 CalculateAttributeBonuses();
4537 CalculateVolumeAndWeight();
4538 CalculateEmitation();
4539 CalculateBodyPartMaxHPs(0);
4540 CalculateMaxStamina();
4541 CalculateBurdenState();
4542 CalculateBattleInfo();
4543 Flags
&= ~C_INITIALIZING
;
4547 void character::CalculateHP () {
4548 HP
= sumbodypartproperties()(this, &bodypart::GetHP
);
4552 void character::CalculateMaxHP () {
4553 MaxHP
= sumbodypartproperties()(this, &bodypart::GetMaxHP
);
4557 void character::CalculateBodyPartMaxHPs (feuLong Flags
) {
4558 doforbodypartswithparam
<feuLong
>()(this, &bodypart::CalculateMaxHP
, Flags
);
4564 truth
character::EditAttribute (int Identifier
, int Value
) {
4565 if (Identifier
== ENDURANCE
&& UseMaterialAttributes()) return false;
4566 if (RawEditAttribute(BaseExperience
[Identifier
], Value
)) {
4567 if (!IsInitializing()) {
4568 if (Identifier
== LEG_STRENGTH
) CalculateBurdenState();
4569 else if (Identifier
== ENDURANCE
) CalculateBodyPartMaxHPs();
4570 else if (IsPlayer() && Identifier
== PERCEPTION
) game::SendLOSUpdateRequest();
4571 else if (IsPlayerKind() && (Identifier
== INTELLIGENCE
|| Identifier
== WISDOM
|| Identifier
== CHARISMA
)) UpdatePictures();
4572 CalculateBattleInfo();
4580 truth
character::ActivateRandomState (int Flags
, int Time
, sLong Seed
) {
4582 if (Seed
) femath::SetSeed(Seed
);
4583 sLong ToBeActivated
= GetRandomState(Flags
|DUR_TEMPORARY
);
4585 if (!ToBeActivated
) return false;
4586 BeginTemporaryState(ToBeActivated
, Time
);
4591 truth
character::GainRandomIntrinsic (int Flags
) {
4592 sLong ToBeActivated
= GetRandomState(Flags
|DUR_PERMANENT
);
4593 if (!ToBeActivated
) return false;
4594 GainIntrinsic(ToBeActivated
);
4599 /* Returns 0 if state not found */
4600 sLong
character::GetRandomState (int Flags
) const {
4601 sLong OKStates
[STATES
];
4602 int NumberOfOKStates
= 0;
4603 for (int c
= 0; c
< STATES
; ++c
) {
4604 if (StateData
[c
].Flags
& Flags
& DUR_FLAGS
&& StateData
[c
].Flags
& Flags
& SRC_FLAGS
) OKStates
[NumberOfOKStates
++] = 1 << c
;
4606 return NumberOfOKStates
? OKStates
[RAND() % NumberOfOKStates
] : 0;
4610 int characterprototype::CreateSpecialConfigurations (characterdatabase
**TempConfig
, int Configs
, int Level
) {
4611 if (Level
== 0 && TempConfig
[0]->CreateDivineConfigurations
) {
4612 Configs
= databasecreator
<character
>::CreateDivineConfigurations(this, TempConfig
, Configs
);
4614 if (Level
== 1 && TempConfig
[0]->CreateUndeadConfigurations
) {
4615 for (int c
= 1; c
< protocontainer
<character
>::GetSize(); ++c
) {
4616 const character::prototype
*Proto
= protocontainer
<character
>::GetProto(c
);
4617 const character::database
*const *CharacterConfigData
= Proto
->GetConfigData();
4618 if (!CharacterConfigData
) ABORT("No database entry for character <%s>!", Proto
->GetClassID());
4619 const character::database
*const* End
= CharacterConfigData
+ Proto
->GetConfigSize();
4620 for (++CharacterConfigData
; CharacterConfigData
!= End
; ++CharacterConfigData
) {
4621 const character::database
*CharacterDataBase
= *CharacterConfigData
;
4622 if (CharacterDataBase
->UndeadVersions
) {
4623 character::database
* ConfigDataBase
= new character::database(**TempConfig
);
4624 ConfigDataBase
->InitDefaults(this, (c
<< 8) | CharacterDataBase
->Config
);
4625 ConfigDataBase
->PostFix
<< "of ";
4626 if (CharacterDataBase
->Adjective
.GetSize()) {
4627 if (CharacterDataBase
->UsesLongAdjectiveArticle
) ConfigDataBase
->PostFix
<< "an ";
4628 else ConfigDataBase
->PostFix
<< "a ";
4629 ConfigDataBase
->PostFix
<< CharacterDataBase
->Adjective
<< ' ';
4631 if (CharacterDataBase
->UsesLongArticle
) ConfigDataBase
->PostFix
<< "an ";
4632 else ConfigDataBase
->PostFix
<< "a ";
4634 ConfigDataBase
->PostFix
<< CharacterDataBase
->NameSingular
;
4635 if (CharacterDataBase
->PostFix
.GetSize()) ConfigDataBase
->PostFix
<< ' ' << CharacterDataBase
->PostFix
;
4636 int P1
= TempConfig
[0]->UndeadAttributeModifier
;
4637 int P2
= TempConfig
[0]->UndeadVolumeModifier
;
4639 for (c2
= 0; c2
< ATTRIBUTES
; ++c2
) ConfigDataBase
->*ExpPtr
[c2
] = CharacterDataBase
->*ExpPtr
[c2
] * P1
/ 100;
4640 for (c2
= 0; c2
< EQUIPMENT_DATAS
; ++c2
) ConfigDataBase
->*EquipmentDataPtr
[c2
] = contentscript
<item
>();
4641 ConfigDataBase
->DefaultIntelligence
= 5;
4642 ConfigDataBase
->DefaultWisdom
= 5;
4643 ConfigDataBase
->DefaultCharisma
= 5;
4644 ConfigDataBase
->TotalSize
= CharacterDataBase
->TotalSize
;
4645 ConfigDataBase
->Sex
= CharacterDataBase
->Sex
;
4646 ConfigDataBase
->AttributeBonus
= CharacterDataBase
->AttributeBonus
;
4647 ConfigDataBase
->TotalVolume
= CharacterDataBase
->TotalVolume
* P2
/ 100;
4648 if (TempConfig
[0]->UndeadCopyMaterials
) {
4649 ConfigDataBase
->HeadBitmapPos
= CharacterDataBase
->HeadBitmapPos
;
4650 ConfigDataBase
->HairColor
= CharacterDataBase
->HairColor
;
4651 ConfigDataBase
->EyeColor
= CharacterDataBase
->EyeColor
;
4652 ConfigDataBase
->CapColor
= CharacterDataBase
->CapColor
;
4653 ConfigDataBase
->FleshMaterial
= CharacterDataBase
->FleshMaterial
;
4654 ConfigDataBase
->BloodMaterial
= CharacterDataBase
->BloodMaterial
;
4655 ConfigDataBase
->VomitMaterial
= CharacterDataBase
->VomitMaterial
;
4656 ConfigDataBase
->SweatMaterial
= CharacterDataBase
->SweatMaterial
;
4658 ConfigDataBase
->KnownCWeaponSkills
= CharacterDataBase
->KnownCWeaponSkills
;
4659 ConfigDataBase
->CWeaponSkillHits
= CharacterDataBase
->CWeaponSkillHits
;
4660 ConfigDataBase
->PostProcess();
4661 TempConfig
[Configs
++] = ConfigDataBase
;
4666 if (Level
== 0 && TempConfig
[0]->CreateGolemMaterialConfigurations
) {
4667 for (int c
= 1; c
< protocontainer
<material
>::GetSize(); ++c
) {
4668 const material::prototype
* Proto
= protocontainer
<material
>::GetProto(c
);
4669 const material::database
*const* MaterialConfigData
= Proto
->GetConfigData();
4670 const material::database
*const* End
= MaterialConfigData
+ Proto
->GetConfigSize();
4671 for (++MaterialConfigData
; MaterialConfigData
!= End
; ++MaterialConfigData
) {
4672 const material::database
* MaterialDataBase
= *MaterialConfigData
;
4673 if (MaterialDataBase
->CategoryFlags
& IS_GOLEM_MATERIAL
) {
4674 character::database
* ConfigDataBase
= new character::database(**TempConfig
);
4675 ConfigDataBase
->InitDefaults(this, MaterialDataBase
->Config
);
4676 ConfigDataBase
->Adjective
= MaterialDataBase
->NameStem
;
4677 ConfigDataBase
->UsesLongAdjectiveArticle
= MaterialDataBase
->NameFlags
& USE_AN
;
4678 ConfigDataBase
->AttachedGod
= MaterialDataBase
->AttachedGod
;
4679 TempConfig
[Configs
++] = ConfigDataBase
;
4688 double character::GetTimeToDie (ccharacter
*Enemy
, int Damage
, double ToHitValue
, truth AttackIsBlockable
, truth UseMaxHP
) const {
4689 double DodgeValue
= GetDodgeValue();
4690 if (!Enemy
->CanBeSeenBy(this, true)) ToHitValue
*= 2;
4691 if (!CanBeSeenBy(Enemy
, true)) DodgeValue
*= 2;
4692 double MinHits
= 1000;
4694 for (int c
= 0; c
< BodyParts
; ++c
) {
4695 if (BodyPartIsVital(c
) && GetBodyPart(c
)) {
4696 double Hits
= GetBodyPart(c
)->GetTimeToDie(Damage
, ToHitValue
, DodgeValue
, AttackIsBlockable
, UseMaxHP
);
4697 if (First
) { MinHits
= Hits
; First
= false; } else MinHits
= 1 / (1 / MinHits
+ 1 / Hits
);
4704 double character::GetRelativeDanger (ccharacter
*Enemy
, truth UseMaxHP
) const {
4705 double Danger
= Enemy
->GetTimeToKill(this, UseMaxHP
) / GetTimeToKill(Enemy
, UseMaxHP
);
4706 int EnemyAP
= Enemy
->GetMoveAPRequirement(1);
4707 int ThisAP
= GetMoveAPRequirement(1);
4708 if (EnemyAP
> ThisAP
) Danger
*= 1.25;
4709 else if (ThisAP
> EnemyAP
) Danger
*= 0.80;
4710 if (!Enemy
->CanBeSeenBy(this, true)) Danger
*= Enemy
->IsPlayer() ? 0.2 : 0.5;
4711 if (!CanBeSeenBy(Enemy
, true)) Danger
*= IsPlayer() ? 5. : 2.;
4712 if (GetAttribute(INTELLIGENCE
) < 10 && !IsPlayer()) Danger
*= 0.80;
4713 if (Enemy
->GetAttribute(INTELLIGENCE
) < 10 && !Enemy
->IsPlayer()) Danger
*= 1.25;
4714 return Limit(Danger
, 0.001, 1000.0);
4718 festring
character::GetBodyPartName (int I
, truth Articled
) const {
4719 if (I
== TORSO_INDEX
) return Articled
? CONST_S("a torso") : CONST_S("torso");
4720 ABORT("Illegal character bodypart name request!");
4725 item
*character::SearchForItem(feuLong ID
) const {
4726 item
*Equipment
= findequipment
<feuLong
>()(this, &item::HasID
, ID
);
4727 if (Equipment
) return Equipment
;
4728 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (i
->GetID() == ID
) return *i
;
4733 truth
character::ContentsCanBeSeenBy (ccharacter
*Viewer
) const {
4734 return Viewer
== this;
4738 truth
character::HitEffect (character
*Enemy
, item
* Weapon
, v2 HitPos
, int Type
, int BodyPartIndex
,
4739 int Direction
, truth BlockedByArmour
)
4741 if (Weapon
) return Weapon
->HitEffect(this, Enemy
, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
4743 case UNARMED_ATTACK
: return Enemy
->SpecialUnarmedEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
4744 case KICK_ATTACK
: return Enemy
->SpecialKickEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
4745 case BITE_ATTACK
: return Enemy
->SpecialBiteEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
4751 void character::WeaponSkillHit (item
*Weapon
, int Type
, int Hits
) {
4754 case UNARMED_ATTACK
: Category
= UNARMED
; break;
4755 case WEAPON_ATTACK
: Weapon
->WeaponSkillHit(Hits
); return;
4756 case KICK_ATTACK
: Category
= KICK
; break;
4757 case BITE_ATTACK
: Category
= BITE
; break;
4759 if (!IsHumanoid()) return;
4760 Category
= Weapon
->GetWeaponCategory();
4763 ABORT("Illegal Type %d passed to character::WeaponSkillHit()!", Type
);
4766 if (GetCWeaponSkill(Category
)->AddHit(Hits
)) {
4767 CalculateBattleInfo();
4768 if (IsPlayer()) GetCWeaponSkill(Category
)->AddLevelUpMessage(Category
);
4773 /* Returns 0 if character cannot be duplicated */
4774 character
*character::Duplicate (feuLong Flags
) {
4775 if (!(Flags
& IGNORE_PROHIBITIONS
) && !CanBeCloned()) return 0;
4776 character
*Char
= GetProtoType()->Clone(this);
4777 if (Flags
& MIRROR_IMAGE
) {
4778 DuplicateEquipment(Char
, Flags
& ~IGNORE_PROHIBITIONS
);
4779 Char
->SetLifeExpectancy(Flags
>> LE_BASE_SHIFT
& LE_BASE_RANGE
, Flags
>> LE_RAND_SHIFT
& LE_RAND_RANGE
);
4781 Char
->CalculateAll();
4782 Char
->CalculateEmitation();
4783 Char
->UpdatePictures();
4784 Char
->Flags
&= ~(C_INITIALIZING
|C_IN_NO_MSG_MODE
);
4789 truth
character::TryToEquip (item
*Item
) {
4790 if (!Item
->AllowEquip() || !CanUseEquipment() || GetAttribute(WISDOM
) >= Item
->GetWearWisdomLimit() || Item
->GetSquaresUnder() != 1)
4792 for (int e
= 0; e
< GetEquipments(); ++e
) {
4793 if (GetBodyPartOfEquipment(e
) && EquipmentIsAllowed(e
)) {
4794 sorter Sorter
= EquipmentSorter(e
);
4795 if ((Sorter
== 0 || (Item
->*Sorter
)(this)) &&
4796 ((e
!= RIGHT_WIELDED_INDEX
&& e
!= LEFT_WIELDED_INDEX
) ||
4797 Item
->IsWeapon(this) || Item
->IsShield(this)) && AllowEquipment(Item
, e
)) {
4798 item
*OldEquipment
= GetEquipment(e
);
4799 if (BoundToUse(OldEquipment
, e
)) continue;
4800 lsquare
*LSquareUnder
= GetLSquareUnder();
4801 stack
*StackUnder
= LSquareUnder
->GetStack();
4802 msgsystem::DisableMessages();
4803 Flags
|= C_PICTURE_UPDATES_FORBIDDEN
;
4804 LSquareUnder
->Freeze();
4805 StackUnder
->Freeze();
4806 double Danger
= GetRelativeDanger(PLAYER
);
4807 if (OldEquipment
) OldEquipment
->RemoveFromSlot();
4808 Item
->RemoveFromSlot();
4809 SetEquipment(e
, Item
);
4810 double NewDanger
= GetRelativeDanger(PLAYER
);
4811 Item
->RemoveFromSlot();
4812 StackUnder
->AddItem(Item
);
4813 if (OldEquipment
) SetEquipment(e
, OldEquipment
);
4814 msgsystem::EnableMessages();
4815 Flags
&= ~C_PICTURE_UPDATES_FORBIDDEN
;
4816 LSquareUnder
->UnFreeze();
4817 StackUnder
->UnFreeze();
4819 if (NewDanger
> Danger
|| BoundToUse(Item
, e
)) {
4820 room
*Room
= GetRoom();
4821 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
4822 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
));
4823 if (Room
) Room
->DropItem(this, OldEquipment
, 1);
4824 OldEquipment
->MoveTo(StackUnder
);
4825 Item
->RemoveFromSlot();
4826 SetEquipment(e
, Item
);
4832 if (NewDanger
> Danger
|| (NewDanger
== Danger
&& e
!= RIGHT_WIELDED_INDEX
&& e
!= LEFT_WIELDED_INDEX
) || BoundToUse(Item
, e
)) {
4833 room
*Room
= GetRoom();
4834 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
4835 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up and equips %s.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(INDEFINITE
));
4836 Item
->RemoveFromSlot();
4837 SetEquipment(e
, Item
);
4850 truth
character::TryToConsume (item
*Item
) {
4851 return Item
->CanBeEatenByAI(this) && ConsumeItem(Item
, Item
->GetConsumeMaterial(this)->GetConsumeVerb());
4855 void character::UpdateESPLOS () const {
4856 if (StateIsActivated(ESP
) && !game::IsInWilderness()) {
4857 for (int c
= 0; c
< game::GetTeams(); ++c
) {
4858 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
4859 const character
*ch
= *i
;
4860 if (ch
->IsEnabled()) ch
->SendNewDrawRequest();
4867 int character::GetCWeaponSkillLevel (citem
*Item
) const {
4868 if (Item
->GetWeaponCategory() < GetAllowedWeaponSkillCategories()) return GetCWeaponSkill(Item
->GetWeaponCategory())->GetLevel();
4873 void character::PrintBeginPanicMessage () const {
4874 if (IsPlayer()) ADD_MESSAGE("You panic!");
4875 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s panics.", CHAR_NAME(DEFINITE
));
4879 void character::PrintEndPanicMessage () const {
4880 if (IsPlayer()) ADD_MESSAGE("You finally calm down.");
4881 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s calms down.", CHAR_NAME(DEFINITE
));
4885 void character::CheckPanic (int Ticks
) {
4886 if (GetPanicLevel() > 1 && !StateIsActivated(PANIC
) && GetHP() * 100 < RAND() % (GetPanicLevel() * GetMaxHP() << 1))
4887 BeginTemporaryState(PANIC
, ((Ticks
* 3) >> 2) + RAND() % ((Ticks
>> 1) + 1)); // 25% randomness to ticks...
4891 /* returns 0 if fails else the newly created character */
4892 character
*character::DuplicateToNearestSquare (character
*Cloner
, feuLong Flags
) {
4893 character
*NewlyCreated
= Duplicate(Flags
);
4894 if (!NewlyCreated
) return 0;
4895 if (Flags
& CHANGE_TEAM
&& Cloner
) NewlyCreated
->ChangeTeam(Cloner
->GetTeam());
4896 NewlyCreated
->PutNear(GetPos());
4897 return NewlyCreated
;
4901 void character::SignalSpoil (material
*m
) {
4902 if (GetMotherEntity()) GetMotherEntity()->SignalSpoil(m
);
4903 else Disappear(0, "spoil", &item::IsVeryCloseToSpoiling
);
4907 truth
character::CanHeal () const {
4908 for (int c
= 0; c
< BodyParts
; ++c
) {
4909 bodypart
*BodyPart
= GetBodyPart(c
);
4910 if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < BodyPart
->GetMaxHP()) return true;
4916 int character::GetRelation (ccharacter
*Who
) const {
4917 return GetTeam()->GetRelation(Who
->GetTeam());
4921 truth (item::*AffectTest
[BASE_ATTRIBUTES
])() const = {
4922 &item::AffectsEndurance
,
4923 &item::AffectsPerception
,
4924 &item::AffectsIntelligence
,
4925 &item::AffectsWisdom
,
4926 &item::AffectsWillPower
,
4927 &item::AffectsCharisma
,
4932 /* Returns nonzero if endurance has decreased and death may occur */
4933 truth
character::CalculateAttributeBonuses () {
4934 doforbodyparts()(this, &bodypart::CalculateAttributeBonuses
);
4935 int BackupBonus
[BASE_ATTRIBUTES
];
4936 int BackupCarryingBonus
= CarryingBonus
;
4939 for (c1
= 0; c1
< BASE_ATTRIBUTES
; ++c1
) {
4940 BackupBonus
[c1
] = AttributeBonus
[c1
];
4941 AttributeBonus
[c1
] = 0;
4943 for (c1
= 0; c1
< GetEquipments(); ++c1
) {
4944 item
*Equipment
= GetEquipment(c1
);
4945 if (!Equipment
|| !Equipment
->IsInCorrectSlot(c1
)) continue;
4946 for (int c2
= 0; c2
< BASE_ATTRIBUTES
; ++c2
) {
4947 if ((Equipment
->*AffectTest
[c2
])()) AttributeBonus
[c2
] += Equipment
->GetEnchantment();
4949 if (Equipment
->AffectsCarryingCapacity()) CarryingBonus
+= Equipment
->GetCarryingBonus();
4952 ApplySpecialAttributeBonuses();
4954 if (IsPlayer() && !IsInitializing() && AttributeBonus
[PERCEPTION
] != BackupBonus
[PERCEPTION
]) game::SendLOSUpdateRequest();
4955 if (IsPlayer() && !IsInitializing() && AttributeBonus
[INTELLIGENCE
] != BackupBonus
[INTELLIGENCE
]) UpdateESPLOS();
4957 if (!IsInitializing() && CarryingBonus
!= BackupCarryingBonus
) CalculateBurdenState();
4959 if (!IsInitializing() && AttributeBonus
[ENDURANCE
] != BackupBonus
[ENDURANCE
]) {
4960 CalculateBodyPartMaxHPs();
4961 CalculateMaxStamina();
4962 return AttributeBonus
[ENDURANCE
] < BackupBonus
[ENDURANCE
];
4969 void character::ApplyEquipmentAttributeBonuses (item
*Equipment
) {
4970 if (Equipment
->AffectsEndurance()) {
4971 AttributeBonus
[ENDURANCE
] += Equipment
->GetEnchantment();
4972 CalculateBodyPartMaxHPs();
4973 CalculateMaxStamina();
4975 if (Equipment
->AffectsPerception()) {
4976 AttributeBonus
[PERCEPTION
] += Equipment
->GetEnchantment();
4977 if (IsPlayer()) game::SendLOSUpdateRequest();
4979 if (Equipment
->AffectsIntelligence()) {
4980 AttributeBonus
[INTELLIGENCE
] += Equipment
->GetEnchantment();
4981 if (IsPlayer()) UpdateESPLOS();
4983 if (Equipment
->AffectsWisdom()) AttributeBonus
[WISDOM
] += Equipment
->GetEnchantment();
4984 if (Equipment
->AffectsWillPower()) AttributeBonus
[WILL_POWER
] += Equipment
->GetEnchantment();
4985 if (Equipment
->AffectsCharisma()) AttributeBonus
[CHARISMA
] += Equipment
->GetEnchantment();
4986 if (Equipment
->AffectsMana()) AttributeBonus
[MANA
] += Equipment
->GetEnchantment();
4987 if (Equipment
->AffectsCarryingCapacity()) {
4988 CarryingBonus
+= Equipment
->GetCarryingBonus();
4989 CalculateBurdenState();
4994 void character::ReceiveAntidote (sLong Amount
) {
4995 if (StateIsActivated(POISONED
)) {
4996 if (GetTemporaryStateCounter(POISONED
) > Amount
) {
4997 EditTemporaryStateCounter(POISONED
, -Amount
);
5000 if (IsPlayer()) ADD_MESSAGE("Aaaah... You feel much better.");
5001 Amount
-= GetTemporaryStateCounter(POISONED
);
5002 DeActivateTemporaryState(POISONED
);
5005 if ((Amount
>= 100 || RAND_N(100) < Amount
) && StateIsActivated(PARASITIZED
)) {
5006 if (IsPlayer()) ADD_MESSAGE("Something in your belly didn't seem to like this stuff.");
5007 DeActivateTemporaryState(PARASITIZED
);
5008 Amount
-= Min(100, Amount
);
5010 if ((Amount
>= 100 || RAND_N(100) < Amount
) && StateIsActivated(LEPROSY
)) {
5011 if (IsPlayer()) ADD_MESSAGE("You are not falling to pieces anymore.");
5012 DeActivateTemporaryState(LEPROSY
);
5013 Amount
-= Min(100, Amount
);
5018 void character::AddAntidoteConsumeEndMessage () const {
5019 if (StateIsActivated(POISONED
)) {
5020 // true only if the antidote didn't cure the poison completely
5021 if (IsPlayer()) ADD_MESSAGE("Your body processes the poison in your veins with rapid speed.");
5026 truth
character::IsDead () const {
5027 for (int c
= 0; c
< BodyParts
; ++c
) {
5028 bodypart
*BodyPart
= GetBodyPart(c
);
5029 if (BodyPartIsVital(c
) && (!BodyPart
|| BodyPart
->GetHP() < 1)) return true;
5035 void character::SignalSpoilLevelChange (material
*m
) {
5036 if (GetMotherEntity()) GetMotherEntity()->SignalSpoilLevelChange(m
); else UpdatePictures();
5040 void character::AddOriginalBodyPartID (int I
, feuLong What
) {
5041 if (std::find(OriginalBodyPartID
[I
].begin(), OriginalBodyPartID
[I
].end(), What
) == OriginalBodyPartID
[I
].end()) {
5042 OriginalBodyPartID
[I
].push_back(What
);
5043 if (OriginalBodyPartID
[I
].size() > 100) OriginalBodyPartID
[I
].erase(OriginalBodyPartID
[I
].begin());
5048 void character::AddToInventory (const fearray
<contentscript
<item
> > &ItemArray
, int SpecialFlags
) {
5049 for (uInt c1
= 0; c1
< ItemArray
.Size
; ++c1
) {
5050 if (ItemArray
[c1
].IsValid()) {
5051 const interval
*TimesPtr
= ItemArray
[c1
].GetTimes();
5052 int Times
= TimesPtr
? TimesPtr
->Randomize() : 1;
5053 for (int c2
= 0; c2
< Times
; ++c2
) {
5054 item
*Item
= ItemArray
[c1
].Instantiate(SpecialFlags
);
5056 Stack
->AddItem(Item
);
5057 Item
->SpecialGenerationHandler();
5065 truth
character::HasHadBodyPart (citem
*Item
) const {
5066 for (int c
= 0; c
< BodyParts
; ++c
)
5067 if (std::find(OriginalBodyPartID
[c
].begin(), OriginalBodyPartID
[c
].end(), Item
->GetID()) != OriginalBodyPartID
[c
].end())
5069 return GetPolymorphBackup() && GetPolymorphBackup()->HasHadBodyPart(Item
);
5073 festring
&character::ProcessMessage (festring
&Msg
) const {
5074 SEARCH_N_REPLACE(Msg
, "@nu", GetName(UNARTICLED
));
5075 SEARCH_N_REPLACE(Msg
, "@ni", GetName(INDEFINITE
));
5076 SEARCH_N_REPLACE(Msg
, "@nd", GetName(DEFINITE
));
5077 SEARCH_N_REPLACE(Msg
, "@du", GetDescription(UNARTICLED
));
5078 SEARCH_N_REPLACE(Msg
, "@di", GetDescription(INDEFINITE
));
5079 SEARCH_N_REPLACE(Msg
, "@dd", GetDescription(DEFINITE
));
5080 SEARCH_N_REPLACE(Msg
, "@pp", GetPersonalPronoun());
5081 SEARCH_N_REPLACE(Msg
, "@sp", GetPossessivePronoun());
5082 SEARCH_N_REPLACE(Msg
, "@op", GetObjectPronoun());
5083 SEARCH_N_REPLACE(Msg
, "@Nu", GetName(UNARTICLED
).CapitalizeCopy());
5084 SEARCH_N_REPLACE(Msg
, "@Ni", GetName(INDEFINITE
).CapitalizeCopy());
5085 SEARCH_N_REPLACE(Msg
, "@Nd", GetName(DEFINITE
).CapitalizeCopy());
5086 SEARCH_N_REPLACE(Msg
, "@Du", GetDescription(UNARTICLED
).CapitalizeCopy());
5087 SEARCH_N_REPLACE(Msg
, "@Di", GetDescription(INDEFINITE
).CapitalizeCopy());
5088 SEARCH_N_REPLACE(Msg
, "@Dd", GetDescription(DEFINITE
).CapitalizeCopy());
5089 SEARCH_N_REPLACE(Msg
, "@Pp", GetPersonalPronoun().CapitalizeCopy());
5090 SEARCH_N_REPLACE(Msg
, "@Sp", GetPossessivePronoun().CapitalizeCopy());
5091 SEARCH_N_REPLACE(Msg
, "@Op", GetObjectPronoun().CapitalizeCopy());
5092 SEARCH_N_REPLACE(Msg
, "@Gd", GetMasterGod()->GetName());
5097 void character::ProcessAndAddMessage (festring Msg
) const {
5098 ADD_MESSAGE("%s", ProcessMessage(Msg
).CStr());
5102 void character::BeTalkedTo () {
5104 if (GetRelation(PLAYER
) == HOSTILE
)
5105 ProcessAndAddMessage(GetHostileReplies()[RandomizeReply(Said
, GetHostileReplies().Size
)]);
5107 ProcessAndAddMessage(GetFriendlyReplies()[RandomizeReply(Said
, GetFriendlyReplies().Size
)]);
5111 truth
character::CheckZap () {
5113 ADD_MESSAGE("This monster type can't zap.");
5120 void character::DamageAllItems (character
*Damager
, int Damage
, int Type
) {
5121 GetStack()->ReceiveDamage(Damager
, Damage
, Type
);
5122 for (int c
= 0; c
< GetEquipments(); ++c
) {
5123 item
*Equipment
= GetEquipment(c
);
5124 if (Equipment
) Equipment
->ReceiveDamage(Damager
, Damage
, Type
);
5129 truth
character::Equips (citem
*Item
) const {
5130 return combineequipmentpredicateswithparam
<feuLong
>()(this, &item::HasID
, Item
->GetID(), 1);
5134 void character::PrintBeginConfuseMessage () const {
5135 if (IsPlayer()) ADD_MESSAGE("You feel quite happy.");
5139 void character::PrintEndConfuseMessage () const {
5140 if (IsPlayer()) ADD_MESSAGE("The world is boring again.");
5144 v2
character::ApplyStateModification (v2 TryDirection
) const {
5145 if (!StateIsActivated(CONFUSED
) || RAND() & 15 || game::IsInWilderness()) return TryDirection
;
5146 v2 To
= GetLevel()->GetFreeAdjacentSquare(this, GetPos(), true);
5147 if (To
== ERROR_V2
) return TryDirection
;
5149 if (To
!= TryDirection
&& IsPlayer()) ADD_MESSAGE("Whoa! You somehow don't manage to walk straight.");
5154 void character::AddConfuseHitMessage () const {
5155 if (IsPlayer()) ADD_MESSAGE("This stuff is confusing.");
5159 item
*character::SelectFromPossessions (cfestring
&Topic
, sorter Sorter
) {
5160 itemvector ReturnVector
;
5161 SelectFromPossessions(ReturnVector
, Topic
, NO_MULTI_SELECT
, Sorter
);
5162 return !ReturnVector
.empty() ? ReturnVector
[0] : 0;
5166 truth
character::SelectFromPossessions (itemvector
&ReturnVector
, cfestring
&Topic
, int Flags
, sorter Sorter
) {
5168 truth InventoryPossible
= GetStack()->SortedItems(this, Sorter
);
5169 if (InventoryPossible
) List
.AddEntry(CONST_S("choose from inventory"), LIGHT_GRAY
, 20, game::AddToItemDrawVector(itemvector()));
5174 for (c
= 0; c
< BodyParts
; ++c
) {
5175 bodypart
*BodyPart
= GetBodyPart(c
);
5176 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) {
5177 Item
.push_back(BodyPart
);
5179 BodyPart
->AddName(Entry
, UNARTICLED
);
5180 int ImageKey
= game::AddToItemDrawVector(itemvector(1, BodyPart
));
5181 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
5185 for (c
= 0; c
< GetEquipments(); ++c
) {
5186 item
*Equipment
= GetEquipment(c
);
5187 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) {
5188 Item
.push_back(Equipment
);
5189 Entry
= GetEquipmentName(c
);
5192 Equipment
->AddInventoryEntry(this, Entry
, 1, true);
5193 AddSpecialEquipmentInfo(Entry
, c
);
5194 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
5195 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
5200 game::SetStandardListAttributes(List
);
5201 List
.SetFlags(SELECTABLE
|DRAW_BACKGROUND_AFTERWARDS
);
5202 List
.SetEntryDrawer(game::ItemEntryDrawer
);
5203 game::DrawEverythingNoBlit();
5204 int Chosen
= List
.Draw();
5205 game::ClearItemDrawVector();
5206 if (Chosen
!= ESCAPED
) {
5207 if ((InventoryPossible
&& !Chosen
) || Chosen
& FELIST_ERROR_BIT
) {
5208 GetStack()->DrawContents(ReturnVector
, this, Topic
, Flags
, Sorter
);
5210 ReturnVector
.push_back(Item
[InventoryPossible
? Chosen
- 1 : Chosen
]);
5211 if (Flags
& SELECT_PAIR
&& ReturnVector
[0]->HandleInPairs()) {
5212 item
*PairEquipment
= GetPairEquipment(ReturnVector
[0]->GetEquipmentIndex());
5213 if (PairEquipment
&& PairEquipment
->CanBePiledWith(ReturnVector
[0], this)) ReturnVector
.push_back(PairEquipment
);
5218 if (!GetStack()->SortedItems(this, Sorter
)) return false;
5219 game::ClearItemDrawVector();
5220 GetStack()->DrawContents(ReturnVector
, this, Topic
, Flags
, Sorter
);
5226 truth
character::EquipsSomething (sorter Sorter
) {
5227 for (int c
= 0; c
< GetEquipments(); ++c
) {
5228 item
*Equipment
= GetEquipment(c
);
5229 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) return true;
5235 material
*character::CreateBodyPartMaterial (int, sLong Volume
) const {
5236 return MAKE_MATERIAL(GetFleshMaterial(), Volume
);
5240 truth
character::CheckTalk () {
5242 ADD_MESSAGE("This monster does not know the art of talking.");
5249 truth
character::MoveTowardsHomePos () {
5250 if (HomeDataIsValid() && IsEnabled()) {
5251 SetGoingTo(HomeData
->Pos
);
5252 return MoveTowardsTarget(false) || (!GetPos().IsAdjacent(HomeData
->Pos
) && MoveRandomly());
5258 truth
character::TryToChangeEquipment (stack
*MainStack
, stack
*SecStack
, int Chosen
) {
5259 if (!GetBodyPartOfEquipment(Chosen
)) {
5260 ADD_MESSAGE("Bodypart missing!");
5263 item
*OldEquipment
= GetEquipment(Chosen
);
5264 if (!IsPlayer() && BoundToUse(OldEquipment
, Chosen
)) {
5265 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE
), OldEquipment
->CHAR_NAME(DEFINITE
));
5268 if (OldEquipment
) OldEquipment
->MoveTo(MainStack
);
5269 sorter Sorter
= EquipmentSorter(Chosen
);
5270 if (!MainStack
->SortedItems(this, Sorter
) && (!SecStack
|| !SecStack
->SortedItems(this, Sorter
))) {
5271 ADD_MESSAGE("You haven't got any item that could be used for this purpose.");
5274 game::DrawEverythingNoBlit();
5275 itemvector ItemVector
;
5276 int Return
= MainStack
->DrawContents(ItemVector
, SecStack
, this,
5277 CONST_S("Choose ") + GetEquipmentName(Chosen
) + ':',
5278 SecStack
? CONST_S("Items in your inventory") : CONST_S(""),
5279 SecStack
? festring(CONST_S("Items in ") + GetPossessivePronoun() + " inventory") : CONST_S(""),
5280 SecStack
? festring(GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState()) : CONST_S(""),
5281 GetVerbalBurdenStateColor(),
5282 NONE_AS_CHOICE
|NO_MULTI_SELECT
,
5284 if (Return
== ESCAPED
) {
5286 OldEquipment
->RemoveFromSlot();
5287 SetEquipment(Chosen
, OldEquipment
);
5291 item
*Item
= ItemVector
.empty() ? 0 : ItemVector
[0];
5293 if (!IsPlayer() && !AllowEquipment(Item
, Chosen
)) {
5294 ADD_MESSAGE("%s refuses to equip %s.", CHAR_DESCRIPTION(DEFINITE
), Item
->CHAR_NAME(DEFINITE
));
5297 Item
->RemoveFromSlot();
5298 SetEquipment(Chosen
, Item
);
5299 if (CheckIfEquipmentIsNotUsable(Chosen
)) Item
->MoveTo(MainStack
); // small bug?
5301 return Item
!= OldEquipment
;
5305 void character::PrintBeginParasitizedMessage () const {
5306 if (IsPlayer()) ADD_MESSAGE("You feel you are no longer alone.");
5310 void character::PrintEndParasitizedMessage () const {
5311 if (IsPlayer()) ADD_MESSAGE("A feeling of sLong welcome emptiness overwhelms you.");
5315 void character::ParasitizedHandler () {
5317 if (!(RAND() % 250)) {
5318 if (IsPlayer()) ADD_MESSAGE("Ugh. You feel something violently carving its way through your intestines.");
5319 ReceiveDamage(0, 1, POISON
, TORSO
, 8, false, false, false, false);
5320 CheckDeath(CONST_S("killed by a vile parasite"), 0);
5325 truth
character::CanFollow () const {
5326 return CanMove() && !StateIsActivated(PANIC
) && !IsStuck();
5330 festring
character::GetKillName () const {
5331 if (!GetPolymorphBackup()) return GetName(INDEFINITE
);
5333 GetPolymorphBackup()->AddName(KillName
, INDEFINITE
);
5334 KillName
<< " polymorphed into ";
5335 id::AddName(KillName
, INDEFINITE
);
5340 festring
character::GetPanelName () const {
5342 Name
<< AssignedName
<< " the " << game::GetVerbalPlayerAlignment() << ' ';
5343 id::AddName(Name
, UNARTICLED
);
5348 sLong
character::GetMoveAPRequirement (int Difficulty
) const {
5349 return (!StateIsActivated(PANIC
) ? 10000000 : 8000000) * Difficulty
/ (APBonus(GetAttribute(AGILITY
)) * GetMoveEase());
5353 bodypart
*character::HealHitPoint() {
5354 int NeedHeal
= 0, NeedHealIndex
[MAX_BODYPARTS
];
5355 for (int c
= 0; c
< BodyParts
; ++c
) {
5356 bodypart
*BodyPart
= GetBodyPart(c
);
5357 if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < BodyPart
->GetMaxHP()) NeedHealIndex
[NeedHeal
++] = c
;
5360 bodypart
*BodyPart
= GetBodyPart(NeedHealIndex
[RAND() % NeedHeal
]);
5361 BodyPart
->IncreaseHP();
5369 void character::CreateHomeData () {
5370 HomeData
= new homedata
;
5371 lsquare
*Square
= GetLSquareUnder();
5372 HomeData
->Pos
= Square
->GetPos();
5373 HomeData
->Dungeon
= Square
->GetDungeonIndex();
5374 HomeData
->Level
= Square
->GetLevelIndex();
5375 HomeData
->Room
= Square
->GetRoomIndex();
5379 room
*character::GetHomeRoom() const {
5380 if (HomeDataIsValid() && HomeData
->Room
) return GetLevel()->GetRoom(HomeData
->Room
);
5385 void character::RemoveHomeData () {
5391 void character::AddESPConsumeMessage () const {
5392 if (IsPlayer()) ADD_MESSAGE("You feel a strange mental activity.");
5396 void character::SetBodyPart (int I
, bodypart
*What
) {
5397 BodyPartSlot
[I
].PutInItem(What
);
5399 What
->SignalPossibleUsabilityChange();
5401 AddOriginalBodyPartID(I
, What
->GetID());
5402 if (What
->GetMainMaterial()->IsInfectedByLeprosy()) GainIntrinsic(LEPROSY
);
5403 else if (StateIsActivated(LEPROSY
)) What
->GetMainMaterial()->SetIsInfectedByLeprosy(true);
5408 truth
character::ConsumeItem (item
*Item
, cfestring
&ConsumeVerb
) {
5409 if (IsPlayer() && HasHadBodyPart(Item
) && !game::TruthQuestion(CONST_S("Are you sure? You may be able to put it back... [y/N]")))
5411 if (Item
->IsOnGround() && GetRoom() && !GetRoom()->ConsumeItem(this, Item
, 1))
5413 if (IsPlayer()) ADD_MESSAGE("You begin %s %s.", ConsumeVerb
.CStr(), Item
->CHAR_NAME(DEFINITE
));
5414 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins %s %s.", CHAR_NAME(DEFINITE
), ConsumeVerb
.CStr(), Item
->CHAR_NAME(DEFINITE
));
5415 consume
*Consume
= consume::Spawn(this);
5416 Consume
->SetDescription(ConsumeVerb
);
5417 Consume
->SetConsumingID(Item
->GetID());
5424 truth
character::CheckThrow () const {
5426 ADD_MESSAGE("This monster type cannot throw.");
5433 void character::GetHitByExplosion (const explosion
*Explosion
, int Damage
) {
5434 int DamageDirection
= GetPos() == Explosion
->Pos
? RANDOM_DIR
: game::CalculateRoughDirection(GetPos() - Explosion
->Pos
);
5435 if (!IsPet() && Explosion
->Terrorist
&& Explosion
->Terrorist
->IsPet()) Explosion
->Terrorist
->Hostility(this);
5436 GetTorso()->SpillBlood((8 - Explosion
->Size
+ RAND() % (8 - Explosion
->Size
)) >> 1);
5437 v2 SpillPos
= GetPos() + game::GetMoveVector(DamageDirection
);
5438 if (GetArea()->IsValidPos(SpillPos
)) GetTorso()->SpillBlood((8-Explosion
->Size
+RAND()%(8-Explosion
->Size
))>>1, SpillPos
);
5439 if (IsPlayer()) ADD_MESSAGE("You are hit by the explosion!");
5440 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is hit by the explosion.", CHAR_NAME(DEFINITE
));
5441 truth WasUnconscious
= GetAction() && GetAction()->IsUnconsciousness();
5442 ReceiveDamage(Explosion
->Terrorist
, Damage
>> 1, FIRE
, ALL
, DamageDirection
, true, false, false, false);
5444 ReceiveDamage(Explosion
->Terrorist
, Damage
>> 1, PHYSICAL_DAMAGE
, ALL
, DamageDirection
, true, false, false, false);
5445 CheckDeath(Explosion
->DeathMsg
, Explosion
->Terrorist
, !WasUnconscious
? IGNORE_UNCONSCIOUSNESS
: 0);
5450 void character::SortAllItems (const sortdata
&SortData
) {
5451 GetStack()->SortAllItems(SortData
);
5452 doforequipmentswithparam
<const sortdata
&>()(this, &item::SortAllItems
, SortData
);
5456 void character::PrintBeginSearchingMessage () const {
5457 if (IsPlayer()) ADD_MESSAGE("You feel you can now notice even the very smallest details around you.");
5461 void character::PrintEndSearchingMessage () const {
5462 if (IsPlayer()) ADD_MESSAGE("You feel less perceptive.");
5466 void character::SearchingHandler () {
5467 if (!game::IsInWilderness()) Search(15);
5471 void character::Search (int Perception
) {
5472 for (int d
= 0; d
< GetExtendedNeighbourSquares(); ++d
) {
5473 lsquare
*LSquare
= GetNeighbourLSquare(d
);
5474 if (LSquare
) LSquare
->GetStack()->Search(this, Min(Perception
, 200));
5479 // surprisingly returns 0 if fails
5480 character
*character::GetRandomNeighbour (int RelationFlags
) const {
5481 character
*Chars
[MAX_NEIGHBOUR_SQUARES
];
5483 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
5484 lsquare
*LSquare
= GetNeighbourLSquare(d
);
5486 character
*Char
= LSquare
->GetCharacter();
5487 if (Char
&& (GetRelation(Char
) & RelationFlags
)) Chars
[Index
++] = Char
;
5490 return Index
? Chars
[RAND() % Index
] : 0;
5494 void character::ResetStates () {
5495 for (int c
= 0; c
< STATES
; ++c
) {
5496 if (1 << c
!= POLYMORPHED
&& TemporaryStateIsActivated(1 << c
) && TemporaryStateCounter
[c
] != PERMANENT
) {
5497 TemporaryState
&= ~(1 << c
);
5498 if (StateData
[c
].EndHandler
) {
5499 (this->*StateData
[c
].EndHandler
)();
5500 if (!IsEnabled())return;
5507 void characterdatabase::InitDefaults (const characterprototype
*NewProtoType
, int NewConfig
) {
5509 ProtoType
= NewProtoType
;
5515 void character::PrintBeginGasImmunityMessage () const {
5516 if (IsPlayer()) ADD_MESSAGE("All smells fade away.");
5520 void character::PrintEndGasImmunityMessage () const {
5521 if (IsPlayer()) ADD_MESSAGE("Yuck! The world smells bad again.");
5525 void character::ShowAdventureInfo () const {
5526 static const char *lists
[4][4] = {
5527 { "Show massacre history",
5529 "Show message history",
5532 "Show message history",
5535 { "Show message history",
5539 { "Show massacre history",
5540 "Show message history",
5544 // massacre, inventory, messages
5545 static const int nums
[4][3] = {
5552 if (GetStack()->GetItems()) {
5553 idx
= game::MassacreListsEmpty() ? 1 : 0;
5555 idx
= game::MassacreListsEmpty() ? 2 : 3;
5559 sel
= game::ListSelectorArray(sel
, CONST_S("Do you want to see some funny history?"), lists
[idx
]);
5561 if (sel
== nums
[idx
][0] && !game::MassacreListsEmpty()) {
5562 game::DisplayMassacreLists();
5564 if (sel
== nums
[idx
][1] && GetStack()->GetItems()) {
5565 GetStack()->DrawContents(this, CONST_S("Your inventory"), NO_SELECT
);
5566 for(stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) i
->DrawContents(this);
5567 doforequipmentswithparam
<ccharacter
*>()(this, &item::DrawContents
, this);
5569 if (sel
== nums
[idx
][2]) {
5570 msgsystem::DrawMessageHistory();
5576 truth
character::EditAllAttributes (int Amount
) {
5577 if (!Amount
) return true;
5579 truth MayEditMore
= false;
5580 for (c
= 0; c
< BodyParts
; ++c
) {
5581 bodypart
*BodyPart
= GetBodyPart(c
);
5582 if (BodyPart
&& BodyPart
->EditAllAttributes(Amount
)) MayEditMore
= true;
5584 for (c
= 0; c
< BASE_ATTRIBUTES
; ++c
) {
5585 if (BaseExperience
[c
]) {
5586 BaseExperience
[c
] += Amount
* EXP_MULTIPLIER
;
5587 LimitRef(BaseExperience
[c
], MIN_EXP
, MAX_EXP
);
5588 if ((Amount
< 0 && BaseExperience
[c
] != MIN_EXP
) || (Amount
> 0 && BaseExperience
[c
] != MAX_EXP
)) MayEditMore
= true;
5595 game::SendLOSUpdateRequest();
5598 if (IsPlayerKind()) UpdatePictures();
5604 void character::AddAttributeInfo (festring
&Entry
) const {
5606 Entry
<< GetAttribute(ENDURANCE
);
5608 Entry
<< GetAttribute(PERCEPTION
);
5610 Entry
<< GetAttribute(INTELLIGENCE
);
5612 Entry
<< GetAttribute(WISDOM
);
5614 Entry
<< GetAttribute(CHARISMA
);
5616 Entry
<< GetAttribute(MANA
);
5620 void character::AddDefenceInfo (felist
&List
) const {
5622 for (int c
= 0; c
< BodyParts
; ++c
) {
5623 bodypart
*BodyPart
= GetBodyPart(c
);
5625 Entry
= CONST_S(" ");
5626 BodyPart
->AddName(Entry
, UNARTICLED
);
5628 Entry
<< BodyPart
->GetMaxHP();
5630 Entry
<< BodyPart
->GetTotalResistance(PHYSICAL_DAMAGE
);
5631 List
.AddEntry(Entry
, LIGHT_GRAY
);
5637 void character::DetachBodyPart () {
5638 ADD_MESSAGE("You haven't got any extra bodyparts.");
5643 void character::ReceiveHolyBanana (sLong Amount
) {
5645 EditExperience(ARM_STRENGTH
, Amount
, 1 << 13);
5646 EditExperience(LEG_STRENGTH
, Amount
, 1 << 13);
5647 EditExperience(DEXTERITY
, Amount
, 1 << 13);
5648 EditExperience(AGILITY
, Amount
, 1 << 13);
5649 EditExperience(ENDURANCE
, Amount
, 1 << 13);
5650 EditExperience(PERCEPTION
, Amount
, 1 << 13);
5651 EditExperience(INTELLIGENCE
, Amount
, 1 << 13);
5652 EditExperience(WISDOM
, Amount
, 1 << 13);
5653 EditExperience(CHARISMA
, Amount
, 1 << 13);
5658 void character::AddHolyBananaConsumeEndMessage () const {
5659 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
5660 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE
));
5664 void character::ReceiveHolyMango (sLong Amount
) {
5666 EditExperience(ARM_STRENGTH
, Amount
, 1 << 13);
5667 EditExperience(LEG_STRENGTH
, Amount
, 1 << 13);
5668 EditExperience(DEXTERITY
, Amount
, 1 << 13);
5669 EditExperience(AGILITY
, Amount
, 1 << 13);
5670 EditExperience(ENDURANCE
, Amount
, 1 << 13);
5671 EditExperience(PERCEPTION
, Amount
, 1 << 13);
5672 EditExperience(INTELLIGENCE
, Amount
, 1 << 13);
5673 EditExperience(WISDOM
, Amount
, 1 << 13);
5674 EditExperience(CHARISMA
, Amount
, 1 << 13);
5679 void character::AddHolyMangoConsumeEndMessage () const {
5680 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
5681 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE
));
5685 truth
character::PreProcessForBone () {
5686 if (IsPet() && IsEnabled()) {
5687 Die(0, CONST_S(""), FORBID_REINCARNATION
);
5690 if (GetAction()) GetAction()->Terminate(false);
5691 if (TemporaryStateIsActivated(POLYMORPHED
)) {
5692 character
*PolymorphBackup
= GetPolymorphBackup();
5694 PolymorphBackup
->PreProcessForBone();
5697 if (MustBeRemovedFromBone()) return false;
5698 if (IsUnique() && !CanBeGenerated()) game::SignalQuestMonsterFound();
5702 GetStack()->PreProcessForBone();
5703 doforequipments()(this, &item::PreProcessForBone
);
5704 doforbodyparts()(this, &bodypart::PreProcessForBone
);
5705 game::RemoveCharacterID(ID
);
5707 game::AddCharacterID(this, ID
);
5712 truth
character::PostProcessForBone (double &DangerSum
, int& Enemies
) {
5713 if (PostProcessForBone()) {
5714 if (GetRelation(PLAYER
) == HOSTILE
) {
5715 double Danger
= GetRelativeDanger(PLAYER
, true);
5716 if (Danger
> 99.0) game::SetTooGreatDangerFound(true);
5717 else if (!IsUnique() && !IgnoreDanger()) {
5718 DangerSum
+= Danger
;
5728 truth
character::PostProcessForBone () {
5729 feuLong NewID
= game::CreateNewCharacterID(this);
5730 game::GetBoneCharacterIDMap().insert(std::make_pair(-ID
, NewID
));
5731 game::RemoveCharacterID(ID
);
5733 if (IsUnique() && CanBeGenerated()) {
5734 if (DataBase
->Flags
& HAS_BEEN_GENERATED
) return false;
5737 GetStack()->PostProcessForBone();
5738 doforequipments()(this, &item::PostProcessForBone
);
5739 doforbodyparts()(this, &bodypart::PostProcessForBone
);
5744 void character::FinalProcessForBone () {
5746 GetStack()->FinalProcessForBone();
5747 doforequipments()(this, &item::FinalProcessForBone
);
5749 for (c
= 0; c
< BodyParts
; ++c
) {
5750 for (std::list
<feuLong
>::iterator i
= OriginalBodyPartID
[c
].begin(); i
!= OriginalBodyPartID
[c
].end();) {
5751 boneidmap::iterator BI
= game::GetBoneItemIDMap().find(*i
);
5752 if (BI
== game::GetBoneItemIDMap().end()) {
5753 std::list
<feuLong
>::iterator Dirt
= i
++;
5754 OriginalBodyPartID
[c
].erase(Dirt
);
5764 void character::SetSoulID (feuLong What
) {
5765 if (GetPolymorphBackup()) GetPolymorphBackup()->SetSoulID(What
);
5769 truth
character::SearchForItem (citem
*Item
) const {
5770 if (combineequipmentpredicateswithparam
<feuLong
>()(this, &item::HasID
, Item
->GetID(), 1)) return true;
5771 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (*i
== Item
) return true;
5776 item
*character::SearchForItem (const sweaponskill
*SWeaponSkill
) const {
5777 for (int c
= 0; c
< GetEquipments(); ++c
) {
5778 item
*Equipment
= GetEquipment(c
);
5779 if (Equipment
&& SWeaponSkill
->IsSkillOf(Equipment
)) return Equipment
;
5781 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (SWeaponSkill
->IsSkillOf(*i
)) return *i
;
5786 void character::PutNear (v2 Pos
) {
5787 v2 NewPos
= game::GetCurrentLevel()->GetNearestFreeSquare(this, Pos
, false);
5788 if (NewPos
== ERROR_V2
) {
5789 do { NewPos
= game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos
== Pos
);
5795 void character::PutToOrNear (v2 Pos
) {
5796 if (game::IsInWilderness() || (CanMoveOn(game::GetCurrentLevel()->GetLSquare(Pos
)) && IsFreeForMe(game::GetCurrentLevel()->GetLSquare(Pos
))))
5803 void character::PutTo (v2 Pos
) {
5804 SquareUnder
[0] = game::GetCurrentArea()->GetSquare(Pos
);
5805 SquareUnder
[0]->AddCharacter(this);
5809 void character::Remove () {
5810 SquareUnder
[0]->RemoveCharacter();
5815 void character::SendNewDrawRequest () const {
5816 for (int c
= 0; c
< SquaresUnder
; ++c
) {
5817 square
*Square
= GetSquareUnder(c
);
5818 if (Square
) Square
->SendNewDrawRequest();
5823 truth
character::IsOver (v2 Pos
) const {
5824 for (int c
= 0; c
< SquaresUnder
; ++c
) {
5825 square
*Square
= GetSquareUnder(c
);
5826 if (Square
&& Square
->GetPos() == Pos
) return true;
5832 truth
character::CanTheoreticallyMoveOn (const lsquare
*LSquare
) const { return GetMoveType() & LSquare
->GetTheoreticalWalkability(); }
5833 truth
character::CanMoveOn (const lsquare
*LSquare
) const { return GetMoveType() & LSquare
->GetWalkability(); }
5834 truth
character::CanMoveOn (const square
*Square
) const { return GetMoveType() & Square
->GetSquareWalkability(); }
5835 truth
character::CanMoveOn (const olterrain
*OLTerrain
) const { return GetMoveType() & OLTerrain
->GetWalkability(); }
5836 truth
character::CanMoveOn (const oterrain
*OTerrain
) const { return GetMoveType() & OTerrain
->GetWalkability(); }
5837 truth
character::IsFreeForMe(square
*Square
) const { return !Square
->GetCharacter() || Square
->GetCharacter() == this; }
5838 void character::LoadSquaresUnder () { SquareUnder
[0] = game::GetSquareInLoad(); }
5840 truth
character::AttackAdjacentEnemyAI () {
5841 if (!IsEnabled()) return false;
5842 character
*Char
[MAX_NEIGHBOUR_SQUARES
];
5843 v2 Pos
[MAX_NEIGHBOUR_SQUARES
];
5844 int Dir
[MAX_NEIGHBOUR_SQUARES
];
5846 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
5847 square
*Square
= GetNeighbourSquare(d
);
5849 character
*Enemy
= Square
->GetCharacter();
5850 if (Enemy
&& (GetRelation(Enemy
) == HOSTILE
|| StateIsActivated(CONFUSED
))) {
5852 Pos
[Index
] = Square
->GetPos();
5853 Char
[Index
++] = Enemy
;
5858 int ChosenIndex
= RAND() % Index
;
5859 Hit(Char
[ChosenIndex
], Pos
[ChosenIndex
], Dir
[ChosenIndex
]);
5866 void character::SignalStepFrom (lsquare
**OldSquareUnder
) {
5868 lsquare
*NewSquareUnder
[MAX_SQUARES_UNDER
];
5869 for (c
= 0; c
< GetSquaresUnder(); ++c
) NewSquareUnder
[c
] = GetLSquareUnder(c
);
5870 for (c
= 0; c
< GetSquaresUnder(); ++c
) {
5871 if (IsEnabled() && GetLSquareUnder(c
) == NewSquareUnder
[c
]) NewSquareUnder
[c
]->StepOn(this, OldSquareUnder
);
5876 int character::GetSumOfAttributes () const {
5877 return GetAttribute(ENDURANCE
) + GetAttribute(PERCEPTION
) + GetAttribute(INTELLIGENCE
) + GetAttribute(WISDOM
) + GetAttribute(CHARISMA
) + GetAttribute(ARM_STRENGTH
) + GetAttribute(AGILITY
);
5881 void character::IntelligenceAction (int Difficulty
) {
5882 EditAP(-20000 * Difficulty
/ APBonus(GetAttribute(INTELLIGENCE
)));
5883 EditExperience(INTELLIGENCE
, Difficulty
* 15, 1 << 7);
5887 struct walkabilitycontroller
{
5888 static truth
Handler (int x
, int y
) {
5889 return x
>= 0 && y
>= 0 && x
< LevelXSize
&& y
< LevelYSize
&& Map
[x
][y
]->GetTheoreticalWalkability() & MoveType
;
5891 static lsquare
***Map
;
5892 static int LevelXSize
, LevelYSize
;
5893 static int MoveType
;
5897 lsquare
***walkabilitycontroller::Map
;
5898 int walkabilitycontroller::LevelXSize
, walkabilitycontroller::LevelYSize
;
5899 int walkabilitycontroller::MoveType
;
5902 truth
character::CreateRoute () {
5904 if (GetAttribute(INTELLIGENCE
) >= 10 && !StateIsActivated(CONFUSED
)) {
5906 walkabilitycontroller::Map
= GetLevel()->GetMap();
5907 walkabilitycontroller::LevelXSize
= GetLevel()->GetXSize();
5908 walkabilitycontroller::LevelYSize
= GetLevel()->GetYSize();
5909 walkabilitycontroller::MoveType
= GetMoveType();
5911 for (int c
= 0; c
< game::GetTeams(); ++c
)
5912 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
5913 character
*Char
= *i
;
5914 if (Char
->IsEnabled() && !Char
->Route
.empty() && (Char
->GetMoveType()&GetMoveType()) == Char
->GetMoveType()) {
5915 v2 CharGoingTo
= Char
->Route
[0];
5916 v2 iPos
= Char
->Route
.back();
5917 if ((GoingTo
-CharGoingTo
).GetLengthSquare() <= 100 && (Pos
- iPos
).GetLengthSquare() <= 100 &&
5918 mapmath
<walkabilitycontroller
>::DoLine(CharGoingTo
.X
, CharGoingTo
.Y
, GoingTo
.X
, GoingTo
.Y
, SKIP_FIRST
) &&
5919 mapmath
<walkabilitycontroller
>::DoLine(Pos
.X
, Pos
.Y
, iPos
.X
, iPos
.Y
, SKIP_FIRST
)) {
5920 if (!Illegal
.empty() && Illegal
.find(Char
->Route
.back()) != Illegal
.end()) continue;
5921 Node
= GetLevel()->FindRoute(CharGoingTo
, GoingTo
, Illegal
, GetMoveType());
5922 if (Node
) { while(Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
5923 else { Route
.clear(); continue; }
5924 Route
.insert(Route
.end(), Char
->Route
.begin(), Char
->Route
.end());
5925 Node
= GetLevel()->FindRoute(Pos
, iPos
, Illegal
, GetMoveType());
5926 if (Node
) { while (Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
5927 else { Route
.clear(); continue; }
5928 IntelligenceAction(1);
5933 Node
= GetLevel()->FindRoute(Pos
, GoingTo
, Illegal
, GetMoveType());
5934 if (Node
) { while(Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
5935 else TerminateGoingTo();
5936 IntelligenceAction(5);
5943 void character::SetGoingTo (v2 What
) {
5944 if (GoingTo
!= What
) {
5952 void character::TerminateGoingTo () {
5959 truth
character::CheckForFood (int Radius
) {
5960 if (StateIsActivated(PANIC
) || !UsesNutrition() || !IsEnabled()) return false;
5963 for (int r
= 1; r
<= Radius
; ++r
) {
5966 for (y
= Pos
.Y
-r
; y
<= Pos
.Y
+r
; ++y
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
5969 if (x
< GetLevel()->GetXSize()) {
5970 for (y
= Pos
.Y
-r
; y
<= Pos
.Y
+r
; ++y
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
5974 for (x
= Pos
.X
-r
; x
<= Pos
.X
+r
; ++x
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
5977 if (y
< GetLevel()->GetYSize()) {
5978 for (x
= Pos
.X
-r
; x
<= Pos
.X
+r
; ++x
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
5985 truth
character::CheckForFoodInSquare (v2 Pos
) {
5986 level
*Level
= GetLevel();
5987 if (Level
->IsValidPos(Pos
)) {
5988 lsquare
*Square
= Level
->GetLSquare(Pos
);
5989 stack
*Stack
= Square
->GetStack();
5990 if (Stack
->GetItems()) {
5991 for (stackiterator i
= Stack
->GetBottom(); i
.HasItem(); ++i
) {
5992 if (i
->IsPickable(this) && i
->CanBeSeenBy(this) && i
->CanBeEatenByAI(this) && (!Square
->GetRoomIndex() || Square
->GetRoom()->AllowFoodSearch())) {
5994 return MoveTowardsTarget(false);
6003 void character::SetConfig (int NewConfig
, int SpecialFlags
) {
6004 databasecreator
<character
>::InstallDataBase(this, NewConfig
);
6007 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdatePictures();
6011 truth
character::IsOver (citem
*Item
) const {
6012 for (int c1
= 0; c1
< Item
->GetSquaresUnder(); ++c1
)
6013 for (int c2
= 0; c2
< SquaresUnder
; ++c2
)
6014 if (Item
->GetPos(c1
) == GetPos(c2
)) return true;
6019 truth
character::CheckConsume (cfestring
&Verb
) const {
6020 if (!UsesNutrition()) {
6021 if (IsPlayer()) ADD_MESSAGE("In this form you can't and don't need to %s.", Verb
.CStr());
6028 void character::PutTo (lsquare
*To
) {
6029 PutTo(To
->GetPos());
6033 double character::RandomizeBabyExperience (double SumE
) {
6034 if (!SumE
) return 0;
6035 double E
= (SumE
/ 4) - (SumE
/ 32) + (double(RAND()) / MAX_RAND
) * (SumE
/ 16 + 1);
6036 return Limit(E
, MIN_EXP
, MAX_EXP
);
6040 liquid
*character::CreateBlood (sLong Volume
) const {
6041 return liquid::Spawn(GetBloodMaterial(), Volume
);
6045 void character::SpillFluid (character
*Spiller
, liquid
*Liquid
, int SquareIndex
) {
6046 sLong ReserveVolume
= Liquid
->GetVolume() >> 1;
6047 Liquid
->EditVolume(-ReserveVolume
);
6048 GetStack()->SpillFluid(Spiller
, Liquid
, sLong(Liquid
->GetVolume() * sqrt(double(GetStack()->GetVolume()) / GetVolume())));
6049 Liquid
->EditVolume(ReserveVolume
);
6051 sLong Modifier
[MAX_BODYPARTS
], ModifierSum
= 0;
6052 for (c
= 0; c
< BodyParts
; ++c
) {
6053 if (GetBodyPart(c
)) {
6054 Modifier
[c
] = sLong(sqrt(GetBodyPart(c
)->GetVolume()));
6055 if (Modifier
[c
]) Modifier
[c
] *= 1 + (RAND() & 3);
6056 ModifierSum
+= Modifier
[c
];
6061 for (c
= 1; c
< GetBodyParts(); ++c
) {
6062 if (GetBodyPart(c
) && IsEnabled())
6063 GetBodyPart(c
)->SpillFluid(Spiller
, Liquid
->SpawnMoreLiquid(Liquid
->GetVolume() * Modifier
[c
] / ModifierSum
), SquareIndex
);
6066 Liquid
->SetVolume(Liquid
->GetVolume() * Modifier
[TORSO_INDEX
] / ModifierSum
);
6067 GetTorso()->SpillFluid(Spiller
, Liquid
, SquareIndex
);
6072 void character::StayOn (liquid
*Liquid
) {
6073 Liquid
->TouchEffect(this, TORSO_INDEX
);
6077 truth
character::IsAlly (ccharacter
*Char
) const {
6078 return Char
->GetTeam()->GetID() == GetTeam()->GetID();
6082 void character::ResetSpoiling () {
6083 doforbodyparts()(this, &bodypart::ResetSpoiling
);
6087 item
*character::SearchForItem (ccharacter
*Char
, sorter Sorter
) const {
6088 item
*Equipment
= findequipment
<ccharacter
*>()(this, Sorter
, Char
);
6089 if (Equipment
) return Equipment
;
6090 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (((*i
)->*Sorter
)(Char
)) return *i
;
6095 truth
character::DetectMaterial (cmaterial
*Material
) const {
6096 return GetStack()->DetectMaterial(Material
) ||
6097 combinebodypartpredicateswithparam
<cmaterial
*>()(this, &bodypart::DetectMaterial
, Material
, 1) ||
6098 combineequipmentpredicateswithparam
<cmaterial
*>()(this, &item::DetectMaterial
, Material
, 1);
6102 truth
character::DamageTypeDestroysBodyPart (int Type
) {
6103 return (Type
&0xFFF) != PHYSICAL_DAMAGE
;
6107 truth
character::CheckIfTooScaredToHit (ccharacter
*Enemy
) const {
6108 if (IsPlayer() && StateIsActivated(PANIC
)) {
6109 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
6110 square
*Square
= GetNeighbourSquare(d
);
6112 if(CanMoveOn(Square
) && (!Square
->GetCharacter() || Square
->GetCharacter()->IsPet())) {
6113 ADD_MESSAGE("You are too scared to attack %s.", Enemy
->CHAR_DESCRIPTION(DEFINITE
));
6123 void character::PrintBeginLevitationMessage () const {
6125 if (IsPlayer()) ADD_MESSAGE("You rise into the air like a small hot-air balloon.");
6126 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins to float.", CHAR_NAME(DEFINITE
));
6131 void character::PrintEndLevitationMessage () const {
6133 if (IsPlayer()) ADD_MESSAGE("You descend gently onto the ground.");
6134 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s drops onto the ground.", CHAR_NAME(DEFINITE
));
6139 truth
character::IsLimbIndex (int I
) {
6141 case RIGHT_ARM_INDEX
:
6142 case LEFT_ARM_INDEX
:
6143 case RIGHT_LEG_INDEX
:
6144 case LEFT_LEG_INDEX
:
6151 void character::EditExperience (int Identifier
, double Value
, double Speed
) {
6152 if (!AllowExperience() || (Identifier
== ENDURANCE
&& UseMaterialAttributes())) return;
6153 int Change
= RawEditExperience(BaseExperience
[Identifier
], GetNaturalExperience(Identifier
), Value
, Speed
);
6154 if (!Change
) return;
6155 cchar
*PlayerMsg
= 0, *NPCMsg
= 0;
6156 switch (Identifier
) {
6159 PlayerMsg
= "You feel tougher than anything!";
6160 if (IsPet()) NPCMsg
= "Suddenly %s looks tougher.";
6162 PlayerMsg
= "You feel less healthy.";
6163 if (IsPet()) NPCMsg
= "Suddenly %s looks less healthy.";
6165 CalculateBodyPartMaxHPs();
6166 CalculateMaxStamina();
6171 PlayerMsg
= "You now see the world in much better detail than before.";
6173 PlayerMsg
= "You feel very guru.";
6174 game::GetGod(VALPURUS
)->AdjustRelation(100);
6176 game::SendLOSUpdateRequest();
6181 if (Change
> 0) PlayerMsg
= "Suddenly the inner structure of the Multiverse around you looks quite simple.";
6182 else PlayerMsg
= "It surely is hard to think today.";
6185 if (IsPlayerKind()) UpdatePictures();
6189 if (Change
> 0) PlayerMsg
= "You feel your life experience increasing all the time.";
6190 else PlayerMsg
= "You feel like having done something unwise.";
6192 if (IsPlayerKind()) UpdatePictures();
6196 PlayerMsg
= "You feel very confident of your social skills.";
6198 if (GetAttribute(CHARISMA
) <= 15) NPCMsg
= "%s looks less ugly.";
6199 else NPCMsg
= "%s looks more attractive.";
6202 PlayerMsg
= "You feel somehow disliked.";
6204 if (GetAttribute(CHARISMA
) < 15) NPCMsg
= "%s looks more ugly.";
6205 else NPCMsg
= "%s looks less attractive.";
6208 if (IsPlayerKind()) UpdatePictures();
6212 PlayerMsg
= "You feel magical forces coursing through your body!";
6213 NPCMsg
= "You notice an odd glow around %s.";
6215 PlayerMsg
= "You feel your magical abilities withering slowly.";
6216 NPCMsg
= "You notice strange vibrations in the air around %s. But they disappear rapidly.";
6221 if (IsPlayer()) ADD_MESSAGE("%s", PlayerMsg
);
6222 else if (NPCMsg
&& CanBeSeenByPlayer()) ADD_MESSAGE(NPCMsg
, CHAR_NAME(DEFINITE
));
6224 CalculateBattleInfo();
6228 int character::RawEditExperience (double &Exp
, double NaturalExp
, double Value
, double Speed
) const {
6229 double OldExp
= Exp
;
6234 if(!OldExp
|| !Value
|| (Value
> 0 && OldExp
>= NaturalExp
* (100 + Value
) / 100) ||
6235 (Value
< 0 && OldExp
<= NaturalExp
* (100 + Value
) / 100)) return 0;
6236 if (!IsPlayer()) Speed
*= 1.5;
6237 Exp
+= (NaturalExp
* (100 + Value
) - 100 * OldExp
) * Speed
* EXP_DIVISOR
;
6238 LimitRef(Exp
, MIN_EXP
, MAX_EXP
);
6239 int NewA
= int(Exp
* EXP_DIVISOR
);
6240 int OldA
= int(OldExp
* EXP_DIVISOR
);
6241 int Delta
= NewA
- OldA
;
6242 if (Delta
> 0) Exp
= Max(Exp
, (NewA
+ 0.05) * EXP_MULTIPLIER
);
6243 else if (Delta
< 0) Exp
= Min(Exp
, (NewA
+ 0.95) * EXP_MULTIPLIER
);
6244 LimitRef(Exp
, MIN_EXP
, MAX_EXP
);
6249 int character::GetAttribute (int Identifier
, truth AllowBonus
) const {
6250 int A
= int(BaseExperience
[Identifier
] * EXP_DIVISOR
);
6251 if (AllowBonus
&& Identifier
== INTELLIGENCE
&& BrainsHurt()) return Max((A
+ AttributeBonus
[INTELLIGENCE
]) / 3, 1);
6252 return A
&& AllowBonus
? Max(A
+ AttributeBonus
[Identifier
], 1) : A
;
6256 void characterdatabase::PostProcess () {
6257 double AM
= (100 + AttributeBonus
) * EXP_MULTIPLIER
/ 100;
6258 for (int c
= 0; c
< ATTRIBUTES
; ++c
) NaturalExperience
[c
] = this->*ExpPtr
[c
] * AM
;
6262 void character::EditDealExperience (sLong Price
) {
6263 EditExperience(CHARISMA
, sqrt(Price
) / 5, 1 << 9);
6267 void character::PrintBeginLeprosyMessage () const {
6268 if (IsPlayer()) ADD_MESSAGE("You feel you're falling in pieces.");
6272 void character::PrintEndLeprosyMessage () const {
6273 if (IsPlayer()) ADD_MESSAGE("You feel your limbs are stuck in place tightly."); // CHANGE OR DIE
6277 void character::TryToInfectWithLeprosy (ccharacter
*Infector
) {
6278 if (!IsImmuneToLeprosy() &&
6279 ((GetRelation(Infector
) == HOSTILE
&& !RAND_N(50 * GetAttribute(ENDURANCE
))) ||
6280 !RAND_N(500 * GetAttribute(ENDURANCE
)))) GainIntrinsic(LEPROSY
);
6284 void character::SignalGeneration () {
6285 const_cast<database
*>(DataBase
)->Flags
|= HAS_BEEN_GENERATED
;
6289 void character::CheckIfSeen () {
6290 if (IsPlayer() || CanBeSeenByPlayer()) SignalSeen();
6294 void character::SignalSeen () {
6295 if (!(WarnFlags
& WARNED
) && GetRelation(PLAYER
) == HOSTILE
) {
6296 double Danger
= GetRelativeDanger(PLAYER
);
6298 game::SetDangerFound(Max(game::GetDangerFound(), Danger
));
6299 if (Danger
> 500.0 && !(WarnFlags
& HAS_CAUSED_PANIC
)) {
6300 WarnFlags
|= HAS_CAUSED_PANIC
;
6301 game::SetCausePanicFlag(true);
6303 WarnFlags
|= WARNED
;
6306 const_cast<database
*>(DataBase
)->Flags
|= HAS_BEEN_SEEN
;
6310 int character::GetPolymorphIntelligenceRequirement () const {
6311 if (DataBase
->PolymorphIntelligenceRequirement
== DEPENDS_ON_ATTRIBUTES
) return Max(GetAttributeAverage() - 5, 0);
6312 return DataBase
->PolymorphIntelligenceRequirement
;
6316 void character::RemoveAllItems () {
6317 GetStack()->Clean();
6318 for (int c
= 0; c
< GetEquipments(); ++c
) {
6319 item
*Equipment
= GetEquipment(c
);
6321 Equipment
->RemoveFromSlot();
6322 Equipment
->SendToHell();
6328 int character::CalculateWeaponSkillHits (ccharacter
*Enemy
) const {
6329 if (Enemy
->IsPlayer()) {
6330 configid
ConfigID(GetType(), GetConfig());
6331 const dangerid
& DangerID
= game::GetDangerMap().find(ConfigID
)->second
;
6332 return Min(int(DangerID
.EquippedDanger
* 2000), 1000);
6334 return Min(int(GetRelativeDanger(Enemy
, true) * 2000), 1000);
6338 truth
character::CanUseEquipment (int I
) const {
6339 return CanUseEquipment() && I
< GetEquipments() && GetBodyPartOfEquipment(I
) && EquipmentIsAllowed(I
);
6343 /* Target mustn't have any equipment */
6344 void character::DonateEquipmentTo (character
*Character
) {
6346 feuLong
*EquipmentMemory
= game::GetEquipmentMemory();
6347 for (int c
= 0; c
< MAX_EQUIPMENT_SLOTS
; ++c
) {
6348 item
*Item
= GetEquipment(c
);
6350 if (Character
->CanUseEquipment(c
)) {
6351 Item
->RemoveFromSlot();
6352 Character
->SetEquipment(c
, Item
);
6354 EquipmentMemory
[c
] = Item
->GetID();
6355 Item
->MoveTo(Character
->GetStack());
6357 } else if (CanUseEquipment(c
)) {
6358 EquipmentMemory
[c
] = 0;
6359 } else if (EquipmentMemory
[c
] && Character
->CanUseEquipment(c
)) {
6360 for (stackiterator i
= Character
->GetStack()->GetBottom(); i
.HasItem(); ++i
) {
6361 if (i
->GetID() == EquipmentMemory
[c
]) {
6363 Item
->RemoveFromSlot();
6364 Character
->SetEquipment(c
, Item
);
6368 EquipmentMemory
[c
] = 0;
6372 for (int c
= 0; c
< GetEquipments(); ++c
) {
6373 item
*Item
= GetEquipment(c
);
6375 if (Character
->CanUseEquipment(c
)) {
6376 Item
->RemoveFromSlot();
6377 Character
->SetEquipment(c
, Item
);
6379 Item
->MoveTo(Character
->GetStackUnder());
6387 void character::ReceivePeaSoup (sLong
) {
6388 lsquare
*Square
= GetLSquareUnder();
6389 if (Square
->IsFlyable()) Square
->AddSmoke(gas::Spawn(FART
, 250));
6393 void character::AddPeaSoupConsumeEndMessage () const {
6395 if (CanHear()) ADD_MESSAGE("Mmmh! The soup is very tasty. You hear a small puff.");
6396 else ADD_MESSAGE("Mmmh! The soup is very tasty.");
6397 } else if (CanBeSeenByPlayer() && PLAYER
->CanHear()) {
6399 ADD_MESSAGE("You hear a small puff.");
6404 void character::CalculateMaxStamina () {
6405 MaxStamina
= TorsoIsAlive() ? GetAttribute(ENDURANCE
) * 10000 : 0;
6409 void character::EditStamina (int Amount
, truth CanCauseUnconsciousness
) {
6410 if (!TorsoIsAlive()) return;
6411 int UnconsciousnessStamina
= MaxStamina
>> 3;
6412 if (!CanCauseUnconsciousness
&& Amount
< 0) {
6413 if (Stamina
> UnconsciousnessStamina
) {
6415 if (Stamina
< UnconsciousnessStamina
) Stamina
= UnconsciousnessStamina
;
6419 int OldStamina
= Stamina
;
6421 if (Stamina
> MaxStamina
) {
6422 Stamina
= MaxStamina
;
6423 } else if (Stamina
< 0) {
6425 LoseConsciousness(250 + RAND_N(250));
6426 } else if (IsPlayer()) {
6427 if (OldStamina
>= MaxStamina
>> 2 && Stamina
< MaxStamina
>> 2) {
6428 ADD_MESSAGE("You are getting a little tired.");
6429 } else if(OldStamina
>= UnconsciousnessStamina
&& Stamina
< UnconsciousnessStamina
) {
6430 ADD_MESSAGE("You are seriously out of breath!");
6431 game::SetPlayerIsRunning(false);
6434 if (IsPlayer() && StateIsActivated(PANIC
) && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6438 void character::RegenerateStamina () {
6439 if (GetTirednessState() != UNTIRED
) {
6440 EditExperience(ENDURANCE
, 50, 1);
6441 if (Sweats() && TorsoIsAlive() && !RAND_N(30) && !game::IsInWilderness()) {
6442 sLong Volume
= sLong(0.05 * sqrt(GetBodyVolume()));
6443 if (GetTirednessState() == FAINTING
) Volume
<<= 1;
6444 for (int c
= 0; c
< SquaresUnder
; ++c
) GetLSquareUnder(c
)->SpillFluid(0, CreateSweat(Volume
), false, false);
6449 if (Action
->IsRest()) {
6450 if (SquaresUnder
== 1) {
6451 Bonus
= GetSquareUnder()->GetRestModifier() << 1;
6453 int Lowest
= GetSquareUnder(0)->GetRestModifier();
6454 for (int c
= 1; c
< GetSquaresUnder(); ++c
) {
6455 int Mod
= GetSquareUnder(c
)->GetRestModifier();
6456 if (Mod
< Lowest
) Lowest
= Mod
;
6458 Bonus
= Lowest
<< 1;
6460 } else if (Action
->IsUnconsciousness()) Bonus
= 2;
6463 switch (GetBurdenState()) {
6464 case OVER_LOADED
: Plus1
= 25; break;
6465 case STRESSED
: Plus1
= 50; break;
6466 case BURDENED
: Plus1
= 75; break;
6470 switch (GetHungerState()) {
6471 case STARVING
: Plus2
= 25; break;
6472 case VERY_HUNGRY
: Plus2
= 50; break;
6473 case HUNGRY
: Plus2
= 75; break;
6476 Stamina
+= Plus1
* Plus2
* Bonus
/ 1000;
6477 if (Stamina
> MaxStamina
) Stamina
= MaxStamina
;
6478 if (IsPlayer() && StateIsActivated(PANIC
) && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6482 void character::BeginPanic () {
6483 if (IsPlayer() && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6484 DeActivateVoluntaryAction();
6488 void character::EndPanic () {
6489 if (IsPlayer()) game::SetPlayerIsRunning(false);
6493 int character::GetTirednessState () const {
6494 if (Stamina
>= MaxStamina
>> 2) return UNTIRED
;
6495 if (Stamina
>= MaxStamina
>> 3) return EXHAUSTED
;
6500 void character::ReceiveBlackUnicorn (sLong Amount
) {
6501 if (!(RAND() % 160)) game::DoEvilDeed(Amount
/ 50);
6502 BeginTemporaryState(TELEPORT
, Amount
/ 100);
6503 for (int c
= 0; c
< STATES
; ++c
) {
6504 if (StateData
[c
].Flags
& DUR_TEMPORARY
) {
6505 BeginTemporaryState(1 << c
, Amount
/ 100);
6506 if (!IsEnabled()) return;
6507 } else if (StateData
[c
].Flags
& DUR_PERMANENT
) {
6508 GainIntrinsic(1 << c
);
6509 if (!IsEnabled()) return;
6515 void character::ReceiveGrayUnicorn (sLong Amount
) {
6516 if (!(RAND() % 80)) game::DoEvilDeed(Amount
/ 50);
6517 BeginTemporaryState(TELEPORT
, Amount
/ 100);
6518 for (int c
= 0; c
< STATES
; ++c
) {
6519 if (1 << c
!= TELEPORT
) {
6520 DecreaseStateCounter(1 << c
, -Amount
/ 100);
6521 if (!IsEnabled()) return;
6527 void character::ReceiveWhiteUnicorn (sLong Amount
) {
6528 if (!(RAND() % 40)) game::DoEvilDeed(Amount
/ 50);
6529 BeginTemporaryState(TELEPORT
, Amount
/ 100);
6530 DecreaseStateCounter(LYCANTHROPY
, -Amount
/ 100);
6531 DecreaseStateCounter(POISONED
, -Amount
/ 100);
6532 DecreaseStateCounter(PARASITIZED
, -Amount
/ 100);
6533 DecreaseStateCounter(LEPROSY
, -Amount
/ 100);
6534 DecreaseStateCounter(VAMPIRISM
, -Amount
/ 100);
6538 /* Counter should be negative. Removes intrinsics. */
6539 void character::DecreaseStateCounter (sLong State
, int Counter
) {
6541 for (Index
= 0; Index
< STATES
; ++Index
) if (1 << Index
== State
) break;
6542 if (Index
== STATES
) ABORT("DecreaseTemporaryStateCounter works only when State == 2Â ^ n!");
6543 if (TemporaryState
& State
) {
6544 if (TemporaryStateCounter
[Index
] == PERMANENT
|| (TemporaryStateCounter
[Index
] += Counter
) <= 0) {
6545 TemporaryState
&= ~State
;
6546 if (!(EquipmentState
& State
)) {
6547 if (StateData
[Index
].EndHandler
) {
6548 (this->*StateData
[Index
].EndHandler
)();
6549 if (!IsEnabled()) return;
6551 (this->*StateData
[Index
].PrintEndMessage
)();
6558 truth
character::IsImmuneToLeprosy () const {
6559 return DataBase
->IsImmuneToLeprosy
|| UseMaterialAttributes();
6563 void character::LeprosyHandler () {
6564 EditExperience(ARM_STRENGTH
, -25, 1 << 1);
6565 EditExperience(LEG_STRENGTH
, -25, 1 << 1);
6566 EditExperience(DEXTERITY
, -25, 1 << 1);
6567 EditExperience(AGILITY
, -25, 1 << 1);
6568 EditExperience(ENDURANCE
, -25, 1 << 1);
6569 EditExperience(CHARISMA
, -25, 1 << 1);
6570 CheckDeath(CONST_S("killed by leprosy"));
6574 bodypart
*character::SearchForOriginalBodyPart (int I
) const {
6575 for (stackiterator i1
= GetStackUnder()->GetBottom(); i1
.HasItem(); ++i1
) {
6576 for (std::list
<feuLong
>::iterator i2
= OriginalBodyPartID
[I
].begin(); i2
!= OriginalBodyPartID
[I
].end(); ++i2
)
6577 if (i1
->GetID() == *i2
) return static_cast<bodypart
*>(*i1
);
6583 void character::SetLifeExpectancy (int Base
, int RandPlus
) {
6585 for (c
= 0; c
< BodyParts
; ++c
) {
6586 bodypart
*BodyPart
= GetBodyPart(c
);
6587 if (BodyPart
) BodyPart
->SetLifeExpectancy(Base
, RandPlus
);
6589 for (c
= 0; c
< GetEquipments(); ++c
) {
6590 item
*Equipment
= GetEquipment(c
);
6591 if (Equipment
) Equipment
->SetLifeExpectancy(Base
, RandPlus
);
6596 /* Receiver should be a fresh duplicate of this */
6597 void character::DuplicateEquipment (character
*Receiver
, feuLong Flags
) {
6598 for (int c
= 0; c
< GetEquipments(); ++c
) {
6599 item
*Equipment
= GetEquipment(c
);
6601 item
*Duplicate
= Equipment
->Duplicate(Flags
);
6602 Receiver
->SetEquipment(c
, Duplicate
);
6608 void character::Disappear (corpse
*Corpse
, cchar
*Verb
, truth (item::*ClosePredicate
)() const) {
6609 truth TorsoDisappeared
= false;
6610 truth CanBeSeen
= Corpse
? Corpse
->CanBeSeenByPlayer() : IsPlayer() || CanBeSeenByPlayer();
6612 if ((GetTorso()->*ClosePredicate
)()) {
6614 if (Corpse
) ADD_MESSAGE("%s %ss.", Corpse
->CHAR_NAME(DEFINITE
), Verb
);
6615 else if (IsPlayer()) ADD_MESSAGE("You %s.", Verb
);
6616 else ADD_MESSAGE("%s %ss.", CHAR_NAME(DEFINITE
), Verb
);
6618 TorsoDisappeared
= true;
6619 for (c
= 0; c
< GetEquipments(); ++c
) {
6620 item
*Equipment
= GetEquipment(c
);
6621 if (Equipment
&& (Equipment
->*ClosePredicate
)()) {
6622 Equipment
->RemoveFromSlot();
6623 Equipment
->SendToHell();
6626 itemvector ItemVector
;
6627 GetStack()->FillItemVector(ItemVector
);
6628 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
6629 if (ItemVector
[c
] && (ItemVector
[c
]->*ClosePredicate
)()) {
6630 ItemVector
[c
]->RemoveFromSlot();
6631 ItemVector
[c
]->SendToHell();
6635 for (c
= 1; c
< GetBodyParts(); ++c
) {
6636 bodypart
*BodyPart
= GetBodyPart(c
);
6638 if ((BodyPart
->*ClosePredicate
)()) {
6639 if (!TorsoDisappeared
&& CanBeSeen
) {
6640 if(IsPlayer()) ADD_MESSAGE("Your %s %ss.", GetBodyPartName(c
).CStr(), Verb
);
6641 else ADD_MESSAGE("The %s of %s %ss.", GetBodyPartName(c
).CStr(), CHAR_NAME(DEFINITE
), Verb
);
6643 BodyPart
->DropEquipment();
6644 item
*BodyPart
= SevereBodyPart(c
);
6645 if (BodyPart
) BodyPart
->SendToHell();
6646 } else if (TorsoDisappeared
) {
6647 BodyPart
->DropEquipment();
6648 item
*BodyPart
= SevereBodyPart(c
);
6650 if (Corpse
) Corpse
->GetSlot()->AddFriendItem(BodyPart
);
6651 else if (!game::IsInWilderness()) GetStackUnder()->AddItem(BodyPart
);
6652 else BodyPart
->SendToHell();
6657 if (TorsoDisappeared
) {
6659 Corpse
->RemoveFromSlot();
6660 Corpse
->SendToHell();
6662 CheckDeath(festring(Verb
) + "ed", 0, FORCE_DEATH
|DISALLOW_CORPSE
|DISALLOW_MSG
);
6665 CheckDeath(festring(Verb
) + "ed", 0, DISALLOW_MSG
);
6670 void character::SignalDisappearance () {
6671 if (GetMotherEntity()) GetMotherEntity()->SignalDisappearance();
6672 else Disappear(0, "disappear", &item::IsVeryCloseToDisappearance
);
6676 truth
character::HornOfFearWorks () const {
6677 return CanHear() && GetPanicLevel() > RAND() % 33;
6681 void character::BeginLeprosy () {
6682 doforbodypartswithparam
<truth
>()(this, &bodypart::SetIsInfectedByLeprosy
, true);
6686 void character::EndLeprosy () {
6687 doforbodypartswithparam
<truth
>()(this, &bodypart::SetIsInfectedByLeprosy
, false);
6691 truth
character::IsSameAs (ccharacter
*What
) const {
6692 return What
->GetType() == GetType() && What
->GetConfig() == GetConfig();
6696 feuLong
character::GetCommandFlags () const {
6697 return !StateIsActivated(PANIC
) ? CommandFlags
: CommandFlags
|FLEE_FROM_ENEMIES
;
6701 feuLong
character::GetConstantCommandFlags () const {
6702 return !StateIsActivated(PANIC
) ? DataBase
->ConstantCommandFlags
: DataBase
->ConstantCommandFlags
|FLEE_FROM_ENEMIES
;
6706 feuLong
character::GetPossibleCommandFlags () const {
6707 int Int
= GetAttribute(INTELLIGENCE
);
6708 feuLong Flags
= ALL_COMMAND_FLAGS
;
6709 if (!CanMove() || Int
< 4) Flags
&= ~FOLLOW_LEADER
;
6710 if (!CanMove() || Int
< 6) Flags
&= ~FLEE_FROM_ENEMIES
;
6711 if (!CanUseEquipment() || Int
< 8) Flags
&= ~DONT_CHANGE_EQUIPMENT
;
6712 if (!UsesNutrition() || Int
< 8) Flags
&= ~DONT_CONSUME_ANYTHING_VALUABLE
;
6717 truth
character::IsRetreating () const {
6718 return StateIsActivated(PANIC
) || (CommandFlags
& FLEE_FROM_ENEMIES
&& IsPet());
6722 truth
character::ChatMenu () {
6723 if (GetAction() && !GetAction()->CanBeTalkedTo()) {
6724 ADD_MESSAGE("%s is silent.", CHAR_DESCRIPTION(DEFINITE
));
6725 PLAYER
->EditAP(-200);
6728 feuLong ManagementFlags
= GetManagementFlags();
6729 if (ManagementFlags
== CHAT_IDLY
|| !IsPet()) return ChatIdly();
6730 static cchar
*const ChatMenuEntry
[CHAT_MENU_ENTRIES
] = {
6737 static const petmanagementfunction PMF
[CHAT_MENU_ENTRIES
] = {
6738 &character::ChangePetEquipment
,
6739 &character::TakePetItems
,
6740 &character::GivePetItems
,
6741 &character::IssuePetCommands
,
6742 &character::ChatIdly
6744 felist
List(CONST_S("Choose action:"));
6745 game::SetStandardListAttributes(List
);
6746 List
.AddFlags(SELECTABLE
);
6748 for (c
= 0; c
< CHAT_MENU_ENTRIES
; ++c
) if (1 << c
& ManagementFlags
) List
.AddEntry(ChatMenuEntry
[c
], LIGHT_GRAY
);
6749 int Chosen
= List
.Draw();
6750 if (Chosen
& FELIST_ERROR_BIT
) return false;
6751 for (c
= 0, i
= 0; c
< CHAT_MENU_ENTRIES
; ++c
) {
6752 if (1 << c
& ManagementFlags
&& i
++ == Chosen
) return (this->*PMF
[c
])();
6754 return false; // dummy
6758 truth
character::ChangePetEquipment () {
6759 if (EquipmentScreen(PLAYER
->GetStack(), GetStack())) {
6767 truth
character::TakePetItems () {
6768 truth Success
= false;
6769 stack::SetSelected(0);
6772 game::DrawEverythingNoBlit();
6773 GetStack()->DrawContents(
6777 CONST_S("What do you want to take from ") + CHAR_DESCRIPTION(DEFINITE
) + '?',
6780 GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState(),
6781 GetVerbalBurdenStateColor(),
6783 if (ToTake
.empty()) break;
6784 for (uInt c
= 0; c
< ToTake
.size(); ++c
) ToTake
[c
]->MoveTo(PLAYER
->GetStack());
6785 ADD_MESSAGE("You take %s.", ToTake
[0]->GetName(DEFINITE
, ToTake
.size()).CStr());
6790 PLAYER
->DexterityAction(2);
6796 truth
character::GivePetItems () {
6797 truth Success
= false;
6798 stack::SetSelected(0);
6801 game::DrawEverythingNoBlit();
6802 PLAYER
->GetStack()->DrawContents(
6806 CONST_S("What do you want to give to ") + CHAR_DESCRIPTION(DEFINITE
) + '?',
6809 GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState(),
6810 GetVerbalBurdenStateColor(),
6812 if (ToGive
.empty()) break;
6813 for (uInt c
= 0; c
< ToGive
.size(); ++c
) ToGive
[c
]->MoveTo(GetStack());
6814 ADD_MESSAGE("You give %s to %s.", ToGive
[0]->GetName(DEFINITE
, ToGive
.size()).CStr(), CHAR_DESCRIPTION(DEFINITE
));
6819 PLAYER
->DexterityAction(2);
6825 truth
character::IssuePetCommands () {
6826 if (!IsConscious()) {
6827 ADD_MESSAGE("%s is unconscious.", CHAR_DESCRIPTION(DEFINITE
));
6830 feuLong PossibleC
= GetPossibleCommandFlags();
6832 ADD_MESSAGE("%s cannot be commanded.", CHAR_DESCRIPTION(DEFINITE
));
6835 feuLong OldC
= GetCommandFlags();
6836 feuLong NewC
= OldC
, VaryFlags
= 0;
6837 game::CommandScreen(CONST_S("Issue commands to ")+GetDescription(DEFINITE
), PossibleC
, GetConstantCommandFlags(), VaryFlags
, NewC
);
6838 if (NewC
== OldC
) return false;
6839 SetCommandFlags(NewC
);
6840 PLAYER
->EditAP(-500);
6841 PLAYER
->EditExperience(CHARISMA
, 25, 1 << 7);
6846 truth
character::ChatIdly () {
6847 if (!TryToTalkAboutScience()) {
6849 PLAYER
->EditExperience(CHARISMA
, 75, 1 << 7);
6851 PLAYER
->EditAP(-1000);
6856 truth
character::EquipmentScreen (stack
*MainStack
, stack
*SecStack
) {
6857 if (!CanUseEquipment()) {
6858 ADD_MESSAGE("%s cannot use equipment.", CHAR_DESCRIPTION(DEFINITE
));
6862 truth EquipmentChanged
= false;
6863 felist
List(CONST_S("Equipment menu [ESC exits]"));
6867 List
.EmptyDescription();
6869 List
.AddDescription(CONST_S(""));
6870 List
.AddDescription(festring(GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState()).CapitalizeCopy(), GetVerbalBurdenStateColor());
6872 for (int c
= 0; c
< GetEquipments(); ++c
) {
6873 Entry
= GetEquipmentName(c
);
6876 item
*Equipment
= GetEquipment(c
);
6878 Equipment
->AddInventoryEntry(this, Entry
, 1, true);
6879 AddSpecialEquipmentInfo(Entry
, c
);
6880 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
6881 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
6883 Entry
<< (GetBodyPartOfEquipment(c
) ? "-" : "can't use");
6884 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, game::AddToItemDrawVector(itemvector()));
6887 game::DrawEverythingNoBlit();
6888 game::SetStandardListAttributes(List
);
6889 List
.SetFlags(SELECTABLE
|DRAW_BACKGROUND_AFTERWARDS
);
6890 List
.SetEntryDrawer(game::ItemEntryDrawer
);
6891 Chosen
= List
.Draw();
6892 game::ClearItemDrawVector();
6893 if (Chosen
>= GetEquipments()) break;
6894 EquipmentChanged
= TryToChangeEquipment(MainStack
, SecStack
, Chosen
);
6896 if (EquipmentChanged
) DexterityAction(5);
6897 return EquipmentChanged
;
6901 feuLong
character::GetManagementFlags () const {
6902 feuLong Flags
= ALL_MANAGEMENT_FLAGS
;
6903 if (!CanUseEquipment() || !AllowPlayerToChangeEquipment()) Flags
&= ~CHANGE_EQUIPMENT
;
6904 if (!GetStack()->GetItems()) Flags
&= ~TAKE_ITEMS
;
6905 if (!WillCarryItems()) Flags
&= ~GIVE_ITEMS
;
6906 if (!GetPossibleCommandFlags()) Flags
&= ~ISSUE_COMMANDS
;
6911 cchar
*VerbalBurdenState
[] = { "overloaded", "stressed", "burdened", "unburdened" };
6912 col16 VerbalBurdenStateColor
[] = { RED
, BLUE
, BLUE
, WHITE
};
6914 cchar
*character::GetVerbalBurdenState () const { return VerbalBurdenState
[BurdenState
]; }
6915 col16
character::GetVerbalBurdenStateColor () const { return VerbalBurdenStateColor
[BurdenState
]; }
6916 int character::GetAttributeAverage () const { return GetSumOfAttributes()/7; }
6918 cfestring
&character::GetStandVerb() const {
6919 if (ForceCustomStandVerb()) return DataBase
->StandVerb
;
6920 static festring Hovering
= "hovering";
6921 static festring Swimming
= "swimming";
6922 if (StateIsActivated(LEVITATION
)) return Hovering
;
6923 if (IsSwimming()) return Swimming
;
6924 return DataBase
->StandVerb
;
6928 truth
character::CheckApply () const {
6930 ADD_MESSAGE("This monster type cannot apply.");
6937 void character::EndLevitation () {
6938 if (!IsFlying() && GetSquareUnder()) {
6939 if (!game::IsInWilderness()) SignalStepFrom(0);
6940 if (game::IsInWilderness() || !GetLSquareUnder()->IsFreezed()) TestWalkability();
6945 truth
character::CanMove () const {
6946 return !IsRooted() || StateIsActivated(LEVITATION
);
6950 void character::CalculateEnchantments () {
6951 doforequipments()(this, &item::CalculateEnchantment
);
6952 GetStack()->CalculateEnchantments();
6956 truth
character::GetNewFormForPolymorphWithControl (character
*&NewForm
) {
6957 festring Topic
, Temp
;
6960 festring Temp
= game::DefaultQuestion(CONST_S("What do you want to become? [press '?' for a list]"), game::GetDefaultPolymorphTo(), &game::PolymorphControlKeyHandler
);
6961 NewForm
= protosystem::CreateMonster(Temp
);
6963 if (NewForm
->IsSameAs(this)) {
6965 ADD_MESSAGE("You choose not to polymorph.");
6969 if (PolymorphBackup
&& NewForm
->IsSameAs(PolymorphBackup
)) {
6971 NewForm
= ForceEndPolymorph();
6974 if (NewForm
->GetPolymorphIntelligenceRequirement() > GetAttribute(INTELLIGENCE
) && !game::WizardModeIsActive()) {
6975 ADD_MESSAGE("You feel your mind isn't yet powerful enough to call forth the form of %s.", NewForm
->CHAR_NAME(INDEFINITE
));
6979 NewForm
->RemoveAllItems();
6987 liquid
*character::CreateSweat(sLong Volume
) const {
6988 //return liquid::Spawn(GetSweatMaterial(), Volume);
6989 return liquid::Spawn(GetCurrentSweatMaterial(), Volume
);
6993 truth
character::TeleportRandomItem (truth TryToHinderVisibility
) {
6994 if (IsImmuneToItemTeleport()) return false;
6995 itemvector ItemVector
;
6996 std::vector
<sLong
> PossibilityVector
;
6997 int TotalPossibility
= 0;
6998 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
6999 ItemVector
.push_back(*i
);
7000 int Possibility
= i
->GetTeleportPriority();
7001 if (TryToHinderVisibility
) Possibility
+= i
->GetHinderVisibilityBonus(this);
7002 PossibilityVector
.push_back(Possibility
);
7003 TotalPossibility
+= Possibility
;
7005 for (int c
= 0; c
< GetEquipments(); ++c
) {
7006 item
*Equipment
= GetEquipment(c
);
7008 ItemVector
.push_back(Equipment
);
7009 int Possibility
= Equipment
->GetTeleportPriority();
7010 if (TryToHinderVisibility
) Possibility
+= Equipment
->GetHinderVisibilityBonus(this);
7011 PossibilityVector
.push_back(Possibility
<<= 1);
7012 TotalPossibility
+= Possibility
;
7015 if (!TotalPossibility
) return false;
7016 int Chosen
= femath::WeightedRand(PossibilityVector
, TotalPossibility
);
7017 item
*Item
= ItemVector
[Chosen
];
7018 truth Equipped
= PLAYER
->Equips(Item
);
7019 truth Seen
= Item
->CanBeSeenByPlayer();
7020 Item
->RemoveFromSlot();
7021 if (Seen
) ADD_MESSAGE("%s disappears.", Item
->CHAR_NAME(DEFINITE
));
7022 if (Equipped
) game::AskForEscPress(CONST_S("Equipment lost!"));
7024 int Range
= Item
->GetEmitation() && TryToHinderVisibility
? 25 : 5;
7025 rect
Border(Pos
+ v2(-Range
, -Range
), Pos
+ v2(Range
, Range
));
7026 Pos
= GetLevel()->GetRandomSquare(this, 0, &Border
);
7027 if (Pos
== ERROR_V2
) Pos
= GetLevel()->GetRandomSquare();
7028 GetNearLSquare(Pos
)->GetStack()->AddItem(Item
);
7029 if (Item
->CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", Item
->CHAR_NAME(INDEFINITE
));
7034 truth
character::HasClearRouteTo (v2 Pos
) const {
7035 pathcontroller::Map
= GetLevel()->GetMap();
7036 pathcontroller::Character
= this;
7037 v2 ThisPos
= GetPos();
7038 return mapmath
<pathcontroller
>::DoLine(ThisPos
.X
, ThisPos
.Y
, Pos
.X
, Pos
.Y
, SKIP_FIRST
);
7042 truth
character::IsTransparent () const {
7043 return !IsEnormous() || GetTorso()->GetMainMaterial()->IsTransparent() || StateIsActivated(INVISIBLE
);
7047 void character::SignalPossibleTransparencyChange () {
7048 if (!game::IsInWilderness()) {
7049 for (int c
= 0; c
< SquaresUnder
; ++c
) {
7050 lsquare
*Square
= GetLSquareUnder(c
);
7051 if (Square
) Square
->SignalPossibleTransparencyChange();
7057 int character::GetCursorData () const {
7059 int Color
= game::PlayerIsRunning() ? BLUE_CURSOR
: DARK_CURSOR
;
7060 for (int c
= 0; c
< BodyParts
; ++c
) {
7061 bodypart
*BodyPart
= GetBodyPart(c
);
7062 if (BodyPart
&& BodyPart
->IsUsable()) {
7063 int ConditionColorIndex
= BodyPart
->GetConditionColorIndex();
7064 if ((BodyPartIsVital(c
) && !ConditionColorIndex
) || (ConditionColorIndex
<= 1 && ++Bad
== 2)) return Color
|CURSOR_FLASH
;
7065 } else if (++Bad
== 2) return Color
|CURSOR_FLASH
;
7067 Color
= game::PlayerIsRunning() ? YELLOW_CURSOR
: RED_CURSOR
;
7068 return Bad
? Color
|CURSOR_FLASH
: Color
;
7072 void character::TryToName () {
7073 if (!IsPet()) ADD_MESSAGE("%s refuses to let YOU decide what %s's called.", CHAR_NAME(DEFINITE
), CHAR_PERSONAL_PRONOUN
);
7074 else if (IsPlayer()) ADD_MESSAGE("You can't rename yourself.");
7075 else if (!IsNameable()) ADD_MESSAGE("%s refuses to be called anything else but %s.", CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
));
7077 festring Topic
= CONST_S("What name will you give to ")+GetName(DEFINITE
)+'?';
7078 festring Name
= game::StringQuestion(Topic
, WHITE
, 0, 80, true);
7079 if (Name
.GetSize()) SetAssignedName(Name
);
7084 double character::GetSituationDanger (ccharacter
*Enemy
, v2 ThisPos
, v2 EnemyPos
, truth SeesEnemy
) const {
7086 if (IgnoreDanger() && !IsPlayer()) {
7087 if (Enemy
->IgnoreDanger() && !Enemy
->IsPlayer()) {
7088 Danger
= double(GetHP())*GetHPRequirementForGeneration()/(Enemy
->GetHP()*Enemy
->GetHPRequirementForGeneration());
7091 Danger
= 0.25*GetHPRequirementForGeneration()/Enemy
->GetHP();
7093 } else if (Enemy
->IgnoreDanger() && !Enemy
->IsPlayer()) {
7094 Danger
= 4.0*GetHP()/Enemy
->GetHPRequirementForGeneration();
7096 Danger
= GetRelativeDanger(Enemy
);
7098 Danger
*= 3.0/((EnemyPos
-ThisPos
).GetManhattanLength()+2);
7099 if (!SeesEnemy
) Danger
*= 0.2;
7100 if (StateIsActivated(PANIC
)) Danger
*= 0.2;
7101 Danger
*= double(GetHP())*Enemy
->GetMaxHP()/(Enemy
->GetHP()*GetMaxHP());
7106 void character::ModifySituationDanger (double &Danger
) const {
7107 switch (GetTirednessState()) {
7108 case FAINTING
: Danger
*= 1.5;
7109 case EXHAUSTED
: Danger
*= 1.25;
7111 for (int c
= 0; c
< STATES
; ++c
) {
7112 if (StateIsActivated(1 << c
) && StateData
[c
].SituationDangerModifier
!= 0) (this->*StateData
[c
].SituationDangerModifier
)(Danger
);
7117 void character::LycanthropySituationDangerModifier (double &Danger
) const {
7118 character
*Wolf
= werewolfwolf::Spawn();
7119 double DangerToWolf
= GetRelativeDanger(Wolf
);
7120 Danger
*= pow(DangerToWolf
, 0.1);
7125 void character::PoisonedSituationDangerModifier (double &Danger
) const {
7126 int C
= GetTemporaryStateCounter(POISONED
);
7127 Danger
*= (1+(C
*C
)/(GetHP()*10000.0*(GetGlobalResistance(POISON
)+1)));
7131 void character::PolymorphingSituationDangerModifier (double &Danger
) const {
7132 if (!StateIsActivated(POLYMORPH_CONTROL
)) Danger
*= 1.5;
7136 void character::PanicSituationDangerModifier (double &Danger
) const {
7141 void character::ConfusedSituationDangerModifier (double &Danger
) const {
7146 void character::ParasitizedSituationDangerModifier (double &Danger
) const {
7151 void character::LeprosySituationDangerModifier (double &Danger
) const {
7156 void character::AddRandomScienceName (festring
&String
) const {
7157 festring Science
= GetScienceTalkName().GetRandomElement().CStr();
7158 if (Science
[0] == '!') {
7159 String
<< Science
.CStr()+1;
7162 festring Attribute
= GetScienceTalkAdjectiveAttribute().GetRandomElement();
7164 truth NoAttrib
= Attribute
.IsEmpty(), NoSecondAdjective
= false;
7165 if (!Attribute
.IsEmpty() && Attribute
[0] == '!') {
7166 NoSecondAdjective
= true;
7167 Attribute
.Erase(0, 1);
7169 if (!Science
.Find("the ")) {
7170 Science
.Erase(0, 4);
7171 if (!Attribute
.Find("the ", 0, 4)) Attribute
<< " the"; else Attribute
.Insert(0, "the ", 4);
7173 if (islower(Science
[0]) && Science
.Find(' ') == festring::NPos
&& Science
.Find('-') == festring::NPos
&&
7174 Science
.Find("phobia") == festring::NPos
) {
7175 Prefix
= GetScienceTalkPrefix().GetRandomElement();
7176 if (!Prefix
.IsEmpty() && Science
.Find(Prefix
) != festring::NPos
) Prefix
.Empty();
7178 int L
= Prefix
.GetSize();
7179 if (L
&& Prefix
[L
-1] == Science
[0]) Science
.Erase(0, 1);
7180 if (!NoAttrib
&& !NoSecondAdjective
== !RAND_GOOD(3)) {
7181 int S1
= NoSecondAdjective
? 0 : GetScienceTalkAdjectiveAttribute().Size
;
7182 int S2
= GetScienceTalkSubstantiveAttribute().Size
;
7183 festring OtherAttribute
;
7184 int Chosen
= RAND_GOOD(S1
+S2
);
7185 if (Chosen
< S1
) OtherAttribute
= GetScienceTalkAdjectiveAttribute()[Chosen
];
7186 else OtherAttribute
= GetScienceTalkSubstantiveAttribute()[Chosen
- S1
];
7187 if (!OtherAttribute
.IsEmpty() && OtherAttribute
.Find("the ", 0, 4) && Attribute
.Find(OtherAttribute
) == festring::NPos
) {
7188 String
<< Attribute
<< ' ' << OtherAttribute
<< ' ' << Prefix
<< Science
;
7192 String
<< Attribute
;
7193 if (!NoAttrib
) String
<< ' ';
7194 String
<< Prefix
<< Science
;
7198 truth
character::TryToTalkAboutScience () {
7199 if (GetRelation(PLAYER
) == HOSTILE
||
7200 GetScienceTalkPossibility() <= RAND_GOOD(100) ||
7201 PLAYER
->GetAttribute(INTELLIGENCE
) < GetScienceTalkIntelligenceRequirement() ||
7202 PLAYER
->GetAttribute(WISDOM
) < GetScienceTalkWisdomRequirement() ||
7203 PLAYER
->GetAttribute(CHARISMA
) < GetScienceTalkCharismaRequirement())
7207 AddRandomScienceName(Science
);
7210 AddRandomScienceName(S1
);
7211 AddRandomScienceName(S2
);
7212 if (S1
.Find(S2
) == festring::NPos
&& S2
.Find(S1
) == festring::NPos
) {
7213 switch (RAND_GOOD(3)) {
7214 case 0: Science
= "the relation of "; break;
7215 case 1: Science
= "the differences of "; break;
7216 case 2: Science
= "the similarities of "; break;
7218 Science
<< S1
<< " and " << S2
;
7221 AddRandomScienceName(Science
);
7224 switch ((RAND() + GET_TICK()) % 10) {
7226 ADD_MESSAGE("You have a rather pleasant chat about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7229 ADD_MESSAGE("%s explains a few of %s opinions regarding %s to you.", CHAR_DESCRIPTION(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, Science
.CStr());
7232 ADD_MESSAGE("%s reveals a number of %s insightful views of %s to you.", CHAR_DESCRIPTION(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, Science
.CStr());
7235 ADD_MESSAGE("You exhange some information pertaining to %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7238 ADD_MESSAGE("You engage in a pretty intriguing conversation about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7241 ADD_MESSAGE("You discuss at length about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7244 ADD_MESSAGE("You have a somewhat boring talk concerning %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7247 ADD_MESSAGE("You are drawn into a heated argument regarding %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7250 ADD_MESSAGE("%s delivers a sLong monologue concerning eg. %s.", CHAR_DESCRIPTION(DEFINITE
), Science
.CStr());
7253 ADD_MESSAGE("You dive into a brief but thought-provoking debate over %s with %s", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7256 PLAYER
->EditExperience(INTELLIGENCE
, 1000, 50. * GetScienceTalkIntelligenceModifier() / ++ScienceTalks
);
7257 PLAYER
->EditExperience(WISDOM
, 1000, 50. * GetScienceTalkWisdomModifier() / ++ScienceTalks
);
7258 PLAYER
->EditExperience(CHARISMA
, 1000, 50. * GetScienceTalkCharismaModifier() / ++ScienceTalks
);
7263 truth
character::IsUsingWeaponOfCategory (int Category
) const {
7265 ((GetMainWielded() && GetMainWielded()->GetWeaponCategory() == Category
) ||
7266 (GetSecondaryWielded() && GetSecondaryWielded()->GetWeaponCategory() == Category
));
7270 truth
character::TryToUnStickTraps (v2 Dir
) {
7271 if (!TrapData
) return true;
7272 std::vector
<trapdata
> TrapVector
;
7273 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) TrapVector
.push_back(*TrapData
);
7274 for (uInt c
= 0; c
< TrapVector
.size(); ++c
) {
7276 entity
*Trap
= game::SearchTrap(TrapVector
[c
].TrapID
);
7277 /*k8:??? if(!Trap->Exists()) int esko = esko = 2; */
7278 if (!Trap
->Exists()) continue; /*k8: ??? added by me; what this means? */
7279 if (Trap
->GetVictimID() == GetID() && Trap
->TryToUnStick(this, Dir
)) break;
7282 return !TrapData
&& IsEnabled();
7286 struct trapidcomparer
{
7287 trapidcomparer (feuLong ID
) : ID(ID
) {}
7288 truth
operator () (const trapdata
*T
) const { return T
->TrapID
== ID
; }
7293 void character::RemoveTrap (feuLong ID
) {
7294 trapdata
*&T
= ListFind(TrapData
, trapidcomparer(ID
));
7296 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7300 void character::AddTrap (feuLong ID
, feuLong BodyParts
) {
7301 trapdata
*&T
= ListFind(TrapData
, trapidcomparer(ID
));
7302 if (T
) T
->BodyParts
|= BodyParts
;
7303 else T
= new trapdata(ID
, GetID(), BodyParts
);
7304 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7308 truth
character::IsStuckToTrap (feuLong ID
) const {
7309 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) if (T
->TrapID
== ID
) return true;
7314 void character::RemoveTraps () {
7315 for (trapdata
*T
= TrapData
; T
;) {
7316 entity
*Trap
= game::SearchTrap(T
->TrapID
);
7317 if (Trap
) Trap
->UnStick();
7318 trapdata
*ToDel
= T
;
7323 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7327 void character::RemoveTraps (int BodyPartIndex
) {
7328 feuLong Flag
= 1 << BodyPartIndex
;
7329 for (trapdata
**T
= &TrapData
; *T
;) {
7330 if ((*T
)->BodyParts
& Flag
) {
7331 entity
*Trap
= game::SearchTrap((*T
)->TrapID
);
7332 if (!((*T
)->BodyParts
&= ~Flag
)) {
7333 if (Trap
) Trap
->UnStick();
7334 trapdata
*ToDel
= *T
;
7338 if (Trap
) Trap
->UnStick(BodyPartIndex
);
7346 if (GetBodyPart(BodyPartIndex
)) GetBodyPart(BodyPartIndex
)->SignalPossibleUsabilityChange();
7350 festring
character::GetTrapDescription () const {
7352 std::pair
<entity
*, int> TrapStack
[3];
7354 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) {
7356 entity
*Trap
= game::SearchTrap(T
->TrapID
);
7359 for (c
= 0; c
< Index
; ++c
) if (TrapStack
[c
].first
->GetTrapType() == Trap
->GetTrapType()) ++TrapStack
[c
].second
;
7360 if (c
== Index
) TrapStack
[Index
++] = std::make_pair(Trap
, 1);
7368 TrapStack
[0].first
->AddTrapName(Desc
, TrapStack
[0].second
);
7371 TrapStack
[1].first
->AddTrapName(Desc
, TrapStack
[1].second
);
7372 } else if (Index
== 3) {
7374 TrapStack
[1].first
->AddTrapName(Desc
, TrapStack
[1].second
);
7376 TrapStack
[2].first
->AddTrapName(Desc
, TrapStack
[2].second
);
7379 Desc
<< "lots of traps";
7385 int character::RandomizeHurtBodyPart (feuLong BodyParts
) const {
7386 int BodyPartIndex
[MAX_BODYPARTS
];
7388 for (int c
= 0; c
< GetBodyParts(); ++c
) {
7389 if (1 << c
& BodyParts
) {
7390 /*k8: ??? if(!GetBodyPart(c)) int esko = esko = 2; */
7391 if (!GetBodyPart(c
)) continue;
7392 BodyPartIndex
[Index
++] = c
;
7394 /*k8: ??? if(!Index) int esko = esko = 2;*/
7396 if (!Index
) abort();
7397 return BodyPartIndex
[RAND_N(Index
)];
7401 truth
character::BodyPartIsStuck (int I
) const {
7402 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) if (1 << I
& T
->BodyParts
) return true;
7407 void character::PrintAttribute (cchar
*Desc
, int I
, int PanelPosX
, int PanelPosY
) const {
7408 int Attribute
= GetAttribute(I
);
7409 int NoBonusAttribute
= GetAttribute(I
, false);
7410 col16 C
= game::GetAttributeColor(I
);
7411 festring String
= Desc
;
7413 String
<< Attribute
;
7415 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
* 10), C
, "%s", String
.CStr());
7416 if (Attribute
!= NoBonusAttribute
) {
7417 int Where
= PanelPosX
+ ((String
.GetSize() + 1) << 3);
7418 FONT
->Printf(DOUBLE_BUFFER
, v2(Where
, PanelPosY
* 10), LIGHT_GRAY
, "%d", NoBonusAttribute
);
7423 truth
character::AllowUnconsciousness () const {
7424 return DataBase
->AllowUnconsciousness
&& TorsoIsAlive();
7428 truth
character::CanPanic () const {
7429 return !Action
|| !Action
->IsUnconsciousness();
7433 int character::GetRandomBodyPart (feuLong Possible
) const {
7434 int OKBodyPart
[MAX_BODYPARTS
];
7435 int OKBodyParts
= 0;
7436 for (int c
= 0; c
< BodyParts
; ++c
) if (1 << c
& Possible
&& GetBodyPart(c
)) OKBodyPart
[OKBodyParts
++] = c
;
7437 return OKBodyParts
? OKBodyPart
[RAND_N(OKBodyParts
)] : NONE_INDEX
;
7441 void character::EditNP (sLong What
) {
7442 int OldState
= GetHungerState();
7444 int NewState
= GetHungerState();
7445 if (OldState
> VERY_HUNGRY
&& NewState
== VERY_HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
7446 if (OldState
> STARVING
&& NewState
== STARVING
) DeActivateVoluntaryAction(CONST_S("You are getting extremely hungry."));
7450 truth
character::IsSwimming () const {
7451 return !IsFlying() && GetSquareUnder() && GetSquareUnder()->GetSquareWalkability() & SWIM
;
7455 void character::AddBlackUnicornConsumeEndMessage () const {
7456 if (IsPlayer()) ADD_MESSAGE("You feel dirty and loathsome.");
7460 void character::AddGrayUnicornConsumeEndMessage () const {
7461 if (IsPlayer()) ADD_MESSAGE("You feel neutralized.");
7465 void character::AddWhiteUnicornConsumeEndMessage () const {
7466 if (IsPlayer()) ADD_MESSAGE("You feel purified.");
7470 void character::AddOmmelBoneConsumeEndMessage () const {
7471 if (IsPlayer()) ADD_MESSAGE("You feel the power of all your canine ancestors combining in your body.");
7472 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s looks extremely ferocious. You shudder.", CHAR_NAME(DEFINITE
));
7476 void character::AddLiquidHorrorConsumeEndMessage () const {
7477 if (IsPlayer()) ADD_MESSAGE("Untold horrors flash before your eyes. The melancholy of the world is on your shoulders!");
7478 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks as if the melancholy of the world is on %s shoulders!.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
7482 int character::GetBodyPartSparkleFlags (int) const {
7484 ((GetNaturalSparkleFlags() & SKIN_COLOR
? SPARKLING_A
: 0) |
7485 (GetNaturalSparkleFlags() & TORSO_MAIN_COLOR
? SPARKLING_B
: 0) |
7486 (GetNaturalSparkleFlags() & TORSO_SPECIAL_COLOR
? SPARKLING_D
: 0));
7490 truth
character::IsAnimated () const {
7491 return combinebodypartpredicates()(this, &bodypart::IsAnimated
, 1);
7495 double character::GetNaturalExperience (int Identifier
) const {
7496 return DataBase
->NaturalExperience
[Identifier
];
7500 truth
character::HasBodyPart (sorter Sorter
) const {
7501 if (Sorter
== 0) return true;
7502 return combinebodypartpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1);
7506 truth
character::PossessesItem (sorter Sorter
) const {
7507 if (Sorter
== 0) return true;
7509 (GetStack()->SortedItems(this, Sorter
) ||
7510 combinebodypartpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1) ||
7511 combineequipmentpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1));
7516 cchar
*character::GetRunDescriptionLine (int I
) const {
7517 if (!GetRunDescriptionLineOne().IsEmpty()) return !I
? GetRunDescriptionLineOne().CStr() : GetRunDescriptionLineTwo().CStr();
7518 if (IsFlying()) return !I
? "Flying" : "very fast";
7519 if (IsSwimming()) return !I
? "Swimming" : "very fast";
7520 return !I
? "Running" : "";
7524 void character::VomitAtRandomDirection (int Amount
) {
7525 if (game::IsInWilderness()) return;
7526 /* Lacks support of multitile monsters */
7529 for (int d
= 0; d
< 9; ++d
) {
7530 lsquare
*Square
= GetLSquareUnder()->GetNeighbourLSquare(d
);
7531 if (Square
&& !Square
->VomitingIsDangerous(this)) Possible
[Index
++] = Square
->GetPos();
7533 if (Index
) Vomit(Possible
[RAND_N(Index
)], Amount
);
7534 else Vomit(GetPos(), Amount
);
7538 void character::RemoveLifeSavers () {
7539 for (int c
= 0; c
< GetEquipments(); ++c
) {
7540 item
*Equipment
= GetEquipment(c
);
7541 if (Equipment
&& Equipment
->IsInCorrectSlot(c
) && Equipment
->GetGearStates() & LIFE_SAVED
) {
7542 Equipment
->SendToHell();
7543 Equipment
->RemoveFromSlot();
7549 ccharacter
*character::FindCarrier () const {
7550 return this; //check
7554 void character::PrintBeginHiccupsMessage () const {
7555 if (IsPlayer()) ADD_MESSAGE("Your diaphragm is spasming vehemently.");
7559 void character::PrintEndHiccupsMessage () const {
7560 if (IsPlayer()) ADD_MESSAGE("You feel your annoying hiccoughs have finally subsided.");
7564 void character::HiccupsHandler () {
7566 if (!(RAND() % 2000)) {
7567 if (IsPlayer()) ADD_MESSAGE("");
7568 else if (CanBeSeenByPlayer()) ADD_MESSAGE("");
7569 else if ((PLAYER->GetPos()-GetPos()).GetLengthSquare() <= 400) ADD_MESSAGE("");
7570 game::CallForAttention(GetPos(), 400);
7576 void character::VampirismHandler () {
7577 //EditExperience(ARM_STRENGTH, -25, 1 << 1);
7578 //EditExperience(LEG_STRENGTH, -25, 1 << 1);
7579 //EditExperience(DEXTERITY, -25, 1 << 1);
7580 //EditExperience(AGILITY, -25, 1 << 1);
7581 //EditExperience(ENDURANCE, -25, 1 << 1);
7582 EditExperience(CHARISMA
, -25, 1 << 1);
7583 EditExperience(WISDOM
, -25, 1 << 1);
7584 EditExperience(INTELLIGENCE
, -25, 1 << 1);
7585 CheckDeath(CONST_S("killed by vampirism"));
7589 void character::HiccupsSituationDangerModifier (double &Danger
) const {
7594 void character::VampirismSituationDangerModifier (double &Danger
) const {
7595 character
*Vampire
= vampire::Spawn();
7596 double DangerToVampire
= GetRelativeDanger(Vampire
);
7597 Danger
*= pow(DangerToVampire
, 0.1);
7602 bool character::IsConscious () const {
7603 return !Action
|| !Action
->IsUnconsciousness();
7607 wsquare
*character::GetNearWSquare (v2 Pos
) const {
7608 return static_cast<wsquare
*>(GetSquareUnder()->GetArea()->GetSquare(Pos
));
7612 wsquare
*character::GetNearWSquare (int x
, int y
) const {
7613 return static_cast<wsquare
*>(GetSquareUnder()->GetArea()->GetSquare(x
, y
));
7617 void character::ForcePutNear (v2 Pos
) {
7618 /* GUM SOLUTION!!! */
7619 v2 NewPos
= game::GetCurrentLevel()->GetNearestFreeSquare(PLAYER
, Pos
, false);
7620 if (NewPos
== ERROR_V2
) do { NewPos
= game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos
== Pos
);
7625 void character::ReceiveMustardGas (int BodyPart
, sLong Volume
) {
7626 if (Volume
) GetBodyPart(BodyPart
)->AddFluid(liquid::Spawn(MUSTARD_GAS_LIQUID
, Volume
), CONST_S("skin"), 0, true);
7630 void character::ReceiveMustardGasLiquid (int BodyPartIndex
, sLong Modifier
) {
7631 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
7632 if (BodyPart
->GetMainMaterial()->GetInteractionFlags() & IS_AFFECTED_BY_MUSTARD_GAS
) {
7633 sLong Tries
= Modifier
;
7634 Modifier
-= Tries
; //opt%?
7636 for (sLong c
= 0; c
< Tries
; ++c
) if (!(RAND() % 100)) ++Damage
;
7637 if (Modifier
&& !(RAND() % 1000 / Modifier
)) ++Damage
;
7639 feuLong Minute
= game::GetTotalMinutes();
7640 if (GetLastAcidMsgMin() != Minute
&& (CanBeSeenByPlayer() || IsPlayer())) {
7641 SetLastAcidMsgMin(Minute
);
7642 if (IsPlayer()) ADD_MESSAGE("Mustard gas dissolves the skin of your %s.", BodyPart
->GetBodyPartName().CStr());
7643 else ADD_MESSAGE("Mustard gas dissolves %s.", CHAR_NAME(DEFINITE
));
7645 ReceiveBodyPartDamage(0, Damage
, MUSTARD_GAS_DAMAGE
, BodyPartIndex
, YOURSELF
, false, false, false);
7646 CheckDeath(CONST_S("killed by a fatal exposure to mustard gas"));
7652 truth
character::IsBadPath (v2 Pos
) const {
7653 if (!IsGoingSomeWhere()) return false;
7654 v2 TPos
= !Route
.empty() ? Route
.back() : GoingTo
;
7655 return ((TPos
- Pos
).GetManhattanLength() > (TPos
- GetPos()).GetManhattanLength());
7659 double &character::GetExpModifierRef (expid E
) {
7660 return ExpModifierMap
.insert(std::make_pair(E
, 1.)).first
->second
;
7664 /* Should probably do more. Now only makes Player forget gods */
7665 truth
character::ForgetRandomThing () {
7667 /* hopefully this code isn't some where else */
7668 std::vector
<god
*> Known
;
7669 for (int c
= 1; c
<= GODS
; ++c
) if (game::GetGod(c
)->IsKnown()) Known
.push_back(game::GetGod(c
));
7670 if (Known
.empty()) return false;
7671 int RandomGod
= RAND_N(Known
.size());
7672 Known
.at(RAND_N(Known
.size()))->SetIsKnown(false);
7673 ADD_MESSAGE("You forget how to pray to %s.", Known
.at(RandomGod
)->GetName());
7680 int character::CheckForBlock (character
*Enemy
, item
*Weapon
, double ToHitValue
, int Damage
, int Success
, int Type
) {
7685 void character::ApplyAllGodsKnownBonus () {
7686 stack
*AddPlace
= GetStackUnder();
7687 if (game::IsInWilderness()) AddPlace
= GetStack(); else AddPlace
= GetStackUnder();
7688 pantheonbook
*NewBook
= pantheonbook::Spawn();
7689 AddPlace
->AddItem(NewBook
);
7690 ADD_MESSAGE("\"MORTAL! BEHOLD THE HOLY SAGA\"");
7691 ADD_MESSAGE("%s materializes near your feet.", NewBook
->CHAR_NAME(INDEFINITE
));
7695 void character::ReceiveSirenSong (character
*Siren
) {
7696 if (Siren
->GetTeam() == GetTeam()) return;
7698 if (IsPlayer()) ADD_MESSAGE("The beautiful melody of %s makes you feel sleepy.", Siren
->CHAR_NAME(DEFINITE
));
7699 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The beautiful melody of %s makes %s look sleepy.", Siren
->CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
)); /*k8*/
7700 Stamina
-= (1 + RAND_N(4)) * 10000;
7703 if (!IsPlayer() && IsCharmable() && !RAND_N(5)) {
7704 ChangeTeam(Siren
->GetTeam());
7705 ADD_MESSAGE("%s seems to be totally brainwashed by %s melodies.", CHAR_NAME(DEFINITE
), Siren
->CHAR_NAME(DEFINITE
));
7709 item
*What
= GiveMostExpensiveItem(Siren
);
7712 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
);
7714 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
);
7717 if (IsPlayer()) ADD_MESSAGE("You would like to give something to %s.", Siren
->CHAR_NAME(DEFINITE
));
7724 // return 0, if no item found
7725 item
*character::FindMostExpensiveItem () const {
7727 item
*MostExpensive
= 0;
7728 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
7729 if ((*i
)->GetPrice() > MaxPrice
) {
7730 MaxPrice
= (*i
)->GetPrice();
7731 MostExpensive
= (*i
);
7734 for (int c
= 0; c
< GetEquipments(); ++c
) {
7735 item
*Equipment
= GetEquipment(c
);
7736 if (Equipment
&& Equipment
->GetPrice() > MaxPrice
) {
7737 MaxPrice
= Equipment
->GetPrice();
7738 MostExpensive
= Equipment
;
7741 return MostExpensive
;
7745 // returns 0 if no items available
7746 item
*character::GiveMostExpensiveItem(character
*ToWhom
) {
7747 item
*ToGive
= FindMostExpensiveItem();
7748 if (!ToGive
) return 0;
7749 truth Equipped
= PLAYER
->Equips(ToGive
);
7750 ToGive
->RemoveFromSlot();
7751 if (Equipped
) game::AskForEscPress(CONST_S("Equipment lost!"));
7752 ToWhom
->ReceiveItemAsPresent(ToGive
);
7758 void character::ReceiveItemAsPresent (item
*Present
) {
7759 if (TestForPickup(Present
)) GetStack()->AddItem(Present
); else GetStackUnder()->AddItem(Present
);
7763 /* returns 0 if no enemies in sight */
7764 character
*character::GetNearestEnemy () const {
7765 character
*NearestEnemy
= 0;
7766 sLong NearestEnemyDistance
= 0x7FFFFFFF;
7768 for (int c
= 0; c
< game::GetTeams(); ++c
) {
7769 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
7770 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
7771 if ((*i
)->IsEnabled()) {
7772 sLong ThisDistance
= Max
<sLong
>(abs((*i
)->GetPos().X
- Pos
.X
), abs((*i
)->GetPos().Y
- Pos
.Y
));
7773 if ((ThisDistance
< NearestEnemyDistance
|| (ThisDistance
== NearestEnemyDistance
&& !(RAND() % 3))) && (*i
)->CanBeSeenBy(this)) {
7775 NearestEnemyDistance
= ThisDistance
;
7781 return NearestEnemy
;
7785 truth
character::MindWormCanPenetrateSkull (mindworm
*) const {
7790 truth
character::CanTameWithDulcis (const character
*Tamer
) const {
7791 int TamingDifficulty
= GetTamingDifficulty();
7792 if (TamingDifficulty
== NO_TAMING
) return false;
7793 if (GetAttachedGod() == DULCIS
) return true;
7794 int Modifier
= Tamer
->GetAttribute(WISDOM
) + Tamer
->GetAttribute(CHARISMA
);
7795 if (Tamer
->IsPlayer()) Modifier
+= game::GetGod(DULCIS
)->GetRelation() / 20;
7796 else if (Tamer
->GetAttachedGod() == DULCIS
) Modifier
+= 50;
7797 if (TamingDifficulty
== 0) {
7798 if (!IgnoreDanger()) TamingDifficulty
= int(10 * GetRelativeDanger(Tamer
));
7799 else TamingDifficulty
= 10 * GetHPRequirementForGeneration()/Max(Tamer
->GetHP(), 1);
7801 return Modifier
>= TamingDifficulty
* 3;
7805 truth
character::CanTameWithLyre (const character
*Tamer
) const {
7806 int TamingDifficulty
= GetTamingDifficulty();
7807 if (TamingDifficulty
== NO_TAMING
) return false;
7808 if (TamingDifficulty
== 0) {
7809 if (!IgnoreDanger()) TamingDifficulty
= int(10 * GetRelativeDanger(Tamer
));
7810 else TamingDifficulty
= 10*GetHPRequirementForGeneration()/Max(Tamer
->GetHP(), 1);
7812 return Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
;
7816 truth
character::CanTameWithScroll (const character
*Tamer
) const {
7817 int TamingDifficulty
= GetTamingDifficulty();
7819 (TamingDifficulty
!= NO_TAMING
&&
7820 (TamingDifficulty
== 0 ||
7821 Tamer
->GetAttribute(INTELLIGENCE
) * 4 + Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
* 5));
7825 truth
character::CheckSadism () {
7826 if (!IsSadist() || !HasSadistAttackMode() || !IsSmall()) return false; // gum
7828 for (int d
= 0; d
< 8; ++d
) {
7829 square
*Square
= GetNeighbourSquare(d
);
7831 character
*Char
= Square
->GetCharacter();
7832 if (Char
&& Char
->IsMasochist() && GetRelation(Char
) == FRIEND
&&
7833 Char
->GetHP() * 3 >= Char
->GetMaxHP() * 2 && Hit(Char
, Square
->GetPos(), d
, SADIST_HIT
)) {
7844 truth
character::CheckForBeverage () {
7845 if (StateIsActivated(PANIC
) || !IsEnabled() || !UsesNutrition() || CheckIfSatiated()) return false;
7846 itemvector ItemVector
;
7847 GetStack()->FillItemVector(ItemVector
);
7848 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) if (ItemVector
[c
]->IsBeverage(this) && TryToConsume(ItemVector
[c
])) return true;
7853 void character::Haste () {
7854 doforbodyparts()(this, &bodypart::Haste
);
7855 doforequipments()(this, &item::Haste
);
7856 BeginTemporaryState(HASTE
, 500 + RAND() % 1000);
7860 void character::Slow () {
7861 doforbodyparts()(this, &bodypart::Slow
);
7862 doforequipments()(this, &item::Slow
);
7863 //BeginTemporaryState(HASTE, 500 + RAND() % 1000); // this seems to be a bug
7864 BeginTemporaryState(SLOW
, 500 + RAND() % 1000);
7868 void character::SurgicallyDetachBodyPart () {
7869 ADD_MESSAGE("You haven't got any extra bodyparts.");