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
257 characterprototype::characterprototype (const characterprototype
*Base
, characterspawner Spawner
,
258 charactercloner Cloner
, cchar
*ClassID
)
264 Index
= protocontainer
<character
>::Add(this);
268 std::list
<character
*>::iterator
character::GetTeamIterator () { return TeamIterator
; }
269 void character::SetTeamIterator (std::list
<character
*>::iterator What
) { TeamIterator
= What
; }
270 void character::CreateInitialEquipment (int SpecialFlags
) { AddToInventory(DataBase
->Inventory
, SpecialFlags
); }
271 void character::EditAP (long What
) { AP
= Limit
<long>(AP
+What
, -12000, 1200); }
272 int character::GetRandomStepperBodyPart () const { return TORSO_INDEX
; }
273 void character::GainIntrinsic (long What
) { BeginTemporaryState(What
, PERMANENT
); }
274 truth
character::IsUsingArms () const { return GetAttackStyle() & USE_ARMS
; }
275 truth
character::IsUsingLegs () const { return GetAttackStyle() & USE_LEGS
; }
276 truth
character::IsUsingHead () const { return GetAttackStyle() & USE_HEAD
; }
277 void character::CalculateAllowedWeaponSkillCategories () { AllowedWeaponSkillCategories
= MARTIAL_SKILL_CATEGORIES
; }
278 festring
character::GetBeVerb () const { return IsPlayer() ? CONST_S("are") : CONST_S("is"); }
279 void character::SetEndurance (int What
) { BaseExperience
[ENDURANCE
] = What
* EXP_MULTIPLIER
; }
280 void character::SetPerception (int What
) { BaseExperience
[PERCEPTION
] = What
* EXP_MULTIPLIER
; }
281 void character::SetIntelligence (int What
) { BaseExperience
[INTELLIGENCE
] = What
* EXP_MULTIPLIER
; }
282 void character::SetWisdom (int What
) { BaseExperience
[WISDOM
] = What
* EXP_MULTIPLIER
; }
283 void character::SetWillPower (int What
) { BaseExperience
[WILL_POWER
] = What
* EXP_MULTIPLIER
; }
284 void character::SetCharisma (int What
) { BaseExperience
[CHARISMA
] = What
* EXP_MULTIPLIER
; }
285 void character::SetMana (int What
) { BaseExperience
[MANA
] = What
* EXP_MULTIPLIER
; }
286 truth
character::IsOnGround () const { return MotherEntity
&& MotherEntity
->IsOnGround(); }
287 truth
character::LeftOversAreUnique () const { return GetArticleMode() || AssignedName
.GetSize(); }
288 truth
character::HomeDataIsValid () const { return (HomeData
&& HomeData
->Level
== GetLSquareUnder()->GetLevelIndex() && HomeData
->Dungeon
== GetLSquareUnder()->GetDungeonIndex()); }
289 void character::SetHomePos (v2 Pos
) { HomeData
->Pos
= Pos
; }
290 cchar
*character::FirstPersonUnarmedHitVerb () const { return "hit"; }
291 cchar
*character::FirstPersonCriticalUnarmedHitVerb () const { return "critically hit"; }
292 cchar
*character::ThirdPersonUnarmedHitVerb () const { return "hits"; }
293 cchar
*character::ThirdPersonCriticalUnarmedHitVerb () const { return "critically hits"; }
294 cchar
*character::FirstPersonKickVerb () const { return "kick"; }
295 cchar
*character::FirstPersonCriticalKickVerb () const { return "critically kick"; }
296 cchar
*character::ThirdPersonKickVerb () const { return "kicks"; }
297 cchar
*character::ThirdPersonCriticalKickVerb () const { return "critically kicks"; }
298 cchar
*character::FirstPersonBiteVerb () const { return "bite"; }
299 cchar
*character::FirstPersonCriticalBiteVerb () const { return "critically bite"; }
300 cchar
*character::ThirdPersonBiteVerb () const { return "bites"; }
301 cchar
*character::ThirdPersonCriticalBiteVerb () const { return "critically bites"; }
302 cchar
*character::UnarmedHitNoun () const { return "attack"; }
303 cchar
*character::KickNoun () const { return "kick"; }
304 cchar
*character::BiteNoun () const { return "attack"; }
305 cchar
*character::GetEquipmentName (int) const { return ""; }
306 const std::list
<ulong
> &character::GetOriginalBodyPartID (int I
) const { return OriginalBodyPartID
[I
]; }
307 square
*character::GetNeighbourSquare (int I
) const { return GetSquareUnder()->GetNeighbourSquare(I
); }
308 lsquare
*character::GetNeighbourLSquare (int I
) const { return static_cast<lsquare
*>(GetSquareUnder())->GetNeighbourLSquare(I
); }
309 wsquare
*character::GetNeighbourWSquare (int I
) const { return static_cast<wsquare
*>(GetSquareUnder())->GetNeighbourWSquare(I
); }
310 god
*character::GetMasterGod () const { return game::GetGod(GetConfig()); }
311 col16
character::GetBodyPartColorA (int, truth
) const { return GetSkinColor(); }
312 col16
character::GetBodyPartColorB (int, truth
) const { return GetTorsoMainColor(); }
313 col16
character::GetBodyPartColorC (int, truth
) const { return GetBeltColor(); } // sorry...
314 col16
character::GetBodyPartColorD (int, truth
) const { return GetTorsoSpecialColor(); }
315 int character::GetRandomApplyBodyPart () const { return TORSO_INDEX
; }
316 truth
character::MustBeRemovedFromBone () const { return IsUnique() && !CanBeGenerated(); }
317 truth
character::IsPet () const { return GetTeam()->GetID() == PLAYER_TEAM
; }
318 character
* character::GetLeader () const { return GetTeam()->GetLeader(); }
319 int character::GetMoveType () const { return (!StateIsActivated(LEVITATION
) ? DataBase
->MoveType
: DataBase
->MoveType
| FLY
); }
320 festring
character::GetZombieDescription () const { return " of "+GetName(INDEFINITE
); }
321 truth
character::BodyPartCanBeSevered (int I
) const { return I
; }
322 truth
character::HasBeenSeen () const { return DataBase
->Flags
& HAS_BEEN_SEEN
; }
323 truth
character::IsTemporary () const { return GetTorso()->GetLifeExpectancy(); }
324 cchar
*character::GetNormalDeathMessage () const { return "killed @k"; }
327 int characterdatabase::*ExpPtr
[ATTRIBUTES
] = {
328 &characterdatabase::DefaultEndurance
,
329 &characterdatabase::DefaultPerception
,
330 &characterdatabase::DefaultIntelligence
,
331 &characterdatabase::DefaultWisdom
,
332 &characterdatabase::DefaultWillPower
,
333 &characterdatabase::DefaultCharisma
,
334 &characterdatabase::DefaultMana
,
335 &characterdatabase::DefaultArmStrength
,
336 &characterdatabase::DefaultLegStrength
,
337 &characterdatabase::DefaultDexterity
,
338 &characterdatabase::DefaultAgility
342 contentscript
<item
> characterdatabase::*EquipmentDataPtr
[EQUIPMENT_DATAS
] = {
343 &characterdatabase::Helmet
,
344 &characterdatabase::Amulet
,
345 &characterdatabase::Cloak
,
346 &characterdatabase::BodyArmor
,
347 &characterdatabase::Belt
,
348 &characterdatabase::RightWielded
,
349 &characterdatabase::LeftWielded
,
350 &characterdatabase::RightRing
,
351 &characterdatabase::LeftRing
,
352 &characterdatabase::RightGauntlet
,
353 &characterdatabase::LeftGauntlet
,
354 &characterdatabase::RightBoot
,
355 &characterdatabase::LeftBoot
359 character::character (ccharacter
&Char
) :
360 entity(Char
), id(Char
), NP(Char
.NP
), AP(Char
.AP
),
361 TemporaryState(Char
.TemporaryState
&~POLYMORPHED
),
362 Team(Char
.Team
), GoingTo(ERROR_V2
), Money(0),
363 AssignedName(Char
.AssignedName
), Action(0),
364 DataBase(Char
.DataBase
), MotherEntity(0),
365 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
366 AllowedWeaponSkillCategories(Char
.AllowedWeaponSkillCategories
),
367 BodyParts(Char
.BodyParts
),
368 RegenerationCounter(Char
.RegenerationCounter
),
369 SquaresUnder(Char
.SquaresUnder
), LastAcidMsgMin(0),
370 Stamina(Char
.Stamina
), MaxStamina(Char
.MaxStamina
),
371 BlocksSinceLastTurn(0), GenerationDanger(Char
.GenerationDanger
),
372 CommandFlags(Char
.CommandFlags
), WarnFlags(0),
373 ScienceTalks(Char
.ScienceTalks
), TrapData(0), CounterToMindWormHatch(0)
377 Flags
|= C_INITIALIZING
|C_IN_NO_MSG_MODE
;
378 Stack
= new stack(0, this, HIDDEN
);
379 for (c
= 0; c
< STATES
; ++c
) TemporaryStateCounter
[c
] = Char
.TemporaryStateCounter
[c
];
380 if (Team
) TeamIterator
= Team
->Add(this);
381 for (c
= 0; c
< BASE_ATTRIBUTES
; ++c
) BaseExperience
[c
] = Char
.BaseExperience
[c
];
382 BodyPartSlot
= new bodypartslot
[BodyParts
];
383 OriginalBodyPartID
= new std::list
<ulong
>[BodyParts
];
384 CWeaponSkill
= new cweaponskill
[AllowedWeaponSkillCategories
];
385 SquareUnder
= new square
*[SquaresUnder
];
386 if (SquaresUnder
== 1) *SquareUnder
= 0; else memset(SquareUnder
, 0, SquaresUnder
*sizeof(square
*));
387 for (c
= 0; c
< BodyParts
; ++c
) {
388 BodyPartSlot
[c
].SetMaster(this);
389 bodypart
*CharBodyPart
= Char
.GetBodyPart(c
);
390 OriginalBodyPartID
[c
] = Char
.OriginalBodyPartID
[c
];
392 bodypart
*BodyPart
= static_cast<bodypart
*>(CharBodyPart
->Duplicate());
393 SetBodyPart(c
, BodyPart
);
394 BodyPart
->CalculateEmitation();
397 for (c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) CWeaponSkill
[c
] = Char
.CWeaponSkill
[c
];
398 HomeData
= Char
.HomeData
? new homedata(*Char
.HomeData
) : 0;
399 ID
= game::CreateNewCharacterID(this);
403 character::character () :
404 entity(HAS_BE
), NP(50000), AP(0), TemporaryState(0), Team(0),
405 GoingTo(ERROR_V2
), Money(0), Action(0), MotherEntity(0),
406 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
407 RegenerationCounter(0), HomeData(0), LastAcidMsgMin(0),
408 BlocksSinceLastTurn(0), GenerationDanger(DEFAULT_GENERATION_DANGER
),
409 WarnFlags(0), ScienceTalks(0), TrapData(0), CounterToMindWormHatch(0)
411 Stack
= new stack(0, this, HIDDEN
);
415 character::~character () {
416 if (Action
) delete Action
;
417 if (Team
) Team
->Remove(GetTeamIterator());
419 for (int c
= 0; c
< BodyParts
; ++c
) delete GetBodyPart(c
);
420 delete [] BodyPartSlot
;
421 delete [] OriginalBodyPartID
;
422 delete PolymorphBackup
;
423 delete [] SquareUnder
;
424 delete [] CWeaponSkill
;
426 for (trapdata
*T
= TrapData
; T
;) {
431 game::RemoveCharacterID(ID
);
435 void character::Hunger () {
436 switch (GetBurdenState()) {
440 EditExperience(LEG_STRENGTH
, 150, 1 << 2);
441 EditExperience(AGILITY
, -50, 1 << 2);
445 EditExperience(LEG_STRENGTH
, 75, 1 << 1);
446 EditExperience(AGILITY
, -25, 1 << 1);
453 switch (GetHungerState()) {
455 EditExperience(ARM_STRENGTH
, -75, 1 << 3);
456 EditExperience(LEG_STRENGTH
, -75, 1 << 3);
459 EditExperience(ARM_STRENGTH
, -50, 1 << 2);
460 EditExperience(LEG_STRENGTH
, -50, 1 << 2);
463 EditExperience(ARM_STRENGTH
, -25, 1 << 1);
464 EditExperience(LEG_STRENGTH
, -25, 1 << 1);
467 EditExperience(AGILITY
, -25, 1 << 1);
470 EditExperience(AGILITY
, -50, 1 << 2);
473 EditExperience(AGILITY
, -75, 1 << 3);
476 CheckStarvationDeath(CONST_S("starved to death"));
480 int character::TakeHit (character
*Enemy
, item
*Weapon
, bodypart
*EnemyBodyPart
, v2 HitPos
, double Damage
,
481 double ToHitValue
, int Success
, int Type
, int GivenDir
, truth Critical
, truth ForceHit
)
484 game::mActor
= Enemy
;
485 game::RunOnCharEvent(this, CONST_S("take_hit"));
487 int Dir
= Type
== BITE_ATTACK
? YOURSELF
: GivenDir
;
488 double DodgeValue
= GetDodgeValue();
489 if (!Enemy
->IsPlayer() && GetAttackWisdomLimit() != NO_LIMIT
) Enemy
->EditExperience(WISDOM
, 75, 1 << 13);
490 if (!Enemy
->CanBeSeenBy(this)) ToHitValue
*= 2;
491 if (!CanBeSeenBy(Enemy
)) DodgeValue
*= 2;
492 if (Enemy
->StateIsActivated(CONFUSED
)) ToHitValue
*= 0.75;
494 switch (Enemy
->GetTirednessState()) {
500 switch (GetTirednessState()) {
508 if (!IsRetreating()) SetGoingTo(Enemy
->GetPos());
509 else SetGoingTo(GetPos()-((Enemy
->GetPos()-GetPos())<<4));
510 if (!Enemy
->IsRetreating()) Enemy
->SetGoingTo(GetPos());
511 else Enemy
->SetGoingTo(Enemy
->GetPos()-((GetPos()-Enemy
->GetPos())<<4));
514 /* Effectively, the average chance to hit is 100% / (DV/THV + 1). */
515 if (RAND() % int(100+ToHitValue
/DodgeValue
*(100+Success
)) < 100 && !Critical
&& !ForceHit
) {
516 Enemy
->AddMissMessage(this);
517 EditExperience(AGILITY
, 150, 1 << 7);
518 EditExperience(PERCEPTION
, 75, 1 << 7);
519 if (Enemy
->CanBeSeenByPlayer())
520 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
522 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
526 int TrueDamage
= int(Damage
*(100+Success
)/100)+(RAND()%3 ? 1 : 0);
528 TrueDamage
+= TrueDamage
>> 1;
532 int BodyPart
= ChooseBodyPartToReceiveHit(ToHitValue
, DodgeValue
);
536 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalUnarmedHitVerb(), Enemy
->ThirdPersonCriticalUnarmedHitVerb(), BodyPart
);
539 Enemy
->AddWeaponHitMessage(this, Weapon
, BodyPart
, true);
542 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalKickVerb(), Enemy
->ThirdPersonCriticalKickVerb(), BodyPart
);
545 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalBiteVerb(), Enemy
->ThirdPersonCriticalBiteVerb(), BodyPart
);
551 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonUnarmedHitVerb(), Enemy
->ThirdPersonUnarmedHitVerb(), BodyPart
);
554 Enemy
->AddWeaponHitMessage(this, Weapon
, BodyPart
, false);
557 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonKickVerb(), Enemy
->ThirdPersonKickVerb(), BodyPart
);
560 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonBiteVerb(), Enemy
->ThirdPersonBiteVerb(), BodyPart
);
565 if (!Critical
&& TrueDamage
&& Enemy
->AttackIsBlockable(Type
)) {
566 TrueDamage
= CheckForBlock(Enemy
, Weapon
, ToHitValue
, TrueDamage
, Success
, Type
);
567 if (!TrueDamage
|| (Weapon
&& !Weapon
->Exists())) {
568 if (Enemy
->CanBeSeenByPlayer())
569 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
571 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
576 int WeaponSkillHits
= CalculateWeaponSkillHits(Enemy
);
577 int DoneDamage
= ReceiveBodyPartDamage(Enemy
, TrueDamage
, PHYSICAL_DAMAGE
, BodyPart
, Dir
, false, Critical
, true, Type
== BITE_ATTACK
&& Enemy
->BiteCapturesBodyPart());
578 truth Succeeded
= (GetBodyPart(BodyPart
) && HitEffect(Enemy
, Weapon
, HitPos
, Type
, BodyPart
, Dir
, !DoneDamage
)) || DoneDamage
;
579 if (Succeeded
) Enemy
->WeaponSkillHit(Weapon
, Type
, WeaponSkillHits
);
582 if (Weapon
->Exists() && DoneDamage
< TrueDamage
) Weapon
->ReceiveDamage(Enemy
, TrueDamage
-DoneDamage
, PHYSICAL_DAMAGE
);
583 if (Weapon
->Exists() && DoneDamage
&& SpillsBlood() && GetBodyPart(BodyPart
) &&
584 (GetBodyPart(BodyPart
)->IsAlive() || GetBodyPart(BodyPart
)->GetMainMaterial()->IsLiquid()))
585 Weapon
->SpillFluid(0, CreateBlood(15+RAND()%15));
588 if (Enemy
->AttackIsBlockable(Type
)) SpecialBodyDefenceEffect(Enemy
, EnemyBodyPart
, Type
);
591 if (Enemy
->CanBeSeenByPlayer())
592 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
594 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
596 return DID_NO_DAMAGE
;
599 if (CheckDeath(GetNormalDeathMessage(), Enemy
, Enemy
->IsPlayer() ? FORCE_MSG
: 0)) return HAS_DIED
;
601 if (Enemy
->CanBeSeenByPlayer())
602 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
604 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
610 struct svpriorityelement
{
611 svpriorityelement (int BodyPart
, int StrengthValue
) : BodyPart(BodyPart
), StrengthValue(StrengthValue
) {}
612 bool operator < (const svpriorityelement
&AnotherPair
) const { return StrengthValue
> AnotherPair
.StrengthValue
; }
618 int character::ChooseBodyPartToReceiveHit (double ToHitValue
, double DodgeValue
) {
619 if (BodyParts
== 1) return 0;
620 std::priority_queue
<svpriorityelement
> SVQueue
;
621 for (int c
= 0; c
< BodyParts
; ++c
) {
622 bodypart
*BodyPart
= GetBodyPart(c
);
623 if (BodyPart
&& (BodyPart
->GetHP() != 1 || BodyPart
->CanBeSevered(PHYSICAL_DAMAGE
)))
624 SVQueue
.push(svpriorityelement(c
, ModifyBodyPartHitPreference(c
, BodyPart
->GetStrengthValue()+BodyPart
->GetHP())));
626 while (SVQueue
.size()) {
627 svpriorityelement E
= SVQueue
.top();
628 int ToHitPercentage
= int(GLOBAL_WEAK_BODYPART_HIT_MODIFIER
*ToHitValue
*GetBodyPart(E
.BodyPart
)->GetBodyPartVolume()/(DodgeValue
*GetBodyVolume()));
629 ToHitPercentage
= ModifyBodyPartToHitChance(E
.BodyPart
, ToHitPercentage
);
630 if (ToHitPercentage
< 1) ToHitPercentage
= 1;
631 else if (ToHitPercentage
> 95) ToHitPercentage
= 95;
632 if (ToHitPercentage
> RAND()%100) return E
.BodyPart
;
639 void character::Be () {
640 if (game::ForceJumpToPlayerBe()) {
641 if (!IsPlayer()) return;
642 game::SetForceJumpToPlayerBe(false);
644 truth ForceBe
= HP
!= MaxHP
|| AllowSpoil();
645 for (int c
= 0; c
< BodyParts
; ++c
) {
646 bodypart
*BodyPart
= GetBodyPart(c
);
647 if (BodyPart
&& (ForceBe
|| BodyPart
->NeedsBe())) BodyPart
->Be();
650 if (!IsEnabled()) return;
651 if (GetTeam() == PLAYER
->GetTeam()) {
652 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) {
653 if (CWeaponSkill
[c
].Tick() && IsPlayer()) CWeaponSkill
[c
].AddLevelDownMessage(c
);
658 if (GetHungerState() == STARVING
&& !(RAND()%50)) LoseConsciousness(250+RAND_N(250), true);
659 if (!Action
|| Action
->AllowFoodConsumption()) Hunger();
661 if (Stamina
!= MaxStamina
) RegenerateStamina();
662 if (HP
!= MaxHP
) Regenerate();
663 if (Action
&& AP
>= 1000) ActionAutoTermination();
664 if (Action
&& AP
>= 1000) {
666 if (!IsEnabled()) return;
668 EditAP(GetStateAPGain(100));
672 SpecialTurnHandler();
673 BlocksSinceLastTurn
= 0;
675 static int Timer
= 0;
676 if (ivanconfig::GetAutoSaveInterval() && !GetAction() && ++Timer
>= ivanconfig::GetAutoSaveInterval()) {
677 game::Save(game::GetAutoSaveFileName());
680 game::CalculateNextDanger();
681 if (!StateIsActivated(POLYMORPHED
)) game::UpdatePlayerAttributeAverage();
682 if (!game::IsInWilderness()) Search(GetAttribute(PERCEPTION
));
686 if (Action
->ShowEnvironment()) {
687 static int Counter
= 0;
688 if (++Counter
== 10) {
689 game::DrawEverything();
693 msgsystem::ThyMessagesAreNowOld();
694 if (Action
->IsVoluntary() && READ_KEY()) Action
->Terminate(false);
697 if (!Action
&& !game::IsInWilderness()) GetAICommand();
703 void character::Move (v2 MoveTo
, truth TeleportMove
, truth Run
) {
704 if (!IsEnabled()) return;
705 /* Test whether the player is stuck to something */
706 if (!TeleportMove
&& !TryToUnStickTraps(MoveTo
-GetPos())) return;
707 if (Run
&& !IsPlayer() && TorsoIsAlive() && (Stamina
<= 10000 / Max(GetAttribute(LEG_STRENGTH
), 1) ||
708 (!StateIsActivated(PANIC
) && Stamina
< MaxStamina
>> 2)))
711 if (GetBurdenState() != OVER_LOADED
|| TeleportMove
) {
712 lsquare
*OldSquareUnder
[MAX_SQUARES_UNDER
];
713 if (!game::IsInWilderness()) {
714 for (int c
= 0; c
< GetSquaresUnder(); ++c
) OldSquareUnder
[c
] = GetLSquareUnder(c
);
719 /* Multitiled creatures should behave differently, maybe? */
721 int ED
= GetSquareUnder()->GetEntryDifficulty();
722 EditAP(-GetMoveAPRequirement(ED
) >> 1);
724 EditExperience(AGILITY
, 125, ED
<< 7);
727 switch (GetHungerState()) {
728 case SATIATED
: Base
= 11000; break;
729 case BLOATED
: Base
= 12500; break;
730 case OVER_FED
: Base
= 15000; break;
733 EditStamina(-Base
/ Max(GetAttribute(LEG_STRENGTH
), 1), true);
735 int ED
= GetSquareUnder()->GetEntryDifficulty();
736 EditAP(-GetMoveAPRequirement(ED
));
738 EditExperience(AGILITY
, 75, ED
<< 7);
741 if (IsPlayer()) ShowNewPosInfo();
742 if (IsPlayer() && !game::IsInWilderness()) GetStackUnder()->SetSteppedOn(true);
743 if (!game::IsInWilderness()) SignalStepFrom(OldSquareUnder
);
746 cchar
*CrawlVerb
= StateIsActivated(LEVITATION
) ? "float" : "crawl";
747 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb
);
754 void character::GetAICommand () {
755 SeekLeader(GetLeader());
756 if (FollowLeader(GetLeader())) return;
757 if (CheckForEnemies(true, true, true)) return;
758 if (CheckForUsefulItemsOnGround()) return;
759 if (CheckForDoors()) return;
760 if (CheckSadism()) return;
761 if (MoveRandomly()) return;
766 truth
character::MoveTowardsTarget (truth Run
) {
771 if (!Route
.empty()) {
774 } else TPos
= GoingTo
;
776 MoveTo
[0] = v2(0, 0);
777 MoveTo
[1] = v2(0, 0);
778 MoveTo
[2] = v2(0, 0);
780 if (TPos
.X
< Pos
.X
) {
781 if (TPos
.Y
< Pos
.Y
) {
782 MoveTo
[0] = v2(-1, -1);
783 MoveTo
[1] = v2(-1, 0);
784 MoveTo
[2] = v2( 0, -1);
785 } else if (TPos
.Y
== Pos
.Y
) {
786 MoveTo
[0] = v2(-1, 0);
787 MoveTo
[1] = v2(-1, -1);
788 MoveTo
[2] = v2(-1, 1);
789 } else if (TPos
.Y
> Pos
.Y
) {
790 MoveTo
[0] = v2(-1, 1);
791 MoveTo
[1] = v2(-1, 0);
792 MoveTo
[2] = v2( 0, 1);
794 } else if (TPos
.X
== Pos
.X
) {
795 if (TPos
.Y
< Pos
.Y
) {
796 MoveTo
[0] = v2( 0, -1);
797 MoveTo
[1] = v2(-1, -1);
798 MoveTo
[2] = v2( 1, -1);
799 } else if (TPos
.Y
== Pos
.Y
) {
802 } else if (TPos
.Y
> Pos
.Y
) {
803 MoveTo
[0] = v2( 0, 1);
804 MoveTo
[1] = v2(-1, 1);
805 MoveTo
[2] = v2( 1, 1);
807 } else if (TPos
.X
> Pos
.X
) {
808 if (TPos
.Y
< Pos
.Y
) {
809 MoveTo
[0] = v2(1, -1);
810 MoveTo
[1] = v2(1, 0);
811 MoveTo
[2] = v2(0, -1);
812 } else if (TPos
.Y
== Pos
.Y
) {
813 MoveTo
[0] = v2(1, 0);
814 MoveTo
[1] = v2(1, -1);
815 MoveTo
[2] = v2(1, 1);
816 } else if (TPos
.Y
> Pos
.Y
) {
817 MoveTo
[0] = v2(1, 1);
818 MoveTo
[1] = v2(1, 0);
819 MoveTo
[2] = v2(0, 1);
823 v2 ModifiedMoveTo
= ApplyStateModification(MoveTo
[0]);
825 if (TryMove(ModifiedMoveTo
, true, Run
)) return true;
827 int L
= (Pos
-TPos
).GetManhattanLength();
829 if (RAND()&1) Swap(MoveTo
[1], MoveTo
[2]);
831 if (Pos
.IsAdjacent(TPos
)) {
836 if ((Pos
+MoveTo
[1]-TPos
).GetManhattanLength() <= L
&& TryMove(ApplyStateModification(MoveTo
[1]), true, Run
)) return true;
837 if ((Pos
+MoveTo
[2]-TPos
).GetManhattanLength() <= L
&& TryMove(ApplyStateModification(MoveTo
[2]), true, Run
)) return true;
838 Illegal
.insert(Pos
+ModifiedMoveTo
);
839 if (CreateRoute()) return true;
844 int character::CalculateNewSquaresUnder (lsquare
**NewSquare
, v2 Pos
) const {
845 if (GetLevel()->IsValidPos(Pos
)) {
846 *NewSquare
= GetNearLSquare(Pos
);
853 truth
character::TryMove (v2 MoveVector
, truth Important
, truth Run
) {
854 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
855 character
*Pet
[MAX_SQUARES_UNDER
];
856 character
*Neutral
[MAX_SQUARES_UNDER
];
857 character
*Hostile
[MAX_SQUARES_UNDER
];
858 v2 PetPos
[MAX_SQUARES_UNDER
];
859 v2 NeutralPos
[MAX_SQUARES_UNDER
];
860 v2 HostilePos
[MAX_SQUARES_UNDER
];
861 v2 MoveTo
= GetPos()+MoveVector
;
862 int Direction
= game::GetDirectionForVector(MoveVector
);
863 if (Direction
== DIR_ERROR
) ABORT("Direction fault.");
864 if (!game::IsInWilderness()) {
865 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, MoveTo
);
870 for (int c
= 0; c
< Squares
; ++c
) {
871 character
* Char
= MoveToSquare
[c
]->GetCharacter();
872 if (Char
&& Char
!= this) {
873 v2 Pos
= MoveToSquare
[c
]->GetPos();
876 PetPos
[Pets
++] = Pos
;
877 } else if (Char
->GetRelation(this) != HOSTILE
) {
878 Neutral
[Neutrals
] = Char
;
879 NeutralPos
[Neutrals
++] = Pos
;
881 Hostile
[Hostiles
] = Char
;
882 HostilePos
[Hostiles
++] = Pos
;
887 if (Hostiles
== 1) return Hit(Hostile
[0], HostilePos
[0], Direction
);
889 int Index
= RAND() % Hostiles
;
890 return Hit(Hostile
[Index
], HostilePos
[Index
], Direction
);
894 if (!IsPlayer() && !Pets
&& Important
&& CanMoveOn(MoveToSquare
[0]))
895 return HandleCharacterBlockingTheWay(Neutral
[0], NeutralPos
[0], Direction
);
897 return IsPlayer() && Hit(Neutral
[0], NeutralPos
[0], Direction
);
898 } else if (Neutrals
) {
900 int Index
= RAND() % Neutrals
;
901 return Hit(Neutral
[Index
], NeutralPos
[Index
], Direction
);
907 for (int c
= 0; c
< Squares
; ++c
) if (MoveToSquare
[c
]->IsScary(this)) return false;
911 if (IsPlayer() && !ivanconfig::GetBeNice() && Pet
[0]->IsMasochist() && HasSadistAttackMode() &&
912 game::TruthQuestion("Do you want to punish " + Pet
[0]->GetObjectPronoun() + "? [y/N]"))
913 return Hit(Pet
[0], PetPos
[0], Direction
, SADIST_HIT
);
915 return (Important
&& (CanMoveOn(MoveToSquare
[0]) ||
916 (IsPlayer() && game::GoThroughWallsCheatIsActive())) && Displace(Pet
[0]));
917 } else if (Pets
) return false;
919 if ((CanMove() && CanMoveOn(MoveToSquare
[0])) || ((game::GoThroughWallsCheatIsActive() && IsPlayer()))) {
920 Move(MoveTo
, false, Run
);
921 if (IsEnabled() && GetPos() == GoingTo
) TerminateGoingTo();
924 for (int c
= 0; c
< Squares
; ++c
) {
925 olterrain
*Terrain
= MoveToSquare
[c
]->GetOLTerrain();
926 if (Terrain
&& Terrain
->CanBeOpened()) {
928 if (Terrain
->IsLocked()) {
931 if (ivanconfig::GetKickDownDoors()) {
932 Kick(MoveToSquare
[c
], Direction
);
936 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 */
938 } else if (Important
&& CheckKick()) {
939 room
*Room
= MoveToSquare
[c
]->GetRoom();
940 if (!Room
|| Room
->AllowKick(this, MoveToSquare
[c
])) {
941 int HP
= Terrain
->GetHP();
942 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s kicks %s.", CHAR_NAME(DEFINITE
), Terrain
->CHAR_NAME(DEFINITE
));
943 Kick(MoveToSquare
[c
], Direction
);
944 olterrain
*NewTerrain
= MoveToSquare
[c
]->GetOLTerrain();
945 if (NewTerrain
== Terrain
&& Terrain
->GetHP() == HP
) { // BUG!
946 Illegal
.insert(MoveTo
);
952 } else { /* if (Terrain->IsLocked()) */
953 /*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);*/
954 /* Non-players always try to open it */
955 if (!IsPlayer()) return MoveToSquare
[c
]->Open(this);
956 if (game::TruthQuestion(CONST_S("Do you want to ")+(ivanconfig::GetKickDownDoors()?"kick ":"open ")+
957 Terrain
->GetName(DEFINITE
)+"? [y/N]", false, game::GetMoveCommandKeyBetweenPoints(PLAYER
->GetPos(),
958 MoveToSquare
[0]->GetPos()))) {
959 if (ivanconfig::GetKickDownDoors()) {
960 Kick(MoveToSquare
[c
], Direction
);
963 return MoveToSquare
[c
]->Open(this);
966 } /* if (Terrain->IsLocked()) */
967 } else { /* if (CanOpen()) */
969 ADD_MESSAGE("This monster type cannot open doors.");
973 Illegal
.insert(MoveTo
);
974 return CreateRoute();
976 } /* if (CanOpen()) */
977 } /* if (Terrain && Terrain->CanBeOpened()) */
982 if (IsPlayer() && !IsStuck() && GetLevel()->IsOnGround() && game::TruthQuestion(CONST_S("Do you want to leave ")+game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex())+"? [y/N]")) {
983 if (HasPetrussNut() && !HasGoldenEagleShirt()) {
984 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!"));
985 game::GetCurrentArea()->SendNewDrawRequest();
986 game::DrawEverything();
988 festring Msg
= CONST_S("killed Petrus and became the Avatar of Chaos");
989 PLAYER
->AddScoreEntry(Msg
, 3, false);
993 if (game::TryTravel(WORLD_MAP
, WORLD_MAP
, game::GetCurrentDungeonIndex())) return true;
998 /** No multitile support */
999 if (CanMove() && GetArea()->IsValidPos(MoveTo
) && (CanMoveOn(GetNearWSquare(MoveTo
)) || game::GoThroughWallsCheatIsActive())) {
1000 if (!game::GoThroughWallsCheatIsActive()) {
1001 charactervector
&V
= game::GetWorldMap()->GetPlayerGroup();
1002 truth Discard
= false;
1003 for (uint c
= 0; c
< V
.size(); ++c
) {
1004 if (!V
[c
]->CanMoveOn(GetNearWSquare(MoveTo
))) {
1006 ADD_MESSAGE("One or more of your team members cannot cross this terrain.");
1007 if (!game::TruthQuestion("Discard them? [y/N]")) return false;
1010 if (Discard
) delete V
[c
];
1011 V
.erase(V
.begin() + c
--);
1015 Move(MoveTo
, false);
1024 void character::CreateCorpse (lsquare
*Square
) {
1025 if (!BodyPartsDisappearWhenSevered() && !game::AllBodyPartsVanish()) {
1026 corpse
*Corpse
= corpse::Spawn(0, NO_MATERIALS
);
1027 Corpse
->SetDeceased(this);
1028 Square
->AddItem(Corpse
);
1036 void character::Die (ccharacter
*Killer
, cfestring
&Msg
, ulong DeathFlags
) {
1037 /* Note: This function musn't delete any objects, since one of these may be
1038 the one currently processed by pool::Be()! */
1039 if (!IsEnabled()) return;
1040 game::mActor
= Killer
;
1041 game::RunOnCharEvent(this, CONST_S("die"));
1045 ADD_MESSAGE("You die.");
1046 game::DrawEverything();
1047 if (game::TruthQuestion(CONST_S("Do you want to save screenshot? [y/n]"), REQUIRES_ANSWER
)) {
1048 festring dir
= inputfile::GetMyDir()+"/DeathShots";
1050 mkdir(dir
.CStr(), 0755);
1055 time_t t
= time(NULL
);
1056 struct tm
*ts
= localtime(&t
);
1058 timestr
<< (int)(ts
->tm_year
%100);
1059 int t
= ts
->tm_mon
+1;
1060 if (t
< 10) timestr
<< '0'; timestr
<< t
;
1061 t
= ts
->tm_mday
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1063 t
= ts
->tm_hour
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1064 t
= ts
->tm_min
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1065 t
= ts
->tm_sec
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1069 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1070 festring ext
= ".png";
1072 festring ext
= ".bmp";
1074 festring fname
= dir
+"/deathshot_"+timestr
;
1075 if (inputfile::fileExists(fname
+ext
)) {
1076 for (int f
= 0; f
< 1000; f
++) {
1078 sprintf(buf
, "%03d", f
);
1079 festring fn
= fname
+buf
;
1080 if (!inputfile::fileExists(fn
+ext
)) {
1087 fprintf(stderr
, "deathshot: %s\n", fname
.CStr());
1088 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1089 DOUBLE_BUFFER
->SavePNG(fname
);
1091 DOUBLE_BUFFER
->SaveBMP(fname
);
1094 if (game::WizardModeIsActive()) {
1095 game::DrawEverything();
1096 if (!game::TruthQuestion(CONST_S("Do you want to do this, cheater? [y/n]"), REQUIRES_ANSWER
)) {
1102 SetNP(SATIATED_LEVEL
);
1103 SendNewDrawRequest();
1107 } else if (CanBeSeenByPlayer() && !(DeathFlags
& DISALLOW_MSG
)) {
1108 ProcessAndAddMessage(GetDeathMessage());
1109 } else if (DeathFlags
& FORCE_MSG
) {
1110 ADD_MESSAGE("You sense the death of something.");
1113 if (!(DeathFlags
& FORBID_REINCARNATION
)) {
1114 if (StateIsActivated(LIFE_SAVED
) && CanMoveOn(!game::IsInWilderness() ? GetSquareUnder() : PLAYER
->GetSquareUnder())) {
1118 if (SpecialSaveLife()) return;
1119 } else if (StateIsActivated(LIFE_SAVED
)) {
1123 Flags
|= C_IN_NO_MSG_MODE
;
1124 character
*Ghost
= 0;
1126 game::RemoveSaves();
1127 if (!game::IsInWilderness()) {
1128 Ghost
= game::CreateGhost();
1133 square
*SquareUnder
[MAX_SQUARES_UNDER
];
1134 memset(SquareUnder
, 0, sizeof(SquareUnder
));
1136 if (IsPlayer() || !game::IsInWilderness()) {
1137 for (int c
= 0; c
< SquaresUnder
; ++c
) SquareUnder
[c
] = GetSquareUnder(c
);
1140 charactervector
& V
= game::GetWorldMap()->GetPlayerGroup();
1141 V
.erase(std::find(V
.begin(), V
.end(), this));
1143 //lsquare **LSquareUnder = reinterpret_cast<lsquare **>(SquareUnder); /* warning; wtf? */
1144 lsquare
*LSquareUnder
[MAX_SQUARES_UNDER
];
1145 memcpy(LSquareUnder
, SquareUnder
, sizeof(SquareUnder
));
1147 if (!game::IsInWilderness()) {
1148 if (!StateIsActivated(POLYMORPHED
)) {
1149 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(this, Killer
, Msg
);
1150 if (!(DeathFlags
& DISALLOW_CORPSE
)) CreateCorpse(LSquareUnder
[0]); else SendToHell();
1152 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(GetPolymorphBackup(), Killer
, Msg
);
1153 GetPolymorphBackup()->CreateCorpse(LSquareUnder
[0]);
1154 GetPolymorphBackup()->Flags
&= ~C_POLYMORPHED
;
1155 SetPolymorphBackup(0);
1159 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(this, Killer
, Msg
);
1164 if (!game::IsInWilderness()) {
1165 for (int c
= 0; c
< GetSquaresUnder(); ++c
) LSquareUnder
[c
]->SetTemporaryEmitation(GetEmitation());
1167 ShowAdventureInfo();
1168 if (!game::IsInWilderness()) {
1169 for(int c
= 0; c
< GetSquaresUnder(); ++c
) LSquareUnder
[c
]->SetTemporaryEmitation(0);
1173 if (!game::IsInWilderness()) {
1174 if (GetSquaresUnder() == 1) {
1175 stack
*StackUnder
= LSquareUnder
[0]->GetStack();
1176 GetStack()->MoveItemsTo(StackUnder
);
1177 doforbodypartswithparam
<stack
*>()(this, &bodypart::DropEquipment
, StackUnder
);
1179 while (GetStack()->GetItems()) GetStack()->GetBottom()->MoveTo(LSquareUnder
[RAND_N(GetSquaresUnder())]->GetStack());
1180 for (int c
= 0; c
< BodyParts
; ++c
) {
1181 bodypart
*BodyPart
= GetBodyPart(c
);
1182 if (BodyPart
) BodyPart
->DropEquipment(LSquareUnder
[RAND_N(GetSquaresUnder())]->GetStack());
1187 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(0);
1189 Flags
&= ~C_IN_NO_MSG_MODE
;
1193 if (!game::IsInWilderness()) {
1194 Ghost
->PutTo(LSquareUnder
[0]->GetPos());
1198 game::TextScreen(CONST_S("Unfortunately you died."), ZERO_V2
, WHITE
, true, true, &game::ShowDeathSmiley
);
1204 void character::AddMissMessage (ccharacter
*Enemy
) const {
1206 if (Enemy
->IsPlayer()) Msg
= GetDescription(DEFINITE
)+" misses you!";
1207 else if (IsPlayer()) Msg
= CONST_S("You miss ")+Enemy
->GetDescription(DEFINITE
)+'!';
1208 else if (CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer()) Msg
= GetDescription(DEFINITE
)+" misses "+Enemy
->GetDescription(DEFINITE
)+'!';
1210 ADD_MESSAGE("%s", Msg
.CStr());
1214 void character::AddBlockMessage (ccharacter
*Enemy
, citem
*Blocker
, cfestring
&HitNoun
, truth Partial
) const {
1216 festring BlockVerb
= (Partial
? " to partially block the " : " to block the ")+HitNoun
;
1218 Msg
<< "You manage" << BlockVerb
<< " with your " << Blocker
->GetName(UNARTICLED
) << '!';
1219 } else if (Enemy
->IsPlayer() || Enemy
->CanBeSeenByPlayer()) {
1220 if (CanBeSeenByPlayer())
1221 Msg
<< GetName(DEFINITE
) << " manages" << BlockVerb
<< " with " << GetPossessivePronoun() << ' ' << Blocker
->GetName(UNARTICLED
) << '!';
1223 Msg
<< "Something manages" << BlockVerb
<< " with something!";
1227 ADD_MESSAGE("%s", Msg
.CStr());
1231 void character::AddPrimitiveHitMessage (ccharacter
*Enemy
, cfestring
&FirstPersonHitVerb
,
1232 cfestring
&ThirdPersonHitVerb
, int BodyPart
) const
1235 festring BodyPartDescription
;
1236 if (BodyPart
&& (Enemy
->CanBeSeenByPlayer() || Enemy
->IsPlayer()))
1237 BodyPartDescription
<< " in the " << Enemy
->GetBodyPartName(BodyPart
);
1238 if (Enemy
->IsPlayer())
1239 Msg
<< GetDescription(DEFINITE
) << ' ' << ThirdPersonHitVerb
<< " you" << BodyPartDescription
<< '!';
1240 else if (IsPlayer())
1241 Msg
<< "You " << FirstPersonHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
<< '!';
1242 else if (CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer())
1243 Msg
<< GetDescription(DEFINITE
) << ' ' << ThirdPersonHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) + BodyPartDescription
<< '!';
1246 ADD_MESSAGE("%s", Msg
.CStr());
1250 cchar
*const HitVerb
[] = { "strike", "slash", "stab" };
1251 cchar
*const HitVerb3rdPersonEnd
[] = { "s", "es", "s" };
1254 void character::AddWeaponHitMessage (ccharacter
*Enemy
, citem
*Weapon
, int BodyPart
, truth Critical
) const {
1256 festring BodyPartDescription
;
1258 if (BodyPart
&& (Enemy
->CanBeSeenByPlayer() || Enemy
->IsPlayer()))
1259 BodyPartDescription
<< " in the " << Enemy
->GetBodyPartName(BodyPart
);
1261 int FittingTypes
= 0;
1262 int DamageFlags
= Weapon
->GetDamageFlags();
1265 for (int c
= 0; c
< DAMAGE_TYPES
; ++c
) {
1266 if (1 << c
& DamageFlags
) {
1267 if (!FittingTypes
|| !RAND_N(FittingTypes
+1)) DamageType
= c
;
1272 if (!FittingTypes
) ABORT("No damage flags specified for %s!", Weapon
->CHAR_NAME(UNARTICLED
));
1274 festring NewHitVerb
= Critical
? " critically " : " ";
1275 NewHitVerb
<< HitVerb
[DamageType
];
1276 cchar
*const E
= HitVerb3rdPersonEnd
[DamageType
];
1278 if (Enemy
->IsPlayer()) {
1279 Msg
<< GetDescription(DEFINITE
) << NewHitVerb
<< E
<< " you" << BodyPartDescription
;
1280 if (CanBeSeenByPlayer()) Msg
<< " with " << GetPossessivePronoun() << ' ' << Weapon
->GetName(UNARTICLED
);
1282 } else if (IsPlayer()) {
1283 Msg
<< "You" << NewHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
<< '!';
1284 } else if(CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer()) {
1285 Msg
<< GetDescription(DEFINITE
) << NewHitVerb
<< E
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
;
1286 if (CanBeSeenByPlayer()) Msg
<< " with " << GetPossessivePronoun() << ' ' << Weapon
->GetName(UNARTICLED
);
1291 ADD_MESSAGE("%s", Msg
.CStr());
1295 item
*character::GeneralFindItem (ItemCheckerCB chk
) const {
1296 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1298 if (it
&& chk(it
)) return it
;
1304 static truth
isElpuriHead (item
*i
) { return i
->IsHeadOfElpuri(); }
1305 truth
character::HasHeadOfElpuri () const {
1306 if (GeneralFindItem(::isElpuriHead
)) return true;
1307 return combineequipmentpredicates()(this, &item::IsHeadOfElpuri
, 1);
1311 static truth
isPetrussNut (item
*i
) { return i
->IsPetrussNut(); }
1312 truth
character::HasPetrussNut () const {
1313 if (GeneralFindItem(::isPetrussNut
)) return true;
1314 return combineequipmentpredicates()(this, &item::IsPetrussNut
, 1);
1318 static truth
isGoldenEagleShirt (item
*i
) { return i
->IsGoldenEagleShirt(); }
1319 truth
character::HasGoldenEagleShirt () const {
1320 if (GeneralFindItem(::isGoldenEagleShirt
)) return true;
1321 return combineequipmentpredicates()(this, &item::IsGoldenEagleShirt
, 1);
1325 int character::GeneralRemoveItem (ItemCheckerCB chk
, truth allItems
) {
1331 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1333 if (Item
&& chk(Item
)) {
1334 Item
->RemoveFromSlot();
1337 if (!allItems
) return cnt
;
1346 for (int c
= 0; c
< GetEquipments(); ++c
) {
1347 item
*Item
= GetEquipment(c
);
1348 if (Item
&& chk(Item
)) {
1349 Item
->RemoveFromSlot();
1352 if (!allItems
) return cnt
;
1362 static truth
isEncryptedScroll (item
*i
) { return i
->IsEncryptedScroll(); }
1363 truth
character::RemoveEncryptedScroll () { return GeneralRemoveItem(::isEncryptedScroll
) != 0; }
1366 static truth
isMondedrPass (item
*i
) { return i
->IsMondedrPass(); }
1367 truth
character::RemoveMondedrPass () { return GeneralRemoveItem(::isMondedrPass
) != 0; }
1370 static truth
isRingOfThieves (item
*i
) { return i
->IsRingOfThieves(); }
1371 truth
character::RemoveRingOfThieves () { return GeneralRemoveItem(::isRingOfThieves
) != 0; }
1374 truth
character::ReadItem (item
*ToBeRead
) {
1375 if (!ToBeRead
->CanBeRead(this)) {
1376 if (IsPlayer()) ADD_MESSAGE("You can't read this.");
1379 if (!GetLSquareUnder()->IsDark() || game::GetSeeWholeMapCheatMode()) {
1380 if (StateIsActivated(CONFUSED
) && !(RAND()&7)) {
1381 if (!ToBeRead
->IsDestroyable(this)) {
1382 ADD_MESSAGE("You read some words of %s and understand exactly nothing.", ToBeRead
->CHAR_NAME(DEFINITE
));
1384 ADD_MESSAGE("%s is very confusing. Or perhaps you are just too confused?", ToBeRead
->CHAR_NAME(DEFINITE
));
1385 ActivateRandomState(SRC_CONFUSE_READ
, 1000+RAND()%1500);
1386 ToBeRead
->RemoveFromSlot();
1387 ToBeRead
->SendToHell();
1392 if (ToBeRead
->Read(this)) {
1393 if (!game::WizardModeIsActive()) {
1394 /* This AP is used to take the stuff out of backpack */
1401 if (IsPlayer()) ADD_MESSAGE("It's too dark here to read.");
1406 void character::CalculateBurdenState () {
1407 int OldBurdenState
= BurdenState
;
1408 long SumOfMasses
= GetCarriedWeight();
1409 long CarryingStrengthUnits
= long(GetCarryingStrength())*2500;
1410 if (SumOfMasses
> (CarryingStrengthUnits
<< 1) + CarryingStrengthUnits
) BurdenState
= OVER_LOADED
;
1411 else if (SumOfMasses
> CarryingStrengthUnits
<< 1) BurdenState
= STRESSED
;
1412 else if (SumOfMasses
> CarryingStrengthUnits
) BurdenState
= BURDENED
;
1413 else BurdenState
= UNBURDENED
;
1414 if (!IsInitializing() && BurdenState
!= OldBurdenState
) CalculateBattleInfo();
1418 void character::Save (outputfile
&SaveFile
) const {
1419 SaveFile
<< (ushort
)GetType();
1420 Stack
->Save(SaveFile
);
1422 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) SaveFile
<< BaseExperience
[c
];
1424 SaveFile
<< ExpModifierMap
;
1425 SaveFile
<< NP
<< AP
<< Stamina
<< GenerationDanger
<< ScienceTalks
<< CounterToMindWormHatch
;
1426 SaveFile
<< TemporaryState
<< EquipmentState
<< Money
<< GoingTo
<< RegenerationCounter
<< Route
<< Illegal
;
1427 SaveFile
.Put(!!IsEnabled());
1428 SaveFile
<< HomeData
<< BlocksSinceLastTurn
<< CommandFlags
;
1429 SaveFile
<< WarnFlags
<< (ushort
)Flags
;
1431 for (int c
= 0; c
< BodyParts
; ++c
) SaveFile
<< BodyPartSlot
[c
] << OriginalBodyPartID
[c
];
1433 SaveLinkedList(SaveFile
, TrapData
);
1436 for (int c
= 0; c
< STATES
; ++c
) SaveFile
<< TemporaryStateCounter
[c
];
1440 SaveFile
<< Team
->GetID();
1442 SaveFile
.Put(false);
1445 if (GetTeam() && GetTeam()->GetLeader() == this) SaveFile
.Put(true); else SaveFile
.Put(false);
1447 SaveFile
<< AssignedName
<< PolymorphBackup
;
1449 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) SaveFile
<< CWeaponSkill
[c
];
1451 SaveFile
<< (ushort
)GetConfig();
1455 void character::Load (inputfile
&SaveFile
) {
1457 Stack
->Load(SaveFile
);
1459 game::AddCharacterID(this, ID
);
1461 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) SaveFile
>> BaseExperience
[c
];
1463 SaveFile
>> ExpModifierMap
;
1464 SaveFile
>> NP
>> AP
>> Stamina
>> GenerationDanger
>> ScienceTalks
>> CounterToMindWormHatch
;
1465 SaveFile
>> TemporaryState
>> EquipmentState
>> Money
>> GoingTo
>> RegenerationCounter
>> Route
>> Illegal
;
1467 if (!SaveFile
.Get()) Disable();
1469 SaveFile
>> HomeData
>> BlocksSinceLastTurn
>> CommandFlags
;
1470 SaveFile
>> WarnFlags
;
1471 WarnFlags
&= ~WARNED
;
1472 Flags
|= ReadType
<ushort
>(SaveFile
) & ~ENTITY_FLAGS
;
1474 for (int c
= 0; c
< BodyParts
; ++c
) {
1475 SaveFile
>> BodyPartSlot
[c
] >> OriginalBodyPartID
[c
];
1476 item
*BodyPart
= *BodyPartSlot
[c
];
1477 if (BodyPart
) BodyPart
->Disable();
1480 LoadLinkedList(SaveFile
, TrapData
);
1483 if (Action
) Action
->SetActor(this);
1485 for (int c
= 0; c
< STATES
; ++c
) SaveFile
>> TemporaryStateCounter
[c
];
1487 if (SaveFile
.Get()) SetTeam(game::GetTeam(ReadType
<int>(SaveFile
)));
1489 if (SaveFile
.Get()) GetTeam()->SetLeader(this);
1491 SaveFile
>> AssignedName
>> PolymorphBackup
;
1493 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) SaveFile
>> CWeaponSkill
[c
];
1495 databasecreator
<character
>::InstallDataBase(this, ReadType
<ushort
>(SaveFile
));
1497 if (IsEnabled() && !game::IsInWilderness()) {
1498 for (int c
= 1; c
< GetSquaresUnder(); ++c
) GetSquareUnder(c
)->SetCharacter(this);
1503 truth
character::Engrave (cfestring
&What
) {
1504 GetLSquareUnder()->Engrave(What
);
1508 truth
character::MoveRandomly () {
1509 if (!IsEnabled()) return false;
1510 for (int c
= 0; c
< 10; ++c
) {
1511 v2 ToTry
= game::GetMoveVector(RAND()&7);
1512 if (GetLevel()->IsValidPos(GetPos()+ToTry
)) {
1513 lsquare
*Square
= GetNearLSquare(GetPos()+ToTry
);
1514 if (!Square
->IsDangerous(this) && !Square
->IsScary(this) && TryMove(ToTry
, false, false)) return true;
1521 truth
character::TestForPickup (item
*ToBeTested
) const {
1522 if (MakesBurdened(ToBeTested
->GetWeight()+GetCarriedWeight())) return false;
1527 void character::AddScoreEntry (cfestring
&Description
, double Multiplier
, truth AddEndLevel
) const {
1528 if (!game::WizardModeIsReallyActive()) {
1530 if (!HScore
.CheckVersion()) {
1531 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;
1534 festring Desc
= game::GetPlayerName();
1535 Desc
<< ", " << Description
;
1536 if (AddEndLevel
) Desc
<< " in "+(game::IsInWilderness() ? "the world map" : game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex()));
1537 HScore
.Add(long(game::GetScore()*Multiplier
), Desc
);
1543 truth
character::CheckDeath (cfestring
&Msg
, ccharacter
*Murderer
, ulong DeathFlags
) {
1544 if (!IsEnabled()) return true;
1545 if (game::IsSumoWrestling() && IsDead()) {
1546 game::EndSumoWrestling(!!IsPlayer());
1549 if (DeathFlags
& FORCE_DEATH
|| IsDead()) {
1550 if (Murderer
&& Murderer
->IsPlayer() && GetTeam()->GetKillEvilness()) game::DoEvilDeed(GetTeam()->GetKillEvilness());
1551 festring SpecifierMsg
;
1552 int SpecifierParts
= 0;
1553 if (GetPolymorphBackup()) {
1554 SpecifierMsg
<< " polymorphed into ";
1555 id::AddName(SpecifierMsg
, INDEFINITE
);
1558 if (!(DeathFlags
& IGNORE_TRAPS
) && IsStuck()) {
1559 if (SpecifierParts
++) SpecifierMsg
<< " and";
1560 SpecifierMsg
<< " caught in " << GetTrapDescription();
1562 if (GetAction() && !(DeathFlags
& IGNORE_UNCONSCIOUSNESS
&& GetAction()->IsUnconsciousness())) {
1563 festring ActionMsg
= GetAction()->GetDeathExplanation();
1564 if (!ActionMsg
.IsEmpty()) {
1565 if (SpecifierParts
> 1) {
1566 SpecifierMsg
= ActionMsg
<< ',' << SpecifierMsg
;
1568 if (SpecifierParts
) SpecifierMsg
<< " and";
1569 SpecifierMsg
<< ActionMsg
;
1574 festring NewMsg
= Msg
;
1575 if (Murderer
== this) {
1576 SEARCH_N_REPLACE(NewMsg
, "@bkp", CONST_S("by ") + GetPossessivePronoun(false) + " own");
1577 SEARCH_N_REPLACE(NewMsg
, "@bk", CONST_S("by ") + GetObjectPronoun(false) + "self");
1578 SEARCH_N_REPLACE(NewMsg
, "@k", GetObjectPronoun(false) + "self");
1580 SEARCH_N_REPLACE(NewMsg
, "@bkp", CONST_S("by ") + Murderer
->GetName(INDEFINITE
) + "'s");
1581 SEARCH_N_REPLACE(NewMsg
, "@bk", CONST_S("by ") + Murderer
->GetName(INDEFINITE
));
1582 SEARCH_N_REPLACE(NewMsg
, "@k", CONST_S("by ") + Murderer
->GetName(INDEFINITE
));
1584 if (SpecifierParts
) NewMsg
<< " while" << SpecifierMsg
;
1585 if (IsPlayer() && game::WizardModeIsActive()) ADD_MESSAGE("Death message: %s. Score: %ld.", NewMsg
.CStr(), game::GetScore());
1586 Die(Murderer
, NewMsg
, DeathFlags
);
1593 truth
character::CheckStarvationDeath (cfestring
&Msg
) {
1594 if (GetNP() < 1 && UsesNutrition()) return CheckDeath(Msg
, 0, FORCE_DEATH
);
1599 void character::ThrowItem (int Direction
, item
*ToBeThrown
) {
1600 if (Direction
> 7) ABORT("Throw in TOO odd direction...");
1601 ToBeThrown
->Fly(this, Direction
, GetAttribute(ARM_STRENGTH
));
1605 void character::HasBeenHitByItem (character
*Thrower
, item
*Thingy
, int Damage
, double ToHitValue
, int Direction
) {
1606 if (IsPlayer()) ADD_MESSAGE("%s hits you.", Thingy
->CHAR_NAME(DEFINITE
));
1607 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s.", Thingy
->CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
));
1608 int BodyPart
= ChooseBodyPartToReceiveHit(ToHitValue
, DodgeValue
);
1609 int WeaponSkillHits
= Thrower
? CalculateWeaponSkillHits(Thrower
) : 0;
1610 int DoneDamage
= ReceiveBodyPartDamage(Thrower
, Damage
, PHYSICAL_DAMAGE
, BodyPart
, Direction
);
1611 truth Succeeded
= (GetBodyPart(BodyPart
) && HitEffect(Thrower
, Thingy
, Thingy
->GetPos(), THROW_ATTACK
, BodyPart
, Direction
, !DoneDamage
)) || DoneDamage
;
1612 if (Succeeded
&& Thrower
) Thrower
->WeaponSkillHit(Thingy
, THROW_ATTACK
, WeaponSkillHits
);
1613 festring DeathMsg
= CONST_S("killed by a flying ")+Thingy
->GetName(UNARTICLED
);
1614 if (CheckDeath(DeathMsg
, Thrower
)) return;
1616 if (Thrower
->CanBeSeenByPlayer())
1617 DeActivateVoluntaryAction(CONST_S("The attack of ")+Thrower
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
1619 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
1621 DeActivateVoluntaryAction(CONST_S("The hit interrupts you."));
1626 truth
character::DodgesFlyingItem (item
*Item
, double ToHitValue
) {
1627 return !Item
->EffectIsGood() && RAND() % int(100+ToHitValue
/DodgeValue
*100) < 100;
1631 void character::GetPlayerCommand () {
1633 truth HasActed
= false;
1635 game::DrawEverything();
1636 if (game::GetDangerFound()) {
1637 if (game::GetDangerFound() > 500.) {
1638 if (game::GetCausePanicFlag()) {
1639 game::SetCausePanicFlag(false);
1640 BeginTemporaryState(PANIC
, 500+RAND_N(500));
1642 game::AskForEscPress(CONST_S("You are horrified by your situation!"));
1643 } else if (ivanconfig::GetWarnAboutDanger()) {
1644 if (game::GetDangerFound() > 50.) game::AskForEscPress(CONST_S("You sense great danger!"));
1645 else game::AskForEscPress(CONST_S("You sense danger!"));
1647 game::SetDangerFound(0);
1649 game::SetIsInGetCommand(true);
1650 int Key
= GET_KEY();
1651 game::SetIsInGetCommand(false);
1652 if (Key
!= '+' && Key
!= '-' && Key
!= 'M') msgsystem::ThyMessagesAreNowOld(); // gum
1653 truth ValidKeyPressed
= false;
1655 for (c
= 0; c
< DIRECTION_COMMAND_KEYS
; ++c
) {
1656 if (Key
== game::GetMoveCommandKey(c
)) {
1657 HasActed
= TryMove(ApplyStateModification(game::GetMoveVector(c
)), true, game::PlayerIsRunning());
1658 ValidKeyPressed
= true;
1661 for (c
= 1; (cmd
= commandsystem::GetCommand(c
)); ++c
) {
1663 /* Numpad aliases for most commonly used commands */
1664 if (Key
== KEY_DEL
&& cmd
->GetLinkedFunction() == commandsystem::Eat
) Key
= cmd
->GetKey();
1665 if (Key
== KEY_INS
&& cmd
->GetLinkedFunction() == commandsystem::PickUp
) Key
= cmd
->GetKey();
1666 if (Key
== KEY_PLUS
&& cmd
->GetLinkedFunction() == commandsystem::EquipmentScreen
) Key
= cmd
->GetKey();
1668 if (Key
== cmd
->GetKey()) {
1669 if (game::IsInWilderness() && !commandsystem::GetCommand(c
)->IsUsableInWilderness())
1670 ADD_MESSAGE("This function cannot be used while in wilderness.");
1671 else if (!game::WizardModeIsActive() && commandsystem::GetCommand(c
)->IsWizardModeFunction())
1672 ADD_MESSAGE("Activate wizardmode to use this function.");
1674 HasActed
= commandsystem::GetCommand(c
)->GetLinkedFunction()(this);
1675 ValidKeyPressed
= true;
1679 if (!ValidKeyPressed
) ADD_MESSAGE("Unknown key. Press '?' for a list of commands.");
1681 game::IncreaseTurn();
1685 void character::Vomit (v2 Pos
, int Amount
, truth ShowMsg
) {
1686 if (!CanVomit()) return;
1688 if (IsPlayer()) ADD_MESSAGE("You vomit.");
1689 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s vomits.", CHAR_NAME(DEFINITE
));
1691 if (VomittingIsUnhealthy()) {
1692 EditExperience(ARM_STRENGTH
, -75, 1 << 9);
1693 EditExperience(LEG_STRENGTH
, -75, 1 << 9);
1696 EditNP(-2500-RAND()%2501);
1697 CheckStarvationDeath(CONST_S("vomited himself to death"));
1699 if (StateIsActivated(PARASITIZED
) && !(RAND() & 7)) {
1700 if (IsPlayer()) ADD_MESSAGE("You notice a dead broad tapeworm among your former stomach contents.");
1701 DeActivateTemporaryState(PARASITIZED
);
1703 if (!game::IsInWilderness()) {
1704 GetNearLSquare(Pos
)->ReceiveVomit(this, liquid::Spawn(GetVomitMaterial(), long(sqrt(GetBodyVolume())*Amount
/1000)));
1709 truth
character::Polymorph (character
*NewForm
, int Counter
) {
1710 if (!IsPolymorphable() || (!IsPlayer() && game::IsInWilderness())) {
1715 if (GetAction()) GetAction()->Terminate(false);
1716 NewForm
->SetAssignedName("");
1718 ADD_MESSAGE("Your body glows in a crimson light. You transform into %s!", NewForm
->CHAR_NAME(INDEFINITE
));
1719 else if (CanBeSeenByPlayer())
1720 ADD_MESSAGE("%s glows in a crimson light and %s transforms into %s!", CHAR_NAME(DEFINITE
), GetPersonalPronoun().CStr(), NewForm
->CHAR_NAME(INDEFINITE
));
1722 Flags
|= C_IN_NO_MSG_MODE
;
1723 NewForm
->Flags
|= C_IN_NO_MSG_MODE
;
1724 NewForm
->ChangeTeam(GetTeam());
1725 NewForm
->GenerationDanger
= GenerationDanger
;
1726 NewForm
->mOnEvents
= this->mOnEvents
;
1728 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(NewForm
);
1732 NewForm
->PutToOrNear(Pos
);
1733 NewForm
->SetAssignedName(GetAssignedName());
1734 NewForm
->ActivateTemporaryState(POLYMORPHED
);
1735 NewForm
->SetTemporaryStateCounter(POLYMORPHED
, Counter
);
1737 if (TemporaryStateIsActivated(POLYMORPHED
)) {
1738 NewForm
->SetPolymorphBackup(GetPolymorphBackup());
1739 SetPolymorphBackup(0);
1742 NewForm
->SetPolymorphBackup(this);
1743 Flags
|= C_POLYMORPHED
;
1747 GetStack()->MoveItemsTo(NewForm
->GetStack());
1748 NewForm
->SetMoney(GetMoney());
1749 DonateEquipmentTo(NewForm
);
1750 Flags
&= ~C_IN_NO_MSG_MODE
;
1751 NewForm
->Flags
&= ~C_IN_NO_MSG_MODE
;
1752 NewForm
->CalculateAll();
1756 game::SetPlayer(NewForm
);
1757 game::SendLOSUpdateRequest();
1761 NewForm
->TestWalkability();
1766 void character::BeKicked (character
*Kicker
, item
*Boot
, bodypart
*Leg
, v2 HitPos
, double KickDamage
,
1767 double ToHitValue
, int Success
, int Direction
, truth Critical
, truth ForceHit
)
1770 game::mActor
= Kicker
;
1771 game::RunOnCharEvent(this, CONST_S("before_be_kicked"));
1773 switch (TakeHit(Kicker
, Boot
, Leg
, HitPos
, KickDamage
, ToHitValue
, Success
, KICK_ATTACK
, Direction
, Critical
, ForceHit
)) {
1777 if (IsEnabled() && !CheckBalance(KickDamage
)) {
1778 if (IsPlayer()) ADD_MESSAGE("The kick throws you off balance.");
1779 else if (Kicker
->IsPlayer()) ADD_MESSAGE("The kick throws %s off balance.", CHAR_DESCRIPTION(DEFINITE
));
1780 v2 FallToPos
= GetPos()+game::GetMoveVector(Direction
);
1781 FallTo(Kicker
, FallToPos
);
1787 /* Return true if still in balance */
1788 truth
character::CheckBalance (double KickDamage
) {
1789 return !CanMove() || IsStuck() || !KickDamage
|| (!IsFlying() && KickDamage
*5 < RAND()%GetSize());
1793 void character::FallTo (character
*GuiltyGuy
, v2 Where
) {
1795 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
1796 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, Where
);
1798 truth NoRoom
= false;
1799 for (int c
= 0; c
< Squares
; ++c
) {
1800 olterrain
*Terrain
= MoveToSquare
[c
]->GetOLTerrain();
1801 if (Terrain
&& !CanMoveOn(Terrain
)) { NoRoom
= true; break; }
1805 if (IsPlayer()) ADD_MESSAGE("You hit your head on the wall.");
1806 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s head on the wall.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
1808 ReceiveDamage(GuiltyGuy
, 1+RAND()%5, PHYSICAL_DAMAGE
, HEAD
);
1809 CheckDeath(CONST_S("killed by hitting a wall due to being kicked @bk"), GuiltyGuy
);
1811 if (IsFreeForMe(MoveToSquare
[0])) Move(Where
, true);
1812 // Place code that handles characters bouncing to each other here
1818 truth
character::CheckCannibalism (cmaterial
*What
) const {
1819 return GetTorso()->GetMainMaterial()->IsSameAs(What
);
1823 void character::StandIdleAI () {
1824 SeekLeader(GetLeader());
1825 if (CheckForEnemies(true, true, true)) return;
1826 if (CheckForUsefulItemsOnGround()) return;
1827 if (FollowLeader(GetLeader())) return;
1828 if (CheckForDoors()) return;
1829 if (MoveTowardsHomePos()) return;
1830 if (CheckSadism()) return;
1835 truth
character::LoseConsciousness (int Counter
, truth HungerFaint
) {
1836 if (!AllowUnconsciousness()) return false;
1837 action
*Action
= GetAction();
1839 if (HungerFaint
&& !Action
->AllowUnconsciousness()) return false;
1840 if (Action
->IsUnconsciousness()) {
1841 static_cast<unconsciousness
*>(Action
)->RaiseCounterTo(Counter
);
1844 Action
->Terminate(false);
1846 if (IsPlayer()) ADD_MESSAGE("You lose consciousness.");
1847 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s loses consciousness.", CHAR_NAME(DEFINITE
));
1848 unconsciousness
*Unconsciousness
= unconsciousness::Spawn(this);
1849 Unconsciousness
->SetCounter(Counter
);
1850 SetAction(Unconsciousness
);
1855 void character::DeActivateVoluntaryAction (cfestring
&Reason
) {
1856 if (GetAction() && GetAction()->IsVoluntary()) {
1858 if (Reason
.GetSize()) ADD_MESSAGE("%s", Reason
.CStr());
1859 if (game::TruthQuestion(CONST_S("Continue ") + GetAction()->GetDescription()+"? [y/N]")) GetAction()->ActivateInDNDMode();
1860 else GetAction()->Terminate(false);
1863 GetAction()->Terminate(false);
1869 void character::ActionAutoTermination () {
1870 if (!GetAction() || !GetAction()->IsVoluntary() || GetAction()->InDNDMode()) return;
1872 for (int c
= 0; c
< game::GetTeams(); ++c
) {
1873 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
1874 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
1876 if (ch
->IsEnabled() && ch
->CanBeSeenBy(this, false, true) && (ch
->CanMove() || ch
->GetPos().IsAdjacent(Pos
)) && ch
->CanAttack()) {
1878 ADD_MESSAGE("%s seems to be hostile.", ch
->CHAR_NAME(DEFINITE
));
1879 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"? [y/N]")) GetAction()->ActivateInDNDMode();
1880 else GetAction()->Terminate(false);
1882 GetAction()->Terminate(false);
1892 truth
character::CheckForEnemies (truth CheckDoors
, truth CheckGround
, truth MayMoveRandomly
, truth RunTowardsTarget
) {
1893 if (!IsEnabled()) return false;
1894 truth HostileCharsNear
= false;
1895 character
*NearestChar
= 0;
1896 long NearestDistance
= 0x7FFFFFFF;
1898 for (int c
= 0; c
< game::GetTeams(); ++c
) {
1899 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
1900 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
1902 if (ch
->IsEnabled() && GetAttribute(WISDOM
) < ch
->GetAttackWisdomLimit()) {
1903 long ThisDistance
= Max
<long>(abs(ch
->GetPos().X
- Pos
.X
), abs(ch
->GetPos().Y
- Pos
.Y
));
1904 if (ThisDistance
<= GetLOSRangeSquare()) HostileCharsNear
= true;
1905 if ((ThisDistance
< NearestDistance
|| (ThisDistance
== NearestDistance
&& !(RAND() % 3))) &&
1906 ch
->CanBeSeenBy(this, false, IsGoingSomeWhere()) &&
1907 (!IsGoingSomeWhere() || HasClearRouteTo(ch
->GetPos()))) {
1909 NearestDistance
= ThisDistance
;
1917 if (GetAttribute(INTELLIGENCE
) >= 10 || IsSpy()) game::CallForAttention(GetPos(), 100);
1918 if (SpecialEnemySightedReaction(NearestChar
)) return true;
1919 if (IsExtraCoward() && !StateIsActivated(PANIC
) && NearestChar
->GetRelativeDanger(this) >= 0.5) {
1920 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s sees %s.", CHAR_NAME(DEFINITE
), NearestChar
->CHAR_DESCRIPTION(DEFINITE
));
1921 BeginTemporaryState(PANIC
, 500+RAND()%500);
1923 if (!IsRetreating()) {
1924 if (CheckGround
&& NearestDistance
> 2 && CheckForUsefulItemsOnGround(false)) return true;
1925 SetGoingTo(NearestChar
->GetPos());
1927 SetGoingTo(Pos
-((NearestChar
->GetPos()-Pos
)<<4));
1929 return MoveTowardsTarget(true);
1931 character
*Leader
= GetLeader();
1932 if (Leader
== this) Leader
= 0;
1933 if (!Leader
&& IsGoingSomeWhere()) {
1934 if (!MoveTowardsTarget(RunTowardsTarget
)) {
1938 if (!IsEnabled()) return true;
1939 if (GetPos() == GoingTo
) TerminateGoingTo();
1943 if ((!Leader
|| (Leader
&& !IsGoingSomeWhere())) && HostileCharsNear
) {
1944 if (CheckDoors
&& CheckForDoors()) return true;
1945 if (CheckGround
&& CheckForUsefulItemsOnGround()) return true;
1946 if (MayMoveRandomly
&& MoveRandomly()) return true; // one has heard that an enemy is near but doesn't know where
1954 truth
character::CheckForDoors () {
1955 if (!CanOpen() || !IsEnabled()) return false;
1956 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
1957 lsquare
*Square
= GetNeighbourLSquare(d
);
1958 if (Square
&& Square
->GetOLTerrain() && Square
->GetOLTerrain()->Open(this)) return true;
1964 truth
character::CheckForUsefulItemsOnGround (truth CheckFood
) {
1965 if (StateIsActivated(PANIC
) || !IsEnabled()) return false;
1966 itemvector ItemVector
;
1967 GetStackUnder()->FillItemVector(ItemVector
);
1968 for (uint c
= 0; c
< ItemVector
.size(); ++c
) {
1969 if (ItemVector
[c
]->CanBeSeenBy(this) && ItemVector
[c
]->IsPickable(this)) {
1970 if (!(CommandFlags
& DONT_CHANGE_EQUIPMENT
) && TryToEquip(ItemVector
[c
])) return true;
1971 if (CheckFood
&& UsesNutrition() && !CheckIfSatiated() && TryToConsume(ItemVector
[c
])) return true;
1978 truth
character::FollowLeader (character
*Leader
) {
1979 if (!Leader
|| Leader
== this || !IsEnabled()) return false;
1980 if (CommandFlags
& FOLLOW_LEADER
&& Leader
->CanBeSeenBy(this) && Leader
->SquareUnderCanBeSeenBy(this, true)) {
1981 v2 Distance
= GetPos()-GoingTo
;
1982 if (abs(Distance
.X
) <= 2 && abs(Distance
.Y
) <= 2) return false;
1983 return MoveTowardsTarget(false);
1985 if (IsGoingSomeWhere()) {
1986 if (!MoveTowardsTarget(true)) {
1996 void character::SeekLeader (ccharacter
*Leader
) {
1997 if (Leader
&& Leader
!= this) {
1998 if (Leader
->CanBeSeenBy(this) && (Leader
->SquareUnderCanBeSeenBy(this, true) || !IsGoingSomeWhere())) {
1999 if (CommandFlags
& FOLLOW_LEADER
) SetGoingTo(Leader
->GetPos());
2000 } else if (!IsGoingSomeWhere()) {
2001 team
*Team
= GetTeam();
2002 for (std::list
<character
*>::const_iterator i
= Team
->GetMember().begin(); i
!= Team
->GetMember().end(); ++i
) {
2004 if (ch
->IsEnabled() && ch
->GetID() != GetID() &&
2005 (CommandFlags
& FOLLOW_LEADER
) == (ch
->CommandFlags
& FOLLOW_LEADER
) && ch
->CanBeSeenBy(this)) {
2006 v2 Pos
= ch
->GetPos();
2007 v2 Distance
= GetPos()-Pos
;
2008 if (abs(Distance
.X
) > 2 && abs(Distance
.Y
) > 2) {
2019 int character::GetMoveEase () const {
2020 switch (BurdenState
) {
2022 case STRESSED
: return 50;
2023 case BURDENED
: return 75;
2024 case UNBURDENED
: return 100;
2030 int character::GetLOSRange () const {
2031 if (!game::IsInWilderness()) return GetAttribute(PERCEPTION
)*GetLevel()->GetLOSModifier()/48;
2036 truth
character::Displace (character
*Who
, truth Forced
) {
2037 if (GetBurdenState() == OVER_LOADED
) {
2039 cchar
*CrawlVerb
= StateIsActivated(LEVITATION
) ? "float" : "crawl";
2040 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb
);
2047 double Danger
= GetRelativeDanger(Who
);
2048 int PriorityDifference
= Limit(GetDisplacePriority()-Who
->GetDisplacePriority(), -31, 31);
2050 if (IsPlayer()) ++PriorityDifference
;
2051 else if (Who
->IsPlayer()) --PriorityDifference
;
2053 if (PriorityDifference
>= 0) Danger
*= 1 << PriorityDifference
;
2054 else Danger
/= 1 << -PriorityDifference
;
2056 if (IsSmall() && Who
->IsSmall() &&
2057 (Forced
|| Danger
> 1.0 || !(Who
->IsPlayer() || Who
->IsBadPath(GetPos()))) &&
2058 !IsStuck() && !Who
->IsStuck() && (!Who
->GetAction() || Who
->GetAction()->TryDisplace()) &&
2059 CanMove() && Who
->CanMove() && Who
->CanMoveOn(GetLSquareUnder())) {
2060 if (IsPlayer()) ADD_MESSAGE("You displace %s!", Who
->CHAR_DESCRIPTION(DEFINITE
));
2061 else if (Who
->IsPlayer()) ADD_MESSAGE("%s displaces you!", CHAR_DESCRIPTION(DEFINITE
));
2062 else if (CanBeSeenByPlayer() || Who
->CanBeSeenByPlayer()) ADD_MESSAGE("%s displaces %s!", CHAR_DESCRIPTION(DEFINITE
), Who
->CHAR_DESCRIPTION(DEFINITE
));
2063 lsquare
*OldSquareUnder1
[MAX_SQUARES_UNDER
];
2064 lsquare
*OldSquareUnder2
[MAX_SQUARES_UNDER
];
2065 for (int c
= 0; c
< GetSquaresUnder(); ++c
) OldSquareUnder1
[c
] = GetLSquareUnder(c
);
2066 for (int c
= 0; c
< Who
->GetSquaresUnder(); ++c
) OldSquareUnder2
[c
] = Who
->GetLSquareUnder(c
);
2068 v2 WhoPos
= Who
->GetPos();
2073 EditAP(-GetMoveAPRequirement(GetSquareUnder()->GetEntryDifficulty()) - 500);
2074 EditNP(-12*GetSquareUnder()->GetEntryDifficulty());
2075 EditExperience(AGILITY
, 75, GetSquareUnder()->GetEntryDifficulty() << 7);
2076 if (IsPlayer()) ShowNewPosInfo();
2077 if (Who
->IsPlayer()) Who
->ShowNewPosInfo();
2078 SignalStepFrom(OldSquareUnder1
);
2079 Who
->SignalStepFrom(OldSquareUnder2
);
2083 ADD_MESSAGE("%s resists!", Who
->CHAR_DESCRIPTION(DEFINITE
));
2092 void character::SetNP (long What
) {
2093 int OldState
= GetHungerState();
2096 int NewState
= GetHungerState();
2097 if (NewState
== STARVING
&& OldState
> STARVING
) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
2098 else if (NewState
== VERY_HUNGRY
&& OldState
> VERY_HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting very hungry."));
2099 else if (NewState
== HUNGRY
&& OldState
> HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting hungry."));
2104 void character::ShowNewPosInfo () const {
2105 msgsystem::EnterBigMessageMode();
2108 if (ivanconfig::GetAutoCenterMap()) {
2109 game::UpdateCameraX();
2110 game::UpdateCameraY();
2112 if (Pos
.X
< game::GetCamera().X
+3 || Pos
.X
>= game::GetCamera().X
+game::GetScreenXSize()-3) game::UpdateCameraX();
2113 if (Pos
.Y
< game::GetCamera().Y
+3 || Pos
.Y
>= game::GetCamera().Y
+game::GetScreenYSize()-3) game::UpdateCameraY();
2116 game::SendLOSUpdateRequest();
2117 game::DrawEverythingNoBlit();
2120 if (!game::IsInWilderness()) {
2121 if (GetLSquareUnder()->IsDark() && !game::GetSeeWholeMapCheatMode()) ADD_MESSAGE("It's dark in here!");
2123 GetLSquareUnder()->ShowSmokeMessage();
2124 itemvectorvector PileVector
;
2125 GetStackUnder()->Pile(PileVector
, this, CENTER
);
2127 if (PileVector
.size()) {
2128 truth Feel
= !GetLSquareUnder()->IsTransparent() || GetLSquareUnder()->IsDark();
2129 if (PileVector
.size() == 1) {
2130 if (Feel
) ADD_MESSAGE("You feel %s lying here.", PileVector
[0][0]->GetName(INDEFINITE
, PileVector
[0].size()).CStr());
2131 else ADD_MESSAGE("%s %s lying here.", PileVector
[0][0]->GetName(INDEFINITE
, PileVector
[0].size()).CStr(), PileVector
[0].size() == 1 ? "is" : "are");
2134 for (uint c
= 0; c
< PileVector
.size(); ++c
) {
2135 if ((Items
+= PileVector
[c
].size()) > 3) break;
2138 if (Feel
) ADD_MESSAGE("You feel several items lying here.");
2139 else ADD_MESSAGE("Several items are lying here.");
2141 if (Feel
) ADD_MESSAGE("You feel a few items lying here.");
2142 else ADD_MESSAGE("A few items are lying here.");
2148 GetLSquareUnder()->GetSideItemDescription(SideItems
);
2150 if (!SideItems
.IsEmpty()) ADD_MESSAGE("There is %s.", SideItems
.CStr());
2152 if (GetLSquareUnder()->HasEngravings()) {
2153 if (CanRead()) ADD_MESSAGE("Something has been engraved here: \"%s\"", GetLSquareUnder()->GetEngraved());
2154 else ADD_MESSAGE("Something has been engraved here.");
2158 msgsystem::LeaveBigMessageMode();
2162 void character::Hostility (character
*Enemy
) {
2163 if (Enemy
== this || !Enemy
|| !Team
|| !Enemy
->Team
) return;
2164 if (Enemy
->IsMasochist() && GetRelation(Enemy
) == FRIEND
) return;
2165 if (!IsAlly(Enemy
)) {
2166 GetTeam()->Hostility(Enemy
->GetTeam());
2167 } else if (IsPlayer() && !Enemy
->IsPlayer()) {
2168 // I believe both may be players due to polymorph feature...
2169 if (Enemy
->CanBeSeenByPlayer()) ADD_MESSAGE("%s becomes enraged.", Enemy
->CHAR_NAME(DEFINITE
));
2170 Enemy
->ChangeTeam(game::GetTeam(BETRAYED_TEAM
));
2175 stack
*character::GetGiftStack () const {
2176 if (GetLSquareUnder()->GetRoomIndex() && !GetLSquareUnder()->GetRoom()->AllowDropGifts()) return GetStack();
2177 return GetStackUnder();
2181 truth
character::MoveRandomlyInRoom () {
2182 for (int c
= 0; c
< 10; ++c
) {
2183 v2 ToTry
= game::GetMoveVector(RAND()&7);
2184 if (GetLevel()->IsValidPos(GetPos()+ToTry
)) {
2185 lsquare
*Square
= GetNearLSquare(GetPos()+ToTry
);
2186 if (!Square
->IsDangerous(this) && !Square
->IsScary(this) &&
2187 (!Square
->GetOLTerrain() || !Square
->GetOLTerrain()->IsDoor()) &&
2188 TryMove(ToTry
, false, false)) return true;
2195 truth
character::IsPassableSquare (int x
, int y
) const {
2196 area
*ca
= GetSquareUnder()->GetArea();
2197 if (x
< 0 || y
< 0 || x
>= ca
->GetXSize() || y
>= ca
->GetYSize()) return false;
2198 lsquare
*sq
= static_cast<lsquare
*>(ca
->GetSquare(x
, y
));
2199 return sq
&& CanMoveOn(sq
);
2203 truth
character::IsInCorridor () const { return IsInCorridor(GetPos().X
, GetPos().Y
); }
2205 truth
character::IsInCorridor (int x
, int y
) const {
2206 if (!IsPassableSquare(x
, y
-1) && !IsPassableSquare(x
, y
+1)) return true;
2207 if (!IsPassableSquare(x
-1, y
) && !IsPassableSquare(x
+1, y
)) return true;
2208 if (IsPassableSquare(x
, y
-1) && IsPassableSquare(x
, y
+1) &&
2209 (IsPassableSquare(x
-1, y
) || IsPassableSquare(x
+1, y
))) return false;
2210 if (IsPassableSquare(x
-1, y
) && IsPassableSquare(x
+1, y
) &&
2211 (IsPassableSquare(x
, y
-1) || IsPassableSquare(x
, y
+1))) return false;
2212 if (!IsPassableSquare(x
-1, y
-1) && !IsPassableSquare(x
+1, y
-1) &&
2213 !IsPassableSquare(x
-1, y
+1) && !IsPassableSquare(x
+1, y
+1)) return true;
2218 truth
character::IsInCorridor (const v2 dir
) const {
2219 v2 nxy
= GetPos()+dir
;
2220 return IsInCorridor(nxy
.X
, nxy
.Y
);
2224 void character::GoOn (go
*Go
, truth FirstStep
) {
2225 //fprintf(stderr, "corridor: %s\n", IsInCorridor() ? "tan" : "ona");
2226 if (FirstStep
) Go
->SetIsWalkingInOpen(!IsInCorridor());
2228 v2 MoveVector
= ApplyStateModification(game::GetMoveVector(Go
->GetDirection()));
2229 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
2231 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, GetPos()+MoveVector
);
2232 if (!Squares
|| !CanMoveOn(MoveToSquare
[0])) {
2233 Go
->Terminate(false);
2237 if (!FirstStep
&& !Go
->IsWalkingInOpen() && !IsInCorridor(MoveVector
)) {
2238 Go
->Terminate(false);
2242 uint OldRoomIndex
= GetLSquareUnder()->GetRoomIndex();
2243 uint CurrentRoomIndex
= MoveToSquare
[0]->GetRoomIndex();
2245 if ((OldRoomIndex
&& (CurrentRoomIndex
!= OldRoomIndex
)) && !FirstStep
) {
2246 Go
->Terminate(false);
2250 for (int c
= 0; c
< Squares
; ++c
) {
2251 if ((MoveToSquare
[c
]->GetCharacter() && GetTeam() != MoveToSquare
[c
]->GetCharacter()->GetTeam()) || MoveToSquare
[c
]->IsDangerous(this)) {
2252 Go
->Terminate(false);
2268 static const int revDir
[8] = { 7, 6, 5, 4, 3, 3, 1, 0 };
2269 static const bool orthoDir
[8] = { false, true, false, true, true, false, true, false };
2271 int OKDirectionsCounter
= 0, gd
= Go
->GetDirection(), odc
= 0;
2272 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2273 lsquare
*Square
= GetNeighbourLSquare(d
);
2274 if (Square
&& CanMoveOn(Square
)) ++OKDirectionsCounter
;
2278 if (orthoDir
[gd
] && !Go
->IsWalkingInOpen()) {
2279 // check if there is dead end forward and we have only one turn
2280 v2 nxy
= GetPos()+MoveVector
;
2281 v2 nxyff
= nxy
+MoveVector
;
2283 lsquare
*sqf
= static_cast<lsquare
*>(GetSquareUnder()->GetArea()->GetSquare(nxy
));
2284 lsquare
*sqff
= static_cast<lsquare
*>(GetSquareUnder()->GetArea()->GetSquare(nxyff
));
2285 if (sqf
&& CanMoveOn(sqf
) && (!sqff
|| !CanMoveOn(sqff
))) {
2287 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2288 if (d
== gd
|| d
== revDir
[gd
] || !orthoDir
[d
]) continue;
2289 v2 sqxy
= nxy
+game::GetMoveVector(d
);
2290 lsquare
*sq
= static_cast<lsquare
*>(GetSquareUnder()->GetArea()->GetSquare(sqxy
));
2291 if (sq
&& CanMoveOn(sq
)) {
2297 //fprintf(stderr, "*: %d; nDir: %d\n", odc, nDir);
2300 bool doStop
= false;
2301 if (!Go
->IsWalkingInOpen()) {
2303 //fprintf(stderr, "dc: %d\n", OKDirectionsCounter);
2304 if (Go
->prevWasTurn()) {
2305 Go
->SetPrevWasTurn(false);
2306 } else if (odc
== 1) {
2307 // if we will step on something, do it
2308 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2309 lsquare
*Square
= GetNeighbourLSquare(d
);
2310 if (Square
&& Square
->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
2316 // follow the turn; 3: back, forward and turn
2321 if (gd
== 3) newDir
= 0;
2322 else if (gd
== 4) newDir
= 2;
2325 if (gd
== 1) newDir
= 0;
2326 else if (gd
== 6) newDir
= 5;
2329 if (gd
== 1) newDir
= 2;
2330 else if (gd
== 6) newDir
= 7;
2333 if (gd
== 3) newDir
= 5;
2334 else if (gd
== 4) newDir
= 7;
2337 //if (newDir < 0) ABORT("go error");
2338 if (newDir
< 0) { Go
->Terminate(false); return; }
2339 lsquare
*Square
= GetNeighbourLSquare(newDir
);
2340 if (Square
&& CanMoveOn(Square
)) {
2341 // fuckin' copypasta
2342 MoveVector
= ApplyStateModification(game::GetMoveVector(newDir
));
2343 int squares
= CalculateNewSquaresUnder(MoveToSquare
, GetPos()+MoveVector
);
2345 for (int c
= 0; c
< squares
; ++c
) {
2346 if ((MoveToSquare
[c
]->GetCharacter() && GetTeam() != MoveToSquare
[c
]->GetCharacter()->GetTeam()) || MoveToSquare
[c
]->IsDangerous(this)) {
2357 if (newDir
< 0) { Go
->Terminate(false); return; }
2359 //fprintf(stderr, "newDir: %d\n", newDir);
2360 Go
->SetDirection(newDir
);
2361 Go
->SetPrevWasTurn(true);
2362 v2 nxyf
= GetPos()+MoveVector
+game::GetMoveVector(newDir
);
2363 v2 nxyff
= nxyf
+game::GetMoveVector(newDir
);
2364 lsquare
*sqf
= static_cast<lsquare
*>(GetSquareUnder()->GetArea()->GetSquare(nxyf
));
2365 lsquare
*sqff
= static_cast<lsquare
*>(GetSquareUnder()->GetArea()->GetSquare(nxyff
));
2366 if (sqf
&& CanMoveOn(sqf
)) {
2367 Go
->SetPrevWasTurn(sqff
&& CanMoveOn(sqff
));
2370 } else if (!IsInCorridor()) {
2371 Go
->Terminate(false);
2375 Go
->SetPrevWasTurn(false);
2376 //if (OKDirectionsCounter <= 2) Go->SetIsWalkingInOpen(false);
2377 Go
->SetIsWalkingInOpen(!IsInCorridor());
2380 square
*BeginSquare
= GetSquareUnder();
2383 for (int c
= 0; c
< Squares
; ++c
) {
2384 if (MoveToSquare
[c
]->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
2391 truth moveOk
= TryMove(MoveVector
, true, game::PlayerIsRunning());
2392 if (!moveOk
|| BeginSquare
== GetSquareUnder() || (CurrentRoomIndex
&& (OldRoomIndex
!= CurrentRoomIndex
))) {
2394 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2395 game::DrawEverything();
2397 Go
->Terminate(false);
2401 if (doStop
/* || GetStackUnder()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())*/) {
2402 Go
->Terminate(false);
2404 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2406 game::DrawEverything();
2410 void character::SetTeam (team
*What
) {
2411 /*k8 if(Team) int esko = esko = 2; */
2413 SetTeamIterator(What
->Add(this));
2417 void character::ChangeTeam (team
*What
) {
2418 if (Team
) Team
->Remove(GetTeamIterator());
2420 SendNewDrawRequest();
2421 if (Team
) SetTeamIterator(Team
->Add(this));
2425 truth
character::ChangeRandomAttribute (int HowMuch
) {
2426 for (int c
= 0; c
< 50; ++c
) {
2427 int AttribID
= RAND()%ATTRIBUTES
;
2428 if (EditAttribute(AttribID
, HowMuch
)) return true;
2434 int character::RandomizeReply (long &Said
, int Replies
) {
2435 truth NotSaid
= false;
2436 for (int c
= 0; c
< Replies
; ++c
) {
2437 if (!(Said
& (1 << c
))) {
2442 if (!NotSaid
) Said
= 0;
2444 while (Said
& 1 << (ToSay
= RAND() % Replies
));
2450 void character::DisplayInfo (festring
&Msg
) {
2452 Msg
<< " You are " << GetStandVerb() << " here.";
2454 Msg
<< ' ' << GetName(INDEFINITE
).CapitalizeCopy() << " is " << GetStandVerb() << " here. " << GetPersonalPronoun().CapitalizeCopy();
2455 cchar
*Separator1
= GetAction() ? "," : " and";
2456 cchar
*Separator2
= " and";
2457 if (GetTeam() == PLAYER
->GetTeam()) {
2460 int Relation
= GetRelation(PLAYER
);
2461 if (Relation
== HOSTILE
) Msg
<< " is hostile";
2462 else if (Relation
== UNCARING
) {
2463 Msg
<< " does not care about you";
2464 Separator1
= Separator2
= " and is";
2466 Msg
<< " is friendly";
2469 if (StateIsActivated(PANIC
)) {
2470 Msg
<< Separator1
<< " panicked";
2471 Separator2
= " and";
2473 if (GetAction()) Msg
<< Separator2
<< ' ' << GetAction()->GetDescription();
2479 void character::TestWalkability () {
2480 if (!IsEnabled()) return;
2481 square
*SquareUnder
= !game::IsInWilderness() ? GetSquareUnder() : PLAYER
->GetSquareUnder();
2482 if (SquareUnder
->IsFatalToStay() && !CanMoveOn(SquareUnder
)) {
2483 truth Alive
= false;
2484 if (!game::IsInWilderness() || IsPlayer()) {
2485 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2486 square
*Square
= GetNeighbourSquare(d
);
2487 if (Square
&& CanMoveOn(Square
) && IsFreeForMe(Square
)) {
2488 if (IsPlayer()) ADD_MESSAGE("%s.", SquareUnder
->SurviveMessage(this));
2489 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE
), SquareUnder
->MonsterSurviveMessage(this));
2490 Move(Square
->GetPos(), true); // actually, this shouldn't be a teleport move
2491 SquareUnder
->SurviveEffect(this);
2501 festring DeathMsg
= festring(SquareUnder
->DeathMessage(this));
2502 game::AskForEscPress(DeathMsg
+".");
2503 festring Msg
= SquareUnder
->ScoreEntry(this);
2504 PLAYER
->AddScoreEntry(Msg
);
2507 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE
), SquareUnder
->MonsterDeathVerb(this));
2508 Die(0, SquareUnder
->ScoreEntry(this), DISALLOW_MSG
);
2515 int character::GetSize () const {
2516 return GetTorso()->GetSize();
2520 void character::SetMainMaterial (material
*NewMaterial
, int SpecialFlags
) {
2521 NewMaterial
->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
2522 GetBodyPart(0)->SetMainMaterial(NewMaterial
, SpecialFlags
);
2523 for (int c
= 1; c
< BodyParts
; ++c
) {
2524 NewMaterial
= NewMaterial
->SpawnMore(GetBodyPart(c
)->GetMainMaterial()->GetVolume());
2525 GetBodyPart(c
)->SetMainMaterial(NewMaterial
, SpecialFlags
);
2530 void character::ChangeMainMaterial (material
*NewMaterial
, int SpecialFlags
) {
2531 NewMaterial
->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
2532 GetBodyPart(0)->ChangeMainMaterial(NewMaterial
, SpecialFlags
);
2533 for (int c
= 1; c
< BodyParts
; ++c
) {
2534 NewMaterial
= NewMaterial
->SpawnMore(GetBodyPart(c
)->GetMainMaterial()->GetVolume());
2535 GetBodyPart(c
)->ChangeMainMaterial(NewMaterial
, SpecialFlags
);
2540 void character::SetSecondaryMaterial (material
*, int) {
2541 ABORT("Illegal character::SetSecondaryMaterial call!");
2545 void character::ChangeSecondaryMaterial (material
*, int) {
2546 ABORT("Illegal character::ChangeSecondaryMaterial call!");
2550 void character::TeleportRandomly (truth Intentional
) {
2551 v2 TelePos
= ERROR_V2
;
2552 if (StateIsActivated(TELEPORT_CONTROL
)) {
2554 v2 Input
= game::PositionQuestion(CONST_S("Where do you wish to teleport? [direction keys move cursor, space accepts]"), GetPos(), &game::TeleportHandler
, 0, false);
2555 if (Input
== ERROR_V2
) Input
= GetPos(); // esc pressed
2556 lsquare
*Square
= GetNearLSquare(Input
);
2557 if (CanMoveOn(Square
) || game::GoThroughWallsCheatIsActive()) {
2558 if (Square
->GetPos() == GetPos()) {
2559 ADD_MESSAGE("You disappear and reappear.");
2562 if (IsFreeForMe(Square
)) {
2563 if ((Input
-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
2564 EditExperience(INTELLIGENCE
, 100, 1 << 10);
2567 ADD_MESSAGE("You cannot concentrate yourself enough to control a teleport that far.");
2570 character
*C
= Square
->GetCharacter();
2571 if (C
) ADD_MESSAGE("For a moment you feel very much like %s.", C
->CHAR_NAME(INDEFINITE
));
2572 else ADD_MESSAGE("You feel that something weird has happened, but can't really tell what exactly.");
2575 ADD_MESSAGE("You feel like having been hit by something really hard from the inside.");
2577 } else if (!Intentional
) {
2578 if (IsGoingSomeWhere() && GetLevel()->IsValidPos(GoingTo
)) {
2579 v2 Where
= GetLevel()->GetNearestFreeSquare(this, GoingTo
);
2580 if (Where
!= ERROR_V2
&& (Where
-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
2581 EditExperience(INTELLIGENCE
, 100, 1 << 10);
2589 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.");
2592 if (TelePos
!= ERROR_V2
) Move(TelePos
, true);
2593 else Move(GetLevel()->GetRandomSquare(this), true);
2595 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE
));
2597 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
2601 void character::RestoreHP () {
2602 doforbodyparts()(this, &bodypart::FastRestoreHP
);
2607 void character::RestoreLivingHP () {
2609 for (int c
= 0; c
< BodyParts
; ++c
) {
2610 bodypart
*BodyPart
= GetBodyPart(c
);
2611 if (BodyPart
&& BodyPart
->CanRegenerate()) {
2612 BodyPart
->FastRestoreHP();
2613 HP
+= BodyPart
->GetHP();
2619 truth
character::AllowDamageTypeBloodSpill (int Type
) {
2620 switch (Type
&0xFFF) {
2621 case PHYSICAL_DAMAGE
:
2630 case MUSTARD_GAS_DAMAGE
:
2634 ABORT("Unknown blood effect destroyed the dungeon!");
2639 /* Returns truly done damage */
2640 int character::ReceiveBodyPartDamage (character
*Damager
, int Damage
, int Type
, int BodyPartIndex
,
2641 int Direction
, truth PenetrateResistance
, truth Critical
, truth ShowNoDamageMsg
, truth CaptureBodyPart
)
2643 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
2644 if (!Damager
|| Damager
->AttackMayDamageArmor()) BodyPart
->DamageArmor(Damager
, Damage
, Type
);
2645 if (!PenetrateResistance
) {
2646 Damage
-= (BodyPart
->GetTotalResistance(Type
)>>1)+RAND()%((BodyPart
->GetTotalResistance(Type
)>>1)+1);
2648 if (int(Damage
) < 1) {
2652 if (ShowNoDamageMsg
) {
2653 if (IsPlayer()) ADD_MESSAGE("You are not hurt.");
2654 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is not hurt.", GetPersonalPronoun().CStr());
2660 if (Critical
&& AllowDamageTypeBloodSpill(Type
) && !game::IsInWilderness()) {
2661 BodyPart
->SpillBlood(2+(RAND()&1));
2662 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2663 lsquare
*Square
= GetNeighbourLSquare(d
);
2664 if (Square
&& Square
->IsFlyable()) BodyPart
->SpillBlood(1, Square
->GetPos());
2668 if (BodyPart
->ReceiveDamage(Damager
, Damage
, Type
, Direction
) && BodyPartCanBeSevered(BodyPartIndex
)) {
2669 if (DamageTypeDestroysBodyPart(Type
)) {
2670 if (IsPlayer()) ADD_MESSAGE("Your %s is destroyed!", BodyPart
->GetBodyPartName().CStr());
2671 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is destroyed!", GetPossessivePronoun().CStr(), BodyPart
->GetBodyPartName().CStr());
2672 GetBodyPart(BodyPartIndex
)->DropEquipment();
2673 item
*Severed
= SevereBodyPart(BodyPartIndex
);
2674 if (Severed
) Severed
->DestroyBodyPart(!game::IsInWilderness() ? GetStackUnder() : GetStack());
2675 SendNewDrawRequest();
2676 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart destroyed!"));
2678 if (IsPlayer()) ADD_MESSAGE("Your %s is severed off!", BodyPart
->GetBodyPartName().CStr());
2679 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is severed off!", GetPossessivePronoun().CStr(), BodyPart
->GetBodyPartName().CStr());
2680 item
*Severed
= SevereBodyPart(BodyPartIndex
);
2681 SendNewDrawRequest();
2683 if (CaptureBodyPart
) {
2684 Damager
->GetLSquareUnder()->AddItem(Severed
);
2685 } else if (!game::IsInWilderness()) {
2686 /** No multi-tile humanoid support! */
2687 GetStackUnder()->AddItem(Severed
);
2688 if (Direction
!= YOURSELF
) Severed
->Fly(0, Direction
, Damage
);
2690 GetStack()->AddItem(Severed
);
2692 Severed
->DropEquipment();
2693 } else if (IsPlayer() || CanBeSeenByPlayer()) {
2694 ADD_MESSAGE("It vanishes.");
2696 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart severed!"));
2698 if (CanPanicFromSeveredBodyPart() && RAND()%100 < GetPanicLevel() && !StateIsActivated(PANIC
) && !IsDead()) {
2699 BeginTemporaryState(PANIC
, 1000+RAND()%1001);
2701 SpecialBodyPartSeverReaction();
2704 if (!IsDead()) CheckPanic(500);
2710 /* Returns 0 if bodypart disappears */
2711 item
*character::SevereBodyPart (int BodyPartIndex
, truth ForceDisappearance
, stack
*EquipmentDropStack
) {
2712 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
2713 if (StateIsActivated(LEPROSY
)) BodyPart
->GetMainMaterial()->SetIsInfectedByLeprosy(true);
2714 if (ForceDisappearance
|| BodyPartsDisappearWhenSevered() || StateIsActivated(POLYMORPHED
) || game::AllBodyPartsVanish()) {
2715 BodyPart
->DropEquipment(EquipmentDropStack
);
2716 BodyPart
->RemoveFromSlot();
2717 CalculateAttributeBonuses();
2718 CalculateBattleInfo();
2719 BodyPart
->SendToHell();
2720 SignalPossibleTransparencyChange();
2721 RemoveTraps(BodyPartIndex
);
2724 BodyPart
->SetOwnerDescription("of " + GetName(INDEFINITE
));
2725 BodyPart
->SetIsUnique(LeftOversAreUnique());
2726 UpdateBodyPartPicture(BodyPartIndex
, true);
2727 BodyPart
->RemoveFromSlot();
2728 BodyPart
->RandomizePosition();
2729 CalculateAttributeBonuses();
2730 CalculateBattleInfo();
2732 SignalPossibleTransparencyChange();
2733 RemoveTraps(BodyPartIndex
);
2738 /* The second int is actually TargetFlags, which is not used here, but seems to be used in humanoid::ReceiveDamage.
2739 * Returns true if the character really receives damage */
2740 truth
character::ReceiveDamage (character
*Damager
, int Damage
, int Type
, int, int Direction
,
2741 truth
, truth PenetrateArmor
, truth Critical
, truth ShowMsg
)
2743 truth Affected
= ReceiveBodyPartDamage(Damager
, Damage
, Type
, 0, Direction
, PenetrateArmor
, Critical
, ShowMsg
);
2744 if (DamageTypeAffectsInventory(Type
)) {
2745 for (int c
= 0; c
< GetEquipments(); ++c
) {
2746 item
*Equipment
= GetEquipment(c
);
2747 if (Equipment
) Equipment
->ReceiveDamage(Damager
, Damage
, Type
);
2749 GetStack()->ReceiveDamage(Damager
, Damage
, Type
);
2755 festring
character::GetDescription (int Case
) const {
2756 if (IsPlayer()) return CONST_S("you");
2757 if (CanBeSeenByPlayer()) return GetName(Case
);
2758 return CONST_S("something");
2762 festring
character::GetPersonalPronoun (truth PlayersView
) const {
2763 if (IsPlayer() && PlayersView
) return CONST_S("you");
2764 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
2765 if (GetSex() == MALE
) return CONST_S("he");
2766 return CONST_S("she");
2770 festring
character::GetPossessivePronoun (truth PlayersView
) const {
2771 if (IsPlayer() && PlayersView
) return CONST_S("your");
2772 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("its");
2773 if (GetSex() == MALE
) return CONST_S("his");
2774 return CONST_S("her");
2778 festring
character::GetObjectPronoun (truth PlayersView
) const {
2779 if (IsPlayer() && PlayersView
) return CONST_S("you");
2780 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
2781 if (GetSex() == MALE
) return CONST_S("him");
2782 return CONST_S("her");
2786 void character::AddName (festring
&String
, int Case
) const {
2787 if (AssignedName
.IsEmpty()) {
2788 id::AddName(String
, Case
);
2789 } else if (!(Case
& PLURAL
)) {
2790 if (!ShowClassDescription()) {
2791 String
<< AssignedName
;
2793 String
<< AssignedName
<< ' ';
2794 id::AddName(String
, (Case
|ARTICLE_BIT
)&~INDEFINE_BIT
);
2797 id::AddName(String
, Case
);
2798 String
<< " named " << AssignedName
;
2803 int character::GetHungerState () const {
2804 if (!UsesNutrition()) return NOT_HUNGRY
;
2805 if (GetNP() > OVER_FED_LEVEL
) return OVER_FED
;
2806 if (GetNP() > BLOATED_LEVEL
) return BLOATED
;
2807 if (GetNP() > SATIATED_LEVEL
) return SATIATED
;
2808 if (GetNP() > NOT_HUNGER_LEVEL
) return NOT_HUNGRY
;
2809 if (GetNP() > HUNGER_LEVEL
) return HUNGRY
;
2810 if (GetNP() > VERY_HUNGER_LEVEL
) return VERY_HUNGRY
;
2815 truth
character::CanConsume (material
*Material
) const {
2816 return GetConsumeFlags() & Material
->GetConsumeType();
2820 void character::SetTemporaryStateCounter (long State
, int What
) {
2821 for (int c
= 0; c
< STATES
; ++c
) {
2822 if ((1 << c
) & State
) TemporaryStateCounter
[c
] = What
;
2827 void character::EditTemporaryStateCounter (long State
, int What
) {
2828 for (int c
= 0; c
< STATES
; ++c
) {
2829 if ((1 << c
) & State
) TemporaryStateCounter
[c
] += What
;
2834 int character::GetTemporaryStateCounter (long State
) const {
2835 for (int c
= 0; c
< STATES
; ++c
) {
2836 if ((1 << c
) & State
) return TemporaryStateCounter
[c
];
2838 ABORT("Illegal GetTemporaryStateCounter request!");
2843 truth
character::CheckKick () const {
2845 if (IsPlayer()) ADD_MESSAGE("This race can't kick.");
2852 int character::GetResistance (int Type
) const {
2853 switch (Type
&0xFFF) {
2854 case PHYSICAL_DAMAGE
:
2857 case MUSTARD_GAS_DAMAGE
:
2860 case ENERGY
: return GetEnergyResistance();
2861 case FIRE
: return GetFireResistance();
2862 case POISON
: return GetPoisonResistance();
2863 case ELECTRICITY
: return GetElectricityResistance();
2864 case ACID
: return GetAcidResistance();
2866 ABORT("Resistance lack detected!");
2871 void character::Regenerate () {
2872 if (HP
== MaxHP
) return;
2873 long RegenerationBonus
= 0;
2874 truth NoHealableBodyParts
= true;
2875 for (int c
= 0; c
< BodyParts
; ++c
) {
2876 bodypart
*BodyPart
= GetBodyPart(c
);
2877 if (BodyPart
&& BodyPart
->CanRegenerate()) {
2878 RegenerationBonus
+= BodyPart
->GetMaxHP();
2879 if (NoHealableBodyParts
&& BodyPart
->GetHP() < BodyPart
->GetMaxHP()) NoHealableBodyParts
= false;
2882 if (!RegenerationBonus
|| NoHealableBodyParts
) return;
2883 RegenerationBonus
*= (50+GetAttribute(ENDURANCE
));
2885 if (Action
&& Action
->IsRest()) {
2886 if (SquaresUnder
== 1) RegenerationBonus
*= GetSquareUnder()->GetRestModifier() << 1;
2888 int Lowest
= GetSquareUnder(0)->GetRestModifier();
2889 for (int c
= 1; c
< GetSquaresUnder(); ++c
) {
2890 int Mod
= GetSquareUnder(c
)->GetRestModifier();
2891 if (Mod
< Lowest
) Lowest
= Mod
;
2893 RegenerationBonus
*= Lowest
<< 1;
2897 RegenerationCounter
+= RegenerationBonus
;
2899 while (RegenerationCounter
> 1250000) {
2900 bodypart
*BodyPart
= HealHitPoint();
2901 if (!BodyPart
) break;
2902 EditNP(-Max(7500/MaxHP
, 1));
2903 RegenerationCounter
-= 1250000;
2904 int HP
= BodyPart
->GetHP();
2905 EditExperience(ENDURANCE
, Min(1000*BodyPart
->GetMaxHP()/(HP
*HP
), 300), 1000);
2910 void character::PrintInfo () const {
2911 felist
Info(CONST_S("Information about ")+GetName(DEFINITE
));
2912 for (int c
= 0; c
< GetEquipments(); ++c
) {
2913 item
*Equipment
= GetEquipment(c
);
2914 if ((EquipmentEasilyRecognized(c
) || game::WizardModeIsActive()) && Equipment
) {
2915 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
2916 Info
.AddEntry(festring(GetEquipmentName(c
))+": "+Equipment
->GetName(INDEFINITE
), LIGHT_GRAY
, 0, ImageKey
, true);
2919 if (Info
.IsEmpty()) {
2920 ADD_MESSAGE("There's nothing special to tell about %s.", CHAR_NAME(DEFINITE
));
2922 game::SetStandardListAttributes(Info
);
2923 Info
.SetEntryDrawer(game::ItemEntryDrawer
);
2926 game::ClearItemDrawVector();
2930 truth
character::TryToRiseFromTheDead () {
2931 for (int c
= 0; c
< BodyParts
; ++c
) {
2932 bodypart
*BodyPart
= GetBodyPart(c
);
2934 BodyPart
->ResetSpoiling();
2935 if (BodyPart
->CanRegenerate() || BodyPart
->GetHP() < 1) BodyPart
->SetHP(1);
2943 truth
character::RaiseTheDead (character
*) {
2944 truth Useful
= false;
2945 for (int c
= 0; c
< BodyParts
; ++c
) {
2946 bodypart
*BodyPart
= GetBodyPart(c
);
2947 if (!BodyPart
&& CanCreateBodyPart(c
)) {
2948 CreateBodyPart(c
)->SetHP(1);
2949 if (IsPlayer()) ADD_MESSAGE("Suddenly you grow a new %s.", GetBodyPartName(c
).CStr());
2950 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), GetBodyPartName(c
).CStr());
2952 } else if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < 1) {
2957 if (IsPlayer()) ADD_MESSAGE("You shudder.");
2958 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s shudders.", CHAR_NAME(DEFINITE
));
2964 void character::SetSize (int Size
) {
2965 for (int c
= 0; c
< BodyParts
; ++c
) {
2966 bodypart
*BodyPart
= GetBodyPart(c
);
2967 if (BodyPart
) BodyPart
->SetSize(GetBodyPartSize(c
, Size
));
2972 long character::GetBodyPartSize (int I
, int TotalSize
) const {
2973 if (I
== TORSO_INDEX
) return TotalSize
;
2974 ABORT("Weird bodypart size request for a character!");
2979 long character::GetBodyPartVolume (int I
) const {
2980 if (I
== TORSO_INDEX
) return GetTotalVolume();
2981 ABORT("Weird bodypart volume request for a character!");
2986 void character::CreateBodyParts (int SpecialFlags
) {
2987 for (int c
= 0; c
< BodyParts
; ++c
) if (CanCreateBodyPart(c
)) CreateBodyPart(c
, SpecialFlags
);
2991 void character::RestoreBodyParts () {
2992 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) CreateBodyPart(c
);
2996 void character::UpdatePictures () {
2997 if (!PictureUpdatesAreForbidden()) for (int c
= 0; c
< BodyParts
; ++c
) UpdateBodyPartPicture(c
, false);
3001 bodypart
*character::MakeBodyPart (int I
) const {
3002 if (I
== TORSO_INDEX
) return normaltorso::Spawn(0, NO_MATERIALS
);
3003 ABORT("Weird bodypart to make for a character!");
3008 bodypart
*character::CreateBodyPart (int I
, int SpecialFlags
) {
3009 bodypart
*BodyPart
= MakeBodyPart(I
);
3010 material
*Material
= CreateBodyPartMaterial(I
, GetBodyPartVolume(I
));
3011 BodyPart
->InitMaterials(Material
, false);
3012 BodyPart
->SetSize(GetBodyPartSize(I
, GetTotalSize()));
3013 BodyPart
->SetBloodMaterial(GetBloodMaterial());
3014 BodyPart
->SetNormalMaterial(Material
->GetConfig());
3015 SetBodyPart(I
, BodyPart
);
3016 BodyPart
->InitSpecialAttributes();
3017 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdateBodyPartPicture(I
, false);
3018 if (!IsInitializing()) {
3019 CalculateBattleInfo();
3020 SendNewDrawRequest();
3021 SignalPossibleTransparencyChange();
3027 v2
character::GetBodyPartBitmapPos (int I
, truth
) const {
3028 if (I
== TORSO_INDEX
) return GetTorsoBitmapPos();
3029 ABORT("Weird bodypart BitmapPos request for a character!");
3034 void character::UpdateBodyPartPicture (int I
, truth Severed
) {
3035 bodypart
*BP
= GetBodyPart(I
);
3037 BP
->SetBitmapPos(GetBodyPartBitmapPos(I
, Severed
));
3038 BP
->GetMainMaterial()->SetSkinColor(GetBodyPartColorA(I
, Severed
));
3039 BP
->GetMainMaterial()->SetSkinColorIsSparkling(GetBodyPartSparkleFlags(I
) & SPARKLING_A
);
3040 BP
->SetMaterialColorB(GetBodyPartColorB(I
, Severed
));
3041 BP
->SetMaterialColorC(GetBodyPartColorC(I
, Severed
));
3042 BP
->SetMaterialColorD(GetBodyPartColorD(I
, Severed
));
3043 BP
->SetSparkleFlags(GetBodyPartSparkleFlags(I
));
3044 BP
->SetSpecialFlags(GetSpecialBodyPartFlags(I
));
3045 BP
->SetWobbleData(GetBodyPartWobbleData(I
));
3046 BP
->UpdatePictures();
3051 void character::LoadDataBaseStats () {
3052 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) {
3053 BaseExperience
[c
] = DataBase
->NaturalExperience
[c
];
3054 if (BaseExperience
[c
]) LimitRef(BaseExperience
[c
], MIN_EXP
, MAX_EXP
);
3056 SetMoney(GetDefaultMoney());
3057 const fearray
<long> &Skills
= GetKnownCWeaponSkills();
3059 const fearray
<long> &Hits
= GetCWeaponSkillHits();
3060 if (Hits
.Size
== 1) {
3061 for (uint c
= 0; c
< Skills
.Size
; ++c
) {
3062 if (Skills
[c
] < AllowedWeaponSkillCategories
) CWeaponSkill
[Skills
[c
]].AddHit(Hits
[0]*100);
3064 } else if (Hits
.Size
== Skills
.Size
) {
3065 for (uint c
= 0; c
< Skills
.Size
; ++c
) {
3066 if (Skills
[c
] < AllowedWeaponSkillCategories
) CWeaponSkill
[Skills
[c
]].AddHit(Hits
[c
]*100);
3069 ABORT("Illegal weapon skill hit array size detected!");
3075 character
*characterprototype::SpawnAndLoad (inputfile
&SaveFile
) const {
3076 character
*Char
= Spawner(0, LOAD
);
3077 Char
->Load(SaveFile
);
3078 Char
->CalculateAll();
3083 void character::Initialize (int NewConfig
, int SpecialFlags
) {
3084 Flags
|= C_INITIALIZING
|C_IN_NO_MSG_MODE
;
3085 CalculateBodyParts();
3086 CalculateAllowedWeaponSkillCategories();
3087 CalculateSquaresUnder();
3088 BodyPartSlot
= new bodypartslot
[BodyParts
];
3089 OriginalBodyPartID
= new std::list
<ulong
>[BodyParts
];
3090 CWeaponSkill
= new cweaponskill
[AllowedWeaponSkillCategories
];
3091 SquareUnder
= new square
*[SquaresUnder
];
3093 if (SquaresUnder
== 1) *SquareUnder
= 0; else memset(SquareUnder
, 0, SquaresUnder
*sizeof(square
*));
3095 for (int c
= 0; c
< BodyParts
; ++c
) BodyPartSlot
[c
].SetMaster(this);
3097 if (!(SpecialFlags
& LOAD
)) {
3098 ID
= game::CreateNewCharacterID(this);
3099 databasecreator
<character
>::InstallDataBase(this, NewConfig
);
3100 LoadDataBaseStats();
3101 TemporaryState
|= GetClassStates();
3102 if (TemporaryState
) {
3103 for (int c
= 0; c
< STATES
; ++c
) if (TemporaryState
& (1 << c
)) TemporaryStateCounter
[c
] = PERMANENT
;
3106 CreateBodyParts(SpecialFlags
| NO_PIC_UPDATE
);
3107 InitSpecialAttributes();
3108 CommandFlags
= GetDefaultCommandFlags();
3110 if (GetAttribute(INTELLIGENCE
, false) < 8) CommandFlags
&= ~DONT_CONSUME_ANYTHING_VALUABLE
; // gum
3111 if (!GetDefaultName().IsEmpty()) SetAssignedName(GetDefaultName());
3114 if (!(SpecialFlags
& LOAD
)) PostConstruct();
3116 if (!(SpecialFlags
& LOAD
)) {
3117 if (!(SpecialFlags
& NO_EQUIPMENT
)) CreateInitialEquipment((SpecialFlags
& NO_EQUIPMENT_PIC_UPDATE
) >> 1);
3118 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdatePictures();
3124 Flags
&= ~(C_INITIALIZING
|C_IN_NO_MSG_MODE
);
3128 truth
character::TeleportNear (character
*Caller
) {
3129 v2 Where
= GetLevel()->GetNearestFreeSquare(this, Caller
->GetPos());
3130 if (Where
== ERROR_V2
) return false;
3136 void character::ReceiveHeal (long Amount
) {
3138 for (c
= 0; c
< Amount
/ 10; ++c
) if (!HealHitPoint()) break;
3140 if (RAND()%10 < Amount
) HealHitPoint();
3141 if (Amount
>= 250 || RAND()%250 < Amount
) {
3142 bodypart
*NewBodyPart
= GenerateRandomBodyPart();
3143 if (!NewBodyPart
) return;
3144 NewBodyPart
->SetHP(1);
3145 if (IsPlayer()) ADD_MESSAGE("You grow a new %s.", NewBodyPart
->GetBodyPartName().CStr());
3146 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), NewBodyPart
->GetBodyPartName().CStr());
3151 void character::AddHealingLiquidConsumeEndMessage () const {
3152 if (IsPlayer()) ADD_MESSAGE("You feel better.");
3153 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks healthier.", CHAR_NAME(DEFINITE
));
3157 void character::ReceiveSchoolFood (long SizeOfEffect
) {
3158 SizeOfEffect
+= RAND()%SizeOfEffect
;
3159 if (SizeOfEffect
>= 250) VomitAtRandomDirection(SizeOfEffect
);
3160 if (!(RAND() % 3) && SizeOfEffect
>= 500 && EditAttribute(ENDURANCE
, SizeOfEffect
/500)) {
3161 if (IsPlayer()) ADD_MESSAGE("You gain a little bit of toughness for surviving this stuff.");
3162 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks tougher.", CHAR_NAME(DEFINITE
));
3164 BeginTemporaryState(POISONED
, (SizeOfEffect
>>1));
3168 void character::AddSchoolFoodConsumeEndMessage () const {
3169 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff tasted like vomit and old mousepads.");
3173 void character::AddSchoolFoodHitMessage () const {
3174 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff feels like vomit and old mousepads.");
3178 void character::ReceiveNutrition (long SizeOfEffect
) {
3179 EditNP(SizeOfEffect
);
3183 void character::ReceiveOmmelUrine (long Amount
) {
3184 EditExperience(ARM_STRENGTH
, 500, Amount
<<4);
3185 EditExperience(LEG_STRENGTH
, 500, Amount
<<4);
3186 if (IsPlayer()) game::DoEvilDeed(Amount
/25);
3190 void character::ReceiveOmmelCerumen (long Amount
) {
3191 EditExperience(INTELLIGENCE
, 500, Amount
<< 5);
3192 EditExperience(WISDOM
, 500, Amount
<< 5);
3193 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3197 void character::ReceiveOmmelSweat (long Amount
) {
3198 EditExperience(AGILITY
, 500, Amount
<< 4);
3199 EditExperience(DEXTERITY
, 500, Amount
<< 4);
3201 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3205 void character::ReceiveOmmelTears (long Amount
) {
3206 EditExperience(PERCEPTION
, 500, Amount
<< 4);
3207 EditExperience(CHARISMA
, 500, Amount
<< 4);
3208 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3212 void character::ReceiveOmmelSnot (long Amount
) {
3213 EditExperience(ENDURANCE
, 500, Amount
<< 5);
3215 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3219 void character::ReceiveOmmelBone (long Amount
) {
3220 EditExperience(ARM_STRENGTH
, 500, Amount
<< 6);
3221 EditExperience(LEG_STRENGTH
, 500, Amount
<< 6);
3222 EditExperience(DEXTERITY
, 500, Amount
<< 6);
3223 EditExperience(AGILITY
, 500, Amount
<< 6);
3224 EditExperience(ENDURANCE
, 500, Amount
<< 6);
3225 EditExperience(PERCEPTION
, 500, Amount
<< 6);
3226 EditExperience(INTELLIGENCE
, 500, Amount
<< 6);
3227 EditExperience(WISDOM
, 500, Amount
<< 6);
3228 EditExperience(CHARISMA
, 500, Amount
<< 6);
3231 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3235 void character::AddOmmelConsumeEndMessage () const {
3236 if (IsPlayer()) ADD_MESSAGE("You feel a primitive force coursing through your veins.");
3237 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks more powerful.", CHAR_NAME(DEFINITE
));
3241 void character::ReceivePepsi (long Amount
) {
3242 ReceiveDamage(0, Amount
/ 100, POISON
, TORSO
);
3243 EditExperience(PERCEPTION
, Amount
, 1 << 14);
3244 if (CheckDeath(CONST_S("was poisoned by pepsi"), 0)) return;
3245 if (IsPlayer()) game::DoEvilDeed(Amount
/ 10);
3249 void character::AddPepsiConsumeEndMessage () const {
3250 if (IsPlayer()) ADD_MESSAGE("Urgh. You feel your guruism fading away.");
3251 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very lame.", CHAR_NAME(DEFINITE
));
3255 void character::ReceiveDarkness (long Amount
) {
3256 EditExperience(INTELLIGENCE
, -Amount
/ 5, 1 << 13);
3257 EditExperience(WISDOM
, -Amount
/ 5, 1 << 13);
3258 EditExperience(CHARISMA
, -Amount
/ 5, 1 << 13);
3259 if (IsPlayer()) game::DoEvilDeed(int(Amount
/ 50));
3263 void character::AddFrogFleshConsumeEndMessage () const {
3264 if (IsPlayer()) ADD_MESSAGE("Arg. You feel the fate of a navastater placed upon you...");
3265 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks like a navastater.", CHAR_NAME(DEFINITE
));
3269 void character::ReceiveKoboldFlesh (long) {
3270 /* As it is commonly known, the possibility of fainting per 500 cubic
3271 centimeters of kobold flesh is exactly 5%. */
3272 if (!(RAND() % 20)) {
3273 if (IsPlayer()) ADD_MESSAGE("You lose control of your legs and fall down.");
3274 LoseConsciousness(250 + RAND_N(250));
3279 void character::AddKoboldFleshConsumeEndMessage () const {
3280 if (IsPlayer()) ADD_MESSAGE("This stuff tasted really funny.");
3284 void character::AddKoboldFleshHitMessage () const {
3285 if (IsPlayer()) ADD_MESSAGE("You feel very funny.");
3289 void character::AddBoneConsumeEndMessage () const {
3290 if (IsPlayer()) ADD_MESSAGE("You feel like a hippie.");
3291 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s barks happily.", CHAR_NAME(DEFINITE
)); // this suspects that nobody except dogs can eat bones
3294 truth
character::RawEditAttribute (double &Experience
, int Amount
) const {
3295 /* Check if the attribute is disabled for creature */
3296 if (!Experience
) return false;
3297 if ((Amount
< 0 && Experience
< 2 * EXP_MULTIPLIER
) || (Amount
> 0 && Experience
> 999 * EXP_MULTIPLIER
)) return false;
3298 Experience
+= Amount
* EXP_MULTIPLIER
;
3299 LimitRef
<double>(Experience
, MIN_EXP
, MAX_EXP
);
3304 void character::DrawPanel (truth AnimationDraw
) const {
3305 if (AnimationDraw
) { DrawStats(true); return; }
3306 igraph::BlitBackGround(v2(19 + (game::GetScreenXSize() << 4), 0), v2(RES
.X
- 19 - (game::GetScreenXSize() << 4), RES
.Y
));
3307 igraph::BlitBackGround(v2(16, 45 + (game::GetScreenYSize() << 4)), v2(game::GetScreenXSize() << 4, 9));
3308 FONT
->Printf(DOUBLE_BUFFER
, v2(16, 45 + (game::GetScreenYSize() << 4)), WHITE
, "%s", GetPanelName().CStr());
3309 game::UpdateAttributeMemory();
3310 int PanelPosX
= RES
.X
- 96;
3311 int PanelPosY
= DrawStats(false);
3312 PrintAttribute("End", ENDURANCE
, PanelPosX
, PanelPosY
++);
3313 PrintAttribute("Per", PERCEPTION
, PanelPosX
, PanelPosY
++);
3314 PrintAttribute("Int", INTELLIGENCE
, PanelPosX
, PanelPosY
++);
3315 PrintAttribute("Wis", WISDOM
, PanelPosX
, PanelPosY
++);
3316 PrintAttribute("Wil", WILL_POWER
, PanelPosX
, PanelPosY
++);
3317 PrintAttribute("Cha", CHARISMA
, PanelPosX
, PanelPosY
++);
3318 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Siz %d", GetSize());
3319 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), IsInBadCondition() ? RED
: WHITE
, "HP %d/%d", GetHP(), GetMaxHP());
3321 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Gold: %ld", GetMoney());
3324 if (game::IsInWilderness())
3325 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Worldmap");
3327 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", game::GetCurrentDungeon()->GetShortLevelDescription(game::GetCurrentLevelIndex()).CapitalizeCopy().CStr());
3330 game::GetTime(Time
);
3331 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Day %d", Time
.Day
);
3332 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Time %d:%s%d", Time
.Hour
, Time
.Min
< 10 ? "0" : "", Time
.Min
);
3333 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Turn %ld", game::GetTurn());
3338 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", festring(GetAction()->GetDescription()).CapitalizeCopy().CStr());
3340 for (int c
= 0; c
< STATES
; ++c
)
3341 if (!(StateData
[c
].Flags
& SECRET
) && StateIsActivated(1 << c
) && (1 << c
!= HASTE
|| !StateIsActivated(SLOW
)) && (1 << c
!= SLOW
|| !StateIsActivated(HASTE
)))
3342 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), (1 << c
) & EquipmentState
|| TemporaryStateCounter
[c
] == PERMANENT
? BLUE
: WHITE
, "%s", StateData
[c
].Description
);
3344 /* Make this more elegant!!! */
3345 if (GetHungerState() == STARVING
)
3346 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Starving");
3347 else if (GetHungerState() == VERY_HUNGRY
)
3348 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Very hungry");
3349 else if (GetHungerState() == HUNGRY
)
3350 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Hungry");
3351 else if (GetHungerState() == SATIATED
)
3352 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Satiated");
3353 else if (GetHungerState() == BLOATED
)
3354 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Bloated");
3355 else if (GetHungerState() == OVER_FED
)
3356 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Overfed!");
3358 switch (GetBurdenState()) {
3360 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Overload!");
3363 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Stressed");
3366 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Burdened");
3370 switch (GetTirednessState()) {
3372 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Fainting");
3375 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Exhausted");
3379 if (game::PlayerIsRunning()) {
3380 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, GetRunDescriptionLine(0));
3381 cchar
*SecondLine
= GetRunDescriptionLine(1);
3382 if (strlen(SecondLine
)) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, SecondLine
);
3387 void character::CalculateDodgeValue () {
3388 DodgeValue
= 0.05 * GetMoveEase() * GetAttribute(AGILITY
) / sqrt(GetSize());
3389 if (IsFlying()) DodgeValue
*= 2;
3390 if (DodgeValue
< 1) DodgeValue
= 1;
3394 truth
character::DamageTypeAffectsInventory (int Type
) {
3395 switch (Type
&0xFFF) {
3402 case PHYSICAL_DAMAGE
:
3405 case MUSTARD_GAS_DAMAGE
:
3409 ABORT("Unknown reaping effect destroyed dungeon!");
3414 int character::CheckForBlockWithArm (character
*Enemy
, item
*Weapon
, arm
*Arm
,
3415 double WeaponToHitValue
, int Damage
, int Success
, int Type
)
3417 int BlockStrength
= Arm
->GetBlockCapability();
3418 double BlockValue
= Arm
->GetBlockValue();
3419 if (BlockStrength
&& BlockValue
) {
3420 item
*Blocker
= Arm
->GetWielded();
3421 if (RAND() % int(100+WeaponToHitValue
/BlockValue
/(1<<BlocksSinceLastTurn
)*(100+Success
)) < 100) {
3422 int NewDamage
= BlockStrength
< Damage
? Damage
-BlockStrength
: 0;
3424 case UNARMED_ATTACK
: AddBlockMessage(Enemy
, Blocker
, Enemy
->UnarmedHitNoun(), NewDamage
); break;
3425 case WEAPON_ATTACK
: AddBlockMessage(Enemy
, Blocker
, "attack", NewDamage
); break;
3426 case KICK_ATTACK
: AddBlockMessage(Enemy
, Blocker
, Enemy
->KickNoun(), NewDamage
); break;
3427 case BITE_ATTACK
: AddBlockMessage(Enemy
, Blocker
, Enemy
->BiteNoun(), NewDamage
); break;
3429 long Weight
= Blocker
->GetWeight();
3430 long StrExp
= Limit(15 * Weight
/ 200L, 75L, 300L);
3431 long DexExp
= Weight
? Limit(75000L / Weight
, 75L, 300L) : 300;
3432 Arm
->EditExperience(ARM_STRENGTH
, StrExp
, 1 << 8);
3433 Arm
->EditExperience(DEXTERITY
, DexExp
, 1 << 8);
3434 EditStamina(-10000 / GetAttribute(ARM_STRENGTH
), false);
3435 if (Arm
->TwoHandWieldIsActive()) {
3436 arm
*PairArm
= Arm
->GetPairArm();
3437 PairArm
->EditExperience(ARM_STRENGTH
, StrExp
, 1 << 8);
3438 PairArm
->EditExperience(DEXTERITY
, DexExp
, 1 << 8);
3440 Blocker
->WeaponSkillHit(Enemy
->CalculateWeaponSkillHits(this));
3441 Blocker
->ReceiveDamage(this, Damage
, PHYSICAL_DAMAGE
);
3442 Blocker
->BlockEffect(this, Enemy
, Weapon
, Type
);
3443 if (Weapon
) Weapon
->ReceiveDamage(Enemy
, Damage
- NewDamage
, PHYSICAL_DAMAGE
);
3444 if (BlocksSinceLastTurn
< 16) ++BlocksSinceLastTurn
;
3452 long character::GetStateAPGain (long BaseAPGain
) const {
3453 if (!StateIsActivated(HASTE
) == !StateIsActivated(SLOW
)) return BaseAPGain
;
3454 if (StateIsActivated(HASTE
)) return (BaseAPGain
* 5) >> 2;
3455 return (BaseAPGain
<< 2) / 5;
3459 void character::SignalEquipmentAdd (int EquipmentIndex
) {
3460 item
*Equipment
= GetEquipment(EquipmentIndex
);
3461 if (Equipment
->IsInCorrectSlot(EquipmentIndex
)) {
3462 long AddedStates
= Equipment
->GetGearStates();
3464 for (int c
= 0; c
< STATES
; ++c
) {
3465 if (AddedStates
& (1 << c
)) {
3466 if (!StateIsActivated(1 << c
)) {
3467 if (!IsInNoMsgMode()) (this->*StateData
[c
].PrintBeginMessage
)();
3468 EquipmentState
|= 1 << c
;
3469 if (StateData
[c
].BeginHandler
) (this->*StateData
[c
].BeginHandler
)();
3471 EquipmentState
|= 1 << c
;
3477 if (!IsInitializing() && Equipment
->IsInCorrectSlot(EquipmentIndex
)) ApplyEquipmentAttributeBonuses(Equipment
);
3481 void character::SignalEquipmentRemoval (int, citem
*Item
) {
3482 CalculateEquipmentState();
3483 if (CalculateAttributeBonuses()) CheckDeath(festring("lost ")+GetPossessivePronoun(false)+" vital "+Item
->GetName(INDEFINITE
));
3487 void character::CalculateEquipmentState () {
3488 long Back
= EquipmentState
;
3490 for (int c
= 0; c
< GetEquipments(); ++c
) {
3491 item
*Equipment
= GetEquipment(c
);
3492 if (Equipment
&& Equipment
->IsInCorrectSlot(c
)) EquipmentState
|= Equipment
->GetGearStates();
3494 for (int c
= 0; c
< STATES
; ++c
) {
3495 if (Back
& (1 << c
) && !StateIsActivated(1 << c
)) {
3496 if (StateData
[c
].EndHandler
) {
3497 (this->*StateData
[c
].EndHandler
)();
3498 if (!IsEnabled()) return;
3500 if (!IsInNoMsgMode()) (this->*StateData
[c
].PrintEndMessage
)();
3506 /* Counter = duration in ticks */
3507 void character::BeginTemporaryState (long State
, int Counter
) {
3508 if (!Counter
) return;
3510 if (State
== POLYMORPHED
) ABORT("No Polymorphing with BeginTemporaryState!");
3511 for (Index
= 0; Index
< STATES
; ++Index
) if (1 << Index
== State
) break;
3512 if (Index
== STATES
) ABORT("BeginTemporaryState works only when State == 2Â ^ n!");
3513 if (TemporaryStateIsActivated(State
)) {
3514 int OldCounter
= GetTemporaryStateCounter(State
);
3515 if (OldCounter
!= PERMANENT
) EditTemporaryStateCounter(State
, Max(Counter
, 50-OldCounter
));
3516 } else if (StateData
[Index
].IsAllowed
== 0 || (this->*StateData
[Index
].IsAllowed
)()) {
3517 SetTemporaryStateCounter(State
, Max(Counter
, 50));
3518 if (!EquipmentStateIsActivated(State
)) {
3519 if (!IsInNoMsgMode()) (this->*StateData
[Index
].PrintBeginMessage
)();
3520 ActivateTemporaryState(State
);
3521 if (StateData
[Index
].BeginHandler
) (this->*StateData
[Index
].BeginHandler
)();
3523 ActivateTemporaryState(State
);
3529 void character::HandleStates () {
3530 if (!TemporaryState
&& !EquipmentState
) return;
3531 for (int c
= 0; c
< STATES
; ++c
) {
3532 if (TemporaryState
& (1 << c
) && TemporaryStateCounter
[c
] != PERMANENT
) {
3533 if (!--TemporaryStateCounter
[c
]) {
3534 TemporaryState
&= ~(1 << c
);
3535 if (!(EquipmentState
& (1 << c
))) {
3536 if (StateData
[c
].EndHandler
) {
3537 (this->*StateData
[c
].EndHandler
)();
3538 if (!IsEnabled()) return;
3540 if (!TemporaryStateCounter
[c
]) (this->*StateData
[c
].PrintEndMessage
)();
3544 if (StateIsActivated(1 << c
)) {
3545 if (StateData
[c
].Handler
) (this->*StateData
[c
].Handler
)();
3547 if (!IsEnabled()) return;
3552 void character::PrintBeginPolymorphControlMessage () const {
3553 if (IsPlayer()) ADD_MESSAGE("You feel your mind has total control over your body.");
3557 void character::PrintEndPolymorphControlMessage () const {
3558 if (IsPlayer()) ADD_MESSAGE("You are somehow uncertain of your willpower.");
3562 void character::PrintBeginLifeSaveMessage () const {
3563 if (IsPlayer()) ADD_MESSAGE("You hear Hell's gates being locked just now.");
3567 void character::PrintEndLifeSaveMessage () const {
3568 if (IsPlayer()) ADD_MESSAGE("You feel the Afterlife is welcoming you once again.");
3572 void character::PrintBeginLycanthropyMessage () const {
3573 if (IsPlayer()) ADD_MESSAGE("You suddenly notice you've always loved full moons.");
3577 void character::PrintEndLycanthropyMessage () const {
3578 if (IsPlayer()) ADD_MESSAGE("You feel the wolf inside you has had enough of your bad habits.");
3582 void character::PrintBeginInvisibilityMessage () const {
3583 if ((PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm()) || (PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5)) {
3584 if (IsPlayer()) ADD_MESSAGE("You seem somehow transparent.");
3585 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems somehow transparent.", CHAR_NAME(DEFINITE
));
3587 if (IsPlayer()) ADD_MESSAGE("You fade away.");
3588 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears!", CHAR_NAME(DEFINITE
));
3593 void character::PrintEndInvisibilityMessage () const {
3594 if ((PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm()) || (PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5)) {
3595 if (IsPlayer()) ADD_MESSAGE("Your notice your transparency has ended.");
3596 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The appearance of %s seems far more solid now.", CHAR_NAME(INDEFINITE
));
3598 if (IsPlayer()) ADD_MESSAGE("You reappear.");
3599 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s appears from nowhere!", CHAR_NAME(INDEFINITE
));
3604 void character::PrintBeginInfraVisionMessage () const {
3606 if (StateIsActivated(INVISIBLE
) && IsWarm() && !(StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5))
3607 ADD_MESSAGE("You reappear.");
3609 ADD_MESSAGE("You feel your perception being magically altered.");
3614 void character::PrintEndInfraVisionMessage () const {
3616 if (StateIsActivated(INVISIBLE
) && IsWarm() && !(StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5))
3617 ADD_MESSAGE("You disappear.");
3619 ADD_MESSAGE("You feel your perception returning to normal.");
3624 void character::PrintBeginESPMessage () const {
3625 if (IsPlayer()) ADD_MESSAGE("You suddenly feel like being only a tiny part of a great network of intelligent minds.");
3629 void character::PrintEndESPMessage () const {
3630 if (IsPlayer()) ADD_MESSAGE("You are filled with desire to be just yourself from now on.");
3634 void character::PrintBeginHasteMessage () const {
3635 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
3636 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE
));
3640 void character::PrintEndHasteMessage () const {
3641 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
3642 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE
));
3646 void character::PrintBeginSlowMessage () const {
3647 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
3648 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE
));
3652 void character::PrintEndSlowMessage () const {
3653 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
3654 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE
));
3658 void character::EndPolymorph () {
3659 ForceEndPolymorph();
3663 character
*character::ForceEndPolymorph () {
3665 ADD_MESSAGE("You return to your true form.");
3666 } else if (game::IsInWilderness()) {
3667 ActivateTemporaryState(POLYMORPHED
);
3668 SetTemporaryStateCounter(POLYMORPHED
, 10);
3669 return this; // fast gum solution, state ends when the player enters a dungeon
3671 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s returns to %s true form.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
3673 if (GetAction()) GetAction()->Terminate(false);
3677 character
*Char
= GetPolymorphBackup();
3678 Flags
|= C_IN_NO_MSG_MODE
;
3679 Char
->Flags
|= C_IN_NO_MSG_MODE
;
3680 Char
->ChangeTeam(GetTeam());
3681 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(Char
);
3682 SetPolymorphBackup(0);
3683 Char
->PutToOrNear(Pos
);
3685 Char
->Flags
&= ~C_POLYMORPHED
;
3686 GetStack()->MoveItemsTo(Char
->GetStack());
3687 DonateEquipmentTo(Char
);
3688 Char
->SetMoney(GetMoney());
3689 Flags
&= ~C_IN_NO_MSG_MODE
;
3690 Char
->Flags
&= ~C_IN_NO_MSG_MODE
;
3691 Char
->CalculateAll();
3692 Char
->SetAssignedName(GetAssignedName());
3695 game::SetPlayer(Char
);
3696 game::SendLOSUpdateRequest();
3699 Char
->TestWalkability();
3704 void character::LycanthropyHandler () {
3705 if (!(RAND() % 2000)) {
3706 if (StateIsActivated(POLYMORPH_CONTROL
) && !game::TruthQuestion(CONST_S("Do you wish to change into a werewolf? [y/N]"))) return;
3707 Polymorph(werewolfwolf::Spawn(), 1000 + RAND() % 2000);
3712 void character::SaveLife () {
3713 if (TemporaryStateIsActivated(LIFE_SAVED
)) {
3715 ADD_MESSAGE("But wait! You glow briefly red and seem to be in a better shape!");
3716 else if (CanBeSeenByPlayer())
3717 ADD_MESSAGE("But wait, suddenly %s glows briefly red and seems to be in a better shape!", GetPersonalPronoun().CStr());
3718 DeActivateTemporaryState(LIFE_SAVED
);
3720 item
*LifeSaver
= 0;
3721 for (int c
= 0; c
< GetEquipments(); ++c
) {
3722 item
*Equipment
= GetEquipment(c
);
3723 if (Equipment
&& Equipment
->IsInCorrectSlot(c
) && Equipment
->GetGearStates() & LIFE_SAVED
) LifeSaver
= Equipment
;
3725 if (!LifeSaver
) ABORT("The Universe can only kill you once!");
3727 ADD_MESSAGE("But wait! Your %s glows briefly red and disappears and you seem to be in a better shape!", LifeSaver
->CHAR_NAME(UNARTICLED
));
3728 else if (CanBeSeenByPlayer())
3729 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());
3730 LifeSaver
->RemoveFromSlot();
3731 LifeSaver
->SendToHell();
3734 if (IsPlayer()) game::AskForEscPress(CONST_S("Life saved!"));
3742 if (GetNP() < SATIATED_LEVEL
) SetNP(SATIATED_LEVEL
);
3744 SendNewDrawRequest();
3746 if (GetAction()) GetAction()->Terminate(false);
3750 character
*character::PolymorphRandomly (int MinDanger
, int MaxDanger
, int Time
) {
3751 character
*NewForm
= 0;
3752 if (StateIsActivated(POLYMORPH_CONTROL
)) {
3754 if (!GetNewFormForPolymorphWithControl(NewForm
)) return NewForm
;
3757 NewForm
= protosystem::CreateMonster(MinDanger
* 10, MaxDanger
* 10, NO_EQUIPMENT
);
3760 NewForm
= protosystem::CreateMonster(MinDanger
, MaxDanger
, NO_EQUIPMENT
);
3762 Polymorph(NewForm
, Time
);
3767 /* In reality, the reading takes Time / (Intelligence * 10) turns */
3768 void character::StartReading (item
*Item
, long Time
) {
3769 study
*Read
= study::Spawn(this);
3770 Read
->SetLiteratureID(Item
->GetID());
3771 if (game::WizardModeIsActive()) Time
= 1;
3772 Read
->SetCounter(Time
);
3774 if (IsPlayer()) ADD_MESSAGE("You start reading %s.", Item
->CHAR_NAME(DEFINITE
));
3775 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s starts reading %s.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(DEFINITE
));
3779 /* Call when one makes something with his/her/its hands.
3780 * Difficulty of 5 takes about one turn, so it's the most common to use. */
3781 void character::DexterityAction (int Difficulty
) {
3782 EditAP(-20000 * Difficulty
/ APBonus(GetAttribute(DEXTERITY
)));
3783 EditExperience(DEXTERITY
, Difficulty
* 15, 1 << 7);
3787 /* If Theoretically != false, range is not a factor. */
3788 truth
character::CanBeSeenByPlayer (truth Theoretically
, truth IgnoreESP
) const {
3789 if (IsEnabled() && !game::IsGenerating() && (Theoretically
|| GetSquareUnder())) {
3790 truth MayBeESPSeen
= PLAYER
->IsEnabled() && !IgnoreESP
&& PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5;
3791 truth MayBeInfraSeen
= PLAYER
->IsEnabled() && PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm();
3792 truth Visible
= !StateIsActivated(INVISIBLE
) || MayBeESPSeen
|| MayBeInfraSeen
;
3793 if (game::IsInWilderness()) return Visible
;
3794 if (MayBeESPSeen
&& (Theoretically
|| GetDistanceSquareFrom(PLAYER
) <= PLAYER
->GetESPRangeSquare())) return true;
3795 if (!Visible
) return false;
3796 return Theoretically
|| SquareUnderCanBeSeenByPlayer(MayBeInfraSeen
);
3802 truth
character::CanBeSeenBy (ccharacter
*Who
, truth Theoretically
, truth IgnoreESP
) const {
3803 if (Who
->IsPlayer()) return CanBeSeenByPlayer(Theoretically
, IgnoreESP
);
3804 if (IsEnabled() && !game::IsGenerating() && (Theoretically
|| GetSquareUnder())) {
3805 truth MayBeESPSeen
= Who
->IsEnabled() && !IgnoreESP
&& Who
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5;
3806 truth MayBeInfraSeen
= Who
->IsEnabled() && Who
->StateIsActivated(INFRA_VISION
) && IsWarm();
3807 truth Visible
= !StateIsActivated(INVISIBLE
) || MayBeESPSeen
|| MayBeInfraSeen
;
3808 if (game::IsInWilderness()) return Visible
;
3809 if (MayBeESPSeen
&& (Theoretically
|| GetDistanceSquareFrom(Who
) <= Who
->GetESPRangeSquare())) return true;
3810 if (!Visible
) return false;
3811 return Theoretically
|| SquareUnderCanBeSeenBy(Who
, MayBeInfraSeen
);
3817 truth
character::SquareUnderCanBeSeenByPlayer (truth IgnoreDarkness
) const {
3818 if (!GetSquareUnder()) return false;
3819 int S1
= SquaresUnder
, S2
= PLAYER
->SquaresUnder
;
3820 if (S1
== 1 && S2
== 1) {
3821 if (GetSquareUnder()->CanBeSeenByPlayer(IgnoreDarkness
)) return true;
3822 if (IgnoreDarkness
) {
3823 int LOSRangeSquare
= PLAYER
->GetLOSRangeSquare();
3824 if ((GetPos() - PLAYER
->GetPos()).GetLengthSquare() <= LOSRangeSquare
) {
3825 eyecontroller::Map
= GetLevel()->GetMap();
3826 return mapmath
<eyecontroller
>::DoLine(PLAYER
->GetPos().X
, PLAYER
->GetPos().Y
, GetPos().X
, GetPos().Y
, SKIP_FIRST
);
3831 for (int c1
= 0; c1
< S1
; ++c1
) {
3832 lsquare
*Square
= GetLSquareUnder(c1
);
3833 if (Square
->CanBeSeenByPlayer(IgnoreDarkness
)) return true;
3834 else if (IgnoreDarkness
) {
3835 v2 Pos
= Square
->GetPos();
3836 int LOSRangeSquare
= PLAYER
->GetLOSRangeSquare();
3837 for (int c2
= 0; c2
< S2
; ++c2
) {
3838 v2 PlayerPos
= PLAYER
->GetPos(c2
);
3839 if ((Pos
-PlayerPos
).GetLengthSquare() <= LOSRangeSquare
) {
3840 eyecontroller::Map
= GetLevel()->GetMap();
3841 if (mapmath
<eyecontroller
>::DoLine(PlayerPos
.X
, PlayerPos
.Y
, Pos
.X
, Pos
.Y
, SKIP_FIRST
)) return true;
3851 truth
character::SquareUnderCanBeSeenBy (ccharacter
*Who
, truth IgnoreDarkness
) const {
3852 int S1
= SquaresUnder
, S2
= Who
->SquaresUnder
;
3853 int LOSRangeSquare
= Who
->GetLOSRangeSquare();
3854 if (S1
== 1 && S2
== 1) return GetSquareUnder()->CanBeSeenFrom(Who
->GetPos(), LOSRangeSquare
, IgnoreDarkness
);
3855 for (int c1
= 0; c1
< S1
; ++c1
) {
3856 lsquare
*Square
= GetLSquareUnder(c1
);
3857 for (int c2
= 0; c2
< S2
; ++c2
) if (Square
->CanBeSeenFrom(Who
->GetPos(c2
), LOSRangeSquare
, IgnoreDarkness
)) return true;
3863 int character::GetDistanceSquareFrom (ccharacter
*Who
) const {
3864 int S1
= SquaresUnder
, S2
= Who
->SquaresUnder
;
3865 if (S1
== 1 && S2
== 1) return (GetPos() - Who
->GetPos()).GetLengthSquare();
3866 v2
MinDist(0x7FFF, 0x7FFF);
3867 int MinLength
= 0xFFFF;
3868 for (int c1
= 0; c1
< S1
; ++c1
) {
3869 for (int c2
= 0; c2
< S2
; ++c2
) {
3870 v2 Dist
= GetPos(c1
)-Who
->GetPos(c2
);
3871 if (Dist
.X
< 0) Dist
.X
= -Dist
.X
;
3872 if (Dist
.Y
< 0) Dist
.Y
= -Dist
.Y
;
3873 if (Dist
.X
<= MinDist
.X
&& Dist
.Y
<= MinDist
.Y
) {
3875 MinLength
= Dist
.GetLengthSquare();
3876 } else if (Dist
.X
< MinDist
.X
|| Dist
.Y
< MinDist
.Y
) {
3877 int Length
= Dist
.GetLengthSquare();
3878 if (Length
< MinLength
) {
3889 void character::AttachBodyPart (bodypart
*BodyPart
) {
3890 SetBodyPart(BodyPart
->GetBodyPartIndex(), BodyPart
);
3891 if (!AllowSpoil()) BodyPart
->ResetSpoiling();
3892 BodyPart
->ResetPosition();
3893 BodyPart
->UpdatePictures();
3894 CalculateAttributeBonuses();
3895 CalculateBattleInfo();
3896 SendNewDrawRequest();
3897 SignalPossibleTransparencyChange();
3901 /* Returns true if the character has all bodyparts, false if not. */
3902 truth
character::HasAllBodyParts () const {
3903 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) return false;
3908 bodypart
*character::GenerateRandomBodyPart () {
3909 int NeededBodyPart
[MAX_BODYPARTS
];
3911 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) NeededBodyPart
[Index
++] = c
;
3912 return Index
? CreateBodyPart(NeededBodyPart
[RAND() % Index
]) : 0;
3916 /* Searches the character's Stack and if it find some bodyparts there that are the character's
3917 * old bodyparts returns a stackiterator to one of them (choosen in random).
3918 * If no fitting bodyparts are found the function returns 0 */
3919 bodypart
*character::FindRandomOwnBodyPart (truth AllowNonLiving
) const {
3920 itemvector LostAndFound
;
3921 for (int c
= 0; c
< BodyParts
; ++c
) {
3922 if (!GetBodyPart(c
)) {
3923 for (std::list
<ulong
>::iterator i
= OriginalBodyPartID
[c
].begin(); i
!= OriginalBodyPartID
[c
].end(); ++i
) {
3924 bodypart
*Found
= static_cast<bodypart
*>(SearchForItem(*i
));
3925 if (Found
&& (AllowNonLiving
|| Found
->CanRegenerate())) LostAndFound
.push_back(Found
);
3929 if (LostAndFound
.empty()) return 0;
3930 return static_cast<bodypart
*>(LostAndFound
[RAND() % LostAndFound
.size()]);
3934 void character::PrintBeginPoisonedMessage () const {
3935 if (IsPlayer()) ADD_MESSAGE("You seem to be very ill.");
3936 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very ill.", CHAR_NAME(DEFINITE
));
3940 void character::PrintEndPoisonedMessage () const {
3941 if (IsPlayer()) ADD_MESSAGE("You feel better again.");
3942 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks better.", CHAR_NAME(DEFINITE
));
3946 void character::PoisonedHandler () {
3947 if (!(RAND() % 100)) VomitAtRandomDirection(500 + RAND_N(250));
3949 for (int Used
= 0; Used
< GetTemporaryStateCounter(POISONED
); Used
+= 100) if (!(RAND() % 100)) ++Damage
;
3951 ReceiveDamage(0, Damage
, POISON
, ALL
, 8, false, false, false, false);
3952 CheckDeath(CONST_S("died of acute poisoning"), 0);
3957 truth
character::IsWarm () const {
3958 return combinebodypartpredicates()(this, &bodypart::IsWarm
, 1);
3962 void character::BeginInvisibility () {
3964 SendNewDrawRequest();
3965 SignalPossibleTransparencyChange();
3969 void character::BeginInfraVision () {
3970 if (IsPlayer()) GetArea()->SendNewDrawRequest();
3974 void character::BeginESP () {
3975 if (IsPlayer()) GetArea()->SendNewDrawRequest();
3979 void character::EndInvisibility () {
3981 SendNewDrawRequest();
3982 SignalPossibleTransparencyChange();
3986 void character::EndInfraVision () {
3987 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
3991 void character::EndESP () {
3992 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
3996 void character::Draw (blitdata
&BlitData
) const {
3997 col24 L
= BlitData
.Luminance
;
3998 if (PLAYER
->IsEnabled() &&
3999 ((PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5 &&
4000 (PLAYER
->GetPos() - GetPos()).GetLengthSquare() <= PLAYER
->GetESPRangeSquare()) ||
4001 (PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm())))
4002 BlitData
.Luminance
= ivanconfig::GetContrastLuminance();
4004 DrawBodyParts(BlitData
);
4005 BlitData
.Luminance
= ivanconfig::GetContrastLuminance();
4006 BlitData
.Src
.Y
= 16;
4007 cint SquareIndex
= BlitData
.CustomData
& SQUARE_INDEX_MASK
;
4009 if (GetTeam() == PLAYER
->GetTeam() && !IsPlayer() && SquareIndex
== GetTameSymbolSquareIndex()) {
4010 BlitData
.Src
.X
= 32;
4011 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4014 if (IsFlying() && SquareIndex
== GetFlySymbolSquareIndex()) {
4015 BlitData
.Src
.X
= 128;
4016 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4019 if (IsSwimming() && SquareIndex
== GetSwimmingSymbolSquareIndex()) {
4020 BlitData
.Src
.X
= 240;
4021 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4024 if (GetAction() && GetAction()->IsUnconsciousness() && SquareIndex
== GetUnconsciousSymbolSquareIndex()) {
4025 BlitData
.Src
.X
= 224;
4026 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4029 BlitData
.Src
.X
= BlitData
.Src
.Y
= 0;
4030 BlitData
.Luminance
= L
;
4034 void character::DrawBodyParts (blitdata
&BlitData
) const {
4035 GetTorso()->Draw(BlitData
);
4039 void character::PrintBeginTeleportMessage () const {
4040 if (IsPlayer()) ADD_MESSAGE("You feel jumpy.");
4044 void character::PrintEndTeleportMessage () const {
4045 if (IsPlayer()) ADD_MESSAGE("You suddenly realize you've always preferred walking to jumping.");
4049 void character::TeleportHandler () {
4050 if (!(RAND() % 1500) && !game::IsInWilderness()) {
4051 if (IsPlayer()) ADD_MESSAGE("You feel an urgent spatial relocation is now appropriate.");
4052 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears.", CHAR_NAME(DEFINITE
));
4058 void character::PrintBeginPolymorphMessage () const {
4059 if (IsPlayer()) ADD_MESSAGE("An unconfortable uncertainty of who you really are overwhelms you.");
4063 void character::PrintEndPolymorphMessage () const {
4064 if (IsPlayer()) ADD_MESSAGE("You feel you are you and no one else.");
4068 void character::PolymorphHandler () {
4069 if (!(RAND() % 1500)) PolymorphRandomly(1, 999999, 200 + RAND() % 800);
4072 void character::PrintBeginTeleportControlMessage () const {
4073 if (IsPlayer()) ADD_MESSAGE("You feel very controlled.");
4077 void character::PrintEndTeleportControlMessage () const {
4078 if (IsPlayer()) ADD_MESSAGE("You feel your control slipping.");
4082 void character::DisplayStethoscopeInfo (character
*) const {
4083 felist
Info(CONST_S("Information about ") + GetDescription(DEFINITE
));
4084 AddSpecialStethoscopeInfo(Info
);
4085 Info
.AddEntry(CONST_S("Endurance: ") + GetAttribute(ENDURANCE
), LIGHT_GRAY
);
4086 Info
.AddEntry(CONST_S("Perception: ") + GetAttribute(PERCEPTION
), LIGHT_GRAY
);
4087 Info
.AddEntry(CONST_S("Intelligence: ") + GetAttribute(INTELLIGENCE
), LIGHT_GRAY
);
4088 Info
.AddEntry(CONST_S("Wisdom: ") + GetAttribute(WISDOM
), LIGHT_GRAY
);
4089 //Info.AddEntry(CONST_S("Willpower: ") + GetAttribute(WILL_POWER), LIGHT_GRAY);
4090 Info
.AddEntry(CONST_S("Charisma: ") + GetAttribute(CHARISMA
), LIGHT_GRAY
);
4091 Info
.AddEntry(CONST_S("HP: ") + GetHP() + "/" + GetMaxHP(), IsInBadCondition() ? RED
: LIGHT_GRAY
);
4092 if (GetAction()) Info
.AddEntry(festring(GetAction()->GetDescription()).CapitalizeCopy(), LIGHT_GRAY
);
4093 for (int c
= 0; c
< STATES
; ++c
) {
4094 if (StateIsActivated(1 << c
) && (1 << c
!= HASTE
|| !StateIsActivated(SLOW
)) && (1 << c
!= SLOW
|| !StateIsActivated(HASTE
)))
4095 Info
.AddEntry(StateData
[c
].Description
, LIGHT_GRAY
);
4097 switch (GetTirednessState()) {
4098 case FAINTING
: Info
.AddEntry("Fainting", RED
); break;
4099 case EXHAUSTED
: Info
.AddEntry("Exhausted", LIGHT_GRAY
); break;
4101 game::SetStandardListAttributes(Info
);
4106 truth
character::CanUseStethoscope (truth PrintReason
) const {
4107 if (PrintReason
) ADD_MESSAGE("This type of monster can't use a stethoscope.");
4112 /* Effect used by at least Sophos.
4113 * NOTICE: Doesn't check for death! */
4114 void character::TeleportSomePartsAway (int NumberToTeleport
) {
4115 for (int c
= 0; c
< NumberToTeleport
; ++c
) {
4116 int RandomBodyPart
= GetRandomNonVitalBodyPart();
4117 if (RandomBodyPart
== NONE_INDEX
) {
4118 for (; c
< NumberToTeleport
; ++c
) {
4119 GetTorso()->SetHP((GetTorso()->GetHP() << 2) / 5);
4120 long TorsosVolume
= GetTorso()->GetMainMaterial()->GetVolume() / 10;
4121 if (!TorsosVolume
) break;
4122 long Amount
= (RAND() % TorsosVolume
)+1;
4123 item
*Lump
= GetTorso()->GetMainMaterial()->CreateNaturalForm(Amount
);
4124 GetTorso()->GetMainMaterial()->EditVolume(-Amount
);
4125 Lump
->MoveTo(GetNearLSquare(GetLevel()->GetRandomSquare())->GetStack());
4126 if (IsPlayer()) ADD_MESSAGE("Parts of you teleport away.");
4127 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Parts of %s teleport away.", CHAR_NAME(DEFINITE
));
4130 item
*SeveredBodyPart
= SevereBodyPart(RandomBodyPart
);
4131 if (SeveredBodyPart
) {
4132 GetNearLSquare(GetLevel()->GetRandomSquare())->AddItem(SeveredBodyPart
);
4133 SeveredBodyPart
->DropEquipment();
4134 if (IsPlayer()) ADD_MESSAGE("Your %s teleports away.", GetBodyPartName(RandomBodyPart
).CStr());
4135 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s teleports away.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4137 if (IsPlayer()) ADD_MESSAGE("Your %s disappears.", GetBodyPartName(RandomBodyPart
).CStr());
4138 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s disappears.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4145 /* Returns an index of a random bodypart that is not vital. If no non-vital bodypart is found returns NONE_INDEX */
4146 int character::GetRandomNonVitalBodyPart () const {
4147 int OKBodyPart
[MAX_BODYPARTS
];
4148 int OKBodyParts
= 0;
4149 for (int c
= 0; c
< BodyParts
; ++c
) if (GetBodyPart(c
) && !BodyPartIsVital(c
)) OKBodyPart
[OKBodyParts
++] = c
;
4150 return OKBodyParts
? OKBodyPart
[RAND() % OKBodyParts
] : NONE_INDEX
;
4154 void character::CalculateVolumeAndWeight () {
4155 Volume
= Stack
->GetVolume();
4156 Weight
= Stack
->GetWeight();
4158 CarriedWeight
= Weight
;
4159 for (int c
= 0; c
< BodyParts
; ++c
) {
4160 bodypart
*BodyPart
= GetBodyPart(c
);
4162 BodyVolume
+= BodyPart
->GetBodyPartVolume();
4163 Volume
+= BodyPart
->GetVolume();
4164 CarriedWeight
+= BodyPart
->GetCarriedWeight();
4165 Weight
+= BodyPart
->GetWeight();
4171 void character::SignalVolumeAndWeightChange () {
4172 if (!IsInitializing()) {
4173 CalculateVolumeAndWeight();
4174 if (IsEnabled()) CalculateBurdenState();
4175 if (MotherEntity
) MotherEntity
->SignalVolumeAndWeightChange();
4180 void character::SignalEmitationIncrease (col24 EmitationUpdate
) {
4181 if (game::CompareLights(EmitationUpdate
, Emitation
) > 0) {
4182 game::CombineLights(Emitation
, EmitationUpdate
);
4183 if (MotherEntity
) MotherEntity
->SignalEmitationIncrease(EmitationUpdate
);
4184 else if (SquareUnder
[0] && !game::IsInWilderness()) {
4185 for(int c
= 0; c
< GetSquaresUnder(); ++c
) GetLSquareUnder()->SignalEmitationIncrease(EmitationUpdate
);
4191 void character::SignalEmitationDecrease (col24 EmitationUpdate
) {
4192 if (game::CompareLights(EmitationUpdate
, Emitation
) >= 0 && Emitation
) {
4193 col24 Backup
= Emitation
;
4194 CalculateEmitation();
4195 if (Backup
!= Emitation
) {
4196 if (MotherEntity
) MotherEntity
->SignalEmitationDecrease(EmitationUpdate
);
4197 else if (SquareUnder
[0] && !game::IsInWilderness()) {
4198 for (int c
= 0; c
< GetSquaresUnder(); ++c
) GetLSquareUnder(c
)->SignalEmitationDecrease(EmitationUpdate
);
4205 void character::CalculateEmitation () {
4206 Emitation
= GetBaseEmitation();
4207 for (int c
= 0; c
< BodyParts
; ++c
) {
4208 bodypart
*BodyPart
= GetBodyPart(c
);
4209 if (BodyPart
) game::CombineLights(Emitation
, BodyPart
->GetEmitation());
4211 game::CombineLights(Emitation
, Stack
->GetEmitation());
4215 void character::CalculateAll () {
4216 Flags
|= C_INITIALIZING
;
4217 CalculateAttributeBonuses();
4218 CalculateVolumeAndWeight();
4219 CalculateEmitation();
4220 CalculateBodyPartMaxHPs(0);
4221 CalculateMaxStamina();
4222 CalculateBurdenState();
4223 CalculateBattleInfo();
4224 Flags
&= ~C_INITIALIZING
;
4228 void character::CalculateHP () {
4229 HP
= sumbodypartproperties()(this, &bodypart::GetHP
);
4233 void character::CalculateMaxHP () {
4234 MaxHP
= sumbodypartproperties()(this, &bodypart::GetMaxHP
);
4238 void character::CalculateBodyPartMaxHPs (ulong Flags
) {
4239 doforbodypartswithparam
<ulong
>()(this, &bodypart::CalculateMaxHP
, Flags
);
4245 truth
character::EditAttribute (int Identifier
, int Value
) {
4246 if (Identifier
== ENDURANCE
&& UseMaterialAttributes()) return false;
4247 if (RawEditAttribute(BaseExperience
[Identifier
], Value
)) {
4248 if (!IsInitializing()) {
4249 if (Identifier
== LEG_STRENGTH
) CalculateBurdenState();
4250 else if (Identifier
== ENDURANCE
) CalculateBodyPartMaxHPs();
4251 else if (IsPlayer() && Identifier
== PERCEPTION
) game::SendLOSUpdateRequest();
4252 else if (IsPlayerKind() && (Identifier
== INTELLIGENCE
|| Identifier
== WISDOM
|| Identifier
== CHARISMA
)) UpdatePictures();
4253 CalculateBattleInfo();
4261 truth
character::ActivateRandomState (int Flags
, int Time
, long Seed
) {
4263 if (Seed
) femath::SetSeed(Seed
);
4264 long ToBeActivated
= GetRandomState(Flags
|DUR_TEMPORARY
);
4266 if (!ToBeActivated
) return false;
4267 BeginTemporaryState(ToBeActivated
, Time
);
4272 truth
character::GainRandomIntrinsic (int Flags
) {
4273 long ToBeActivated
= GetRandomState(Flags
|DUR_PERMANENT
);
4274 if (!ToBeActivated
) return false;
4275 GainIntrinsic(ToBeActivated
);
4280 /* Returns 0 if state not found */
4281 long character::GetRandomState (int Flags
) const {
4282 long OKStates
[STATES
];
4283 int NumberOfOKStates
= 0;
4284 for (int c
= 0; c
< STATES
; ++c
) {
4285 if (StateData
[c
].Flags
& Flags
& DUR_FLAGS
&& StateData
[c
].Flags
& Flags
& SRC_FLAGS
) OKStates
[NumberOfOKStates
++] = 1 << c
;
4287 return NumberOfOKStates
? OKStates
[RAND() % NumberOfOKStates
] : 0;
4291 int characterprototype::CreateSpecialConfigurations (characterdatabase
**TempConfig
, int Configs
, int Level
) {
4292 if (Level
== 0 && TempConfig
[0]->CreateDivineConfigurations
) {
4293 Configs
= databasecreator
<character
>::CreateDivineConfigurations(this, TempConfig
, Configs
);
4295 if (Level
== 1 && TempConfig
[0]->CreateUndeadConfigurations
) {
4296 for (int c
= 1; c
< protocontainer
<character
>::GetSize(); ++c
) {
4297 const character::prototype
*Proto
= protocontainer
<character
>::GetProto(c
);
4298 const character::database
*const *CharacterConfigData
= Proto
->GetConfigData();
4299 if (!CharacterConfigData
) ABORT("No database entry for character <%s>!", Proto
->GetClassID());
4300 const character::database
*const* End
= CharacterConfigData
+ Proto
->GetConfigSize();
4301 for (++CharacterConfigData
; CharacterConfigData
!= End
; ++CharacterConfigData
) {
4302 const character::database
*CharacterDataBase
= *CharacterConfigData
;
4303 if (CharacterDataBase
->UndeadVersions
) {
4304 character::database
* ConfigDataBase
= new character::database(**TempConfig
);
4305 ConfigDataBase
->InitDefaults(this, (c
<< 8) | CharacterDataBase
->Config
);
4306 ConfigDataBase
->PostFix
<< "of ";
4307 if (CharacterDataBase
->Adjective
.GetSize()) {
4308 if (CharacterDataBase
->UsesLongAdjectiveArticle
) ConfigDataBase
->PostFix
<< "an ";
4309 else ConfigDataBase
->PostFix
<< "a ";
4310 ConfigDataBase
->PostFix
<< CharacterDataBase
->Adjective
<< ' ';
4312 if (CharacterDataBase
->UsesLongArticle
) ConfigDataBase
->PostFix
<< "an ";
4313 else ConfigDataBase
->PostFix
<< "a ";
4315 ConfigDataBase
->PostFix
<< CharacterDataBase
->NameSingular
;
4316 if (CharacterDataBase
->PostFix
.GetSize()) ConfigDataBase
->PostFix
<< ' ' << CharacterDataBase
->PostFix
;
4317 int P1
= TempConfig
[0]->UndeadAttributeModifier
;
4318 int P2
= TempConfig
[0]->UndeadVolumeModifier
;
4320 for (c2
= 0; c2
< ATTRIBUTES
; ++c2
) ConfigDataBase
->*ExpPtr
[c2
] = CharacterDataBase
->*ExpPtr
[c2
] * P1
/ 100;
4321 for (c2
= 0; c2
< EQUIPMENT_DATAS
; ++c2
) ConfigDataBase
->*EquipmentDataPtr
[c2
] = contentscript
<item
>();
4322 ConfigDataBase
->DefaultIntelligence
= 5;
4323 ConfigDataBase
->DefaultWisdom
= 5;
4324 ConfigDataBase
->DefaultCharisma
= 5;
4325 ConfigDataBase
->TotalSize
= CharacterDataBase
->TotalSize
;
4326 ConfigDataBase
->Sex
= CharacterDataBase
->Sex
;
4327 ConfigDataBase
->AttributeBonus
= CharacterDataBase
->AttributeBonus
;
4328 ConfigDataBase
->TotalVolume
= CharacterDataBase
->TotalVolume
* P2
/ 100;
4329 if (TempConfig
[0]->UndeadCopyMaterials
) {
4330 ConfigDataBase
->HeadBitmapPos
= CharacterDataBase
->HeadBitmapPos
;
4331 ConfigDataBase
->HairColor
= CharacterDataBase
->HairColor
;
4332 ConfigDataBase
->EyeColor
= CharacterDataBase
->EyeColor
;
4333 ConfigDataBase
->CapColor
= CharacterDataBase
->CapColor
;
4334 ConfigDataBase
->FleshMaterial
= CharacterDataBase
->FleshMaterial
;
4335 ConfigDataBase
->BloodMaterial
= CharacterDataBase
->BloodMaterial
;
4336 ConfigDataBase
->VomitMaterial
= CharacterDataBase
->VomitMaterial
;
4337 ConfigDataBase
->SweatMaterial
= CharacterDataBase
->SweatMaterial
;
4339 ConfigDataBase
->KnownCWeaponSkills
= CharacterDataBase
->KnownCWeaponSkills
;
4340 ConfigDataBase
->CWeaponSkillHits
= CharacterDataBase
->CWeaponSkillHits
;
4341 ConfigDataBase
->PostProcess();
4342 TempConfig
[Configs
++] = ConfigDataBase
;
4347 if (Level
== 0 && TempConfig
[0]->CreateGolemMaterialConfigurations
) {
4348 for (int c
= 1; c
< protocontainer
<material
>::GetSize(); ++c
) {
4349 const material::prototype
* Proto
= protocontainer
<material
>::GetProto(c
);
4350 const material::database
*const* MaterialConfigData
= Proto
->GetConfigData();
4351 const material::database
*const* End
= MaterialConfigData
+ Proto
->GetConfigSize();
4352 for (++MaterialConfigData
; MaterialConfigData
!= End
; ++MaterialConfigData
) {
4353 const material::database
* MaterialDataBase
= *MaterialConfigData
;
4354 if (MaterialDataBase
->CategoryFlags
& IS_GOLEM_MATERIAL
) {
4355 character::database
* ConfigDataBase
= new character::database(**TempConfig
);
4356 ConfigDataBase
->InitDefaults(this, MaterialDataBase
->Config
);
4357 ConfigDataBase
->Adjective
= MaterialDataBase
->NameStem
;
4358 ConfigDataBase
->UsesLongAdjectiveArticle
= MaterialDataBase
->NameFlags
& USE_AN
;
4359 ConfigDataBase
->AttachedGod
= MaterialDataBase
->AttachedGod
;
4360 TempConfig
[Configs
++] = ConfigDataBase
;
4369 double character::GetTimeToDie (ccharacter
*Enemy
, int Damage
, double ToHitValue
, truth AttackIsBlockable
, truth UseMaxHP
) const {
4370 double DodgeValue
= GetDodgeValue();
4371 if (!Enemy
->CanBeSeenBy(this, true)) ToHitValue
*= 2;
4372 if (!CanBeSeenBy(Enemy
, true)) DodgeValue
*= 2;
4373 double MinHits
= 1000;
4375 for (int c
= 0; c
< BodyParts
; ++c
) {
4376 if (BodyPartIsVital(c
) && GetBodyPart(c
)) {
4377 double Hits
= GetBodyPart(c
)->GetTimeToDie(Damage
, ToHitValue
, DodgeValue
, AttackIsBlockable
, UseMaxHP
);
4378 if (First
) { MinHits
= Hits
; First
= false; } else MinHits
= 1 / (1 / MinHits
+ 1 / Hits
);
4385 double character::GetRelativeDanger (ccharacter
*Enemy
, truth UseMaxHP
) const {
4386 double Danger
= Enemy
->GetTimeToKill(this, UseMaxHP
) / GetTimeToKill(Enemy
, UseMaxHP
);
4387 int EnemyAP
= Enemy
->GetMoveAPRequirement(1);
4388 int ThisAP
= GetMoveAPRequirement(1);
4389 if (EnemyAP
> ThisAP
) Danger
*= 1.25;
4390 else if (ThisAP
> EnemyAP
) Danger
*= 0.80;
4391 if (!Enemy
->CanBeSeenBy(this, true)) Danger
*= Enemy
->IsPlayer() ? 0.2 : 0.5;
4392 if (!CanBeSeenBy(Enemy
, true)) Danger
*= IsPlayer() ? 5. : 2.;
4393 if (GetAttribute(INTELLIGENCE
) < 10 && !IsPlayer()) Danger
*= 0.80;
4394 if (Enemy
->GetAttribute(INTELLIGENCE
) < 10 && !Enemy
->IsPlayer()) Danger
*= 1.25;
4395 return Limit(Danger
, 0.001, 1000.0);
4399 festring
character::GetBodyPartName (int I
, truth Articled
) const {
4400 if (I
== TORSO_INDEX
) return Articled
? CONST_S("a torso") : CONST_S("torso");
4401 ABORT("Illegal character bodypart name request!");
4406 item
*character::SearchForItem(ulong ID
) const {
4407 item
*Equipment
= findequipment
<ulong
>()(this, &item::HasID
, ID
);
4408 if (Equipment
) return Equipment
;
4409 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (i
->GetID() == ID
) return *i
;
4414 truth
character::ContentsCanBeSeenBy (ccharacter
*Viewer
) const {
4415 return Viewer
== this;
4419 truth
character::HitEffect (character
*Enemy
, item
* Weapon
, v2 HitPos
, int Type
, int BodyPartIndex
,
4420 int Direction
, truth BlockedByArmour
)
4422 if (Weapon
) return Weapon
->HitEffect(this, Enemy
, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
4424 case UNARMED_ATTACK
: return Enemy
->SpecialUnarmedEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
4425 case KICK_ATTACK
: return Enemy
->SpecialKickEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
4426 case BITE_ATTACK
: return Enemy
->SpecialBiteEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
4432 void character::WeaponSkillHit (item
*Weapon
, int Type
, int Hits
) {
4435 case UNARMED_ATTACK
: Category
= UNARMED
; break;
4436 case WEAPON_ATTACK
: Weapon
->WeaponSkillHit(Hits
); return;
4437 case KICK_ATTACK
: Category
= KICK
; break;
4438 case BITE_ATTACK
: Category
= BITE
; break;
4440 if (!IsHumanoid()) return;
4441 Category
= Weapon
->GetWeaponCategory();
4444 ABORT("Illegal Type %d passed to character::WeaponSkillHit()!", Type
);
4447 if (GetCWeaponSkill(Category
)->AddHit(Hits
)) {
4448 CalculateBattleInfo();
4449 if (IsPlayer()) GetCWeaponSkill(Category
)->AddLevelUpMessage(Category
);
4454 /* Returns 0 if character cannot be duplicated */
4455 character
*character::Duplicate (ulong Flags
) {
4456 if (!(Flags
& IGNORE_PROHIBITIONS
) && !CanBeCloned()) return 0;
4457 character
*Char
= GetProtoType()->Clone(this);
4458 if (Flags
& MIRROR_IMAGE
) {
4459 DuplicateEquipment(Char
, Flags
& ~IGNORE_PROHIBITIONS
);
4460 Char
->SetLifeExpectancy(Flags
>> LE_BASE_SHIFT
& LE_BASE_RANGE
, Flags
>> LE_RAND_SHIFT
& LE_RAND_RANGE
);
4462 Char
->CalculateAll();
4463 Char
->CalculateEmitation();
4464 Char
->UpdatePictures();
4465 Char
->Flags
&= ~(C_INITIALIZING
|C_IN_NO_MSG_MODE
);
4470 truth
character::TryToEquip (item
*Item
) {
4471 if (!Item
->AllowEquip() || !CanUseEquipment() || GetAttribute(WISDOM
) >= Item
->GetWearWisdomLimit() || Item
->GetSquaresUnder() != 1)
4473 for (int e
= 0; e
< GetEquipments(); ++e
) {
4474 if (GetBodyPartOfEquipment(e
) && EquipmentIsAllowed(e
)) {
4475 sorter Sorter
= EquipmentSorter(e
);
4476 if ((Sorter
== 0 || (Item
->*Sorter
)(this)) &&
4477 ((e
!= RIGHT_WIELDED_INDEX
&& e
!= LEFT_WIELDED_INDEX
) ||
4478 Item
->IsWeapon(this) || Item
->IsShield(this)) && AllowEquipment(Item
, e
)) {
4479 item
*OldEquipment
= GetEquipment(e
);
4480 if (BoundToUse(OldEquipment
, e
)) continue;
4481 lsquare
*LSquareUnder
= GetLSquareUnder();
4482 stack
*StackUnder
= LSquareUnder
->GetStack();
4483 msgsystem::DisableMessages();
4484 Flags
|= C_PICTURE_UPDATES_FORBIDDEN
;
4485 LSquareUnder
->Freeze();
4486 StackUnder
->Freeze();
4487 double Danger
= GetRelativeDanger(PLAYER
);
4488 if (OldEquipment
) OldEquipment
->RemoveFromSlot();
4489 Item
->RemoveFromSlot();
4490 SetEquipment(e
, Item
);
4491 double NewDanger
= GetRelativeDanger(PLAYER
);
4492 Item
->RemoveFromSlot();
4493 StackUnder
->AddItem(Item
);
4494 if (OldEquipment
) SetEquipment(e
, OldEquipment
);
4495 msgsystem::EnableMessages();
4496 Flags
&= ~C_PICTURE_UPDATES_FORBIDDEN
;
4497 LSquareUnder
->UnFreeze();
4498 StackUnder
->UnFreeze();
4500 if (NewDanger
> Danger
|| BoundToUse(Item
, e
)) {
4501 room
*Room
= GetRoom();
4502 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
4503 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
));
4504 if (Room
) Room
->DropItem(this, OldEquipment
, 1);
4505 OldEquipment
->MoveTo(StackUnder
);
4506 Item
->RemoveFromSlot();
4507 SetEquipment(e
, Item
);
4513 if (NewDanger
> Danger
|| (NewDanger
== Danger
&& e
!= RIGHT_WIELDED_INDEX
&& e
!= LEFT_WIELDED_INDEX
) || BoundToUse(Item
, e
)) {
4514 room
*Room
= GetRoom();
4515 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
4516 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up and equips %s.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(INDEFINITE
));
4517 Item
->RemoveFromSlot();
4518 SetEquipment(e
, Item
);
4531 truth
character::TryToConsume (item
*Item
) {
4532 return Item
->CanBeEatenByAI(this) && ConsumeItem(Item
, Item
->GetConsumeMaterial(this)->GetConsumeVerb());
4536 void character::UpdateESPLOS () const {
4537 if (StateIsActivated(ESP
) && !game::IsInWilderness()) {
4538 for (int c
= 0; c
< game::GetTeams(); ++c
) {
4539 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
4540 const character
*ch
= *i
;
4541 if (ch
->IsEnabled()) ch
->SendNewDrawRequest();
4548 int character::GetCWeaponSkillLevel (citem
*Item
) const {
4549 if (Item
->GetWeaponCategory() < GetAllowedWeaponSkillCategories()) return GetCWeaponSkill(Item
->GetWeaponCategory())->GetLevel();
4554 void character::PrintBeginPanicMessage () const {
4555 if (IsPlayer()) ADD_MESSAGE("You panic!");
4556 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s panics.", CHAR_NAME(DEFINITE
));
4560 void character::PrintEndPanicMessage () const {
4561 if (IsPlayer()) ADD_MESSAGE("You finally calm down.");
4562 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s calms down.", CHAR_NAME(DEFINITE
));
4566 void character::CheckPanic (int Ticks
) {
4567 if (GetPanicLevel() > 1 && !StateIsActivated(PANIC
) && GetHP() * 100 < RAND() % (GetPanicLevel() * GetMaxHP() << 1))
4568 BeginTemporaryState(PANIC
, ((Ticks
* 3) >> 2) + RAND() % ((Ticks
>> 1) + 1)); // 25% randomness to ticks...
4572 /* returns 0 if fails else the newly created character */
4573 character
*character::DuplicateToNearestSquare (character
*Cloner
, ulong Flags
) {
4574 character
*NewlyCreated
= Duplicate(Flags
);
4575 if (!NewlyCreated
) return 0;
4576 if (Flags
& CHANGE_TEAM
&& Cloner
) NewlyCreated
->ChangeTeam(Cloner
->GetTeam());
4577 NewlyCreated
->PutNear(GetPos());
4578 return NewlyCreated
;
4582 void character::SignalSpoil () {
4583 if (GetMotherEntity()) GetMotherEntity()->SignalSpoil(0);
4584 else Disappear(0, "spoil", &item::IsVeryCloseToSpoiling
);
4588 truth
character::CanHeal () const {
4589 for (int c
= 0; c
< BodyParts
; ++c
) {
4590 bodypart
*BodyPart
= GetBodyPart(c
);
4591 if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < BodyPart
->GetMaxHP()) return true;
4597 int character::GetRelation (ccharacter
*Who
) const {
4598 return GetTeam()->GetRelation(Who
->GetTeam());
4602 truth (item::*AffectTest
[BASE_ATTRIBUTES
])() const = {
4603 &item::AffectsEndurance
,
4604 &item::AffectsPerception
,
4605 &item::AffectsIntelligence
,
4606 &item::AffectsWisdom
,
4607 &item::AffectsWillPower
,
4608 &item::AffectsCharisma
,
4613 /* Returns nonzero if endurance has decreased and death may occur */
4614 truth
character::CalculateAttributeBonuses () {
4615 doforbodyparts()(this, &bodypart::CalculateAttributeBonuses
);
4616 int BackupBonus
[BASE_ATTRIBUTES
];
4617 int BackupCarryingBonus
= CarryingBonus
;
4620 for (c1
= 0; c1
< BASE_ATTRIBUTES
; ++c1
) {
4621 BackupBonus
[c1
] = AttributeBonus
[c1
];
4622 AttributeBonus
[c1
] = 0;
4624 for (c1
= 0; c1
< GetEquipments(); ++c1
) {
4625 item
*Equipment
= GetEquipment(c1
);
4626 if (!Equipment
|| !Equipment
->IsInCorrectSlot(c1
)) continue;
4627 for (int c2
= 0; c2
< BASE_ATTRIBUTES
; ++c2
) {
4628 if ((Equipment
->*AffectTest
[c2
])()) AttributeBonus
[c2
] += Equipment
->GetEnchantment();
4630 if (Equipment
->AffectsCarryingCapacity()) CarryingBonus
+= Equipment
->GetCarryingBonus();
4633 ApplySpecialAttributeBonuses();
4635 if (IsPlayer() && !IsInitializing() && AttributeBonus
[PERCEPTION
] != BackupBonus
[PERCEPTION
]) game::SendLOSUpdateRequest();
4636 if (IsPlayer() && !IsInitializing() && AttributeBonus
[INTELLIGENCE
] != BackupBonus
[INTELLIGENCE
]) UpdateESPLOS();
4638 if (!IsInitializing() && CarryingBonus
!= BackupCarryingBonus
) CalculateBurdenState();
4640 if (!IsInitializing() && AttributeBonus
[ENDURANCE
] != BackupBonus
[ENDURANCE
]) {
4641 CalculateBodyPartMaxHPs();
4642 CalculateMaxStamina();
4643 return AttributeBonus
[ENDURANCE
] < BackupBonus
[ENDURANCE
];
4650 void character::ApplyEquipmentAttributeBonuses (item
*Equipment
) {
4651 if (Equipment
->AffectsEndurance()) {
4652 AttributeBonus
[ENDURANCE
] += Equipment
->GetEnchantment();
4653 CalculateBodyPartMaxHPs();
4654 CalculateMaxStamina();
4656 if (Equipment
->AffectsPerception()) {
4657 AttributeBonus
[PERCEPTION
] += Equipment
->GetEnchantment();
4658 if (IsPlayer()) game::SendLOSUpdateRequest();
4660 if (Equipment
->AffectsIntelligence()) {
4661 AttributeBonus
[INTELLIGENCE
] += Equipment
->GetEnchantment();
4662 if (IsPlayer()) UpdateESPLOS();
4664 if (Equipment
->AffectsWisdom()) AttributeBonus
[WISDOM
] += Equipment
->GetEnchantment();
4665 if (Equipment
->AffectsWillPower()) AttributeBonus
[WILL_POWER
] += Equipment
->GetEnchantment();
4666 if (Equipment
->AffectsCharisma()) AttributeBonus
[CHARISMA
] += Equipment
->GetEnchantment();
4667 if (Equipment
->AffectsMana()) AttributeBonus
[MANA
] += Equipment
->GetEnchantment();
4668 if (Equipment
->AffectsCarryingCapacity()) {
4669 CarryingBonus
+= Equipment
->GetCarryingBonus();
4670 CalculateBurdenState();
4675 void character::ReceiveAntidote (long Amount
) {
4676 if (StateIsActivated(POISONED
)) {
4677 if (GetTemporaryStateCounter(POISONED
) > Amount
) {
4678 EditTemporaryStateCounter(POISONED
, -Amount
);
4681 if (IsPlayer()) ADD_MESSAGE("Aaaah... You feel much better.");
4682 Amount
-= GetTemporaryStateCounter(POISONED
);
4683 DeActivateTemporaryState(POISONED
);
4686 if ((Amount
>= 100 || RAND_N(100) < Amount
) && StateIsActivated(PARASITIZED
)) {
4687 if (IsPlayer()) ADD_MESSAGE("Something in your belly didn't seem to like this stuff.");
4688 DeActivateTemporaryState(PARASITIZED
);
4689 Amount
-= Min(100L, Amount
);
4691 if ((Amount
>= 100 || RAND_N(100) < Amount
) && StateIsActivated(LEPROSY
)) {
4692 if (IsPlayer()) ADD_MESSAGE("You are not falling to pieces anymore.");
4693 DeActivateTemporaryState(LEPROSY
);
4694 Amount
-= Min(100L, Amount
);
4699 void character::AddAntidoteConsumeEndMessage () const {
4700 if (StateIsActivated(POISONED
)) {
4701 // true only if the antidote didn't cure the poison completely
4702 if (IsPlayer()) ADD_MESSAGE("Your body processes the poison in your veins with rapid speed.");
4707 truth
character::IsDead () const {
4708 for (int c
= 0; c
< BodyParts
; ++c
) {
4709 bodypart
*BodyPart
= GetBodyPart(c
);
4710 if (BodyPartIsVital(c
) && (!BodyPart
|| BodyPart
->GetHP() < 1)) return true;
4716 void character::SignalSpoilLevelChange () {
4717 if (GetMotherEntity()) GetMotherEntity()->SignalSpoilLevelChange(0); else UpdatePictures();
4721 void character::AddOriginalBodyPartID (int I
, ulong What
) {
4722 if (std::find(OriginalBodyPartID
[I
].begin(), OriginalBodyPartID
[I
].end(), What
) == OriginalBodyPartID
[I
].end()) {
4723 OriginalBodyPartID
[I
].push_back(What
);
4724 if (OriginalBodyPartID
[I
].size() > 100) OriginalBodyPartID
[I
].erase(OriginalBodyPartID
[I
].begin());
4729 void character::AddToInventory (const fearray
<contentscript
<item
> > &ItemArray
, int SpecialFlags
) {
4730 for (uint c1
= 0; c1
< ItemArray
.Size
; ++c1
) {
4731 if (ItemArray
[c1
].IsValid()) {
4732 const interval
*TimesPtr
= ItemArray
[c1
].GetTimes();
4733 int Times
= TimesPtr
? TimesPtr
->Randomize() : 1;
4734 for (int c2
= 0; c2
< Times
; ++c2
) {
4735 item
*Item
= ItemArray
[c1
].Instantiate(SpecialFlags
);
4737 Stack
->AddItem(Item
);
4738 Item
->SpecialGenerationHandler();
4746 truth
character::HasHadBodyPart (citem
*Item
) const {
4747 for (int c
= 0; c
< BodyParts
; ++c
)
4748 if (std::find(OriginalBodyPartID
[c
].begin(), OriginalBodyPartID
[c
].end(), Item
->GetID()) != OriginalBodyPartID
[c
].end())
4750 return GetPolymorphBackup() && GetPolymorphBackup()->HasHadBodyPart(Item
);
4754 festring
&character::ProcessMessage (festring
&Msg
) const {
4755 SEARCH_N_REPLACE(Msg
, "@nu", GetName(UNARTICLED
));
4756 SEARCH_N_REPLACE(Msg
, "@ni", GetName(INDEFINITE
));
4757 SEARCH_N_REPLACE(Msg
, "@nd", GetName(DEFINITE
));
4758 SEARCH_N_REPLACE(Msg
, "@du", GetDescription(UNARTICLED
));
4759 SEARCH_N_REPLACE(Msg
, "@di", GetDescription(INDEFINITE
));
4760 SEARCH_N_REPLACE(Msg
, "@dd", GetDescription(DEFINITE
));
4761 SEARCH_N_REPLACE(Msg
, "@pp", GetPersonalPronoun());
4762 SEARCH_N_REPLACE(Msg
, "@sp", GetPossessivePronoun());
4763 SEARCH_N_REPLACE(Msg
, "@op", GetObjectPronoun());
4764 SEARCH_N_REPLACE(Msg
, "@Nu", GetName(UNARTICLED
).CapitalizeCopy());
4765 SEARCH_N_REPLACE(Msg
, "@Ni", GetName(INDEFINITE
).CapitalizeCopy());
4766 SEARCH_N_REPLACE(Msg
, "@Nd", GetName(DEFINITE
).CapitalizeCopy());
4767 SEARCH_N_REPLACE(Msg
, "@Du", GetDescription(UNARTICLED
).CapitalizeCopy());
4768 SEARCH_N_REPLACE(Msg
, "@Di", GetDescription(INDEFINITE
).CapitalizeCopy());
4769 SEARCH_N_REPLACE(Msg
, "@Dd", GetDescription(DEFINITE
).CapitalizeCopy());
4770 SEARCH_N_REPLACE(Msg
, "@Pp", GetPersonalPronoun().CapitalizeCopy());
4771 SEARCH_N_REPLACE(Msg
, "@Sp", GetPossessivePronoun().CapitalizeCopy());
4772 SEARCH_N_REPLACE(Msg
, "@Op", GetObjectPronoun().CapitalizeCopy());
4773 SEARCH_N_REPLACE(Msg
, "@Gd", GetMasterGod()->GetName());
4778 void character::ProcessAndAddMessage (festring Msg
) const {
4779 ADD_MESSAGE("%s", ProcessMessage(Msg
).CStr());
4783 void character::BeTalkedTo () {
4785 if (GetRelation(PLAYER
) == HOSTILE
)
4786 ProcessAndAddMessage(GetHostileReplies()[RandomizeReply(Said
, GetHostileReplies().Size
)]);
4788 ProcessAndAddMessage(GetFriendlyReplies()[RandomizeReply(Said
, GetFriendlyReplies().Size
)]);
4792 truth
character::CheckZap () {
4794 ADD_MESSAGE("This monster type can't zap.");
4801 void character::DamageAllItems (character
*Damager
, int Damage
, int Type
) {
4802 GetStack()->ReceiveDamage(Damager
, Damage
, Type
);
4803 for (int c
= 0; c
< GetEquipments(); ++c
) {
4804 item
*Equipment
= GetEquipment(c
);
4805 if (Equipment
) Equipment
->ReceiveDamage(Damager
, Damage
, Type
);
4810 truth
character::Equips (citem
*Item
) const {
4811 return combineequipmentpredicateswithparam
<ulong
>()(this, &item::HasID
, Item
->GetID(), 1);
4815 void character::PrintBeginConfuseMessage () const {
4816 if (IsPlayer()) ADD_MESSAGE("You feel quite happy.");
4820 void character::PrintEndConfuseMessage () const {
4821 if (IsPlayer()) ADD_MESSAGE("The world is boring again.");
4825 v2
character::ApplyStateModification (v2 TryDirection
) const {
4826 if (!StateIsActivated(CONFUSED
) || RAND() & 15 || game::IsInWilderness()) return TryDirection
;
4827 v2 To
= GetLevel()->GetFreeAdjacentSquare(this, GetPos(), true);
4828 if (To
== ERROR_V2
) return TryDirection
;
4830 if (To
!= TryDirection
&& IsPlayer()) ADD_MESSAGE("Whoa! You somehow don't manage to walk straight.");
4835 void character::AddConfuseHitMessage () const {
4836 if (IsPlayer()) ADD_MESSAGE("This stuff is confusing.");
4840 item
*character::SelectFromPossessions (cfestring
&Topic
, sorter Sorter
) {
4841 itemvector ReturnVector
;
4842 SelectFromPossessions(ReturnVector
, Topic
, NO_MULTI_SELECT
, Sorter
);
4843 return !ReturnVector
.empty() ? ReturnVector
[0] : 0;
4847 truth
character::SelectFromPossessions (itemvector
&ReturnVector
, cfestring
&Topic
, int Flags
, sorter Sorter
) {
4849 truth InventoryPossible
= GetStack()->SortedItems(this, Sorter
);
4850 if (InventoryPossible
) List
.AddEntry(CONST_S("choose from inventory"), LIGHT_GRAY
, 20, game::AddToItemDrawVector(itemvector()));
4855 for (c
= 0; c
< BodyParts
; ++c
) {
4856 bodypart
*BodyPart
= GetBodyPart(c
);
4857 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) {
4858 Item
.push_back(BodyPart
);
4860 BodyPart
->AddName(Entry
, UNARTICLED
);
4861 int ImageKey
= game::AddToItemDrawVector(itemvector(1, BodyPart
));
4862 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
4866 for (c
= 0; c
< GetEquipments(); ++c
) {
4867 item
*Equipment
= GetEquipment(c
);
4868 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) {
4869 Item
.push_back(Equipment
);
4870 Entry
= GetEquipmentName(c
);
4873 Equipment
->AddInventoryEntry(this, Entry
, 1, true);
4874 AddSpecialEquipmentInfo(Entry
, c
);
4875 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
4876 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
4881 game::SetStandardListAttributes(List
);
4882 List
.SetFlags(SELECTABLE
|DRAW_BACKGROUND_AFTERWARDS
);
4883 List
.SetEntryDrawer(game::ItemEntryDrawer
);
4884 game::DrawEverythingNoBlit();
4885 int Chosen
= List
.Draw();
4886 game::ClearItemDrawVector();
4887 if (Chosen
!= ESCAPED
) {
4888 if ((InventoryPossible
&& !Chosen
) || Chosen
& FELIST_ERROR_BIT
) {
4889 GetStack()->DrawContents(ReturnVector
, this, Topic
, Flags
, Sorter
);
4891 ReturnVector
.push_back(Item
[InventoryPossible
? Chosen
- 1 : Chosen
]);
4892 if (Flags
& SELECT_PAIR
&& ReturnVector
[0]->HandleInPairs()) {
4893 item
*PairEquipment
= GetPairEquipment(ReturnVector
[0]->GetEquipmentIndex());
4894 if (PairEquipment
&& PairEquipment
->CanBePiledWith(ReturnVector
[0], this)) ReturnVector
.push_back(PairEquipment
);
4899 if (!GetStack()->SortedItems(this, Sorter
)) return false;
4900 game::ClearItemDrawVector();
4901 GetStack()->DrawContents(ReturnVector
, this, Topic
, Flags
, Sorter
);
4907 truth
character::EquipsSomething (sorter Sorter
) {
4908 for (int c
= 0; c
< GetEquipments(); ++c
) {
4909 item
*Equipment
= GetEquipment(c
);
4910 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) return true;
4916 material
*character::CreateBodyPartMaterial (int, long Volume
) const {
4917 return MAKE_MATERIAL(GetFleshMaterial(), Volume
);
4921 truth
character::CheckTalk () {
4923 ADD_MESSAGE("This monster does not know the art of talking.");
4930 truth
character::MoveTowardsHomePos () {
4931 if (HomeDataIsValid() && IsEnabled()) {
4932 SetGoingTo(HomeData
->Pos
);
4933 return MoveTowardsTarget(false) || (!GetPos().IsAdjacent(HomeData
->Pos
) && MoveRandomly());
4939 truth
character::TryToChangeEquipment (stack
*MainStack
, stack
*SecStack
, int Chosen
) {
4940 if (!GetBodyPartOfEquipment(Chosen
)) {
4941 ADD_MESSAGE("Bodypart missing!");
4944 item
*OldEquipment
= GetEquipment(Chosen
);
4945 if (!IsPlayer() && BoundToUse(OldEquipment
, Chosen
)) {
4946 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE
), OldEquipment
->CHAR_NAME(DEFINITE
));
4949 if (OldEquipment
) OldEquipment
->MoveTo(MainStack
);
4950 sorter Sorter
= EquipmentSorter(Chosen
);
4951 if (!MainStack
->SortedItems(this, Sorter
) && (!SecStack
|| !SecStack
->SortedItems(this, Sorter
))) {
4952 ADD_MESSAGE("You haven't got any item that could be used for this purpose.");
4955 game::DrawEverythingNoBlit();
4956 itemvector ItemVector
;
4957 int Return
= MainStack
->DrawContents(ItemVector
, SecStack
, this,
4958 CONST_S("Choose ") + GetEquipmentName(Chosen
) + ':',
4959 SecStack
? CONST_S("Items in your inventory") : CONST_S(""),
4960 SecStack
? festring(CONST_S("Items in ") + GetPossessivePronoun() + " inventory") : CONST_S(""),
4961 SecStack
? festring(GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState()) : CONST_S(""),
4962 GetVerbalBurdenStateColor(),
4963 NONE_AS_CHOICE
|NO_MULTI_SELECT
,
4965 if (Return
== ESCAPED
) {
4967 OldEquipment
->RemoveFromSlot();
4968 SetEquipment(Chosen
, OldEquipment
);
4972 item
*Item
= ItemVector
.empty() ? 0 : ItemVector
[0];
4974 if (!IsPlayer() && !AllowEquipment(Item
, Chosen
)) {
4975 ADD_MESSAGE("%s refuses to equip %s.", CHAR_DESCRIPTION(DEFINITE
), Item
->CHAR_NAME(DEFINITE
));
4978 Item
->RemoveFromSlot();
4979 SetEquipment(Chosen
, Item
);
4980 if (CheckIfEquipmentIsNotUsable(Chosen
)) Item
->MoveTo(MainStack
); // small bug?
4982 return Item
!= OldEquipment
;
4986 void character::PrintBeginParasitizedMessage () const {
4987 if (IsPlayer()) ADD_MESSAGE("You feel you are no longer alone.");
4991 void character::PrintEndParasitizedMessage () const {
4992 if (IsPlayer()) ADD_MESSAGE("A feeling of long welcome emptiness overwhelms you.");
4996 void character::ParasitizedHandler () {
4998 if (!(RAND() % 250)) {
4999 if (IsPlayer()) ADD_MESSAGE("Ugh. You feel something violently carving its way through your intestines.");
5000 ReceiveDamage(0, 1, POISON
, TORSO
, 8, false, false, false, false);
5001 CheckDeath(CONST_S("killed by a vile parasite"), 0);
5006 truth
character::CanFollow () const {
5007 return CanMove() && !StateIsActivated(PANIC
) && !IsStuck();
5011 festring
character::GetKillName () const {
5012 if (!GetPolymorphBackup()) return GetName(INDEFINITE
);
5014 GetPolymorphBackup()->AddName(KillName
, INDEFINITE
);
5015 KillName
<< " polymorphed into ";
5016 id::AddName(KillName
, INDEFINITE
);
5021 festring
character::GetPanelName () const {
5023 Name
<< AssignedName
<< " the " << game::GetVerbalPlayerAlignment() << ' ';
5024 id::AddName(Name
, UNARTICLED
);
5029 long character::GetMoveAPRequirement (int Difficulty
) const {
5030 return (!StateIsActivated(PANIC
) ? 10000000 : 8000000) * Difficulty
/ (APBonus(GetAttribute(AGILITY
)) * GetMoveEase());
5034 bodypart
*character::HealHitPoint() {
5035 int NeedHeal
= 0, NeedHealIndex
[MAX_BODYPARTS
];
5036 for (int c
= 0; c
< BodyParts
; ++c
) {
5037 bodypart
*BodyPart
= GetBodyPart(c
);
5038 if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < BodyPart
->GetMaxHP()) NeedHealIndex
[NeedHeal
++] = c
;
5041 bodypart
*BodyPart
= GetBodyPart(NeedHealIndex
[RAND() % NeedHeal
]);
5042 BodyPart
->IncreaseHP();
5050 void character::CreateHomeData () {
5051 HomeData
= new homedata
;
5052 lsquare
*Square
= GetLSquareUnder();
5053 HomeData
->Pos
= Square
->GetPos();
5054 HomeData
->Dungeon
= Square
->GetDungeonIndex();
5055 HomeData
->Level
= Square
->GetLevelIndex();
5056 HomeData
->Room
= Square
->GetRoomIndex();
5060 room
*character::GetHomeRoom() const {
5061 if (HomeDataIsValid() && HomeData
->Room
) return GetLevel()->GetRoom(HomeData
->Room
);
5066 void character::RemoveHomeData () {
5072 void character::AddESPConsumeMessage () const {
5073 if (IsPlayer()) ADD_MESSAGE("You feel a strange mental activity.");
5077 void character::SetBodyPart (int I
, bodypart
*What
) {
5078 BodyPartSlot
[I
].PutInItem(What
);
5080 What
->SignalPossibleUsabilityChange();
5082 AddOriginalBodyPartID(I
, What
->GetID());
5083 if (What
->GetMainMaterial()->IsInfectedByLeprosy()) GainIntrinsic(LEPROSY
);
5084 else if (StateIsActivated(LEPROSY
)) What
->GetMainMaterial()->SetIsInfectedByLeprosy(true);
5089 truth
character::ConsumeItem (item
*Item
, cfestring
&ConsumeVerb
) {
5090 if (IsPlayer() && HasHadBodyPart(Item
) && !game::TruthQuestion(CONST_S("Are you sure? You may be able to put it back... [y/N]")))
5092 if (Item
->IsOnGround() && GetRoom() && !GetRoom()->ConsumeItem(this, Item
, 1))
5094 if (IsPlayer()) ADD_MESSAGE("You begin %s %s.", ConsumeVerb
.CStr(), Item
->CHAR_NAME(DEFINITE
));
5095 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins %s %s.", CHAR_NAME(DEFINITE
), ConsumeVerb
.CStr(), Item
->CHAR_NAME(DEFINITE
));
5096 consume
*Consume
= consume::Spawn(this);
5097 Consume
->SetDescription(ConsumeVerb
);
5098 Consume
->SetConsumingID(Item
->GetID());
5105 truth
character::CheckThrow () const {
5107 ADD_MESSAGE("This monster type cannot throw.");
5114 void character::GetHitByExplosion (const explosion
*Explosion
, int Damage
) {
5115 int DamageDirection
= GetPos() == Explosion
->Pos
? RANDOM_DIR
: game::CalculateRoughDirection(GetPos() - Explosion
->Pos
);
5116 if (!IsPet() && Explosion
->Terrorist
&& Explosion
->Terrorist
->IsPet()) Explosion
->Terrorist
->Hostility(this);
5117 GetTorso()->SpillBlood((8 - Explosion
->Size
+ RAND() % (8 - Explosion
->Size
)) >> 1);
5118 v2 SpillPos
= GetPos() + game::GetMoveVector(DamageDirection
);
5119 if (GetArea()->IsValidPos(SpillPos
)) GetTorso()->SpillBlood((8-Explosion
->Size
+RAND()%(8-Explosion
->Size
))>>1, SpillPos
);
5120 if (IsPlayer()) ADD_MESSAGE("You are hit by the explosion!");
5121 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is hit by the explosion.", CHAR_NAME(DEFINITE
));
5122 truth WasUnconscious
= GetAction() && GetAction()->IsUnconsciousness();
5123 ReceiveDamage(Explosion
->Terrorist
, Damage
>> 1, FIRE
, ALL
, DamageDirection
, true, false, false, false);
5125 ReceiveDamage(Explosion
->Terrorist
, Damage
>> 1, PHYSICAL_DAMAGE
, ALL
, DamageDirection
, true, false, false, false);
5126 CheckDeath(Explosion
->DeathMsg
, Explosion
->Terrorist
, !WasUnconscious
? IGNORE_UNCONSCIOUSNESS
: 0);
5131 void character::SortAllItems (const sortdata
&SortData
) {
5132 GetStack()->SortAllItems(SortData
);
5133 doforequipmentswithparam
<const sortdata
&>()(this, &item::SortAllItems
, SortData
);
5137 void character::PrintBeginSearchingMessage () const {
5138 if (IsPlayer()) ADD_MESSAGE("You feel you can now notice even the very smallest details around you.");
5142 void character::PrintEndSearchingMessage () const {
5143 if (IsPlayer()) ADD_MESSAGE("You feel less perceptive.");
5147 void character::SearchingHandler () {
5148 if (!game::IsInWilderness()) Search(15);
5152 void character::Search (int Perception
) {
5153 for (int d
= 0; d
< GetExtendedNeighbourSquares(); ++d
) {
5154 lsquare
*LSquare
= GetNeighbourLSquare(d
);
5155 if (LSquare
) LSquare
->GetStack()->Search(this, Min(Perception
, 200));
5160 // surprisingly returns 0 if fails
5161 character
*character::GetRandomNeighbour (int RelationFlags
) const {
5162 character
*Chars
[MAX_NEIGHBOUR_SQUARES
];
5164 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
5165 lsquare
*LSquare
= GetNeighbourLSquare(d
);
5167 character
*Char
= LSquare
->GetCharacter();
5168 if (Char
&& (GetRelation(Char
) & RelationFlags
)) Chars
[Index
++] = Char
;
5171 return Index
? Chars
[RAND() % Index
] : 0;
5175 void character::ResetStates () {
5176 for (int c
= 0; c
< STATES
; ++c
) {
5177 if (1 << c
!= POLYMORPHED
&& TemporaryStateIsActivated(1 << c
) && TemporaryStateCounter
[c
] != PERMANENT
) {
5178 TemporaryState
&= ~(1 << c
);
5179 if (StateData
[c
].EndHandler
) {
5180 (this->*StateData
[c
].EndHandler
)();
5181 if (!IsEnabled())return;
5188 void characterdatabase::InitDefaults (const characterprototype
*NewProtoType
, int NewConfig
) {
5190 ProtoType
= NewProtoType
;
5196 void character::PrintBeginGasImmunityMessage () const {
5197 if (IsPlayer()) ADD_MESSAGE("All smells fade away.");
5201 void character::PrintEndGasImmunityMessage () const {
5202 if (IsPlayer()) ADD_MESSAGE("Yuck! The world smells bad again.");
5206 void character::ShowAdventureInfo () const {
5207 static const char *lists
[4][4] = {
5208 { "Show massacre history",
5210 "Show message history",
5213 "Show message history",
5216 { "Show message history",
5220 { "Show massacre history",
5221 "Show message history",
5225 // massacre, inventory, messages
5226 static const int nums
[4][3] = {
5233 if (GetStack()->GetItems()) {
5234 idx
= game::MassacreListsEmpty() ? 1 : 0;
5236 idx
= game::MassacreListsEmpty() ? 2 : 3;
5240 sel
= game::ListSelectorArray(sel
, CONST_S("Do you want to see some funny history?"), lists
[idx
]);
5242 if (sel
== nums
[idx
][0] && !game::MassacreListsEmpty()) game::DisplayMassacreLists();
5243 if (sel
== nums
[idx
][1] && GetStack()->GetItems()) {
5244 GetStack()->DrawContents(this, CONST_S("Your inventory"), NO_SELECT
);
5245 for(stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) i
->DrawContents(this);
5246 doforequipmentswithparam
<ccharacter
*>()(this, &item::DrawContents
, this);
5248 if (sel
== nums
[idx
][2]) msgsystem::DrawMessageHistory();
5253 truth
character::EditAllAttributes (int Amount
) {
5254 if (!Amount
) return true;
5256 truth MayEditMore
= false;
5257 for (c
= 0; c
< BodyParts
; ++c
) {
5258 bodypart
*BodyPart
= GetBodyPart(c
);
5259 if (BodyPart
&& BodyPart
->EditAllAttributes(Amount
)) MayEditMore
= true;
5261 for (c
= 0; c
< BASE_ATTRIBUTES
; ++c
) {
5262 if (BaseExperience
[c
]) {
5263 BaseExperience
[c
] += Amount
* EXP_MULTIPLIER
;
5264 LimitRef(BaseExperience
[c
], MIN_EXP
, MAX_EXP
);
5265 if ((Amount
< 0 && BaseExperience
[c
] != MIN_EXP
) || (Amount
> 0 && BaseExperience
[c
] != MAX_EXP
)) MayEditMore
= true;
5272 game::SendLOSUpdateRequest();
5275 if (IsPlayerKind()) UpdatePictures();
5281 void character::AddAttributeInfo (festring
&Entry
) const {
5283 Entry
<< GetAttribute(ENDURANCE
);
5285 Entry
<< GetAttribute(PERCEPTION
);
5287 Entry
<< GetAttribute(INTELLIGENCE
);
5289 Entry
<< GetAttribute(WISDOM
);
5291 Entry
<< GetAttribute(CHARISMA
);
5293 Entry
<< GetAttribute(MANA
);
5297 void character::AddDefenceInfo (felist
&List
) const {
5299 for (int c
= 0; c
< BodyParts
; ++c
) {
5300 bodypart
*BodyPart
= GetBodyPart(c
);
5302 Entry
= CONST_S(" ");
5303 BodyPart
->AddName(Entry
, UNARTICLED
);
5305 Entry
<< BodyPart
->GetMaxHP();
5307 Entry
<< BodyPart
->GetTotalResistance(PHYSICAL_DAMAGE
);
5308 List
.AddEntry(Entry
, LIGHT_GRAY
);
5314 void character::DetachBodyPart () {
5315 ADD_MESSAGE("You haven't got any extra bodyparts.");
5320 void character::ReceiveHolyBanana (long Amount
) {
5322 EditExperience(ARM_STRENGTH
, Amount
, 1 << 13);
5323 EditExperience(LEG_STRENGTH
, Amount
, 1 << 13);
5324 EditExperience(DEXTERITY
, Amount
, 1 << 13);
5325 EditExperience(AGILITY
, Amount
, 1 << 13);
5326 EditExperience(ENDURANCE
, Amount
, 1 << 13);
5327 EditExperience(PERCEPTION
, Amount
, 1 << 13);
5328 EditExperience(INTELLIGENCE
, Amount
, 1 << 13);
5329 EditExperience(WISDOM
, Amount
, 1 << 13);
5330 EditExperience(CHARISMA
, Amount
, 1 << 13);
5335 void character::AddHolyBananaConsumeEndMessage () const {
5336 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
5337 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE
));
5341 truth
character::PreProcessForBone () {
5342 if (IsPet() && IsEnabled()) {
5343 Die(0, CONST_S(""), FORBID_REINCARNATION
);
5346 if (GetAction()) GetAction()->Terminate(false);
5347 if (TemporaryStateIsActivated(POLYMORPHED
)) {
5348 character
*PolymorphBackup
= GetPolymorphBackup();
5350 PolymorphBackup
->PreProcessForBone();
5353 if (MustBeRemovedFromBone()) return false;
5354 if (IsUnique() && !CanBeGenerated()) game::SignalQuestMonsterFound();
5358 GetStack()->PreProcessForBone();
5359 doforequipments()(this, &item::PreProcessForBone
);
5360 doforbodyparts()(this, &bodypart::PreProcessForBone
);
5361 game::RemoveCharacterID(ID
);
5363 game::AddCharacterID(this, ID
);
5368 truth
character::PostProcessForBone (double &DangerSum
, int& Enemies
) {
5369 if (PostProcessForBone()) {
5370 if (GetRelation(PLAYER
) == HOSTILE
) {
5371 double Danger
= GetRelativeDanger(PLAYER
, true);
5372 if (Danger
> 99.0) game::SetTooGreatDangerFound(true);
5373 else if (!IsUnique() && !IgnoreDanger()) {
5374 DangerSum
+= Danger
;
5384 truth
character::PostProcessForBone () {
5385 ulong NewID
= game::CreateNewCharacterID(this);
5386 game::GetBoneCharacterIDMap().insert(std::make_pair(-ID
, NewID
));
5387 game::RemoveCharacterID(ID
);
5389 if (IsUnique() && CanBeGenerated()) {
5390 if (DataBase
->Flags
& HAS_BEEN_GENERATED
) return false;
5393 GetStack()->PostProcessForBone();
5394 doforequipments()(this, &item::PostProcessForBone
);
5395 doforbodyparts()(this, &bodypart::PostProcessForBone
);
5400 void character::FinalProcessForBone () {
5402 GetStack()->FinalProcessForBone();
5403 doforequipments()(this, &item::FinalProcessForBone
);
5405 for (c
= 0; c
< BodyParts
; ++c
) {
5406 for (std::list
<ulong
>::iterator i
= OriginalBodyPartID
[c
].begin(); i
!= OriginalBodyPartID
[c
].end();) {
5407 boneidmap::iterator BI
= game::GetBoneItemIDMap().find(*i
);
5408 if (BI
== game::GetBoneItemIDMap().end()) {
5409 std::list
<ulong
>::iterator Dirt
= i
++;
5410 OriginalBodyPartID
[c
].erase(Dirt
);
5420 void character::SetSoulID (ulong What
) {
5421 if (GetPolymorphBackup()) GetPolymorphBackup()->SetSoulID(What
);
5425 truth
character::SearchForItem (citem
*Item
) const {
5426 if (combineequipmentpredicateswithparam
<ulong
>()(this, &item::HasID
, Item
->GetID(), 1)) return true;
5427 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (*i
== Item
) return true;
5432 item
*character::SearchForItem (const sweaponskill
*SWeaponSkill
) const {
5433 for (int c
= 0; c
< GetEquipments(); ++c
) {
5434 item
*Equipment
= GetEquipment(c
);
5435 if (Equipment
&& SWeaponSkill
->IsSkillOf(Equipment
)) return Equipment
;
5437 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (SWeaponSkill
->IsSkillOf(*i
)) return *i
;
5442 void character::PutNear (v2 Pos
) {
5443 v2 NewPos
= game::GetCurrentLevel()->GetNearestFreeSquare(this, Pos
, false);
5444 if (NewPos
== ERROR_V2
) {
5445 do { NewPos
= game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos
== Pos
);
5451 void character::PutToOrNear (v2 Pos
) {
5452 if (game::IsInWilderness() || (CanMoveOn(game::GetCurrentLevel()->GetLSquare(Pos
)) && IsFreeForMe(game::GetCurrentLevel()->GetLSquare(Pos
))))
5459 void character::PutTo (v2 Pos
) {
5460 SquareUnder
[0] = game::GetCurrentArea()->GetSquare(Pos
);
5461 SquareUnder
[0]->AddCharacter(this);
5465 void character::Remove () {
5466 SquareUnder
[0]->RemoveCharacter();
5471 void character::SendNewDrawRequest () const {
5472 for (int c
= 0; c
< SquaresUnder
; ++c
) {
5473 square
*Square
= GetSquareUnder(c
);
5474 if (Square
) Square
->SendNewDrawRequest();
5479 truth
character::IsOver (v2 Pos
) const {
5480 for (int c
= 0; c
< SquaresUnder
; ++c
) {
5481 square
*Square
= GetSquareUnder(c
);
5482 if (Square
&& Square
->GetPos() == Pos
) return true;
5488 truth
character::CanTheoreticallyMoveOn (const lsquare
*LSquare
) const { return GetMoveType() & LSquare
->GetTheoreticalWalkability(); }
5489 truth
character::CanMoveOn (const lsquare
*LSquare
) const { return GetMoveType() & LSquare
->GetWalkability(); }
5490 truth
character::CanMoveOn (const square
*Square
) const { return GetMoveType() & Square
->GetSquareWalkability(); }
5491 truth
character::CanMoveOn (const olterrain
*OLTerrain
) const { return GetMoveType() & OLTerrain
->GetWalkability(); }
5492 truth
character::CanMoveOn (const oterrain
*OTerrain
) const { return GetMoveType() & OTerrain
->GetWalkability(); }
5493 truth
character::IsFreeForMe(square
*Square
) const { return !Square
->GetCharacter() || Square
->GetCharacter() == this; }
5494 void character::LoadSquaresUnder () { SquareUnder
[0] = game::GetSquareInLoad(); }
5496 truth
character::AttackAdjacentEnemyAI () {
5497 if (!IsEnabled()) return false;
5498 character
*Char
[MAX_NEIGHBOUR_SQUARES
];
5499 v2 Pos
[MAX_NEIGHBOUR_SQUARES
];
5500 int Dir
[MAX_NEIGHBOUR_SQUARES
];
5502 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
5503 square
*Square
= GetNeighbourSquare(d
);
5505 character
*Enemy
= Square
->GetCharacter();
5506 if (Enemy
&& (GetRelation(Enemy
) == HOSTILE
|| StateIsActivated(CONFUSED
))) {
5508 Pos
[Index
] = Square
->GetPos();
5509 Char
[Index
++] = Enemy
;
5514 int ChosenIndex
= RAND() % Index
;
5515 Hit(Char
[ChosenIndex
], Pos
[ChosenIndex
], Dir
[ChosenIndex
]);
5522 void character::SignalStepFrom (lsquare
**OldSquareUnder
) {
5524 lsquare
*NewSquareUnder
[MAX_SQUARES_UNDER
];
5525 for (c
= 0; c
< GetSquaresUnder(); ++c
) NewSquareUnder
[c
] = GetLSquareUnder(c
);
5526 for (c
= 0; c
< GetSquaresUnder(); ++c
) {
5527 if (IsEnabled() && GetLSquareUnder(c
) == NewSquareUnder
[c
]) NewSquareUnder
[c
]->StepOn(this, OldSquareUnder
);
5532 int character::GetSumOfAttributes () const {
5533 return GetAttribute(ENDURANCE
) + GetAttribute(PERCEPTION
) + GetAttribute(INTELLIGENCE
) + GetAttribute(WISDOM
) + GetAttribute(CHARISMA
) + GetAttribute(ARM_STRENGTH
) + GetAttribute(AGILITY
);
5537 void character::IntelligenceAction (int Difficulty
) {
5538 EditAP(-20000 * Difficulty
/ APBonus(GetAttribute(INTELLIGENCE
)));
5539 EditExperience(INTELLIGENCE
, Difficulty
* 15, 1 << 7);
5543 struct walkabilitycontroller
{
5544 static truth
Handler (int x
, int y
) {
5545 return x
>= 0 && y
>= 0 && x
< LevelXSize
&& y
< LevelYSize
&& Map
[x
][y
]->GetTheoreticalWalkability() & MoveType
;
5547 static lsquare
***Map
;
5548 static int LevelXSize
, LevelYSize
;
5549 static int MoveType
;
5553 lsquare
***walkabilitycontroller::Map
;
5554 int walkabilitycontroller::LevelXSize
, walkabilitycontroller::LevelYSize
;
5555 int walkabilitycontroller::MoveType
;
5558 truth
character::CreateRoute () {
5560 if (GetAttribute(INTELLIGENCE
) >= 10 && !StateIsActivated(CONFUSED
)) {
5562 walkabilitycontroller::Map
= GetLevel()->GetMap();
5563 walkabilitycontroller::LevelXSize
= GetLevel()->GetXSize();
5564 walkabilitycontroller::LevelYSize
= GetLevel()->GetYSize();
5565 walkabilitycontroller::MoveType
= GetMoveType();
5567 for (int c
= 0; c
< game::GetTeams(); ++c
)
5568 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
5569 character
*Char
= *i
;
5570 if (Char
->IsEnabled() && !Char
->Route
.empty() && (Char
->GetMoveType()&GetMoveType()) == Char
->GetMoveType()) {
5571 v2 CharGoingTo
= Char
->Route
[0];
5572 v2 iPos
= Char
->Route
.back();
5573 if ((GoingTo
-CharGoingTo
).GetLengthSquare() <= 100 && (Pos
- iPos
).GetLengthSquare() <= 100 &&
5574 mapmath
<walkabilitycontroller
>::DoLine(CharGoingTo
.X
, CharGoingTo
.Y
, GoingTo
.X
, GoingTo
.Y
, SKIP_FIRST
) &&
5575 mapmath
<walkabilitycontroller
>::DoLine(Pos
.X
, Pos
.Y
, iPos
.X
, iPos
.Y
, SKIP_FIRST
)) {
5576 if (!Illegal
.empty() && Illegal
.find(Char
->Route
.back()) != Illegal
.end()) continue;
5577 Node
= GetLevel()->FindRoute(CharGoingTo
, GoingTo
, Illegal
, GetMoveType());
5578 if (Node
) { while(Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
5579 else { Route
.clear(); continue; }
5580 Route
.insert(Route
.end(), Char
->Route
.begin(), Char
->Route
.end());
5581 Node
= GetLevel()->FindRoute(Pos
, iPos
, Illegal
, GetMoveType());
5582 if (Node
) { while (Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
5583 else { Route
.clear(); continue; }
5584 IntelligenceAction(1);
5589 Node
= GetLevel()->FindRoute(Pos
, GoingTo
, Illegal
, GetMoveType());
5590 if (Node
) { while(Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
5591 else TerminateGoingTo();
5592 IntelligenceAction(5);
5599 void character::SetGoingTo (v2 What
) {
5600 if (GoingTo
!= What
) {
5608 void character::TerminateGoingTo () {
5615 truth
character::CheckForFood (int Radius
) {
5616 if (StateIsActivated(PANIC
) || !UsesNutrition() || !IsEnabled()) return false;
5619 for (int r
= 1; r
<= Radius
; ++r
) {
5622 for (y
= Pos
.Y
-r
; y
<= Pos
.Y
+r
; ++y
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
5625 if (x
< GetLevel()->GetXSize()) {
5626 for (y
= Pos
.Y
-r
; y
<= Pos
.Y
+r
; ++y
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
5630 for (x
= Pos
.X
-r
; x
<= Pos
.X
+r
; ++x
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
5633 if (y
< GetLevel()->GetYSize()) {
5634 for (x
= Pos
.X
-r
; x
<= Pos
.X
+r
; ++x
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
5641 truth
character::CheckForFoodInSquare (v2 Pos
) {
5642 level
*Level
= GetLevel();
5643 if (Level
->IsValidPos(Pos
)) {
5644 lsquare
*Square
= Level
->GetLSquare(Pos
);
5645 stack
*Stack
= Square
->GetStack();
5646 if (Stack
->GetItems()) {
5647 for (stackiterator i
= Stack
->GetBottom(); i
.HasItem(); ++i
) {
5648 if (i
->IsPickable(this) && i
->CanBeSeenBy(this) && i
->CanBeEatenByAI(this) && (!Square
->GetRoomIndex() || Square
->GetRoom()->AllowFoodSearch())) {
5650 return MoveTowardsTarget(false);
5659 void character::SetConfig (int NewConfig
, int SpecialFlags
) {
5660 databasecreator
<character
>::InstallDataBase(this, NewConfig
);
5663 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdatePictures();
5667 truth
character::IsOver (citem
*Item
) const {
5668 for (int c1
= 0; c1
< Item
->GetSquaresUnder(); ++c1
)
5669 for (int c2
= 0; c2
< SquaresUnder
; ++c2
)
5670 if (Item
->GetPos(c1
) == GetPos(c2
)) return true;
5675 truth
character::CheckConsume (cfestring
&Verb
) const {
5676 if (!UsesNutrition()) {
5677 if (IsPlayer()) ADD_MESSAGE("In this form you can't and don't need to %s.", Verb
.CStr());
5684 void character::PutTo (lsquare
*To
) {
5685 PutTo(To
->GetPos());
5689 double character::RandomizeBabyExperience (double SumE
) {
5690 if (!SumE
) return 0;
5691 double E
= (SumE
/ 4) - (SumE
/ 32) + (double(RAND()) / MAX_RAND
) * (SumE
/ 16 + 1);
5692 return Limit(E
, MIN_EXP
, MAX_EXP
);
5696 liquid
*character::CreateBlood (long Volume
) const {
5697 return liquid::Spawn(GetBloodMaterial(), Volume
);
5701 void character::SpillFluid (character
*Spiller
, liquid
*Liquid
, int SquareIndex
) {
5702 long ReserveVolume
= Liquid
->GetVolume() >> 1;
5703 Liquid
->EditVolume(-ReserveVolume
);
5704 GetStack()->SpillFluid(Spiller
, Liquid
, long(Liquid
->GetVolume() * sqrt(double(GetStack()->GetVolume()) / GetVolume())));
5705 Liquid
->EditVolume(ReserveVolume
);
5707 long Modifier
[MAX_BODYPARTS
], ModifierSum
= 0;
5708 for (c
= 0; c
< BodyParts
; ++c
) {
5709 if (GetBodyPart(c
)) {
5710 Modifier
[c
] = long(sqrt(GetBodyPart(c
)->GetVolume()));
5711 if (Modifier
[c
]) Modifier
[c
] *= 1 + (RAND() & 3);
5712 ModifierSum
+= Modifier
[c
];
5717 for (c
= 1; c
< GetBodyParts(); ++c
) {
5718 if (GetBodyPart(c
) && IsEnabled())
5719 GetBodyPart(c
)->SpillFluid(Spiller
, Liquid
->SpawnMoreLiquid(Liquid
->GetVolume() * Modifier
[c
] / ModifierSum
), SquareIndex
);
5722 Liquid
->SetVolume(Liquid
->GetVolume() * Modifier
[TORSO_INDEX
] / ModifierSum
);
5723 GetTorso()->SpillFluid(Spiller
, Liquid
, SquareIndex
);
5728 void character::StayOn (liquid
*Liquid
) {
5729 Liquid
->TouchEffect(this, TORSO_INDEX
);
5733 truth
character::IsAlly (ccharacter
*Char
) const {
5734 return Char
->GetTeam()->GetID() == GetTeam()->GetID();
5738 void character::ResetSpoiling () {
5739 doforbodyparts()(this, &bodypart::ResetSpoiling
);
5743 item
*character::SearchForItem (ccharacter
*Char
, sorter Sorter
) const {
5744 item
*Equipment
= findequipment
<ccharacter
*>()(this, Sorter
, Char
);
5745 if (Equipment
) return Equipment
;
5746 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (((*i
)->*Sorter
)(Char
)) return *i
;
5751 truth
character::DetectMaterial (cmaterial
*Material
) const {
5752 return GetStack()->DetectMaterial(Material
) ||
5753 combinebodypartpredicateswithparam
<cmaterial
*>()(this, &bodypart::DetectMaterial
, Material
, 1) ||
5754 combineequipmentpredicateswithparam
<cmaterial
*>()(this, &item::DetectMaterial
, Material
, 1);
5758 truth
character::DamageTypeDestroysBodyPart (int Type
) {
5759 return (Type
&0xFFF) != PHYSICAL_DAMAGE
;
5763 truth
character::CheckIfTooScaredToHit (ccharacter
*Enemy
) const {
5764 if (IsPlayer() && StateIsActivated(PANIC
)) {
5765 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
5766 square
*Square
= GetNeighbourSquare(d
);
5768 if(CanMoveOn(Square
) && (!Square
->GetCharacter() || Square
->GetCharacter()->IsPet())) {
5769 ADD_MESSAGE("You are too scared to attack %s.", Enemy
->CHAR_DESCRIPTION(DEFINITE
));
5779 void character::PrintBeginLevitationMessage () const {
5781 if (IsPlayer()) ADD_MESSAGE("You rise into the air like a small hot-air balloon.");
5782 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins to float.", CHAR_NAME(DEFINITE
));
5787 void character::PrintEndLevitationMessage () const {
5789 if (IsPlayer()) ADD_MESSAGE("You descend gently onto the ground.");
5790 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s drops onto the ground.", CHAR_NAME(DEFINITE
));
5795 truth
character::IsLimbIndex (int I
) {
5797 case RIGHT_ARM_INDEX
:
5798 case LEFT_ARM_INDEX
:
5799 case RIGHT_LEG_INDEX
:
5800 case LEFT_LEG_INDEX
:
5807 void character::EditExperience (int Identifier
, double Value
, double Speed
) {
5808 if (!AllowExperience() || (Identifier
== ENDURANCE
&& UseMaterialAttributes())) return;
5809 int Change
= RawEditExperience(BaseExperience
[Identifier
], GetNaturalExperience(Identifier
), Value
, Speed
);
5810 if (!Change
) return;
5811 cchar
*PlayerMsg
= 0, *NPCMsg
= 0;
5812 switch (Identifier
) {
5815 PlayerMsg
= "You feel tougher than anything!";
5816 if (IsPet()) NPCMsg
= "Suddenly %s looks tougher.";
5818 PlayerMsg
= "You feel less healthy.";
5819 if (IsPet()) NPCMsg
= "Suddenly %s looks less healthy.";
5821 CalculateBodyPartMaxHPs();
5822 CalculateMaxStamina();
5827 PlayerMsg
= "You now see the world in much better detail than before.";
5829 PlayerMsg
= "You feel very guru.";
5830 game::GetGod(VALPURUS
)->AdjustRelation(100);
5832 game::SendLOSUpdateRequest();
5837 if (Change
> 0) PlayerMsg
= "Suddenly the inner structure of the Multiverse around you looks quite simple.";
5838 else PlayerMsg
= "It surely is hard to think today.";
5841 if (IsPlayerKind()) UpdatePictures();
5845 if (Change
> 0) PlayerMsg
= "You feel your life experience increasing all the time.";
5846 else PlayerMsg
= "You feel like having done something unwise.";
5848 if (IsPlayerKind()) UpdatePictures();
5852 PlayerMsg
= "You feel very confident of your social skills.";
5854 if (GetAttribute(CHARISMA
) <= 15) NPCMsg
= "%s looks less ugly.";
5855 else NPCMsg
= "%s looks more attractive.";
5858 PlayerMsg
= "You feel somehow disliked.";
5860 if (GetAttribute(CHARISMA
) < 15) NPCMsg
= "%s looks more ugly.";
5861 else NPCMsg
= "%s looks less attractive.";
5864 if (IsPlayerKind()) UpdatePictures();
5868 PlayerMsg
= "You feel magical forces coursing through your body!";
5869 NPCMsg
= "You notice an odd glow around %s.";
5871 PlayerMsg
= "You feel your magical abilities withering slowly.";
5872 NPCMsg
= "You notice strange vibrations in the air around %s. But they disappear rapidly.";
5877 if (IsPlayer()) ADD_MESSAGE(PlayerMsg
);
5878 else if (NPCMsg
&& CanBeSeenByPlayer()) ADD_MESSAGE(NPCMsg
, CHAR_NAME(DEFINITE
));
5880 CalculateBattleInfo();
5884 int character::RawEditExperience (double &Exp
, double NaturalExp
, double Value
, double Speed
) const {
5885 double OldExp
= Exp
;
5890 if(!OldExp
|| !Value
|| (Value
> 0 && OldExp
>= NaturalExp
* (100 + Value
) / 100) ||
5891 (Value
< 0 && OldExp
<= NaturalExp
* (100 + Value
) / 100)) return 0;
5892 if (!IsPlayer()) Speed
*= 1.5;
5893 Exp
+= (NaturalExp
* (100 + Value
) - 100 * OldExp
) * Speed
* EXP_DIVISOR
;
5894 LimitRef(Exp
, MIN_EXP
, MAX_EXP
);
5895 int NewA
= int(Exp
* EXP_DIVISOR
);
5896 int OldA
= int(OldExp
* EXP_DIVISOR
);
5897 int Delta
= NewA
- OldA
;
5898 if (Delta
> 0) Exp
= Max(Exp
, (NewA
+ 0.05) * EXP_MULTIPLIER
);
5899 else if (Delta
< 0) Exp
= Min(Exp
, (NewA
+ 0.95) * EXP_MULTIPLIER
);
5900 LimitRef(Exp
, MIN_EXP
, MAX_EXP
);
5905 int character::GetAttribute (int Identifier
, truth AllowBonus
) const {
5906 int A
= int(BaseExperience
[Identifier
] * EXP_DIVISOR
);
5907 if (AllowBonus
&& Identifier
== INTELLIGENCE
&& BrainsHurt()) return Max((A
+ AttributeBonus
[INTELLIGENCE
]) / 3, 1);
5908 return A
&& AllowBonus
? Max(A
+ AttributeBonus
[Identifier
], 1) : A
;
5912 void characterdatabase::PostProcess () {
5913 double AM
= (100 + AttributeBonus
) * EXP_MULTIPLIER
/ 100;
5914 for (int c
= 0; c
< ATTRIBUTES
; ++c
) NaturalExperience
[c
] = this->*ExpPtr
[c
] * AM
;
5918 void character::EditDealExperience (long Price
) {
5919 EditExperience(CHARISMA
, sqrt(Price
) / 5, 1 << 9);
5923 void character::PrintBeginLeprosyMessage () const {
5924 if (IsPlayer()) ADD_MESSAGE("You feel you're falling in pieces.");
5928 void character::PrintEndLeprosyMessage () const {
5929 if (IsPlayer()) ADD_MESSAGE("You feel your limbs are stuck in place tightly."); // CHANGE OR DIE
5933 void character::TryToInfectWithLeprosy (ccharacter
*Infector
) {
5934 if (!IsImmuneToLeprosy() &&
5935 ((GetRelation(Infector
) == HOSTILE
&& !RAND_N(50 * GetAttribute(ENDURANCE
))) ||
5936 !RAND_N(500 * GetAttribute(ENDURANCE
)))) GainIntrinsic(LEPROSY
);
5940 void character::SignalGeneration () {
5941 const_cast<database
*>(DataBase
)->Flags
|= HAS_BEEN_GENERATED
;
5945 void character::CheckIfSeen () {
5946 if (IsPlayer() || CanBeSeenByPlayer()) SignalSeen();
5950 void character::SignalSeen () {
5951 if (!(WarnFlags
& WARNED
) && GetRelation(PLAYER
) == HOSTILE
) {
5952 double Danger
= GetRelativeDanger(PLAYER
);
5954 game::SetDangerFound(Max(game::GetDangerFound(), Danger
));
5955 if (Danger
> 500.0 && !(WarnFlags
& HAS_CAUSED_PANIC
)) {
5956 WarnFlags
|= HAS_CAUSED_PANIC
;
5957 game::SetCausePanicFlag(true);
5959 WarnFlags
|= WARNED
;
5962 const_cast<database
*>(DataBase
)->Flags
|= HAS_BEEN_SEEN
;
5966 int character::GetPolymorphIntelligenceRequirement () const {
5967 if (DataBase
->PolymorphIntelligenceRequirement
== DEPENDS_ON_ATTRIBUTES
) return Max(GetAttributeAverage() - 5, 0);
5968 return DataBase
->PolymorphIntelligenceRequirement
;
5972 void character::RemoveAllItems () {
5973 GetStack()->Clean();
5974 for (int c
= 0; c
< GetEquipments(); ++c
) {
5975 item
*Equipment
= GetEquipment(c
);
5977 Equipment
->RemoveFromSlot();
5978 Equipment
->SendToHell();
5984 int character::CalculateWeaponSkillHits (ccharacter
*Enemy
) const {
5985 if (Enemy
->IsPlayer()) {
5986 configid
ConfigID(GetType(), GetConfig());
5987 const dangerid
& DangerID
= game::GetDangerMap().find(ConfigID
)->second
;
5988 return Min(int(DangerID
.EquippedDanger
* 2000), 1000);
5990 return Min(int(GetRelativeDanger(Enemy
, true) * 2000), 1000);
5994 truth
character::CanUseEquipment (int I
) const {
5995 return CanUseEquipment() && I
< GetEquipments() && GetBodyPartOfEquipment(I
) && EquipmentIsAllowed(I
);
5999 /* Target mustn't have any equipment */
6000 void character::DonateEquipmentTo (character
*Character
) {
6002 ulong
*EquipmentMemory
= game::GetEquipmentMemory();
6003 for (int c
= 0; c
< MAX_EQUIPMENT_SLOTS
; ++c
) {
6004 item
*Item
= GetEquipment(c
);
6006 if (Character
->CanUseEquipment(c
)) {
6007 Item
->RemoveFromSlot();
6008 Character
->SetEquipment(c
, Item
);
6010 EquipmentMemory
[c
] = Item
->GetID();
6011 Item
->MoveTo(Character
->GetStack());
6013 } else if (CanUseEquipment(c
)) {
6014 EquipmentMemory
[c
] = 0;
6015 } else if (EquipmentMemory
[c
] && Character
->CanUseEquipment(c
)) {
6016 for (stackiterator i
= Character
->GetStack()->GetBottom(); i
.HasItem(); ++i
) {
6017 if (i
->GetID() == EquipmentMemory
[c
]) {
6019 Item
->RemoveFromSlot();
6020 Character
->SetEquipment(c
, Item
);
6024 EquipmentMemory
[c
] = 0;
6028 for (int c
= 0; c
< GetEquipments(); ++c
) {
6029 item
*Item
= GetEquipment(c
);
6031 if (Character
->CanUseEquipment(c
)) {
6032 Item
->RemoveFromSlot();
6033 Character
->SetEquipment(c
, Item
);
6035 Item
->MoveTo(Character
->GetStackUnder());
6043 void character::ReceivePeaSoup (long) {
6044 lsquare
*Square
= GetLSquareUnder();
6045 if (Square
->IsFlyable()) Square
->AddSmoke(gas::Spawn(FART
, 250));
6049 void character::AddPeaSoupConsumeEndMessage () const {
6051 if (CanHear()) ADD_MESSAGE("Mmmh! The soup is very tasty. You hear a small puff.");
6052 else ADD_MESSAGE("Mmmh! The soup is very tasty.");
6053 } else if (CanBeSeenByPlayer() && PLAYER
->CanHear()) {
6055 ADD_MESSAGE("You hear a small puff.");
6060 void character::CalculateMaxStamina () {
6061 MaxStamina
= TorsoIsAlive() ? GetAttribute(ENDURANCE
) * 10000 : 0;
6065 void character::EditStamina (int Amount
, truth CanCauseUnconsciousness
) {
6066 if (!TorsoIsAlive()) return;
6067 int UnconsciousnessStamina
= MaxStamina
>> 3;
6068 if (!CanCauseUnconsciousness
&& Amount
< 0) {
6069 if (Stamina
> UnconsciousnessStamina
) {
6071 if (Stamina
< UnconsciousnessStamina
) Stamina
= UnconsciousnessStamina
;
6075 int OldStamina
= Stamina
;
6077 if (Stamina
> MaxStamina
) {
6078 Stamina
= MaxStamina
;
6079 } else if (Stamina
< 0) {
6081 LoseConsciousness(250 + RAND_N(250));
6082 } else if (IsPlayer()) {
6083 if (OldStamina
>= MaxStamina
>> 2 && Stamina
< MaxStamina
>> 2) {
6084 ADD_MESSAGE("You are getting a little tired.");
6085 } else if(OldStamina
>= UnconsciousnessStamina
&& Stamina
< UnconsciousnessStamina
) {
6086 ADD_MESSAGE("You are seriously out of breath!");
6087 game::SetPlayerIsRunning(false);
6090 if (IsPlayer() && StateIsActivated(PANIC
) && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6094 void character::RegenerateStamina () {
6095 if (GetTirednessState() != UNTIRED
) {
6096 EditExperience(ENDURANCE
, 50, 1);
6097 if (Sweats() && TorsoIsAlive() && !RAND_N(30) && !game::IsInWilderness()) {
6098 long Volume
= long(0.05 * sqrt(GetBodyVolume()));
6099 if (GetTirednessState() == FAINTING
) Volume
<<= 1;
6100 for (int c
= 0; c
< SquaresUnder
; ++c
) GetLSquareUnder(c
)->SpillFluid(0, CreateSweat(Volume
), false, false);
6105 if (Action
->IsRest()) {
6106 if (SquaresUnder
== 1) {
6107 Bonus
= GetSquareUnder()->GetRestModifier() << 1;
6109 int Lowest
= GetSquareUnder(0)->GetRestModifier();
6110 for (int c
= 1; c
< GetSquaresUnder(); ++c
) {
6111 int Mod
= GetSquareUnder(c
)->GetRestModifier();
6112 if (Mod
< Lowest
) Lowest
= Mod
;
6114 Bonus
= Lowest
<< 1;
6116 } else if (Action
->IsUnconsciousness()) Bonus
= 2;
6119 switch (GetBurdenState()) {
6120 case OVER_LOADED
: Plus1
= 25; break;
6121 case STRESSED
: Plus1
= 50; break;
6122 case BURDENED
: Plus1
= 75; break;
6126 switch (GetHungerState()) {
6127 case STARVING
: Plus2
= 25; break;
6128 case VERY_HUNGRY
: Plus2
= 50; break;
6129 case HUNGRY
: Plus2
= 75; break;
6132 Stamina
+= Plus1
* Plus2
* Bonus
/ 1000;
6133 if (Stamina
> MaxStamina
) Stamina
= MaxStamina
;
6134 if (IsPlayer() && StateIsActivated(PANIC
) && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6138 void character::BeginPanic () {
6139 if (IsPlayer() && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6140 DeActivateVoluntaryAction();
6144 void character::EndPanic () {
6145 if (IsPlayer()) game::SetPlayerIsRunning(false);
6149 int character::GetTirednessState () const {
6150 if (Stamina
>= MaxStamina
>> 2) return UNTIRED
;
6151 if (Stamina
>= MaxStamina
>> 3) return EXHAUSTED
;
6156 void character::ReceiveBlackUnicorn (long Amount
) {
6157 if (!(RAND() % 160)) game::DoEvilDeed(Amount
/ 50);
6158 BeginTemporaryState(TELEPORT
, Amount
/ 100);
6159 for (int c
= 0; c
< STATES
; ++c
) {
6160 if (StateData
[c
].Flags
& DUR_TEMPORARY
) {
6161 BeginTemporaryState(1 << c
, Amount
/ 100);
6162 if (!IsEnabled()) return;
6163 } else if (StateData
[c
].Flags
& DUR_PERMANENT
) {
6164 GainIntrinsic(1 << c
);
6165 if (!IsEnabled()) return;
6171 void character::ReceiveGrayUnicorn (long Amount
) {
6172 if (!(RAND() % 80)) game::DoEvilDeed(Amount
/ 50);
6173 BeginTemporaryState(TELEPORT
, Amount
/ 100);
6174 for (int c
= 0; c
< STATES
; ++c
) {
6175 if (1 << c
!= TELEPORT
) {
6176 DecreaseStateCounter(1 << c
, -Amount
/ 100);
6177 if (!IsEnabled()) return;
6183 void character::ReceiveWhiteUnicorn (long Amount
) {
6184 if (!(RAND() % 40)) game::DoEvilDeed(Amount
/ 50);
6185 BeginTemporaryState(TELEPORT
, Amount
/ 100);
6186 DecreaseStateCounter(LYCANTHROPY
, -Amount
/ 100);
6187 DecreaseStateCounter(POISONED
, -Amount
/ 100);
6188 DecreaseStateCounter(PARASITIZED
, -Amount
/ 100);
6189 DecreaseStateCounter(LEPROSY
, -Amount
/ 100);
6193 /* Counter should be negative. Removes intrinsics. */
6194 void character::DecreaseStateCounter (long State
, int Counter
) {
6196 for (Index
= 0; Index
< STATES
; ++Index
) if (1 << Index
== State
) break;
6197 if (Index
== STATES
) ABORT("DecreaseTemporaryStateCounter works only when State == 2Â ^ n!");
6198 if (TemporaryState
& State
) {
6199 if (TemporaryStateCounter
[Index
] == PERMANENT
|| (TemporaryStateCounter
[Index
] += Counter
) <= 0) {
6200 TemporaryState
&= ~State
;
6201 if (!(EquipmentState
& State
)) {
6202 if (StateData
[Index
].EndHandler
) {
6203 (this->*StateData
[Index
].EndHandler
)();
6204 if (!IsEnabled()) return;
6206 (this->*StateData
[Index
].PrintEndMessage
)();
6213 truth
character::IsImmuneToLeprosy () const {
6214 return DataBase
->IsImmuneToLeprosy
|| UseMaterialAttributes();
6218 void character::LeprosyHandler () {
6219 EditExperience(ARM_STRENGTH
, -25, 1 << 1);
6220 EditExperience(LEG_STRENGTH
, -25, 1 << 1);
6221 EditExperience(DEXTERITY
, -25, 1 << 1);
6222 EditExperience(AGILITY
, -25, 1 << 1);
6223 EditExperience(ENDURANCE
, -25, 1 << 1);
6224 EditExperience(CHARISMA
, -25, 1 << 1);
6225 CheckDeath(CONST_S("killed by leprosy"));
6229 bodypart
*character::SearchForOriginalBodyPart (int I
) const {
6230 for (stackiterator i1
= GetStackUnder()->GetBottom(); i1
.HasItem(); ++i1
) {
6231 for (std::list
<ulong
>::iterator i2
= OriginalBodyPartID
[I
].begin(); i2
!= OriginalBodyPartID
[I
].end(); ++i2
)
6232 if (i1
->GetID() == *i2
) return static_cast<bodypart
*>(*i1
);
6238 void character::SetLifeExpectancy (int Base
, int RandPlus
) {
6240 for (c
= 0; c
< BodyParts
; ++c
) {
6241 bodypart
*BodyPart
= GetBodyPart(c
);
6242 if (BodyPart
) BodyPart
->SetLifeExpectancy(Base
, RandPlus
);
6244 for (c
= 0; c
< GetEquipments(); ++c
) {
6245 item
*Equipment
= GetEquipment(c
);
6246 if (Equipment
) Equipment
->SetLifeExpectancy(Base
, RandPlus
);
6251 /* Receiver should be a fresh duplicate of this */
6252 void character::DuplicateEquipment (character
*Receiver
, ulong Flags
) {
6253 for (int c
= 0; c
< GetEquipments(); ++c
) {
6254 item
*Equipment
= GetEquipment(c
);
6256 item
*Duplicate
= Equipment
->Duplicate(Flags
);
6257 Receiver
->SetEquipment(c
, Duplicate
);
6263 void character::Disappear (corpse
*Corpse
, cchar
*Verb
, truth (item::*ClosePredicate
)() const) {
6264 truth TorsoDisappeared
= false;
6265 truth CanBeSeen
= Corpse
? Corpse
->CanBeSeenByPlayer() : IsPlayer() || CanBeSeenByPlayer();
6267 if ((GetTorso()->*ClosePredicate
)()) {
6269 if (Corpse
) ADD_MESSAGE("%s %ss.", Corpse
->CHAR_NAME(DEFINITE
), Verb
);
6270 else if (IsPlayer()) ADD_MESSAGE("You %s.", Verb
);
6271 else ADD_MESSAGE("%s %ss.", CHAR_NAME(DEFINITE
), Verb
);
6273 TorsoDisappeared
= true;
6274 for (c
= 0; c
< GetEquipments(); ++c
) {
6275 item
*Equipment
= GetEquipment(c
);
6276 if (Equipment
&& (Equipment
->*ClosePredicate
)()) {
6277 Equipment
->RemoveFromSlot();
6278 Equipment
->SendToHell();
6281 itemvector ItemVector
;
6282 GetStack()->FillItemVector(ItemVector
);
6283 for (uint c
= 0; c
< ItemVector
.size(); ++c
) {
6284 if (ItemVector
[c
] && (ItemVector
[c
]->*ClosePredicate
)()) {
6285 ItemVector
[c
]->RemoveFromSlot();
6286 ItemVector
[c
]->SendToHell();
6290 for (c
= 1; c
< GetBodyParts(); ++c
) {
6291 bodypart
*BodyPart
= GetBodyPart(c
);
6293 if ((BodyPart
->*ClosePredicate
)()) {
6294 if (!TorsoDisappeared
&& CanBeSeen
) {
6295 if(IsPlayer()) ADD_MESSAGE("Your %s %ss.", GetBodyPartName(c
).CStr(), Verb
);
6296 else ADD_MESSAGE("The %s of %s %ss.", GetBodyPartName(c
).CStr(), CHAR_NAME(DEFINITE
), Verb
);
6298 BodyPart
->DropEquipment();
6299 item
*BodyPart
= SevereBodyPart(c
);
6300 if (BodyPart
) BodyPart
->SendToHell();
6301 } else if (TorsoDisappeared
) {
6302 BodyPart
->DropEquipment();
6303 item
*BodyPart
= SevereBodyPart(c
);
6305 if (Corpse
) Corpse
->GetSlot()->AddFriendItem(BodyPart
);
6306 else if (!game::IsInWilderness()) GetStackUnder()->AddItem(BodyPart
);
6307 else BodyPart
->SendToHell();
6312 if (TorsoDisappeared
) {
6314 Corpse
->RemoveFromSlot();
6315 Corpse
->SendToHell();
6317 CheckDeath(festring(Verb
) + "ed", 0, FORCE_DEATH
|DISALLOW_CORPSE
|DISALLOW_MSG
);
6320 CheckDeath(festring(Verb
) + "ed", 0, DISALLOW_MSG
);
6325 void character::SignalDisappearance () {
6326 if (GetMotherEntity()) GetMotherEntity()->SignalDisappearance();
6327 else Disappear(0, "disappear", &item::IsVeryCloseToDisappearance
);
6331 truth
character::HornOfFearWorks () const {
6332 return CanHear() && GetPanicLevel() > RAND() % 33;
6336 void character::BeginLeprosy () {
6337 doforbodypartswithparam
<truth
>()(this, &bodypart::SetIsInfectedByLeprosy
, true);
6341 void character::EndLeprosy () {
6342 doforbodypartswithparam
<truth
>()(this, &bodypart::SetIsInfectedByLeprosy
, false);
6346 truth
character::IsSameAs (ccharacter
*What
) const {
6347 return What
->GetType() == GetType() && What
->GetConfig() == GetConfig();
6351 ulong
character::GetCommandFlags () const {
6352 return !StateIsActivated(PANIC
) ? CommandFlags
: CommandFlags
|FLEE_FROM_ENEMIES
;
6356 ulong
character::GetConstantCommandFlags () const {
6357 return !StateIsActivated(PANIC
) ? DataBase
->ConstantCommandFlags
: DataBase
->ConstantCommandFlags
|FLEE_FROM_ENEMIES
;
6361 ulong
character::GetPossibleCommandFlags () const {
6362 int Int
= GetAttribute(INTELLIGENCE
);
6363 ulong Flags
= ALL_COMMAND_FLAGS
;
6364 if (!CanMove() || Int
< 4) Flags
&= ~FOLLOW_LEADER
;
6365 if (!CanMove() || Int
< 6) Flags
&= ~FLEE_FROM_ENEMIES
;
6366 if (!CanUseEquipment() || Int
< 8) Flags
&= ~DONT_CHANGE_EQUIPMENT
;
6367 if (!UsesNutrition() || Int
< 8) Flags
&= ~DONT_CONSUME_ANYTHING_VALUABLE
;
6372 truth
character::IsRetreating () const {
6373 return StateIsActivated(PANIC
) || (CommandFlags
& FLEE_FROM_ENEMIES
&& IsPet());
6377 truth
character::ChatMenu () {
6378 if (GetAction() && !GetAction()->CanBeTalkedTo()) {
6379 ADD_MESSAGE("%s is silent.", CHAR_DESCRIPTION(DEFINITE
));
6380 PLAYER
->EditAP(-200);
6383 ulong ManagementFlags
= GetManagementFlags();
6384 if (ManagementFlags
== CHAT_IDLY
|| !IsPet()) return ChatIdly();
6385 static cchar
*const ChatMenuEntry
[CHAT_MENU_ENTRIES
] = {
6392 static const petmanagementfunction PMF
[CHAT_MENU_ENTRIES
] = {
6393 &character::ChangePetEquipment
,
6394 &character::TakePetItems
,
6395 &character::GivePetItems
,
6396 &character::IssuePetCommands
,
6397 &character::ChatIdly
6399 felist
List(CONST_S("Choose action:"));
6400 game::SetStandardListAttributes(List
);
6401 List
.AddFlags(SELECTABLE
);
6403 for (c
= 0; c
< CHAT_MENU_ENTRIES
; ++c
) if (1 << c
& ManagementFlags
) List
.AddEntry(ChatMenuEntry
[c
], LIGHT_GRAY
);
6404 int Chosen
= List
.Draw();
6405 if (Chosen
& FELIST_ERROR_BIT
) return false;
6406 for (c
= 0, i
= 0; c
< CHAT_MENU_ENTRIES
; ++c
) {
6407 if (1 << c
& ManagementFlags
&& i
++ == Chosen
) return (this->*PMF
[c
])();
6409 return false; // dummy
6413 truth
character::ChangePetEquipment () {
6414 if (EquipmentScreen(PLAYER
->GetStack(), GetStack())) {
6422 truth
character::TakePetItems () {
6423 truth Success
= false;
6424 stack::SetSelected(0);
6427 game::DrawEverythingNoBlit();
6428 GetStack()->DrawContents(
6432 CONST_S("What do you want to take from ") + CHAR_DESCRIPTION(DEFINITE
) + '?',
6435 GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState(),
6436 GetVerbalBurdenStateColor(),
6438 if (ToTake
.empty()) break;
6439 for (uint c
= 0; c
< ToTake
.size(); ++c
) ToTake
[c
]->MoveTo(PLAYER
->GetStack());
6440 ADD_MESSAGE("You take %s.", ToTake
[0]->GetName(DEFINITE
, ToTake
.size()).CStr());
6445 PLAYER
->DexterityAction(2);
6451 truth
character::GivePetItems () {
6452 truth Success
= false;
6453 stack::SetSelected(0);
6456 game::DrawEverythingNoBlit();
6457 PLAYER
->GetStack()->DrawContents(
6461 CONST_S("What do you want to give to ") + CHAR_DESCRIPTION(DEFINITE
) + '?',
6464 GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState(),
6465 GetVerbalBurdenStateColor(),
6467 if (ToGive
.empty()) break;
6468 for (uint c
= 0; c
< ToGive
.size(); ++c
) ToGive
[c
]->MoveTo(GetStack());
6469 ADD_MESSAGE("You give %s to %s.", ToGive
[0]->GetName(DEFINITE
, ToGive
.size()).CStr(), CHAR_DESCRIPTION(DEFINITE
));
6474 PLAYER
->DexterityAction(2);
6480 truth
character::IssuePetCommands () {
6481 if (!IsConscious()) {
6482 ADD_MESSAGE("%s is unconscious.", CHAR_DESCRIPTION(DEFINITE
));
6485 ulong PossibleC
= GetPossibleCommandFlags();
6487 ADD_MESSAGE("%s cannot be commanded.", CHAR_DESCRIPTION(DEFINITE
));
6490 ulong OldC
= GetCommandFlags();
6491 ulong NewC
= OldC
, VaryFlags
= 0;
6492 game::CommandScreen(CONST_S("Issue commands to ")+GetDescription(DEFINITE
), PossibleC
, GetConstantCommandFlags(), VaryFlags
, NewC
);
6493 if (NewC
== OldC
) return false;
6494 SetCommandFlags(NewC
);
6495 PLAYER
->EditAP(-500);
6496 PLAYER
->EditExperience(CHARISMA
, 25, 1 << 7);
6501 truth
character::ChatIdly () {
6502 if (!TryToTalkAboutScience()) {
6504 PLAYER
->EditExperience(CHARISMA
, 75, 1 << 7);
6506 PLAYER
->EditAP(-1000);
6511 truth
character::EquipmentScreen (stack
*MainStack
, stack
*SecStack
) {
6512 if (!CanUseEquipment()) {
6513 ADD_MESSAGE("%s cannot use equipment.", CHAR_DESCRIPTION(DEFINITE
));
6517 truth EquipmentChanged
= false;
6518 felist
List(CONST_S("Equipment menu [ESC exits]"));
6522 List
.EmptyDescription();
6524 List
.AddDescription(CONST_S(""));
6525 List
.AddDescription(festring(GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState()).CapitalizeCopy(), GetVerbalBurdenStateColor());
6527 for (int c
= 0; c
< GetEquipments(); ++c
) {
6528 Entry
= GetEquipmentName(c
);
6531 item
*Equipment
= GetEquipment(c
);
6533 Equipment
->AddInventoryEntry(this, Entry
, 1, true);
6534 AddSpecialEquipmentInfo(Entry
, c
);
6535 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
6536 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
6538 Entry
<< (GetBodyPartOfEquipment(c
) ? "-" : "can't use");
6539 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, game::AddToItemDrawVector(itemvector()));
6542 game::DrawEverythingNoBlit();
6543 game::SetStandardListAttributes(List
);
6544 List
.SetFlags(SELECTABLE
|DRAW_BACKGROUND_AFTERWARDS
);
6545 List
.SetEntryDrawer(game::ItemEntryDrawer
);
6546 Chosen
= List
.Draw();
6547 game::ClearItemDrawVector();
6548 if (Chosen
>= GetEquipments()) break;
6549 EquipmentChanged
= TryToChangeEquipment(MainStack
, SecStack
, Chosen
);
6551 if (EquipmentChanged
) DexterityAction(5);
6552 return EquipmentChanged
;
6556 ulong
character::GetManagementFlags () const {
6557 ulong Flags
= ALL_MANAGEMENT_FLAGS
;
6558 if (!CanUseEquipment() || !AllowPlayerToChangeEquipment()) Flags
&= ~CHANGE_EQUIPMENT
;
6559 if (!GetStack()->GetItems()) Flags
&= ~TAKE_ITEMS
;
6560 if (!WillCarryItems()) Flags
&= ~GIVE_ITEMS
;
6561 if (!GetPossibleCommandFlags()) Flags
&= ~ISSUE_COMMANDS
;
6566 cchar
*VerbalBurdenState
[] = { "overloaded", "stressed", "burdened", "unburdened" };
6567 col16 VerbalBurdenStateColor
[] = { RED
, BLUE
, BLUE
, WHITE
};
6569 cchar
*character::GetVerbalBurdenState () const { return VerbalBurdenState
[BurdenState
]; }
6570 col16
character::GetVerbalBurdenStateColor () const { return VerbalBurdenStateColor
[BurdenState
]; }
6571 int character::GetAttributeAverage () const { return GetSumOfAttributes()/7; }
6573 cfestring
&character::GetStandVerb() const {
6574 if (ForceCustomStandVerb()) return DataBase
->StandVerb
;
6575 static festring Hovering
= "hovering";
6576 static festring Swimming
= "swimming";
6577 if (StateIsActivated(LEVITATION
)) return Hovering
;
6578 if (IsSwimming()) return Swimming
;
6579 return DataBase
->StandVerb
;
6583 truth
character::CheckApply () const {
6585 ADD_MESSAGE("This monster type cannot apply.");
6592 void character::EndLevitation () {
6593 if (!IsFlying() && GetSquareUnder()) {
6594 if (!game::IsInWilderness()) SignalStepFrom(0);
6595 if (game::IsInWilderness() || !GetLSquareUnder()->IsFreezed()) TestWalkability();
6600 truth
character::CanMove () const {
6601 return !IsRooted() || StateIsActivated(LEVITATION
);
6605 void character::CalculateEnchantments () {
6606 doforequipments()(this, &item::CalculateEnchantment
);
6607 GetStack()->CalculateEnchantments();
6611 truth
character::GetNewFormForPolymorphWithControl (character
*&NewForm
) {
6612 festring Topic
, Temp
;
6615 festring Temp
= game::DefaultQuestion(CONST_S("What do you want to become? [press '?' for a list]"), game::GetDefaultPolymorphTo(), &game::PolymorphControlKeyHandler
);
6616 NewForm
= protosystem::CreateMonster(Temp
);
6618 if (NewForm
->IsSameAs(this)) {
6620 ADD_MESSAGE("You choose not to polymorph.");
6624 if (PolymorphBackup
&& NewForm
->IsSameAs(PolymorphBackup
)) {
6626 NewForm
= ForceEndPolymorph();
6629 if (NewForm
->GetPolymorphIntelligenceRequirement() > GetAttribute(INTELLIGENCE
) && !game::WizardModeIsActive()) {
6630 ADD_MESSAGE("You feel your mind isn't yet powerful enough to call forth the form of %s.", NewForm
->CHAR_NAME(INDEFINITE
));
6634 NewForm
->RemoveAllItems();
6642 liquid
*character::CreateSweat(long Volume
) const {
6643 return liquid::Spawn(GetSweatMaterial(), Volume
);
6647 truth
character::TeleportRandomItem (truth TryToHinderVisibility
) {
6648 if (IsImmuneToItemTeleport()) return false;
6649 itemvector ItemVector
;
6650 std::vector
<long> PossibilityVector
;
6651 int TotalPossibility
= 0;
6652 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
6653 ItemVector
.push_back(*i
);
6654 int Possibility
= i
->GetTeleportPriority();
6655 if (TryToHinderVisibility
) Possibility
+= i
->GetHinderVisibilityBonus(this);
6656 PossibilityVector
.push_back(Possibility
);
6657 TotalPossibility
+= Possibility
;
6659 for (int c
= 0; c
< GetEquipments(); ++c
) {
6660 item
*Equipment
= GetEquipment(c
);
6662 ItemVector
.push_back(Equipment
);
6663 int Possibility
= Equipment
->GetTeleportPriority();
6664 if (TryToHinderVisibility
) Possibility
+= Equipment
->GetHinderVisibilityBonus(this);
6665 PossibilityVector
.push_back(Possibility
<<= 1);
6666 TotalPossibility
+= Possibility
;
6669 if (!TotalPossibility
) return false;
6670 int Chosen
= femath::WeightedRand(PossibilityVector
, TotalPossibility
);
6671 item
*Item
= ItemVector
[Chosen
];
6672 truth Equipped
= PLAYER
->Equips(Item
);
6673 truth Seen
= Item
->CanBeSeenByPlayer();
6674 Item
->RemoveFromSlot();
6675 if (Seen
) ADD_MESSAGE("%s disappears.", Item
->CHAR_NAME(DEFINITE
));
6676 if (Equipped
) game::AskForEscPress(CONST_S("Equipment lost!"));
6678 int Range
= Item
->GetEmitation() && TryToHinderVisibility
? 25 : 5;
6679 rect
Border(Pos
+ v2(-Range
, -Range
), Pos
+ v2(Range
, Range
));
6680 Pos
= GetLevel()->GetRandomSquare(this, 0, &Border
);
6681 if (Pos
== ERROR_V2
) Pos
= GetLevel()->GetRandomSquare();
6682 GetNearLSquare(Pos
)->GetStack()->AddItem(Item
);
6683 if (Item
->CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", Item
->CHAR_NAME(INDEFINITE
));
6688 truth
character::HasClearRouteTo (v2 Pos
) const {
6689 pathcontroller::Map
= GetLevel()->GetMap();
6690 pathcontroller::Character
= this;
6691 v2 ThisPos
= GetPos();
6692 return mapmath
<pathcontroller
>::DoLine(ThisPos
.X
, ThisPos
.Y
, Pos
.X
, Pos
.Y
, SKIP_FIRST
);
6696 truth
character::IsTransparent () const {
6697 return !IsEnormous() || GetTorso()->GetMainMaterial()->IsTransparent() || StateIsActivated(INVISIBLE
);
6701 void character::SignalPossibleTransparencyChange () {
6702 if (!game::IsInWilderness()) {
6703 for (int c
= 0; c
< SquaresUnder
; ++c
) {
6704 lsquare
*Square
= GetLSquareUnder(c
);
6705 if (Square
) Square
->SignalPossibleTransparencyChange();
6711 int character::GetCursorData () const {
6713 int Color
= game::PlayerIsRunning() ? BLUE_CURSOR
: DARK_CURSOR
;
6714 for (int c
= 0; c
< BodyParts
; ++c
) {
6715 bodypart
*BodyPart
= GetBodyPart(c
);
6716 if (BodyPart
&& BodyPart
->IsUsable()) {
6717 int ConditionColorIndex
= BodyPart
->GetConditionColorIndex();
6718 if ((BodyPartIsVital(c
) && !ConditionColorIndex
) || (ConditionColorIndex
<= 1 && ++Bad
== 2)) return Color
|CURSOR_FLASH
;
6719 } else if (++Bad
== 2) return Color
|CURSOR_FLASH
;
6721 Color
= game::PlayerIsRunning() ? YELLOW_CURSOR
: RED_CURSOR
;
6722 return Bad
? Color
|CURSOR_FLASH
: Color
;
6726 void character::TryToName () {
6727 if (!IsPet()) ADD_MESSAGE("%s refuses to let YOU decide what %s's called.", CHAR_NAME(DEFINITE
), CHAR_PERSONAL_PRONOUN
);
6728 else if (IsPlayer()) ADD_MESSAGE("You can't rename yourself.");
6729 else if (!IsNameable()) ADD_MESSAGE("%s refuses to be called anything else but %s.", CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
));
6731 festring Topic
= CONST_S("What name will you give to ")+GetName(DEFINITE
)+'?';
6732 festring Name
= game::StringQuestion(Topic
, WHITE
, 0, 80, true);
6733 if (Name
.GetSize()) SetAssignedName(Name
);
6738 double character::GetSituationDanger (ccharacter
*Enemy
, v2 ThisPos
, v2 EnemyPos
, truth SeesEnemy
) const {
6740 if (IgnoreDanger() && !IsPlayer()) {
6741 if (Enemy
->IgnoreDanger() && !Enemy
->IsPlayer()) {
6742 Danger
= double(GetHP())*GetHPRequirementForGeneration()/(Enemy
->GetHP()*Enemy
->GetHPRequirementForGeneration());
6745 Danger
= 0.25*GetHPRequirementForGeneration()/Enemy
->GetHP();
6747 } else if (Enemy
->IgnoreDanger() && !Enemy
->IsPlayer()) {
6748 Danger
= 4.0*GetHP()/Enemy
->GetHPRequirementForGeneration();
6750 Danger
= GetRelativeDanger(Enemy
);
6752 Danger
*= 3.0/((EnemyPos
-ThisPos
).GetManhattanLength()+2);
6753 if (!SeesEnemy
) Danger
*= 0.2;
6754 if (StateIsActivated(PANIC
)) Danger
*= 0.2;
6755 Danger
*= double(GetHP())*Enemy
->GetMaxHP()/(Enemy
->GetHP()*GetMaxHP());
6760 void character::ModifySituationDanger (double &Danger
) const {
6761 switch (GetTirednessState()) {
6762 case FAINTING
: Danger
*= 1.5;
6763 case EXHAUSTED
: Danger
*= 1.25;
6765 for (int c
= 0; c
< STATES
; ++c
) {
6766 if (StateIsActivated(1 << c
) && StateData
[c
].SituationDangerModifier
!= 0) (this->*StateData
[c
].SituationDangerModifier
)(Danger
);
6771 void character::LycanthropySituationDangerModifier (double &Danger
) const {
6772 character
*Wolf
= werewolfwolf::Spawn();
6773 double DangerToWolf
= GetRelativeDanger(Wolf
);
6774 Danger
*= pow(DangerToWolf
, 0.1);
6779 void character::PoisonedSituationDangerModifier (double &Danger
) const {
6780 int C
= GetTemporaryStateCounter(POISONED
);
6781 Danger
*= (1+(C
*C
)/(GetHP()*10000.0*(GetGlobalResistance(POISON
)+1)));
6785 void character::PolymorphingSituationDangerModifier (double &Danger
) const {
6786 if (!StateIsActivated(POLYMORPH_CONTROL
)) Danger
*= 1.5;
6790 void character::PanicSituationDangerModifier (double &Danger
) const {
6795 void character::ConfusedSituationDangerModifier (double &Danger
) const {
6800 void character::ParasitizedSituationDangerModifier (double &Danger
) const {
6805 void character::LeprosySituationDangerModifier (double &Danger
) const {
6810 void character::AddRandomScienceName (festring
&String
) const {
6811 festring Science
= GetScienceTalkName().GetRandomElement().CStr();
6812 if (Science
[0] == '!') {
6813 String
<< Science
.CStr()+1;
6816 festring Attribute
= GetScienceTalkAdjectiveAttribute().GetRandomElement();
6818 truth NoAttrib
= Attribute
.IsEmpty(), NoSecondAdjective
= false;
6819 if (!Attribute
.IsEmpty() && Attribute
[0] == '!') {
6820 NoSecondAdjective
= true;
6821 Attribute
.Erase(0, 1);
6823 if (!Science
.Find("the ")) {
6824 Science
.Erase(0, 4);
6825 if (!Attribute
.Find("the ", 0, 4)) Attribute
<< " the"; else Attribute
.Insert(0, "the ", 4);
6827 if (islower(Science
[0]) && Science
.Find(' ') == festring::NPos
&& Science
.Find('-') == festring::NPos
&&
6828 Science
.Find("phobia") == festring::NPos
) {
6829 Prefix
= GetScienceTalkPrefix().GetRandomElement();
6830 if (!Prefix
.IsEmpty() && Science
.Find(Prefix
) != festring::NPos
) Prefix
.Empty();
6832 int L
= Prefix
.GetSize();
6833 if (L
&& Prefix
[L
-1] == Science
[0]) Science
.Erase(0, 1);
6834 if (!NoAttrib
&& !NoSecondAdjective
== !RAND_GOOD(3)) {
6835 int S1
= NoSecondAdjective
? 0 : GetScienceTalkAdjectiveAttribute().Size
;
6836 int S2
= GetScienceTalkSubstantiveAttribute().Size
;
6837 festring OtherAttribute
;
6838 int Chosen
= RAND_GOOD(S1
+S2
);
6839 if (Chosen
< S1
) OtherAttribute
= GetScienceTalkAdjectiveAttribute()[Chosen
];
6840 else OtherAttribute
= GetScienceTalkSubstantiveAttribute()[Chosen
- S1
];
6841 if (!OtherAttribute
.IsEmpty() && OtherAttribute
.Find("the ", 0, 4) && Attribute
.Find(OtherAttribute
) == festring::NPos
) {
6842 String
<< Attribute
<< ' ' << OtherAttribute
<< ' ' << Prefix
<< Science
;
6846 String
<< Attribute
;
6847 if (!NoAttrib
) String
<< ' ';
6848 String
<< Prefix
<< Science
;
6852 truth
character::TryToTalkAboutScience () {
6853 if (GetRelation(PLAYER
) == HOSTILE
||
6854 GetScienceTalkPossibility() <= RAND_GOOD(100) ||
6855 PLAYER
->GetAttribute(INTELLIGENCE
) < GetScienceTalkIntelligenceRequirement() ||
6856 PLAYER
->GetAttribute(WISDOM
) < GetScienceTalkWisdomRequirement() ||
6857 PLAYER
->GetAttribute(CHARISMA
) < GetScienceTalkCharismaRequirement())
6861 AddRandomScienceName(Science
);
6864 AddRandomScienceName(S1
);
6865 AddRandomScienceName(S2
);
6866 if (S1
.Find(S2
) == festring::NPos
&& S2
.Find(S1
) == festring::NPos
) {
6867 switch (RAND_GOOD(3)) {
6868 case 0: Science
= "the relation of "; break;
6869 case 1: Science
= "the differences of "; break;
6870 case 2: Science
= "the similarities of "; break;
6872 Science
<< S1
<< " and " << S2
;
6875 AddRandomScienceName(Science
);
6878 switch ((RAND() + GET_TICK()) % 10) {
6880 ADD_MESSAGE("You have a rather pleasant chat about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
6883 ADD_MESSAGE("%s explains a few of %s opinions regarding %s to you.", CHAR_DESCRIPTION(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, Science
.CStr());
6886 ADD_MESSAGE("%s reveals a number of %s insightful views of %s to you.", CHAR_DESCRIPTION(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, Science
.CStr());
6889 ADD_MESSAGE("You exhange some information pertaining to %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
6892 ADD_MESSAGE("You engage in a pretty intriguing conversation about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
6895 ADD_MESSAGE("You discuss at length about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
6898 ADD_MESSAGE("You have a somewhat boring talk concerning %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
6901 ADD_MESSAGE("You are drawn into a heated argument regarding %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
6904 ADD_MESSAGE("%s delivers a long monologue concerning eg. %s.", CHAR_DESCRIPTION(DEFINITE
), Science
.CStr());
6907 ADD_MESSAGE("You dive into a brief but thought-provoking debate over %s with %s", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
6910 PLAYER
->EditExperience(INTELLIGENCE
, 1000, 50. * GetScienceTalkIntelligenceModifier() / ++ScienceTalks
);
6911 PLAYER
->EditExperience(WISDOM
, 1000, 50. * GetScienceTalkWisdomModifier() / ++ScienceTalks
);
6912 PLAYER
->EditExperience(CHARISMA
, 1000, 50. * GetScienceTalkCharismaModifier() / ++ScienceTalks
);
6917 truth
character::IsUsingWeaponOfCategory (int Category
) const {
6919 ((GetMainWielded() && GetMainWielded()->GetWeaponCategory() == Category
) ||
6920 (GetSecondaryWielded() && GetSecondaryWielded()->GetWeaponCategory() == Category
));
6924 truth
character::TryToUnStickTraps (v2 Dir
) {
6925 if (!TrapData
) return true;
6926 std::vector
<trapdata
> TrapVector
;
6927 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) TrapVector
.push_back(*TrapData
);
6928 for (uint c
= 0; c
< TrapVector
.size(); ++c
) {
6930 entity
*Trap
= game::SearchTrap(TrapVector
[c
].TrapID
);
6931 /*k8:??? if(!Trap->Exists()) int esko = esko = 2; */
6932 if (!Trap
->Exists()) continue; /*k8: ??? added by me; what this means? */
6933 if (Trap
->GetVictimID() == GetID() && Trap
->TryToUnStick(this, Dir
)) break;
6936 return !TrapData
&& IsEnabled();
6940 struct trapidcomparer
{
6941 trapidcomparer (ulong ID
) : ID(ID
) {}
6942 truth
operator () (const trapdata
*T
) const { return T
->TrapID
== ID
; }
6947 void character::RemoveTrap (ulong ID
) {
6948 trapdata
*&T
= ListFind(TrapData
, trapidcomparer(ID
));
6950 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
6954 void character::AddTrap (ulong ID
, ulong BodyParts
) {
6955 trapdata
*&T
= ListFind(TrapData
, trapidcomparer(ID
));
6956 if (T
) T
->BodyParts
|= BodyParts
;
6957 else T
= new trapdata(ID
, GetID(), BodyParts
);
6958 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
6962 truth
character::IsStuckToTrap (ulong ID
) const {
6963 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) if (T
->TrapID
== ID
) return true;
6968 void character::RemoveTraps () {
6969 for (trapdata
*T
= TrapData
; T
;) {
6970 entity
*Trap
= game::SearchTrap(T
->TrapID
);
6971 if (Trap
) Trap
->UnStick();
6972 trapdata
*ToDel
= T
;
6977 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
6981 void character::RemoveTraps (int BodyPartIndex
) {
6982 ulong Flag
= 1 << BodyPartIndex
;
6983 for (trapdata
**T
= &TrapData
; *T
;) {
6984 if ((*T
)->BodyParts
& Flag
) {
6985 entity
*Trap
= game::SearchTrap((*T
)->TrapID
);
6986 if (!((*T
)->BodyParts
&= ~Flag
)) {
6987 if (Trap
) Trap
->UnStick();
6988 trapdata
*ToDel
= *T
;
6992 if (Trap
) Trap
->UnStick(BodyPartIndex
);
7000 if (GetBodyPart(BodyPartIndex
)) GetBodyPart(BodyPartIndex
)->SignalPossibleUsabilityChange();
7004 festring
character::GetTrapDescription () const {
7006 std::pair
<entity
*, int> TrapStack
[3];
7008 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) {
7010 entity
*Trap
= game::SearchTrap(T
->TrapID
);
7013 for (c
= 0; c
< Index
; ++c
) if (TrapStack
[c
].first
->GetTrapType() == Trap
->GetTrapType()) ++TrapStack
[c
].second
;
7014 if (c
== Index
) TrapStack
[Index
++] = std::make_pair(Trap
, 1);
7022 TrapStack
[0].first
->AddTrapName(Desc
, TrapStack
[0].second
);
7025 TrapStack
[1].first
->AddTrapName(Desc
, TrapStack
[1].second
);
7026 } else if (Index
== 3) {
7028 TrapStack
[1].first
->AddTrapName(Desc
, TrapStack
[1].second
);
7030 TrapStack
[2].first
->AddTrapName(Desc
, TrapStack
[2].second
);
7033 Desc
<< "lots of traps";
7039 int character::RandomizeHurtBodyPart (ulong BodyParts
) const {
7040 int BodyPartIndex
[MAX_BODYPARTS
];
7042 for (int c
= 0; c
< GetBodyParts(); ++c
) {
7043 if (1 << c
& BodyParts
) {
7044 /*k8: ??? if(!GetBodyPart(c)) int esko = esko = 2; */
7045 if (!GetBodyPart(c
)) continue;
7046 BodyPartIndex
[Index
++] = c
;
7048 /*k8: ??? if(!Index) int esko = esko = 2;*/
7050 if (!Index
) abort();
7051 return BodyPartIndex
[RAND_N(Index
)];
7055 truth
character::BodyPartIsStuck (int I
) const {
7056 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) if (1 << I
& T
->BodyParts
) return true;
7061 void character::PrintAttribute (cchar
*Desc
, int I
, int PanelPosX
, int PanelPosY
) const {
7062 int Attribute
= GetAttribute(I
);
7063 int NoBonusAttribute
= GetAttribute(I
, false);
7064 col16 C
= game::GetAttributeColor(I
);
7065 festring String
= Desc
;
7067 String
<< Attribute
;
7069 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
* 10), C
, String
.CStr());
7070 if (Attribute
!= NoBonusAttribute
) {
7071 int Where
= PanelPosX
+ ((String
.GetSize() + 1) << 3);
7072 FONT
->Printf(DOUBLE_BUFFER
, v2(Where
, PanelPosY
* 10), LIGHT_GRAY
, "%d", NoBonusAttribute
);
7077 truth
character::AllowUnconsciousness () const {
7078 return DataBase
->AllowUnconsciousness
&& TorsoIsAlive();
7082 truth
character::CanPanic () const {
7083 return !Action
|| !Action
->IsUnconsciousness();
7087 int character::GetRandomBodyPart (ulong Possible
) const {
7088 int OKBodyPart
[MAX_BODYPARTS
];
7089 int OKBodyParts
= 0;
7090 for (int c
= 0; c
< BodyParts
; ++c
) if (1 << c
& Possible
&& GetBodyPart(c
)) OKBodyPart
[OKBodyParts
++] = c
;
7091 return OKBodyParts
? OKBodyPart
[RAND_N(OKBodyParts
)] : NONE_INDEX
;
7095 void character::EditNP (long What
) {
7096 int OldState
= GetHungerState();
7098 int NewState
= GetHungerState();
7099 if (OldState
> VERY_HUNGRY
&& NewState
== VERY_HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
7100 if (OldState
> STARVING
&& NewState
== STARVING
) DeActivateVoluntaryAction(CONST_S("You are getting extremely hungry."));
7104 truth
character::IsSwimming () const {
7105 return !IsFlying() && GetSquareUnder() && GetSquareUnder()->GetSquareWalkability() & SWIM
;
7109 void character::AddBlackUnicornConsumeEndMessage () const {
7110 if (IsPlayer()) ADD_MESSAGE("You feel dirty and loathsome.");
7114 void character::AddGrayUnicornConsumeEndMessage () const {
7115 if (IsPlayer()) ADD_MESSAGE("You feel neutralized.");
7119 void character::AddWhiteUnicornConsumeEndMessage () const {
7120 if (IsPlayer()) ADD_MESSAGE("You feel purified.");
7124 void character::AddOmmelBoneConsumeEndMessage () const {
7125 if (IsPlayer()) ADD_MESSAGE("You feel the power of all your canine ancestors combining in your body.");
7126 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s looks extremely ferocious. You shudder.", CHAR_NAME(DEFINITE
));
7130 int character::GetBodyPartSparkleFlags (int) const {
7132 ((GetNaturalSparkleFlags() & SKIN_COLOR
? SPARKLING_A
: 0) |
7133 (GetNaturalSparkleFlags() & TORSO_MAIN_COLOR
? SPARKLING_B
: 0) |
7134 (GetNaturalSparkleFlags() & TORSO_SPECIAL_COLOR
? SPARKLING_D
: 0));
7138 truth
character::IsAnimated () const {
7139 return combinebodypartpredicates()(this, &bodypart::IsAnimated
, 1);
7143 double character::GetNaturalExperience (int Identifier
) const {
7144 return DataBase
->NaturalExperience
[Identifier
];
7148 truth
character::HasBodyPart (sorter Sorter
) const {
7149 if (Sorter
== 0) return true;
7150 return combinebodypartpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1);
7154 truth
character::PossessesItem (sorter Sorter
) const {
7155 if (Sorter
== 0) return true;
7157 (GetStack()->SortedItems(this, Sorter
) ||
7158 combinebodypartpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1) ||
7159 combineequipmentpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1));
7164 cchar
*character::GetRunDescriptionLine (int I
) const {
7165 if (!GetRunDescriptionLineOne().IsEmpty()) return !I
? GetRunDescriptionLineOne().CStr() : GetRunDescriptionLineTwo().CStr();
7166 if (IsFlying()) return !I
? "Flying" : "very fast";
7167 if (IsSwimming()) return !I
? "Swimming" : "very fast";
7168 return !I
? "Running" : "";
7172 void character::VomitAtRandomDirection (int Amount
) {
7173 if (game::IsInWilderness()) return;
7174 /* Lacks support of multitile monsters */
7177 for (int d
= 0; d
< 9; ++d
) {
7178 lsquare
*Square
= GetLSquareUnder()->GetNeighbourLSquare(d
);
7179 if (Square
&& !Square
->VomitingIsDangerous(this)) Possible
[Index
++] = Square
->GetPos();
7181 if (Index
) Vomit(Possible
[RAND_N(Index
)], Amount
);
7182 else Vomit(GetPos(), Amount
);
7186 void character::RemoveLifeSavers () {
7187 for (int c
= 0; c
< GetEquipments(); ++c
) {
7188 item
*Equipment
= GetEquipment(c
);
7189 if (Equipment
&& Equipment
->IsInCorrectSlot(c
) && Equipment
->GetGearStates() & LIFE_SAVED
) {
7190 Equipment
->SendToHell();
7191 Equipment
->RemoveFromSlot();
7197 ccharacter
*character::FindCarrier () const {
7198 return this; //check
7202 void character::PrintBeginHiccupsMessage () const {
7203 if (IsPlayer()) ADD_MESSAGE("Your diaphragm is spasming vehemently.");
7207 void character::PrintEndHiccupsMessage () const {
7208 if (IsPlayer()) ADD_MESSAGE("You feel your annoying hiccoughs have finally subsided.");
7212 void character::HiccupsHandler () {
7214 if (!(RAND() % 2000)) {
7215 if (IsPlayer()) ADD_MESSAGE("");
7216 else if (CanBeSeenByPlayer()) ADD_MESSAGE("");
7217 else if ((PLAYER->GetPos()-GetPos()).GetLengthSquare() <= 400) ADD_MESSAGE("");
7218 game::CallForAttention(GetPos(), 400);
7224 void character::HiccupsSituationDangerModifier (double &Danger
) const {
7229 bool character::IsConscious () const {
7230 return !Action
|| !Action
->IsUnconsciousness();
7234 wsquare
*character::GetNearWSquare (v2 Pos
) const {
7235 return static_cast<wsquare
*>(GetSquareUnder()->GetArea()->GetSquare(Pos
));
7239 wsquare
*character::GetNearWSquare (int x
, int y
) const {
7240 return static_cast<wsquare
*>(GetSquareUnder()->GetArea()->GetSquare(x
, y
));
7244 void character::ForcePutNear (v2 Pos
) {
7245 /* GUM SOLUTION!!! */
7246 v2 NewPos
= game::GetCurrentLevel()->GetNearestFreeSquare(PLAYER
, Pos
, false);
7247 if (NewPos
== ERROR_V2
) do { NewPos
= game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos
== Pos
);
7252 void character::ReceiveMustardGas (int BodyPart
, long Volume
) {
7253 if (Volume
) GetBodyPart(BodyPart
)->AddFluid(liquid::Spawn(MUSTARD_GAS_LIQUID
, Volume
), CONST_S("skin"), 0, true);
7257 void character::ReceiveMustardGasLiquid (int BodyPartIndex
, long Modifier
) {
7258 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
7259 if (BodyPart
->GetMainMaterial()->GetInteractionFlags() & IS_AFFECTED_BY_MUSTARD_GAS
) {
7260 long Tries
= Modifier
;
7261 Modifier
-= Tries
; //opt%?
7263 for (long c
= 0; c
< Tries
; ++c
) if (!(RAND() % 100)) ++Damage
;
7264 if (Modifier
&& !(RAND() % 1000 / Modifier
)) ++Damage
;
7266 ulong Minute
= game::GetTotalMinutes();
7267 if (GetLastAcidMsgMin() != Minute
&& (CanBeSeenByPlayer() || IsPlayer())) {
7268 SetLastAcidMsgMin(Minute
);
7269 if (IsPlayer()) ADD_MESSAGE("Mustard gas dissolves the skin of your %s.", BodyPart
->GetBodyPartName().CStr());
7270 else ADD_MESSAGE("Mustard gas dissolves %s.", CHAR_NAME(DEFINITE
));
7272 ReceiveBodyPartDamage(0, Damage
, MUSTARD_GAS_DAMAGE
, BodyPartIndex
, YOURSELF
, false, false, false);
7273 CheckDeath(CONST_S("killed by a fatal exposure to mustard gas"));
7279 truth
character::IsBadPath (v2 Pos
) const {
7280 if (!IsGoingSomeWhere()) return false;
7281 v2 TPos
= !Route
.empty() ? Route
.back() : GoingTo
;
7282 return ((TPos
- Pos
).GetManhattanLength() > (TPos
- GetPos()).GetManhattanLength());
7286 double &character::GetExpModifierRef (expid E
) {
7287 return ExpModifierMap
.insert(std::make_pair(E
, 1.)).first
->second
;
7291 /* Should probably do more. Now only makes Player forget gods */
7292 truth
character::ForgetRandomThing () {
7294 /* hopefully this code isn't some where else */
7295 std::vector
<god
*> Known
;
7296 for (int c
= 1; c
<= GODS
; ++c
) if (game::GetGod(c
)->IsKnown()) Known
.push_back(game::GetGod(c
));
7297 if (Known
.empty()) return false;
7298 int RandomGod
= RAND_N(Known
.size());
7299 Known
.at(RAND_N(Known
.size()))->SetIsKnown(false);
7300 ADD_MESSAGE("You forget how to pray to %s.",
7301 Known
.at(RandomGod
)->GetName());
7308 int character::CheckForBlock (character
*Enemy
, item
*Weapon
, double ToHitValue
, int Damage
, int Success
, int Type
) {
7313 void character::ApplyAllGodsKnownBonus () {
7314 stack
*AddPlace
= GetStackUnder();
7315 if (game::IsInWilderness()) AddPlace
= GetStack(); else AddPlace
= GetStackUnder();
7316 pantheonbook
*NewBook
= pantheonbook::Spawn();
7317 AddPlace
->AddItem(NewBook
);
7318 ADD_MESSAGE("\"MORTAL! BEHOLD THE HOLY SAGA\"");
7319 ADD_MESSAGE("%s materializes near your feet.", NewBook
->CHAR_NAME(INDEFINITE
));
7323 void character::ReceiveSirenSong (character
*Siren
) {
7324 if (Siren
->GetTeam() == GetTeam()) return;
7326 if (IsPlayer()) ADD_MESSAGE("The beautiful melody of %s makes you feel sleepy.", Siren
->CHAR_NAME(DEFINITE
));
7327 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The beautiful melody of %s makes %s look sleepy.", Siren
->CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
)); /*k8*/
7328 Stamina
-= (1 + RAND_N(4)) * 10000;
7331 if (!IsPlayer() && IsCharmable() && !RAND_N(5)) {
7332 ChangeTeam(Siren
->GetTeam());
7333 ADD_MESSAGE("%s seems to be totally brainwashed by %s melodies.", CHAR_NAME(DEFINITE
), Siren
->CHAR_NAME(DEFINITE
));
7337 item
*What
= GiveMostExpensiveItem(Siren
);
7340 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
);
7342 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
);
7345 if (IsPlayer()) ADD_MESSAGE("You would like to give something to %s.", Siren
->CHAR_NAME(DEFINITE
));
7352 // return 0, if no item found
7353 item
*character::FindMostExpensiveItem () const {
7355 item
*MostExpensive
= 0;
7356 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
7357 if ((*i
)->GetPrice() > MaxPrice
) {
7358 MaxPrice
= (*i
)->GetPrice();
7359 MostExpensive
= (*i
);
7362 for (int c
= 0; c
< GetEquipments(); ++c
) {
7363 item
*Equipment
= GetEquipment(c
);
7364 if (Equipment
&& Equipment
->GetPrice() > MaxPrice
) {
7365 MaxPrice
= Equipment
->GetPrice();
7366 MostExpensive
= Equipment
;
7369 return MostExpensive
;
7373 // returns 0 if no items available
7374 item
*character::GiveMostExpensiveItem(character
*ToWhom
) {
7375 item
*ToGive
= FindMostExpensiveItem();
7376 if (!ToGive
) return 0;
7377 truth Equipped
= PLAYER
->Equips(ToGive
);
7378 ToGive
->RemoveFromSlot();
7379 if (Equipped
) game::AskForEscPress(CONST_S("Equipment lost!"));
7380 ToWhom
->ReceiveItemAsPresent(ToGive
);
7386 void character::ReceiveItemAsPresent (item
*Present
) {
7387 if (TestForPickup(Present
)) GetStack()->AddItem(Present
); else GetStackUnder()->AddItem(Present
);
7391 /* returns 0 if no enemies in sight */
7392 character
*character::GetNearestEnemy () const {
7393 character
*NearestEnemy
= 0;
7394 long NearestEnemyDistance
= 0x7FFFFFFF;
7396 for (int c
= 0; c
< game::GetTeams(); ++c
) {
7397 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
7398 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
7399 if ((*i
)->IsEnabled()) {
7400 long ThisDistance
= Max
<long>(abs((*i
)->GetPos().X
- Pos
.X
), abs((*i
)->GetPos().Y
- Pos
.Y
));
7401 if ((ThisDistance
< NearestEnemyDistance
|| (ThisDistance
== NearestEnemyDistance
&& !(RAND() % 3))) && (*i
)->CanBeSeenBy(this)) {
7403 NearestEnemyDistance
= ThisDistance
;
7409 return NearestEnemy
;
7413 truth
character::MindWormCanPenetrateSkull (mindworm
*) const {
7418 truth
character::CanTameWithDulcis (const character
*Tamer
) const {
7419 int TamingDifficulty
= GetTamingDifficulty();
7420 if (TamingDifficulty
== NO_TAMING
) return false;
7421 if (GetAttachedGod() == DULCIS
) return true;
7422 int Modifier
= Tamer
->GetAttribute(WISDOM
) + Tamer
->GetAttribute(CHARISMA
);
7423 if (Tamer
->IsPlayer()) Modifier
+= game::GetGod(DULCIS
)->GetRelation() / 20;
7424 else if (Tamer
->GetAttachedGod() == DULCIS
) Modifier
+= 50;
7425 if (TamingDifficulty
== 0) {
7426 if (!IgnoreDanger()) TamingDifficulty
= int(10 * GetRelativeDanger(Tamer
));
7427 else TamingDifficulty
= 10 * GetHPRequirementForGeneration()/Max(Tamer
->GetHP(), 1);
7429 return Modifier
>= TamingDifficulty
* 3;
7433 truth
character::CanTameWithLyre (const character
*Tamer
) const {
7434 int TamingDifficulty
= GetTamingDifficulty();
7435 if (TamingDifficulty
== NO_TAMING
) return false;
7436 if (TamingDifficulty
== 0) {
7437 if (!IgnoreDanger()) TamingDifficulty
= int(10 * GetRelativeDanger(Tamer
));
7438 else TamingDifficulty
= 10*GetHPRequirementForGeneration()/Max(Tamer
->GetHP(), 1);
7440 return Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
;
7444 truth
character::CanTameWithScroll (const character
*Tamer
) const {
7445 int TamingDifficulty
= GetTamingDifficulty();
7447 (TamingDifficulty
!= NO_TAMING
&&
7448 (TamingDifficulty
== 0 ||
7449 Tamer
->GetAttribute(INTELLIGENCE
) * 4 + Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
* 5));
7453 truth
character::CheckSadism () {
7454 if (!IsSadist() || !HasSadistAttackMode() || !IsSmall()) return false; // gum
7456 for (int d
= 0; d
< 8; ++d
) {
7457 square
*Square
= GetNeighbourSquare(d
);
7459 character
*Char
= Square
->GetCharacter();
7460 if (Char
&& Char
->IsMasochist() && GetRelation(Char
) == FRIEND
&&
7461 Char
->GetHP() * 3 >= Char
->GetMaxHP() * 2 && Hit(Char
, Square
->GetPos(), d
, SADIST_HIT
)) {
7472 truth
character::CheckForBeverage () {
7473 if (StateIsActivated(PANIC
) || !IsEnabled() || !UsesNutrition() || CheckIfSatiated()) return false;
7474 itemvector ItemVector
;
7475 GetStack()->FillItemVector(ItemVector
);
7476 for (uint c
= 0; c
< ItemVector
.size(); ++c
) if (ItemVector
[c
]->IsBeverage(this) && TryToConsume(ItemVector
[c
])) return true;
7481 void character::Haste () {
7482 doforbodyparts()(this, &bodypart::Haste
);
7483 doforequipments()(this, &item::Haste
);
7484 BeginTemporaryState(HASTE
, 500 + RAND() % 1000);
7488 void character::Slow () {
7489 doforbodyparts()(this, &bodypart::Slow
);
7490 doforequipments()(this, &item::Slow
);
7491 BeginTemporaryState(HASTE
, 500 + RAND() % 1000);